From b309f2708ba25e9e22d697a8b428ad827be2e680 Mon Sep 17 00:00:00 2001 From: Ariel Santangelo Date: Sun, 23 Aug 2026 01:21:20 -0300 Subject: [PATCH] nvidia-drm: Implement plane atomic_async_check and atomic_async_update for cursor planes Implement the .atomic_async_check and .atomic_async_update callbacks in struct drm_plane_helper_funcs for nvidia-drm, and hook the state->async_update fastpath in nv_drm_atomic_commit() via drm_atomic_helper_async_commit(). This connects legacy cursor position updates (DRM_IOCTL_MODE_CURSOR / drmModeMoveCursor) directly to NVKMS's hardware MMIO fastpath via NvKmsKapiMoveCursor, decoupling cursor position updates from VBlank intervals and eliminating cursor-induced stalls. Key changes: - Extend NVKMS KAPI with moveCursor function pointer and implement NvKmsKapiMoveCursor dispatching NVKMS_IOCTL_MOVE_CURSOR. - Add conftest checks for atomic_async_check (Linux 4.13+), atomic_state argument (Linux 5.13+), flip argument (Linux 6.13+/7.1+), and drm_atomic_helper_unprepare_planes (Linux 6.8+). - Implement nv_drm_plane_atomic_async_check validating full plane invariants (1:1 scaling, uncropped source, active CRTC, matching FB/CRTC). - Implement nv_drm_plane_atomic_async_update dispatching directly to nvKms->moveCursor with WARN_ON_ONCE invariant guards. Signed-off-by: Ariel Santangelo --- kernel-open/common/inc/nvkms-kapi.h | 18 ++ kernel-open/conftest.sh | 106 ++++++++++ kernel-open/nvidia-drm/nvidia-drm-crtc.c | 197 ++++++++++++++++++ kernel-open/nvidia-drm/nvidia-drm-helper.h | 5 + kernel-open/nvidia-drm/nvidia-drm-modeset.c | 18 ++ kernel-open/nvidia-drm/nvidia-drm-sources.mk | 4 + .../kapi/interface/nvkms-kapi.h | 18 ++ src/nvidia-modeset/kapi/src/nvkms-kapi.c | 33 +++ 8 files changed, 399 insertions(+) diff --git a/kernel-open/common/inc/nvkms-kapi.h b/kernel-open/common/inc/nvkms-kapi.h index 8e838eafa9..4ba64dc0d3 100644 --- a/kernel-open/common/inc/nvkms-kapi.h +++ b/kernel-open/common/inc/nvkms-kapi.h @@ -1187,6 +1187,24 @@ struct NvKmsKapiFunctionsTable { const NvBool commit ); + /*! + * Move the cursor on the specified head. + * + * \param [in] device A device allocated using allocateDevice(). + * \param [in] head Target head index. + * \param [in] x New X coordinate in display space. + * \param [in] y New Y coordinate in display space. + * + * \return NV_TRUE on success, NV_FALSE on failure. + */ + NvBool (*moveCursor) + ( + struct NvKmsKapiDevice *device, + const NvU32 head, + const NvS16 x, + const NvS16 y + ); + /*! * Return status of flip. * diff --git a/kernel-open/conftest.sh b/kernel-open/conftest.sh index c318c52f4b..2584ec0eae 100755 --- a/kernel-open/conftest.sh +++ b/kernel-open/conftest.sh @@ -1631,6 +1631,24 @@ compile_test() { compile_check_conftest "$CODE" "NV_DRM_ATOMIC_HELPER_LEGACY_GAMMA_SET_PRESENT" "" "functions" ;; + drm_atomic_helper_unprepare_planes) + # + # Determine if the function drm_atomic_helper_unprepare_planes() is + # present. + # + # Added by commit 456ff66cb87b ("drm/atomic-helper: Add + # drm_atomic_helper_unprepare_planes") in v6.8 (2023-12-19). + # Prior to v6.8, drm_atomic_helper_cleanup_planes was used. + # + CODE=" + #include + void conftest_drm_atomic_helper_unprepare_planes(void) { + drm_atomic_helper_unprepare_planes(); + }" + + compile_check_conftest "$CODE" "NV_DRM_ATOMIC_HELPER_UNPREPARE_PLANES_PRESENT" "" "functions" + ;; + drm_plane_create_color_properties) # # Determine if the function drm_plane_create_color_properties() is @@ -3292,6 +3310,94 @@ compile_test() { fi ;; + drm_plane_helper_funcs_has_atomic_async_check) + # + # Determine if drm_plane_helper_funcs has atomic_async_check member. + # + echo "$CONFTEST_PREAMBLE + #include + void conftest_drm_plane_helper_funcs_has_atomic_async_check(void) { + struct drm_plane_helper_funcs funcs; + funcs.atomic_async_check = NULL; + }" > conftest$$.c + + $CC $CFLAGS -c conftest$$.c > /dev/null 2>&1 + rm -f conftest$$.c + + if [ -f conftest$$.o ]; then + rm -f conftest$$.o + echo "#define NV_DRM_PLANE_HELPER_FUNCS_HAS_ATOMIC_ASYNC_CHECK" | append_conftest "types" + else + echo "#undef NV_DRM_PLANE_HELPER_FUNCS_HAS_ATOMIC_ASYNC_CHECK" | append_conftest "types" + fi + ;; + + drm_plane_atomic_async_check_has_atomic_state_arg) + # + # Determine if drm_plane_helper_funcs::atomic_async_check takes 'state' + # argument of 'struct drm_atomic_state' type. + # + # Commit 881db09bc588 / 7c11b99a8e58 in v5.13 passes the full atomic state to + # drm_plane_helper_funcs::atomic_async_check() and atomic_async_update(). + # + echo "$CONFTEST_PREAMBLE + #include + static const struct drm_plane_helper_funcs *funcs; + typeof(*funcs->atomic_async_check) conftest_drm_plane_atomic_async_check_has_atomic_state_arg; + #if defined(NV_DRM_ATOMIC_COMMIT_STRUCT_PRESENT) + int conftest_drm_plane_atomic_async_check_has_atomic_state_arg( + struct drm_plane *plane, struct drm_atomic_commit *state) { + return 0; + } + #else + int conftest_drm_plane_atomic_async_check_has_atomic_state_arg( + struct drm_plane *plane, struct drm_atomic_state *state) { + return 0; + } + #endif" > conftest$$.c + + $CC $CFLAGS -c conftest$$.c > /dev/null 2>&1 + rm -f conftest$$.c + + if [ -f conftest$$.o ]; then + rm -f conftest$$.o + echo "#define NV_DRM_PLANE_ATOMIC_ASYNC_CHECK_HAS_ATOMIC_STATE_ARG" | append_conftest "types" + else + echo "#undef NV_DRM_PLANE_ATOMIC_ASYNC_CHECK_HAS_ATOMIC_STATE_ARG" | append_conftest "types" + fi + ;; + + drm_plane_atomic_async_check_has_flip_arg) + # + # Determine if drm_plane_helper_funcs::atomic_async_check takes 'bool' (flip) + # + echo "$CONFTEST_PREAMBLE + #include + static const struct drm_plane_helper_funcs *funcs; + typeof(*funcs->atomic_async_check) conftest_drm_plane_atomic_async_check_has_flip_arg; + #if defined(NV_DRM_ATOMIC_COMMIT_STRUCT_PRESENT) + int conftest_drm_plane_atomic_async_check_has_flip_arg( + struct drm_plane *plane, struct drm_atomic_commit *state, bool flip) { + return 0; + } + #else + int conftest_drm_plane_atomic_async_check_has_flip_arg( + struct drm_plane *plane, struct drm_atomic_state *state, bool flip) { + return 0; + } + #endif" > conftest$$.c + + $CC $CFLAGS -c conftest$$.c > /dev/null 2>&1 + rm -f conftest$$.c + + if [ -f conftest$$.o ]; then + rm -f conftest$$.o + echo "#define NV_DRM_PLANE_ATOMIC_ASYNC_CHECK_HAS_FLIP_ARG" | append_conftest "types" + else + echo "#undef NV_DRM_PLANE_ATOMIC_ASYNC_CHECK_HAS_FLIP_ARG" | append_conftest "types" + fi + ;; + ib_peer_memory_symbols) # # Determine if the following symbols exist in Module.symvers: diff --git a/kernel-open/nvidia-drm/nvidia-drm-crtc.c b/kernel-open/nvidia-drm/nvidia-drm-crtc.c index 37a4deb77e..bd64339393 100644 --- a/kernel-open/nvidia-drm/nvidia-drm-crtc.c +++ b/kernel-open/nvidia-drm/nvidia-drm-crtc.c @@ -2205,8 +2205,205 @@ static const struct drm_plane_funcs nv_plane_funcs = { .format_mod_supported = nv_drm_plane_format_mod_supported, }; +#if defined(NV_DRM_PLANE_HELPER_FUNCS_HAS_ATOMIC_ASYNC_CHECK) + +#if defined(NV_DRM_PLANE_ATOMIC_ASYNC_CHECK_HAS_FLIP_ARG) +static int nv_drm_plane_atomic_async_check(struct drm_plane *plane, + nv_drm_atomic_state_base_t *state, + bool flip) +#elif defined(NV_DRM_PLANE_ATOMIC_ASYNC_CHECK_HAS_ATOMIC_STATE_ARG) +static int nv_drm_plane_atomic_async_check(struct drm_plane *plane, + nv_drm_atomic_state_base_t *state) +#else +static int nv_drm_plane_atomic_async_check(struct drm_plane *plane, + struct drm_plane_state *plane_state) +#endif +{ + struct drm_plane_state *old_plane_state = plane->state; + struct nv_drm_device *nv_dev = to_nv_device(plane->dev); + struct drm_crtc *crtc; + struct nv_drm_plane_state *nv_plane_state; + struct nv_drm_plane_state *nv_old_plane_state; + +#if defined(NV_DRM_PLANE_ATOMIC_ASYNC_CHECK_HAS_FULL_STATE_ARG) + struct drm_plane_state *plane_state = + drm_atomic_get_new_plane_state(state, plane); +#endif + +#if defined(NV_DRM_PLANE_ATOMIC_ASYNC_CHECK_HAS_FLIP_ARG) + if (flip) { + return -EINVAL; + } +#endif + + if (plane->type != DRM_PLANE_TYPE_CURSOR) { + return -EINVAL; + } + + if (nv_dev->subOwnershipGranted) { + return -EINVAL; + } + + if (plane_state == NULL || old_plane_state == NULL) { + return -EINVAL; + } + + if (plane_state->crtc == NULL || plane_state->fb == NULL || + old_plane_state->crtc == NULL || old_plane_state->fb == NULL) { + return -EINVAL; + } + + /* CRTC and Framebuffer must not change */ + if (plane_state->crtc != old_plane_state->crtc || + plane_state->fb != old_plane_state->fb) { + return -EINVAL; + } + + /* + * Target CRTC must be active (not in DPMS off / suspend / modeset disable). + * In legacy cursor updates, CRTC is not in the atomic transaction state, + * so reading crtc->state directly is safe because caller holds modeset locks. + */ + crtc = plane_state->crtc; + if (crtc->state == NULL || !crtc->state->active) { + return -EINVAL; + } + + /* Destination and Source dimensions must match and cannot change */ + if (plane_state->crtc_w != old_plane_state->crtc_w || + plane_state->crtc_h != old_plane_state->crtc_h || + plane_state->src_w != old_plane_state->src_w || + plane_state->src_h != old_plane_state->src_h) { + return -EINVAL; + } + + /* Source offset must be (0, 0) - hardware cursor does not support cropping */ + if (plane_state->src_x != 0 || plane_state->src_y != 0 || + plane_state->src_x != old_plane_state->src_x || + plane_state->src_y != old_plane_state->src_y) { + return -EINVAL; + } + + /* Hardware cursor requires 1:1 scaling (no up/down-scaling) */ + if ((plane_state->src_w >> 16) != plane_state->crtc_w || + (plane_state->src_h >> 16) != plane_state->crtc_h) { + return -EINVAL; + } + + /* Rotation, Alpha, and Blending mode must not change */ + if (plane_state->rotation != old_plane_state->rotation || + plane_state->alpha != old_plane_state->alpha || + plane_state->pixel_blend_mode != old_plane_state->pixel_blend_mode) { + return -EINVAL; + } + + /* Z-order must not change */ + if (plane_state->zpos != old_plane_state->zpos || + plane_state->normalized_zpos != old_plane_state->normalized_zpos) { + return -EINVAL; + } + + /* Color properties (encoding, range) must not change */ + if (plane_state->color_encoding != old_plane_state->color_encoding || + plane_state->color_range != old_plane_state->color_range) { + return -EINVAL; + } + + /* If explicit fencing is attached, full atomic commit is required */ + if (plane_state->fence != NULL) { + return -EINVAL; + } + + /* Check driver-private plane state extensions */ + nv_plane_state = to_nv_drm_plane_state(plane_state); + nv_old_plane_state = to_nv_drm_plane_state(old_plane_state); + + if (nv_plane_state->input_colorspace != nv_old_plane_state->input_colorspace || + nv_plane_state->degamma_changed || + nv_plane_state->tmo_changed || + nv_plane_state->degamma_tf != nv_old_plane_state->degamma_tf || + nv_plane_state->degamma_lut != nv_old_plane_state->degamma_lut || + nv_plane_state->tmo_lut != nv_old_plane_state->tmo_lut) { + return -EINVAL; + } + +#if defined(NV_DRM_HAS_HDR_OUTPUT_METADATA) + if (nv_plane_state->hdr_output_metadata != nv_old_plane_state->hdr_output_metadata) { + return -EINVAL; + } +#endif + + /* Coordinates must fit in signed 16-bit integers for NvKms */ + if (plane_state->crtc_x < S16_MIN || plane_state->crtc_x > S16_MAX || + plane_state->crtc_y < S16_MIN || plane_state->crtc_y > S16_MAX) { + return -EINVAL; + } + + return 0; +} + +#if defined(NV_DRM_PLANE_ATOMIC_ASYNC_CHECK_HAS_FULL_STATE_ARG) +static void nv_drm_plane_atomic_async_update(struct drm_plane *plane, + nv_drm_atomic_state_base_t *state) +#else +static void nv_drm_plane_atomic_async_update(struct drm_plane *plane, + struct drm_plane_state *plane_state) +#endif +{ +#if defined(NV_DRM_PLANE_ATOMIC_ASYNC_CHECK_HAS_FULL_STATE_ARG) + struct drm_plane_state *plane_state = + drm_atomic_get_new_plane_state(state, plane); +#endif + struct nv_drm_crtc *nv_crtc; + struct nv_drm_device *nv_dev; + + if (plane->state == NULL || plane_state == NULL) { + return; + } + + plane->state->crtc_x = plane_state->crtc_x; + plane->state->crtc_y = plane_state->crtc_y; + plane->state->crtc_w = plane_state->crtc_w; + plane->state->crtc_h = plane_state->crtc_h; + plane->state->src_x = plane_state->src_x; + plane->state->src_y = plane_state->src_y; + plane->state->src_w = plane_state->src_w; + plane->state->src_h = plane_state->src_h; + + if (WARN_ON_ONCE(plane->state->crtc == NULL || + plane->state->crtc->state == NULL || + !plane->state->crtc->state->active)) { + return; + } + + nv_crtc = to_nv_crtc(plane->state->crtc); + nv_dev = to_nv_device(plane->dev); + + if (nv_crtc != NULL && nv_dev != NULL && nvKms != NULL && nvKms->moveCursor != NULL) { + NvBool status = nvKms->moveCursor( + nv_dev->pDevice, + nv_crtc->head, + (NvS16)plane->state->crtc_x, + (NvS16)plane->state->crtc_y); + + if (!status) { + NV_DRM_DEV_LOG_ERR( + nv_dev, + "Failed to move cursor on head %u to (%d, %d)", + nv_crtc->head, + plane->state->crtc_x, + plane->state->crtc_y); + } + } +} +#endif /* NV_DRM_PLANE_HELPER_FUNCS_HAS_ATOMIC_ASYNC_CHECK */ + static const struct drm_plane_helper_funcs nv_plane_helper_funcs = { .atomic_check = nv_drm_plane_atomic_check, +#if defined(NV_DRM_PLANE_HELPER_FUNCS_HAS_ATOMIC_ASYNC_CHECK) + .atomic_async_check = nv_drm_plane_atomic_async_check, + .atomic_async_update = nv_drm_plane_atomic_async_update, +#endif }; static void nv_drm_crtc_destroy(struct drm_crtc *crtc) diff --git a/kernel-open/nvidia-drm/nvidia-drm-helper.h b/kernel-open/nvidia-drm/nvidia-drm-helper.h index 20856c9728..223ef3cbc8 100644 --- a/kernel-open/nvidia-drm/nvidia-drm-helper.h +++ b/kernel-open/nvidia-drm/nvidia-drm-helper.h @@ -170,6 +170,11 @@ typedef struct drm_atomic_state nv_drm_atomic_state_base_t; #define NV_DRM_PLANE_ATOMIC_CHECK_HAS_FULL_STATE_ARG #endif +#if defined(NV_DRM_PLANE_ATOMIC_ASYNC_CHECK_HAS_ATOMIC_STATE_ARG) || \ + defined(NV_DRM_PLANE_ATOMIC_ASYNC_CHECK_HAS_FLIP_ARG) +#define NV_DRM_PLANE_ATOMIC_ASYNC_CHECK_HAS_FULL_STATE_ARG +#endif + static inline nv_drm_atomic_state_base_t * nv_drm_atomic_state_base_alloc(struct drm_device *dev) { diff --git a/kernel-open/nvidia-drm/nvidia-drm-modeset.c b/kernel-open/nvidia-drm/nvidia-drm-modeset.c index 7b18ce6a99..1ea3a3dd96 100644 --- a/kernel-open/nvidia-drm/nvidia-drm-modeset.c +++ b/kernel-open/nvidia-drm/nvidia-drm-modeset.c @@ -700,6 +700,24 @@ int nv_drm_atomic_commit(struct drm_device *dev, struct drm_crtc_state *crtc_state = NULL; struct nv_drm_device *nv_dev = to_nv_device(dev); +#if defined(NV_DRM_PLANE_HELPER_FUNCS_HAS_ATOMIC_ASYNC_CHECK) + if (state->async_update) { + ret = drm_atomic_helper_prepare_planes(dev, state); + if (ret) { + return ret; + } + + drm_atomic_helper_async_commit(dev, state); + +#if defined(NV_DRM_ATOMIC_HELPER_UNPREPARE_PLANES_PRESENT) + drm_atomic_helper_unprepare_planes(dev, state); +#else + drm_atomic_helper_cleanup_planes(dev, state); +#endif + return 0; + } +#endif + /* * XXX: drm_mode_config_funcs::atomic_commit() mandates to return -EBUSY * for nonblocking commit if the commit would need to wait for previous diff --git a/kernel-open/nvidia-drm/nvidia-drm-sources.mk b/kernel-open/nvidia-drm/nvidia-drm-sources.mk index a4770662fd..f9f2fefeeb 100644 --- a/kernel-open/nvidia-drm/nvidia-drm-sources.mk +++ b/kernel-open/nvidia-drm/nvidia-drm-sources.mk @@ -61,6 +61,7 @@ NV_CONFTEST_FUNCTION_COMPILE_TESTS += drm_client_setup NV_CONFTEST_FUNCTION_COMPILE_TESTS += drm_connector_attach_hdr_output_metadata_property NV_CONFTEST_FUNCTION_COMPILE_TESTS += drm_plane_create_color_properties NV_CONFTEST_FUNCTION_COMPILE_TESTS += drm_atomic_helper_legacy_gamma_set +NV_CONFTEST_FUNCTION_COMPILE_TESTS += drm_atomic_helper_unprepare_planes 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 @@ -86,6 +87,9 @@ NV_CONFTEST_TYPE_COMPILE_TESTS += drm_driver_has_gem_prime_callbacks NV_CONFTEST_TYPE_COMPILE_TESTS += drm_crtc_atomic_check_has_atomic_state_arg NV_CONFTEST_TYPE_COMPILE_TESTS += drm_gem_object_vmap_has_map_arg NV_CONFTEST_TYPE_COMPILE_TESTS += drm_plane_atomic_check_has_atomic_state_arg +NV_CONFTEST_TYPE_COMPILE_TESTS += drm_plane_helper_funcs_has_atomic_async_check +NV_CONFTEST_TYPE_COMPILE_TESTS += drm_plane_atomic_async_check_has_atomic_state_arg +NV_CONFTEST_TYPE_COMPILE_TESTS += drm_plane_atomic_async_check_has_flip_arg NV_CONFTEST_TYPE_COMPILE_TESTS += drm_device_has_pdev NV_CONFTEST_TYPE_COMPILE_TESTS += drm_crtc_state_has_no_vblank NV_CONFTEST_TYPE_COMPILE_TESTS += drm_mode_config_has_allow_fb_modifiers diff --git a/src/nvidia-modeset/kapi/interface/nvkms-kapi.h b/src/nvidia-modeset/kapi/interface/nvkms-kapi.h index 8e838eafa9..4ba64dc0d3 100644 --- a/src/nvidia-modeset/kapi/interface/nvkms-kapi.h +++ b/src/nvidia-modeset/kapi/interface/nvkms-kapi.h @@ -1187,6 +1187,24 @@ struct NvKmsKapiFunctionsTable { const NvBool commit ); + /*! + * Move the cursor on the specified head. + * + * \param [in] device A device allocated using allocateDevice(). + * \param [in] head Target head index. + * \param [in] x New X coordinate in display space. + * \param [in] y New Y coordinate in display space. + * + * \return NV_TRUE on success, NV_FALSE on failure. + */ + NvBool (*moveCursor) + ( + struct NvKmsKapiDevice *device, + const NvU32 head, + const NvS16 x, + const NvS16 y + ); + /*! * Return status of flip. * diff --git a/src/nvidia-modeset/kapi/src/nvkms-kapi.c b/src/nvidia-modeset/kapi/src/nvkms-kapi.c index aed2312b1f..19594a2186 100644 --- a/src/nvidia-modeset/kapi/src/nvkms-kapi.c +++ b/src/nvidia-modeset/kapi/src/nvkms-kapi.c @@ -3820,6 +3820,38 @@ static NvBool ApplyModeSetConfig( return KmsFlip(device, requestedConfig, replyConfig, commit); } +static NvBool NvKmsKapiMoveCursor( + struct NvKmsKapiDevice *device, + const NvU32 head, + const NvS16 x, + const NvS16 y) +{ + struct NvKmsMoveCursorParams params = { }; + NvBool status; + + if (device == NULL) { + return NV_FALSE; + } + + params.request.deviceHandle = device->hKmsDevice; + params.request.dispHandle = device->hKmsDisp; + params.request.head = head; + params.request.common.x = x; + params.request.common.y = y; + + status = nvkms_ioctl_from_kapi( + device->pKmsOpen, + NVKMS_IOCTL_MOVE_CURSOR, + ¶ms, sizeof(params)); + + if (!status) { + nvKmsKapiLogDeviceDebug(device, "NVKMS_IOCTL_MOVE_CURSOR ioctl failed"); + return NV_FALSE; + } + + return NV_TRUE; +} + /* * This executes without the nvkms_lock held. The lock will be grabbed * during the kapi dispatching contained in this function. @@ -4181,6 +4213,7 @@ NvBool nvKmsKapiGetFunctionsTableInternal funcsTable->validateDisplayMode = ValidateDisplayMode; funcsTable->applyModeSetConfig = ApplyModeSetConfig; + funcsTable->moveCursor = NvKmsKapiMoveCursor; funcsTable->allocateChannelEvent = nvKmsKapiAllocateChannelEvent; funcsTable->freeChannelEvent = nvKmsKapiFreeChannelEvent;