From 5b50b7c2ce273e6361e44113e3c0efbbc2cd68cf Mon Sep 17 00:00:00 2001 From: Maciej Falkowski Date: Fri, 20 Feb 2026 17:01:16 +0100 Subject: [PATCH 01/23] accel/ivpu: Update FW Boot API to version 3.29.4 Update firmware boot API to the version 3.29.4. Remove unused boot parameters from the vpu_firmware_header structure. Reviewed-by: Lizhi Hou Signed-off-by: Maciej Falkowski Link: https://patch.msgid.link/20260220160116.220367-1-maciej.falkowski@linux.intel.com --- drivers/accel/ivpu/vpu_boot_api.h | 211 +++++++++++++----------------- 1 file changed, 93 insertions(+), 118 deletions(-) diff --git a/drivers/accel/ivpu/vpu_boot_api.h b/drivers/accel/ivpu/vpu_boot_api.h index 218468bbbcad..a41170bbc6b7 100644 --- a/drivers/accel/ivpu/vpu_boot_api.h +++ b/drivers/accel/ivpu/vpu_boot_api.h @@ -1,12 +1,22 @@ /* SPDX-License-Identifier: MIT */ /* - * Copyright (c) 2020-2024, Intel Corporation. + * Copyright (c) 2020-2025, Intel Corporation. + */ + +/** + * @addtogroup Boot + * @{ + */ + +/** + * @file + * @brief Boot API public header file. */ #ifndef VPU_BOOT_API_H #define VPU_BOOT_API_H -/* +/** * The below values will be used to construct the version info this way: * fw_bin_header->api_version[VPU_BOOT_API_VER_ID] = (VPU_BOOT_API_VER_MAJOR << 16) | * VPU_BOOT_API_VER_MINOR; @@ -16,24 +26,24 @@ * partial info a build error will be generated. */ -/* +/** * Major version changes that break backward compatibility. * Major version must start from 1 and can only be incremented. */ #define VPU_BOOT_API_VER_MAJOR 3 -/* +/** * Minor version changes when API backward compatibility is preserved. * Resets to 0 if Major version is incremented. */ -#define VPU_BOOT_API_VER_MINOR 28 +#define VPU_BOOT_API_VER_MINOR 29 -/* +/** * API header changed (field names, documentation, formatting) but API itself has not been changed */ -#define VPU_BOOT_API_VER_PATCH 3 +#define VPU_BOOT_API_VER_PATCH 4 -/* +/** * Index in the API version table * Must be unique for each API */ @@ -41,7 +51,7 @@ #pragma pack(push, 4) -/* +/** * Firmware image header format */ #define VPU_FW_HEADER_SIZE 4096 @@ -61,44 +71,41 @@ struct vpu_firmware_header { u32 firmware_version_size; u64 boot_params_load_address; u32 api_version[VPU_FW_API_VER_NUM]; - /* Size of memory require for firmware execution */ + /** Size of memory require for firmware execution */ u32 runtime_size; u32 shave_nn_fw_size; - /* + /** * Size of primary preemption buffer, assuming a 2-job submission queue. * NOTE: host driver is expected to adapt size accordingly to actual * submission queue size and device capabilities. */ u32 preemption_buffer_1_size; - /* + /** * Size of secondary preemption buffer, assuming a 2-job submission queue. * NOTE: host driver is expected to adapt size accordingly to actual * submission queue size and device capabilities. */ u32 preemption_buffer_2_size; - /* + /** * Maximum preemption buffer size that the FW can use: no need for the host * driver to allocate more space than that specified by these fields. * A value of 0 means no declared limit. */ u32 preemption_buffer_1_max_size; u32 preemption_buffer_2_max_size; - /* Space reserved for future preemption-related fields. */ + /** Space reserved for future preemption-related fields. */ u32 preemption_reserved[4]; - /* FW image read only section start address, 4KB aligned */ + /** FW image read only section start address, 4KB aligned */ u64 ro_section_start_address; - /* FW image read only section size, 4KB aligned */ + /** FW image read only section size, 4KB aligned */ u32 ro_section_size; u32 reserved; }; -/* +/** * Firmware boot parameters format */ -#define VPU_BOOT_PLL_COUNT 3 -#define VPU_BOOT_PLL_OUT_COUNT 4 - /** Values for boot_type field */ #define VPU_BOOT_TYPE_COLDBOOT 0 #define VPU_BOOT_TYPE_WARMBOOT 1 @@ -166,7 +173,7 @@ enum vpu_trace_destination { #define VPU_TRACE_PROC_BIT_ACT_SHV_3 22 #define VPU_TRACE_PROC_NO_OF_HW_DEVS 23 -/* VPU 30xx HW component IDs are sequential, so define first and last IDs. */ +/** VPU 30xx HW component IDs are sequential, so define first and last IDs. */ #define VPU_TRACE_PROC_BIT_30XX_FIRST VPU_TRACE_PROC_BIT_LRT #define VPU_TRACE_PROC_BIT_30XX_LAST VPU_TRACE_PROC_BIT_SHV_15 @@ -175,15 +182,7 @@ struct vpu_boot_l2_cache_config { u8 cfg; }; -struct vpu_warm_boot_section { - u32 src; - u32 dst; - u32 size; - u32 core_id; - u32 is_clear_op; -}; - -/* +/** * When HW scheduling mode is enabled, a present period is defined. * It will be used by VPU to swap between normal and focus priorities * to prevent starving of normal priority band (when implemented). @@ -206,24 +205,24 @@ struct vpu_warm_boot_section { * Enum for dvfs_mode boot param. */ enum vpu_governor { - VPU_GOV_DEFAULT = 0, /* Default Governor for the system */ - VPU_GOV_MAX_PERFORMANCE = 1, /* Maximum performance governor */ - VPU_GOV_ON_DEMAND = 2, /* On Demand frequency control governor */ - VPU_GOV_POWER_SAVE = 3, /* Power save governor */ - VPU_GOV_ON_DEMAND_PRIORITY_AWARE = 4 /* On Demand priority based governor */ + VPU_GOV_DEFAULT = 0, /** Default Governor for the system */ + VPU_GOV_MAX_PERFORMANCE = 1, /** Maximum performance governor */ + VPU_GOV_ON_DEMAND = 2, /** On Demand frequency control governor */ + VPU_GOV_POWER_SAVE = 3, /** Power save governor */ + VPU_GOV_ON_DEMAND_PRIORITY_AWARE = 4 /** On Demand priority based governor */ }; struct vpu_boot_params { u32 magic; u32 vpu_id; u32 vpu_count; - u32 pad0[5]; - /* Clock frequencies: 0x20 - 0xFF */ + u32 reserved_0[5]; + /** Clock frequencies: 0x20 - 0xFF */ u32 frequency; - u32 pll[VPU_BOOT_PLL_COUNT][VPU_BOOT_PLL_OUT_COUNT]; + u32 reserved_1[12]; u32 perf_clk_frequency; - u32 pad1[42]; - /* Memory regions: 0x100 - 0x1FF */ + u32 reserved_2[42]; + /** Memory regions: 0x100 - 0x1FF */ u64 ipc_header_area_start; u32 ipc_header_area_size; u64 shared_region_base; @@ -234,41 +233,24 @@ struct vpu_boot_params { u32 global_aliased_pio_size; u32 autoconfig; struct vpu_boot_l2_cache_config cache_defaults[VPU_BOOT_L2_CACHE_CFG_NUM]; - u64 global_memory_allocator_base; - u32 global_memory_allocator_size; + u32 reserved_3[3]; /** * ShaveNN FW section VPU base address * On VPU2.7 HW this address must be within 2GB range starting from L2C_PAGE_TABLE base */ u64 shave_nn_fw_base; - u64 save_restore_ret_address; /* stores the address of FW's restore entry point */ - u32 pad2[43]; - /* IRQ re-direct numbers: 0x200 - 0x2FF */ + u64 save_restore_ret_address; /** stores the address of FW's restore entry point */ + u32 reserved_4[43]; + /** IRQ re-direct numbers: 0x200 - 0x2FF */ s32 watchdog_irq_mss; s32 watchdog_irq_nce; - /* ARM -> VPU doorbell interrupt. ARM is notifying VPU of async command or compute job. */ + /** ARM -> VPU doorbell interrupt. ARM is notifying VPU of async command or compute job. */ u32 host_to_vpu_irq; - /* VPU -> ARM job done interrupt. VPU is notifying ARM of compute job completion. */ + /** VPU -> ARM job done interrupt. VPU is notifying ARM of compute job completion. */ u32 job_done_irq; - /* VPU -> ARM IRQ line to use to request MMU update. */ - u32 mmu_update_request_irq; - /* ARM -> VPU IRQ line to use to notify of MMU update completion. */ - u32 mmu_update_done_irq; - /* ARM -> VPU IRQ line to use to request power level change. */ - u32 set_power_level_irq; - /* VPU -> ARM IRQ line to use to notify of power level change completion. */ - u32 set_power_level_done_irq; - /* VPU -> ARM IRQ line to use to notify of VPU idle state change */ - u32 set_vpu_idle_update_irq; - /* VPU -> ARM IRQ line to use to request counter reset. */ - u32 metric_query_event_irq; - /* ARM -> VPU IRQ line to use to notify of counter reset completion. */ - u32 metric_query_event_done_irq; - /* VPU -> ARM IRQ line to use to notify of preemption completion. */ - u32 preemption_done_irq; - /* Padding. */ - u32 pad3[52]; - /* Silicon information: 0x300 - 0x3FF */ + /** Padding. */ + u32 reserved_5[60]; + /** Silicon information: 0x300 - 0x3FF */ u32 host_version_id; u32 si_stepping; u64 device_id; @@ -294,7 +276,7 @@ struct vpu_boot_params { u32 crit_tracing_buff_size; u64 verbose_tracing_buff_addr; u32 verbose_tracing_buff_size; - u64 verbose_tracing_sw_component_mask; /* TO BE REMOVED */ + u64 verbose_tracing_sw_component_mask; /** TO BE REMOVED */ /** * Mask of destinations to which logging messages are delivered; bitwise OR * of values defined in vpu_trace_destination enum. @@ -308,11 +290,7 @@ struct vpu_boot_params { /** Mask of trace message formats supported by the driver */ u64 tracing_buff_message_format_mask; u64 trace_reserved_1[2]; - /** - * Period at which the VPU reads the temp sensor values into MMIO, on - * platforms where that is necessary (in ms). 0 to disable reads. - */ - u32 temp_sensor_period_ms; + u32 reserved_6; /** PLL ratio for efficient clock frequency */ u32 pn_freq_pll_ratio; /** @@ -347,11 +325,11 @@ struct vpu_boot_params { * 1: IPC message required to save state on D0i3 entry flow. */ u32 d0i3_delayed_entry; - /* Time spent by VPU in D0i3 state */ + /** Time spent by VPU in D0i3 state */ u64 d0i3_residency_time_us; - /* Value of VPU perf counter at the time of entering D0i3 state . */ + /** Value of VPU perf counter at the time of entering D0i3 state . */ u64 d0i3_entry_vpu_ts; - /* + /** * The system time of the host operating system in microseconds. * E.g the number of microseconds since 1st of January 1970, or whatever * date the host operating system uses to maintain system time. @@ -359,57 +337,52 @@ struct vpu_boot_params { * The KMD is required to update this value on every VPU reset. */ u64 system_time_us; - u32 pad4[2]; - /* + u32 reserved_7[2]; + /** * The delta between device monotonic time and the current value of the * HW timestamp register, in ticks. Written by the firmware during boot. * Can be used by the KMD to calculate device time. */ u64 device_time_delta_ticks; - u32 pad7[14]; - /* Warm boot information: 0x400 - 0x43F */ - u32 warm_boot_sections_count; - u32 warm_boot_start_address_reference; - u32 warm_boot_section_info_address_offset; - u32 pad5[13]; - /* Power States transitions timestamps: 0x440 - 0x46F*/ - struct { - /* VPU_IDLE -> VPU_ACTIVE transition initiated timestamp */ + u32 reserved_8[30]; + /** Power States transitions timestamps: 0x440 - 0x46F*/ + struct power_states_timestamps { + /** VPU_IDLE -> VPU_ACTIVE transition initiated timestamp */ u64 vpu_active_state_requested; - /* VPU_IDLE -> VPU_ACTIVE transition completed timestamp */ + /** VPU_IDLE -> VPU_ACTIVE transition completed timestamp */ u64 vpu_active_state_achieved; - /* VPU_ACTIVE -> VPU_IDLE transition initiated timestamp */ + /** VPU_ACTIVE -> VPU_IDLE transition initiated timestamp */ u64 vpu_idle_state_requested; - /* VPU_ACTIVE -> VPU_IDLE transition completed timestamp */ + /** VPU_ACTIVE -> VPU_IDLE transition completed timestamp */ u64 vpu_idle_state_achieved; - /* VPU_IDLE -> VPU_STANDBY transition initiated timestamp */ + /** VPU_IDLE -> VPU_STANDBY transition initiated timestamp */ u64 vpu_standby_state_requested; - /* VPU_IDLE -> VPU_STANDBY transition completed timestamp */ + /** VPU_IDLE -> VPU_STANDBY transition completed timestamp */ u64 vpu_standby_state_achieved; } power_states_timestamps; - /* VPU scheduling mode. Values defined by VPU_SCHEDULING_MODE_* macros. */ + /** VPU scheduling mode. Values defined by VPU_SCHEDULING_MODE_* macros. */ u32 vpu_scheduling_mode; - /* Present call period in milliseconds. */ + /** Present call period in milliseconds. */ u32 vpu_focus_present_timer_ms; - /* VPU ECC Signaling */ + /** VPU ECC Signaling */ u32 vpu_uses_ecc_mca_signal; - /* Values defined by POWER_PROFILE* macros */ + /** Values defined by POWER_PROFILE* macros */ u32 power_profile; - /* Microsecond value for DCT active cycle */ + /** Microsecond value for DCT active cycle */ u32 dct_active_us; - /* Microsecond value for DCT inactive cycle */ + /** Microsecond value for DCT inactive cycle */ u32 dct_inactive_us; - /* Unused/reserved: 0x488 - 0xFFF */ - u32 pad6[734]; + /** Unused/reserved: 0x488 - 0xFFF */ + u32 reserved_9[734]; }; -/* Magic numbers set between host and vpu to detect corruption of tracing init */ +/** Magic numbers set between host and vpu to detect corruption of tracing init */ #define VPU_TRACING_BUFFER_CANARY (0xCAFECAFE) -/* Tracing buffer message format definitions */ +/** Tracing buffer message format definitions */ #define VPU_TRACING_FORMAT_STRING 0 #define VPU_TRACING_FORMAT_MIPI 2 -/* +/** * Header of the tracing buffer. * The below defined header will be stored at the beginning of * each allocated tracing buffer, followed by a series of 256b @@ -421,53 +394,55 @@ struct vpu_tracing_buffer_header { * @see VPU_TRACING_BUFFER_CANARY */ u32 host_canary_start; - /* offset from start of buffer for trace entries */ + /** offset from start of buffer for trace entries */ u32 read_index; - /* keeps track of wrapping on the reader side */ + /** keeps track of wrapping on the reader side */ u32 read_wrap_count; u32 pad_to_cache_line_size_0[13]; - /* End of first cache line */ + /** End of first cache line */ /** * Magic number set by host to detect corruption * @see VPU_TRACING_BUFFER_CANARY */ u32 vpu_canary_start; - /* offset from start of buffer from write start */ + /** offset from start of buffer from write start */ u32 write_index; - /* counter for buffer wrapping */ + /** counter for buffer wrapping */ u32 wrap_count; - /* legacy field - do not use */ + /** legacy field - do not use */ u32 reserved_0; /** - * Size of the log buffer include this header (@header_size) and space - * reserved for all messages. If @alignment` is greater that 0 the @Size - * must be multiple of @Alignment. + * Size of the log buffer including this header (`header_size`) and space + * reserved for all messages. If `alignment` is greater than 0, the `size` + * must be a multiple of `alignment`. */ u32 size; - /* Header version */ + /** Header version */ u16 header_version; - /* Header size */ + /** Header size */ u16 header_size; - /* + /** * Format of the messages in the trace buffer * 0 - null terminated string * 1 - size + null terminated string * 2 - MIPI-SysT encoding */ u32 format; - /* + /** * Message alignment * 0 - messages are place 1 after another * n - every message starts and multiple on offset */ - u32 alignment; /* 64, 128, 256 */ - /* Name of the logging entity, i.e "LRT", "LNN", "SHV0", etc */ + u32 alignment; /** 64, 128, 256 */ + /** Name of the logging entity, i.e "LRT", "LNN", "SHV0", etc */ char name[16]; u32 pad_to_cache_line_size_1[4]; - /* End of second cache line */ + /** End of second cache line */ }; #pragma pack(pop) #endif + +///@} From bbe814bcb961ed2f30706ffdc806f18150709d2d Mon Sep 17 00:00:00 2001 From: Haikun Zhou Date: Thu, 26 Feb 2026 16:32:35 +0800 Subject: [PATCH 02/23] drm/panel-edp: Change BOE NV140WUM-N64 timings This screen timing requires a backlight off time of more than 100ms from the end of the data stream to avoid screen flickering and red light caused by screen material limitations. Signed-off-by: Haikun Zhou Reviewed-by: Douglas Anderson Signed-off-by: Douglas Anderson Link: https://patch.msgid.link/20260226083235.2176689-1-zhouhaikun5@huaqin.corp-partner.google.com --- drivers/gpu/drm/panel/panel-edp.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/panel/panel-edp.c b/drivers/gpu/drm/panel/panel-edp.c index f5f0e2c505b6..0fce4452dbe7 100644 --- a/drivers/gpu/drm/panel/panel-edp.c +++ b/drivers/gpu/drm/panel/panel-edp.c @@ -1814,6 +1814,13 @@ static const struct panel_delay delay_200_500_e200 = { .enable = 200, }; +static const struct panel_delay delay_200_500_e200_d100 = { + .hpd_absent = 200, + .unprepare = 500, + .enable = 200, + .disable = 100, +}; + static const struct panel_delay delay_200_500_e200_d200 = { .hpd_absent = 200, .unprepare = 500, @@ -2014,7 +2021,7 @@ static const struct edp_panel_entry edp_panels[] = { EDP_PANEL_ENTRY('B', 'O', 'E', 0x0c93, &delay_200_500_e200, "Unknown"), EDP_PANEL_ENTRY('B', 'O', 'E', 0x0cb6, &delay_200_500_e200, "NT116WHM-N44"), EDP_PANEL_ENTRY('B', 'O', 'E', 0x0cf2, &delay_200_500_e200, "NV156FHM-N4S"), - EDP_PANEL_ENTRY('B', 'O', 'E', 0x0cf6, &delay_200_500_e200, "NV140WUM-N64"), + EDP_PANEL_ENTRY('B', 'O', 'E', 0x0cf6, &delay_200_500_e200_d100, "NV140WUM-N64"), EDP_PANEL_ENTRY('B', 'O', 'E', 0x0cfa, &delay_200_500_e50, "NV116WHM-A4D"), EDP_PANEL_ENTRY('B', 'O', 'E', 0x0d45, &delay_200_500_e80, "NV116WHM-N4B"), EDP_PANEL_ENTRY('B', 'O', 'E', 0x0d73, &delay_200_500_e80, "NE140WUM-N6S"), From 4827d6d83f079f7d58d6ec0ed49f85dd2b90837f Mon Sep 17 00:00:00 2001 From: Philipp Stanner Date: Thu, 8 Jan 2026 09:30:20 +0100 Subject: [PATCH 03/23] drm/sched: Remove racy hack from drm_sched_fini() drm_sched_fini() contained a hack to work around a race in amdgpu. According to AMD, the hack should not be necessary anymore. In case there should have been undetected users, commit 975ca62a014c ("drm/sched: Add warning for removing hack in drm_sched_fini()") had added a warning one release cycle ago. Thus, it can be derived that the hack can be savely removed by now. Remove the hack. Acked-by: Danilo Krummrich Signed-off-by: Philipp Stanner Link: https://patch.msgid.link/20260108083019.63532-2-phasta@kernel.org --- drivers/gpu/drm/scheduler/sched_main.c | 38 +------------------------- 1 file changed, 1 insertion(+), 37 deletions(-) diff --git a/drivers/gpu/drm/scheduler/sched_main.c b/drivers/gpu/drm/scheduler/sched_main.c index e6ee35406165..13fa55aed3da 100644 --- a/drivers/gpu/drm/scheduler/sched_main.c +++ b/drivers/gpu/drm/scheduler/sched_main.c @@ -1418,48 +1418,12 @@ static void drm_sched_cancel_remaining_jobs(struct drm_gpu_scheduler *sched) */ void drm_sched_fini(struct drm_gpu_scheduler *sched) { - struct drm_sched_entity *s_entity; int i; drm_sched_wqueue_stop(sched); - for (i = DRM_SCHED_PRIORITY_KERNEL; i < sched->num_rqs; i++) { - struct drm_sched_rq *rq = sched->sched_rq[i]; - - spin_lock(&rq->lock); - list_for_each_entry(s_entity, &rq->entities, list) { - /* - * Prevents reinsertion and marks job_queue as idle, - * it will be removed from the rq in drm_sched_entity_fini() - * eventually - * - * FIXME: - * This lacks the proper spin_lock(&s_entity->lock) and - * is, therefore, a race condition. Most notably, it - * can race with drm_sched_entity_push_job(). The lock - * cannot be taken here, however, because this would - * lead to lock inversion -> deadlock. - * - * The best solution probably is to enforce the life - * time rule of all entities having to be torn down - * before their scheduler. Then, however, locking could - * be dropped alltogether from this function. - * - * For now, this remains a potential race in all - * drivers that keep entities alive for longer than - * the scheduler. - * - * The READ_ONCE() is there to make the lockless read - * (warning about the lockless write below) slightly - * less broken... - */ - if (!READ_ONCE(s_entity->stopped)) - dev_warn(sched->dev, "Tearing down scheduler with active entities!\n"); - s_entity->stopped = true; - } - spin_unlock(&rq->lock); + for (i = DRM_SCHED_PRIORITY_KERNEL; i < sched->num_rqs; i++) kfree(sched->sched_rq[i]); - } /* Wakeup everyone stuck in drm_sched_entity_flush for this scheduler */ wake_up_all(&sched->job_scheduled); From 5ea5b6ff0d63aef1dc3fb25445acea183f61a934 Mon Sep 17 00:00:00 2001 From: Maxime Ripard Date: Tue, 24 Feb 2026 12:03:10 +0100 Subject: [PATCH 04/23] drm/tests: Mark slow tests as slow Some DRM tests cross the 1s execution time threshold that defines a test as slow. Let's flag them as such. Reviewed-by: Arunpravin Paneer Selvam Reviewed-by: Tvrtko Ursulin Reviewed-by: Thomas Zimmermann Link: https://patch.msgid.link/20260224110310.1854608-1-mripard@kernel.org Signed-off-by: Maxime Ripard --- drivers/gpu/drm/scheduler/tests/tests_basic.c | 4 ++-- drivers/gpu/tests/gpu_buddy_test.c | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/gpu/drm/scheduler/tests/tests_basic.c b/drivers/gpu/drm/scheduler/tests/tests_basic.c index 82a41a456b0a..a5a5a35a87b0 100644 --- a/drivers/gpu/drm/scheduler/tests/tests_basic.c +++ b/drivers/gpu/drm/scheduler/tests/tests_basic.c @@ -421,7 +421,7 @@ static void drm_sched_change_priority(struct kunit *test) static struct kunit_case drm_sched_priority_tests[] = { KUNIT_CASE(drm_sched_priorities), - KUNIT_CASE(drm_sched_change_priority), + KUNIT_CASE_SLOW(drm_sched_change_priority), {} }; @@ -546,7 +546,7 @@ static void drm_sched_test_credits(struct kunit *test) } static struct kunit_case drm_sched_credits_tests[] = { - KUNIT_CASE(drm_sched_test_credits), + KUNIT_CASE_SLOW(drm_sched_test_credits), {} }; diff --git a/drivers/gpu/tests/gpu_buddy_test.c b/drivers/gpu/tests/gpu_buddy_test.c index 450e71deed90..5429010f34d3 100644 --- a/drivers/gpu/tests/gpu_buddy_test.c +++ b/drivers/gpu/tests/gpu_buddy_test.c @@ -910,7 +910,7 @@ static struct kunit_case gpu_buddy_tests[] = { KUNIT_CASE(gpu_test_buddy_alloc_contiguous), KUNIT_CASE(gpu_test_buddy_alloc_clear), KUNIT_CASE(gpu_test_buddy_alloc_range_bias), - KUNIT_CASE(gpu_test_buddy_fragmentation_performance), + KUNIT_CASE_SLOW(gpu_test_buddy_fragmentation_performance), KUNIT_CASE(gpu_test_buddy_alloc_exceeds_max_order), {} }; From 41dae5ac5e157b0bb260f381eb3df2f4a4610205 Mon Sep 17 00:00:00 2001 From: Thomas Zimmermann Date: Fri, 27 Feb 2026 14:31:05 +0100 Subject: [PATCH 05/23] drm/nouveau: Test for imported buffers with drm_gem_is_imported() Instead of testing import_attach for imported GEM buffers, invoke drm_gem_is_imported() to do the test. The test itself does not change. Signed-off-by: Thomas Zimmermann Cc: Lyude Paul Cc: Danilo Krummrich Cc: nouveau@lists.freedesktop.org Link: https://patch.msgid.link/20260227133113.235940-6-tzimmermann@suse.de Signed-off-by: Danilo Krummrich --- drivers/gpu/drm/nouveau/nouveau_bo.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/nouveau/nouveau_bo.c b/drivers/gpu/drm/nouveau/nouveau_bo.c index 3c7d2e5b3850..0e8de6d4b36f 100644 --- a/drivers/gpu/drm/nouveau/nouveau_bo.c +++ b/drivers/gpu/drm/nouveau/nouveau_bo.c @@ -144,7 +144,7 @@ nouveau_bo_del_ttm(struct ttm_buffer_object *bo) nouveau_bo_del_io_reserve_lru(bo); nv10_bo_put_tile_region(dev, nvbo->tile, NULL); - if (bo->base.import_attach) + if (drm_gem_is_imported(&bo->base)) drm_prime_gem_destroy(&bo->base, bo->sg); /* From 6618c0fd0cb7dbe6cd99f8c3d7f4673f73ea2019 Mon Sep 17 00:00:00 2001 From: Thomas Zimmermann Date: Fri, 27 Feb 2026 14:31:06 +0100 Subject: [PATCH 06/23] drm/panfrost: Test for imported buffers with drm_gem_is_imported() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Instead of testing import_attach for imported GEM buffers, invoke drm_gem_is_imported() to do the test. The test itself does not change. Signed-off-by: Thomas Zimmermann Cc: Boris Brezillon Cc: Rob Herring Cc: Steven Price Cc: "Adrián Larumbe" Reviewed-by: Steven Price Reviewed-by: Boris Brezillon Signed-off-by: Steven Price Link: https://patch.msgid.link/20260227133113.235940-7-tzimmermann@suse.de --- drivers/gpu/drm/panfrost/panfrost_gem.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/panfrost/panfrost_gem.c b/drivers/gpu/drm/panfrost/panfrost_gem.c index 822633da741e..3a7fce428898 100644 --- a/drivers/gpu/drm/panfrost/panfrost_gem.c +++ b/drivers/gpu/drm/panfrost/panfrost_gem.c @@ -702,7 +702,7 @@ static void panfrost_gem_debugfs_bo_print(struct panfrost_gem_object *bo, resident_size, drm_vma_node_start(&bo->base.base.vma_node)); - if (bo->base.base.import_attach) + if (drm_gem_is_imported(&bo->base.base)) gem_state_flags |= PANFROST_DEBUGFS_GEM_STATE_FLAG_IMPORTED; if (bo->base.base.dma_buf) gem_state_flags |= PANFROST_DEBUGFS_GEM_STATE_FLAG_EXPORTED; From 0dd7a23da214279cfb77b0eb03c38614867524d7 Mon Sep 17 00:00:00 2001 From: Thomas Zimmermann Date: Fri, 27 Feb 2026 14:31:07 +0100 Subject: [PATCH 07/23] drm/panthor: Test for imported buffers with drm_gem_is_imported() Instead of testing import_attach for imported GEM buffers, invoke drm_gem_is_imported() to do the test. The test itself does not change. Signed-off-by: Thomas Zimmermann Cc: Boris Brezillon Cc: Steven Price Cc: Liviu Dudau Reviewed-by: Steven Price Reviewed-by: Boris Brezillon Reviewed-by: Liviu Dudau Signed-off-by: Steven Price Link: https://patch.msgid.link/20260227133113.235940-8-tzimmermann@suse.de --- drivers/gpu/drm/panthor/panthor_gem.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/panthor/panthor_gem.c b/drivers/gpu/drm/panthor/panthor_gem.c index 4b4575dd6e90..6d14b0269574 100644 --- a/drivers/gpu/drm/panthor/panthor_gem.c +++ b/drivers/gpu/drm/panthor/panthor_gem.c @@ -666,7 +666,7 @@ static void panthor_gem_debugfs_bo_print(struct panthor_gem_object *bo, resident_size, drm_vma_node_start(&bo->base.base.vma_node)); - if (bo->base.base.import_attach) + if (drm_gem_is_imported(&bo->base.base)) gem_state_flags |= PANTHOR_DEBUGFS_GEM_STATE_FLAG_IMPORTED; if (bo->base.base.dma_buf) gem_state_flags |= PANTHOR_DEBUGFS_GEM_STATE_FLAG_EXPORTED; From 01086b97dcc92799bd03cd243ed6061fd21db7a6 Mon Sep 17 00:00:00 2001 From: Bart Van Assche Date: Fri, 27 Feb 2026 08:55:01 -0800 Subject: [PATCH 08/23] dma-buf: Suppress a thread-safety complaint MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Handle all possible dma_resv_lock() return values. This patch prepares for enabling compile-time thread-safety analysis. This will cause the compiler to check whether all dma_resv_lock() return values are handled. Signed-off-by: Bart Van Assche Reviewed-by: Christian König Signed-off-by: Christian König Link: https://lore.kernel.org/r/20260227165501.2062829-1-bvanassche@acm.org --- drivers/dma-buf/dma-resv.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/drivers/dma-buf/dma-resv.c b/drivers/dma-buf/dma-resv.c index bea3e9858aca..ce9e6c04897f 100644 --- a/drivers/dma-buf/dma-resv.c +++ b/drivers/dma-buf/dma-resv.c @@ -790,8 +790,11 @@ static int __init dma_resv_lockdep(void) mmap_read_lock(mm); ww_acquire_init(&ctx, &reservation_ww_class); ret = dma_resv_lock(&obj, &ctx); - if (ret == -EDEADLK) + if (ret) { + /* Only EDEADLK from the error injection is possible here */ + WARN_ON(ret != -EDEADLK); dma_resv_lock_slow(&obj, &ctx); + } fs_reclaim_acquire(GFP_KERNEL); /* for unmap_mapping_range on trylocked buffer objects in shrinkers */ i_mmap_lock_write(&mapping); From abdcd4c3586dee8c190ac932cb544036b0c937b9 Mon Sep 17 00:00:00 2001 From: Sanjay Yadav Date: Fri, 27 Feb 2026 18:30:38 +0530 Subject: [PATCH 09/23] gpu/buddy: Introduce gpu_buddy_assert() for kunit-aware assertions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Introduce gpu_buddy_assert(), a small helper that wraps WARN_ON() and, when CONFIG_KUNIT is enabled, also calls kunit_fail_current_test() so that any active KUnit test is marked as failed. In non-KUnit builds the macro reduces to WARN_ON(), preserving existing behaviour. Stringify the asserted condition in the failure message to make it easy to identify which assertion fired. Leave the WARN_ON() in gpu_buddy_block_trim() unchanged, as it returns -EINVAL and the caller already observes the failure via the return code. Cc: Christian König Cc: Arunpravin Paneer Selvam Suggested-by: Matthew Auld Signed-off-by: Sanjay Yadav Reviewed-by: Arunpravin Paneer Selvam Signed-off-by: Arunpravin Paneer Selvam Link: https://patch.msgid.link/20260227130037.53615-2-sanjay.kumar.yadav@intel.com --- drivers/gpu/buddy.c | 36 ++++++++++++++++++++++++++++-------- 1 file changed, 28 insertions(+), 8 deletions(-) diff --git a/drivers/gpu/buddy.c b/drivers/gpu/buddy.c index b27761246d4b..da5a1222f46b 100644 --- a/drivers/gpu/buddy.c +++ b/drivers/gpu/buddy.c @@ -3,8 +3,7 @@ * Copyright © 2021 Intel Corporation */ -#include - +#include #include #include #include @@ -12,6 +11,28 @@ #include +/** + * gpu_buddy_assert - assert a condition in the buddy allocator + * @condition: condition expected to be true + * + * When CONFIG_KUNIT is enabled, evaluates @condition and, if false, triggers + * a WARN_ON() and also calls kunit_fail_current_test() so that any running + * kunit test is properly marked as failed. The stringified condition is + * included in the failure message for easy identification. + * + * When CONFIG_KUNIT is not enabled, this reduces to WARN_ON() so production + * builds retain the same warning semantics as before. + */ +#if IS_ENABLED(CONFIG_KUNIT) +#include +#define gpu_buddy_assert(condition) do { \ + if (WARN_ON(!(condition))) \ + kunit_fail_current_test("gpu_buddy_assert(" #condition ")"); \ +} while (0) +#else +#define gpu_buddy_assert(condition) WARN_ON(!(condition)) +#endif + static struct kmem_cache *slab_blocks; static unsigned int @@ -268,8 +289,8 @@ static int __force_merge(struct gpu_buddy *mm, if (!gpu_buddy_block_is_free(buddy)) continue; - WARN_ON(gpu_buddy_block_is_clear(block) == - gpu_buddy_block_is_clear(buddy)); + gpu_buddy_assert(gpu_buddy_block_is_clear(block) != + gpu_buddy_block_is_clear(buddy)); /* * Advance to the next node when the current node is the buddy, @@ -415,8 +436,7 @@ void gpu_buddy_fini(struct gpu_buddy *mm) start = gpu_buddy_block_offset(mm->roots[i]); __force_merge(mm, start, start + size, order); - if (WARN_ON(!gpu_buddy_block_is_free(mm->roots[i]))) - kunit_fail_current_test("buddy_fini() root"); + gpu_buddy_assert(gpu_buddy_block_is_free(mm->roots[i])); gpu_block_free(mm, mm->roots[i]); @@ -424,7 +444,7 @@ void gpu_buddy_fini(struct gpu_buddy *mm) size -= root_size; } - WARN_ON(mm->avail != mm->size); + gpu_buddy_assert(mm->avail == mm->size); for_each_free_tree(i) kfree(mm->free_trees[i]); @@ -541,7 +561,7 @@ static void __gpu_buddy_free_list(struct gpu_buddy *mm, { struct gpu_buddy_block *block, *on; - WARN_ON(mark_dirty && mark_clear); + gpu_buddy_assert(!(mark_dirty && mark_clear)); list_for_each_entry_safe(block, on, objects, link) { if (mark_clear) From b8eb97ead862de7a321dd55a901542a372f8f1cd Mon Sep 17 00:00:00 2001 From: Marek Vasut Date: Fri, 6 Feb 2026 13:57:53 +0100 Subject: [PATCH 10/23] drm/bridge: waveshare-dsi: Register and attach our DSI device at probe In order to avoid any probe ordering issue, the best practice is to move the secondary MIPI-DSI device registration and attachment to the MIPI-DSI host at probe time. Besides solving the probe ordering problems, this makes the bridge work with R-Car DU. The R-Car DU will attempt to locate the DSI host bridge in its own rcar_du_probe()->rcar_du_modeset_init()->rcar_du_encoder_init() by calling of_drm_find_bridge() which calls of_drm_find_and_get_bridge() and iterates over bridge_list to locate the DSI host bridge. However, unless the WS driver calls mipi_dsi_attach() in probe(), the DSI host bridge .attach callback rcar_mipi_dsi_host_attach() is not called and the DSI host bridge is not added into bridge_list. Therefore the of_drm_find_and_get_bridge() called from du_probe() will never find the DSI host bridge and probe will indefinitelly fail with -EPROBE_DEFER. The circular dependency here is, that if rcar_du_encoder_init() would manage to find the DSI host bridge, it would call the WS driver .attach callback ws_bridge_bridge_attach(), but this is too late and can never happen. This change avoids the circular dependency. Reviewed-by: Luca Ceresoli Signed-off-by: Marek Vasut Link: https://patch.msgid.link/20260206125801.78705-1-marek.vasut+renesas@mailbox.org Signed-off-by: Luca Ceresoli --- drivers/gpu/drm/bridge/waveshare-dsi.c | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/drivers/gpu/drm/bridge/waveshare-dsi.c b/drivers/gpu/drm/bridge/waveshare-dsi.c index 43f4e7412d72..9254446f5495 100644 --- a/drivers/gpu/drm/bridge/waveshare-dsi.c +++ b/drivers/gpu/drm/bridge/waveshare-dsi.c @@ -80,11 +80,6 @@ static int ws_bridge_bridge_attach(struct drm_bridge *bridge, enum drm_bridge_attach_flags flags) { struct ws_bridge *ws = bridge_to_ws_bridge(bridge); - int ret; - - ret = ws_bridge_attach_dsi(ws); - if (ret) - return ret; return drm_bridge_attach(encoder, ws->next_bridge, &ws->bridge, flags); @@ -179,7 +174,7 @@ static int ws_bridge_probe(struct i2c_client *i2c) ws->bridge.of_node = dev->of_node; devm_drm_bridge_add(dev, &ws->bridge); - return 0; + return ws_bridge_attach_dsi(ws); } static const struct of_device_id ws_bridge_of_ids[] = { From e0afbfe321d5131c56005f56fbf5d548340da749 Mon Sep 17 00:00:00 2001 From: Thomas Zimmermann Date: Fri, 6 Feb 2026 14:21:54 +0100 Subject: [PATCH 11/23] drm/client: Export drm_client_buffer_create() The helper drm_client_buffer_create() will be required by various drivers for fbdev emulation. Signed-off-by: Thomas Zimmermann Acked-by: Patrik Jakobsson Link: https://patch.msgid.link/20260206133458.226467-2-tzimmermann@suse.de --- drivers/gpu/drm/drm_client.c | 3 ++- include/drm/drm_client.h | 3 +++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/drm_client.c b/drivers/gpu/drm/drm_client.c index 6236ec46d62a..46c465bce98c 100644 --- a/drivers/gpu/drm/drm_client.c +++ b/drivers/gpu/drm/drm_client.c @@ -204,7 +204,7 @@ void drm_client_buffer_delete(struct drm_client_buffer *buffer) } EXPORT_SYMBOL(drm_client_buffer_delete); -static struct drm_client_buffer * +struct drm_client_buffer * drm_client_buffer_create(struct drm_client_dev *client, u32 width, u32 height, u32 format, u32 handle, u32 pitch) { @@ -265,6 +265,7 @@ drm_client_buffer_create(struct drm_client_dev *client, u32 width, u32 height, kfree(buffer); return ERR_PTR(ret); } +EXPORT_SYMBOL(drm_client_buffer_create); /** * drm_client_buffer_vmap_local - Map DRM client buffer into address space diff --git a/include/drm/drm_client.h b/include/drm/drm_client.h index c972a8a3385b..49a21f3dcb36 100644 --- a/include/drm/drm_client.h +++ b/include/drm/drm_client.h @@ -195,6 +195,9 @@ struct drm_client_buffer { struct drm_framebuffer *fb; }; +struct drm_client_buffer * +drm_client_buffer_create(struct drm_client_dev *client, u32 width, u32 height, + u32 format, u32 handle, u32 pitch); struct drm_client_buffer * drm_client_buffer_create_dumb(struct drm_client_dev *client, u32 width, u32 height, u32 format); void drm_client_buffer_delete(struct drm_client_buffer *buffer); From bbc1d2105481d47d2441ba061529c490ccb7f27f Mon Sep 17 00:00:00 2001 From: Thomas Zimmermann Date: Fri, 6 Feb 2026 14:21:55 +0100 Subject: [PATCH 12/23] drm/gma500: fbdev: Set framebuffer size to GEM object size Framebuffer emulation sets the size of the available memory to the value that has been requested. As the allocated GEM buffer object acts as full framebuffer memory, set the size to the value of the actually allocated buffer. Signed-off-by: Thomas Zimmermann Acked-by: Patrik Jakobsson Link: https://patch.msgid.link/20260206133458.226467-3-tzimmermann@suse.de --- drivers/gpu/drm/gma500/fbdev.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/gma500/fbdev.c b/drivers/gpu/drm/gma500/fbdev.c index c26926babc2a..f5e60ada6de2 100644 --- a/drivers/gpu/drm/gma500/fbdev.c +++ b/drivers/gpu/drm/gma500/fbdev.c @@ -171,12 +171,12 @@ int psb_fbdev_driver_fbdev_probe(struct drm_fb_helper *fb_helper, /* Accessed stolen memory directly */ info->screen_base = dev_priv->vram_addr + backing->offset; - info->screen_size = size; + info->screen_size = obj->size; drm_fb_helper_fill_info(info, fb_helper, sizes); info->fix.smem_start = dev_priv->stolen_base + backing->offset; - info->fix.smem_len = size; + info->fix.smem_len = obj->size; info->fix.ywrapstep = 0; info->fix.ypanstep = 0; info->fix.mmio_start = pci_resource_start(pdev, 0); From aaea2359a6ba7442a4cacb66d915d66b618fe9b6 Mon Sep 17 00:00:00 2001 From: Thomas Zimmermann Date: Fri, 6 Feb 2026 14:21:56 +0100 Subject: [PATCH 13/23] drm/gma500: fbdev: Calculate buffer geometry with format helpers Replace the geometry and size calculation in gma500's fbdev emulation with DRM format helpers. This consists of 4CC lookup from the fbdev parameters, format loockup, pitch calculation and size calculation. Then allocate the GEM buffer object for the framebuffer memory from the calculated size. As before, fallback to 16-bit colors if the stolen memory is insufficient for 32-bit colors. But look at the result from psb_gem_create() instead of guessing before the allocation. The new method is more reliable when others allocate from stolen video memory (e.g., cursors). Set up mode_cmd with the calculated values just before allocating the framebuffer. This code will later be replaced with a DRM client buffer. Signed-off-by: Thomas Zimmermann Acked-by: Patrik Jakobsson Link: https://patch.msgid.link/20260206133458.226467-4-tzimmermann@suse.de --- drivers/gpu/drm/gma500/fbdev.c | 62 ++++++++++++++++------------------ 1 file changed, 29 insertions(+), 33 deletions(-) diff --git a/drivers/gpu/drm/gma500/fbdev.c b/drivers/gpu/drm/gma500/fbdev.c index f5e60ada6de2..a93c3d2fe0ff 100644 --- a/drivers/gpu/drm/gma500/fbdev.c +++ b/drivers/gpu/drm/gma500/fbdev.c @@ -109,56 +109,52 @@ int psb_fbdev_driver_fbdev_probe(struct drm_fb_helper *fb_helper, struct drm_psb_private *dev_priv = to_drm_psb_private(dev); struct pci_dev *pdev = to_pci_dev(dev->dev); struct fb_info *info = fb_helper->info; + u32 fourcc, pitch; + u64 size; + const struct drm_format_info *format; struct drm_framebuffer *fb; struct drm_mode_fb_cmd2 mode_cmd = { }; - int size; int ret; struct psb_gem_object *backing; struct drm_gem_object *obj; - u32 bpp, depth; /* No 24-bit packed mode */ if (sizes->surface_bpp == 24) { sizes->surface_bpp = 32; sizes->surface_depth = 24; } - bpp = sizes->surface_bpp; - depth = sizes->surface_depth; - /* - * If the mode does not fit in 32 bit then switch to 16 bit to get - * a console on full resolution. The X mode setting server will - * allocate its own 32-bit GEM framebuffer. - */ - size = ALIGN(sizes->surface_width * DIV_ROUND_UP(bpp, 8), 64) * - sizes->surface_height; - size = ALIGN(size, PAGE_SIZE); - - if (size > dev_priv->vram_stolen_size) { - sizes->surface_bpp = 16; - sizes->surface_depth = 16; - } - bpp = sizes->surface_bpp; - depth = sizes->surface_depth; - - mode_cmd.width = sizes->surface_width; - mode_cmd.height = sizes->surface_height; - mode_cmd.pitches[0] = ALIGN(mode_cmd.width * DIV_ROUND_UP(bpp, 8), 64); - mode_cmd.pixel_format = drm_mode_legacy_fb_format(bpp, depth); - - size = mode_cmd.pitches[0] * mode_cmd.height; - size = ALIGN(size, PAGE_SIZE); +try_psb_gem_create: + fourcc = drm_mode_legacy_fb_format(sizes->surface_bpp, sizes->surface_depth); + format = drm_get_format_info(dev, fourcc, DRM_FORMAT_MOD_LINEAR); + pitch = ALIGN(drm_format_info_min_pitch(format, 0, sizes->surface_width), SZ_64); + size = ALIGN(pitch * sizes->surface_height, PAGE_SIZE); /* Allocate the framebuffer in the GTT with stolen page backing */ backing = psb_gem_create(dev, size, "fb", true, PAGE_SIZE); - if (IS_ERR(backing)) - return PTR_ERR(backing); + if (IS_ERR(backing)) { + ret = PTR_ERR(backing); + if (ret == -EBUSY && sizes->surface_bpp > 16) { + /* + * If the mode does not fit in 32 bit then switch to 16 bit to + * get a console on full resolution. User-space compositors will + * allocate their own 32-bit framebuffers. + */ + sizes->surface_bpp = 16; + sizes->surface_depth = 16; + goto try_psb_gem_create; + } + return ret; + } obj = &backing->base; - fb = psb_framebuffer_create(dev, - drm_get_format_info(dev, mode_cmd.pixel_format, - mode_cmd.modifier[0]), - &mode_cmd, obj); + mode_cmd.width = sizes->surface_width; + mode_cmd.height = sizes->surface_height; + mode_cmd.pixel_format = fourcc; + mode_cmd.pitches[0] = pitch; + mode_cmd.modifier[0] = DRM_FORMAT_MOD_LINEAR; + + fb = psb_framebuffer_create(dev, format, &mode_cmd, obj); if (IS_ERR(fb)) { ret = PTR_ERR(fb); goto err_drm_gem_object_put; From 581a5bfe758353d95d9829146c0b673843e36e25 Mon Sep 17 00:00:00 2001 From: Thomas Zimmermann Date: Fri, 6 Feb 2026 14:21:57 +0100 Subject: [PATCH 14/23] drm/gma500: fbdev: Use a DRM client buffer Replace the internal DRM framebuffer with a DRM client buffer. The client buffer allocates the DRM framebuffer on a file and also uses GEM object handles via the regular ADDFB2 interfaces. This unifies framebuffer allocation for clients in user space and gma500's internal fbdev emulation. Also simplify the clean-up side of the fbdev emulation. Later patches will allow for streamlining gma500's framebuffer code and DRM's fbdev emulation in general. Signed-off-by: Thomas Zimmermann Acked-by: Patrik Jakobsson Link: https://patch.msgid.link/20260206133458.226467-5-tzimmermann@suse.de --- drivers/gpu/drm/gma500/fbdev.c | 49 ++++++++++++++++++---------------- 1 file changed, 26 insertions(+), 23 deletions(-) diff --git a/drivers/gpu/drm/gma500/fbdev.c b/drivers/gpu/drm/gma500/fbdev.c index a93c3d2fe0ff..d1e93588234f 100644 --- a/drivers/gpu/drm/gma500/fbdev.c +++ b/drivers/gpu/drm/gma500/fbdev.c @@ -72,17 +72,10 @@ static int psb_fbdev_fb_mmap(struct fb_info *info, struct vm_area_struct *vma) static void psb_fbdev_fb_destroy(struct fb_info *info) { struct drm_fb_helper *fb_helper = info->par; - struct drm_framebuffer *fb = fb_helper->fb; - struct drm_gem_object *obj = fb->obj[0]; drm_fb_helper_fini(fb_helper); - drm_framebuffer_unregister_private(fb); - drm_framebuffer_cleanup(fb); - kfree(fb); - - drm_gem_object_put(obj); - + drm_client_buffer_delete(fb_helper->buffer); drm_client_release(&fb_helper->client); } @@ -105,18 +98,20 @@ static const struct drm_fb_helper_funcs psb_fbdev_fb_helper_funcs = { int psb_fbdev_driver_fbdev_probe(struct drm_fb_helper *fb_helper, struct drm_fb_helper_surface_size *sizes) { - struct drm_device *dev = fb_helper->dev; + struct drm_client_dev *client = &fb_helper->client; + struct drm_device *dev = client->dev; + struct drm_file *file = client->file; struct drm_psb_private *dev_priv = to_drm_psb_private(dev); struct pci_dev *pdev = to_pci_dev(dev->dev); struct fb_info *info = fb_helper->info; u32 fourcc, pitch; u64 size; const struct drm_format_info *format; - struct drm_framebuffer *fb; - struct drm_mode_fb_cmd2 mode_cmd = { }; - int ret; + struct drm_client_buffer *buffer; struct psb_gem_object *backing; struct drm_gem_object *obj; + u32 handle; + int ret; /* No 24-bit packed mode */ if (sizes->surface_bpp == 24) { @@ -148,20 +143,20 @@ int psb_fbdev_driver_fbdev_probe(struct drm_fb_helper *fb_helper, } obj = &backing->base; - mode_cmd.width = sizes->surface_width; - mode_cmd.height = sizes->surface_height; - mode_cmd.pixel_format = fourcc; - mode_cmd.pitches[0] = pitch; - mode_cmd.modifier[0] = DRM_FORMAT_MOD_LINEAR; - - fb = psb_framebuffer_create(dev, format, &mode_cmd, obj); - if (IS_ERR(fb)) { - ret = PTR_ERR(fb); + ret = drm_gem_handle_create(file, obj, &handle); + if (ret) goto err_drm_gem_object_put; + + buffer = drm_client_buffer_create(client, sizes->surface_width, sizes->surface_height, + fourcc, handle, pitch); + if (IS_ERR(buffer)) { + ret = PTR_ERR(buffer); + goto err_drm_gem_handle_delete; } fb_helper->funcs = &psb_fbdev_fb_helper_funcs; - fb_helper->fb = fb; + fb_helper->buffer = buffer; + fb_helper->fb = buffer->fb; info->fbops = &psb_fbdev_fb_ops; @@ -182,10 +177,18 @@ int psb_fbdev_driver_fbdev_probe(struct drm_fb_helper *fb_helper, /* Use default scratch pixmap (info->pixmap.flags = FB_PIXMAP_SYSTEM) */ - dev_dbg(dev->dev, "allocated %dx%d fb\n", fb->width, fb->height); + dev_dbg(dev->dev, "allocated %dx%d fb\n", buffer->fb->width, buffer->fb->height); + + /* The handle is only needed for creating the framebuffer.*/ + drm_gem_handle_delete(file, handle); + + /* The framebuffer still holds a references on the GEM object. */ + drm_gem_object_put(obj); return 0; +err_drm_gem_handle_delete: + drm_gem_handle_delete(file, handle); err_drm_gem_object_put: drm_gem_object_put(obj); return ret; From c70708c53ddbda691169f03f8dcbcc4f117518bf Mon Sep 17 00:00:00 2001 From: Thomas Zimmermann Date: Fri, 6 Feb 2026 14:21:58 +0100 Subject: [PATCH 15/23] drm/gma500: Create framebuffers with drm_gem_fb_create() Replace gma500's internal framebuffer creation with DRM's standard helper drm_gem_fb_create(). The result is equivalent. Only keep the existing tests for color depth and pitch alignment. v2: - rebase on upstream changes Signed-off-by: Thomas Zimmermann Acked-by: Patrik Jakobsson Link: https://patch.msgid.link/20260206133458.226467-6-tzimmermann@suse.de --- drivers/gpu/drm/gma500/framebuffer.c | 104 +++------------------------ drivers/gpu/drm/gma500/psb_drv.h | 6 -- 2 files changed, 9 insertions(+), 101 deletions(-) diff --git a/drivers/gpu/drm/gma500/framebuffer.c b/drivers/gpu/drm/gma500/framebuffer.c index fe1f43f0abff..37a9f666c0f2 100644 --- a/drivers/gpu/drm/gma500/framebuffer.c +++ b/drivers/gpu/drm/gma500/framebuffer.c @@ -12,111 +12,25 @@ #include "framebuffer.h" #include "psb_drv.h" -static const struct drm_framebuffer_funcs psb_fb_funcs = { - .destroy = drm_gem_fb_destroy, - .create_handle = drm_gem_fb_create_handle, -}; - -/** - * psb_framebuffer_init - initialize a framebuffer - * @dev: our DRM device - * @fb: framebuffer to set up - * @mode_cmd: mode description - * @obj: backing object - * - * Configure and fill in the boilerplate for our frame buffer. Return - * 0 on success or an error code if we fail. - */ -static int psb_framebuffer_init(struct drm_device *dev, - struct drm_framebuffer *fb, - const struct drm_format_info *info, - const struct drm_mode_fb_cmd2 *mode_cmd, - struct drm_gem_object *obj) +static struct drm_framebuffer * +psb_user_framebuffer_create(struct drm_device *dev, struct drm_file *filp, + const struct drm_format_info *info, + const struct drm_mode_fb_cmd2 *cmd) { - int ret; - /* * Reject unknown formats, YUV formats, and formats with more than * 4 bytes per pixel. */ if (!info->depth || info->cpp[0] > 4) - return -EINVAL; - - if (mode_cmd->pitches[0] & 63) - return -EINVAL; - - drm_helper_mode_fill_fb_struct(dev, fb, info, mode_cmd); - fb->obj[0] = obj; - ret = drm_framebuffer_init(dev, fb, &psb_fb_funcs); - if (ret) { - dev_err(dev->dev, "framebuffer init failed: %d\n", ret); - return ret; - } - return 0; -} - -/** - * psb_framebuffer_create - create a framebuffer backed by gt - * @dev: our DRM device - * @info: pixel format information - * @mode_cmd: the description of the requested mode - * @obj: the backing object - * - * Create a framebuffer object backed by the gt, and fill in the - * boilerplate required - * - * TODO: review object references - */ -struct drm_framebuffer *psb_framebuffer_create(struct drm_device *dev, - const struct drm_format_info *info, - const struct drm_mode_fb_cmd2 *mode_cmd, - struct drm_gem_object *obj) -{ - struct drm_framebuffer *fb; - int ret; - - fb = kzalloc_obj(*fb); - if (!fb) - return ERR_PTR(-ENOMEM); - - ret = psb_framebuffer_init(dev, fb, info, mode_cmd, obj); - if (ret) { - kfree(fb); - return ERR_PTR(ret); - } - return fb; -} - -/** - * psb_user_framebuffer_create - create framebuffer - * @dev: our DRM device - * @filp: client file - * @cmd: mode request - * - * Create a new framebuffer backed by a userspace GEM object - */ -static struct drm_framebuffer *psb_user_framebuffer_create - (struct drm_device *dev, struct drm_file *filp, - const struct drm_format_info *info, - const struct drm_mode_fb_cmd2 *cmd) -{ - struct drm_gem_object *obj; - struct drm_framebuffer *fb; + return ERR_PTR(-EINVAL); /* - * Find the GEM object and thus the gtt range object that is - * to back this space + * Pitch must be aligned to 64 bytes. */ - obj = drm_gem_object_lookup(filp, cmd->handles[0]); - if (obj == NULL) - return ERR_PTR(-ENOENT); + if (cmd->pitches[0] & 63) + return ERR_PTR(-EINVAL); - /* Let the core code do all the work */ - fb = psb_framebuffer_create(dev, info, cmd, obj); - if (IS_ERR(fb)) - drm_gem_object_put(obj); - - return fb; + return drm_gem_fb_create(dev, filp, info, cmd); } static const struct drm_mode_config_funcs psb_mode_funcs = { diff --git a/drivers/gpu/drm/gma500/psb_drv.h b/drivers/gpu/drm/gma500/psb_drv.h index 0b27112ec46f..db197b865b90 100644 --- a/drivers/gpu/drm/gma500/psb_drv.h +++ b/drivers/gpu/drm/gma500/psb_drv.h @@ -592,12 +592,6 @@ struct psb_ops { extern void psb_modeset_init(struct drm_device *dev); extern void psb_modeset_cleanup(struct drm_device *dev); -/* framebuffer */ -struct drm_framebuffer *psb_framebuffer_create(struct drm_device *dev, - const struct drm_format_info *info, - const struct drm_mode_fb_cmd2 *mode_cmd, - struct drm_gem_object *obj); - /* fbdev */ #if defined(CONFIG_DRM_FBDEV_EMULATION) int psb_fbdev_driver_fbdev_probe(struct drm_fb_helper *fb_helper, From ad595a8aeeabfbfcc0026b9892a8155bc0763416 Mon Sep 17 00:00:00 2001 From: Thomas Zimmermann Date: Fri, 27 Feb 2026 14:31:11 +0100 Subject: [PATCH 16/23] drm/vc4: Test for imported buffers with drm_gem_is_imported() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Instead of testing import_attach for imported GEM buffers, invoke drm_gem_is_imported() to do the test. The test itself does not change. Signed-off-by: Thomas Zimmermann Cc: Maxime Ripard Cc: Dave Stevenson Cc: "Maíra Canal" Cc: Raspberry Pi Kernel Maintenance Reviewed-by: Maíra Canal Link: https://patch.msgid.link/20260227133113.235940-12-tzimmermann@suse.de --- drivers/gpu/drm/vc4/vc4_bo.c | 2 +- drivers/gpu/drm/vc4/vc4_gem.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/vc4/vc4_bo.c b/drivers/gpu/drm/vc4/vc4_bo.c index 1f93bc5a3d02..f45ba47b4ba8 100644 --- a/drivers/gpu/drm/vc4/vc4_bo.c +++ b/drivers/gpu/drm/vc4/vc4_bo.c @@ -556,7 +556,7 @@ static void vc4_free_object(struct drm_gem_object *gem_bo) mutex_lock(&vc4->bo_lock); /* If the object references someone else's memory, we can't cache it. */ - if (gem_bo->import_attach) { + if (drm_gem_is_imported(gem_bo)) { vc4_bo_destroy(bo); goto out; } diff --git a/drivers/gpu/drm/vc4/vc4_gem.c b/drivers/gpu/drm/vc4/vc4_gem.c index ad8cbd727b80..2d3df5e621c1 100644 --- a/drivers/gpu/drm/vc4/vc4_gem.c +++ b/drivers/gpu/drm/vc4/vc4_gem.c @@ -1250,7 +1250,7 @@ int vc4_gem_madvise_ioctl(struct drm_device *dev, void *data, /* Not sure it's safe to purge imported BOs. Let's just assume it's * not until proven otherwise. */ - if (gem_obj->import_attach) { + if (drm_gem_is_imported(gem_obj)) { DRM_DEBUG("madvise not supported on imported BOs\n"); ret = -EINVAL; goto out_put_gem; From 86a14330bf0598ce89177cebcc19e2cf810abf44 Mon Sep 17 00:00:00 2001 From: Karol Wachowski Date: Mon, 2 Mar 2026 21:22:07 +0100 Subject: [PATCH 17/23] accel/ivpu: Limit number of maximum contexts and doorbells per user Implement per-user resource limits to prevent resource exhaustion. Root users can allocate all available contexts (128) and doorbells (255), while non-root users are limited to half of the available resources (64 contexts and 127 doorbells respectively). This prevents scenarios where a single user could monopolize NPU resources and starve other users on multi-user systems. Change doorbell ID and command queue ID allocation errors to debug messages as those are user triggered. Signed-off-by: Karol Wachowski Reviewed-by: Lizhi Hou Signed-off-by: Maciej Falkowski Link: https://patch.msgid.link/20260302202207.469442-1-maciej.falkowski@linux.intel.com --- drivers/accel/ivpu/ivpu_drv.c | 94 ++++++++++++++++++++++++++++++++--- drivers/accel/ivpu/ivpu_drv.h | 26 ++++++++-- drivers/accel/ivpu/ivpu_job.c | 36 ++++++++++---- 3 files changed, 136 insertions(+), 20 deletions(-) diff --git a/drivers/accel/ivpu/ivpu_drv.c b/drivers/accel/ivpu/ivpu_drv.c index 5900a40c7a78..dd3a486df5f1 100644 --- a/drivers/accel/ivpu/ivpu_drv.c +++ b/drivers/accel/ivpu/ivpu_drv.c @@ -67,6 +67,73 @@ bool ivpu_force_snoop; module_param_named(force_snoop, ivpu_force_snoop, bool, 0444); MODULE_PARM_DESC(force_snoop, "Force snooping for NPU host memory access"); +static struct ivpu_user_limits *ivpu_user_limits_alloc(struct ivpu_device *vdev, uid_t uid) +{ + struct ivpu_user_limits *limits; + + limits = kzalloc_obj(*limits); + if (!limits) + return ERR_PTR(-ENOMEM); + + kref_init(&limits->ref); + atomic_set(&limits->db_count, 0); + limits->vdev = vdev; + limits->uid = uid; + + /* Allow root user to allocate all contexts */ + if (uid == 0) { + limits->max_ctx_count = ivpu_get_context_count(vdev); + limits->max_db_count = ivpu_get_doorbell_count(vdev); + } else { + limits->max_ctx_count = ivpu_get_context_count(vdev) / 2; + limits->max_db_count = ivpu_get_doorbell_count(vdev) / 2; + } + + hash_add(vdev->user_limits, &limits->hash_node, uid); + + return limits; +} + +static struct ivpu_user_limits *ivpu_user_limits_get(struct ivpu_device *vdev) +{ + struct ivpu_user_limits *limits; + uid_t uid = current_uid().val; + + guard(mutex)(&vdev->user_limits_lock); + + hash_for_each_possible(vdev->user_limits, limits, hash_node, uid) { + if (limits->uid == uid) { + if (kref_read(&limits->ref) >= limits->max_ctx_count) { + ivpu_dbg(vdev, IOCTL, "User %u exceeded max ctx count %u\n", uid, + limits->max_ctx_count); + return ERR_PTR(-EMFILE); + } + + kref_get(&limits->ref); + return limits; + } + } + + return ivpu_user_limits_alloc(vdev, uid); +} + +static void ivpu_user_limits_release(struct kref *ref) +{ + struct ivpu_user_limits *limits = container_of(ref, struct ivpu_user_limits, ref); + struct ivpu_device *vdev = limits->vdev; + + lockdep_assert_held(&vdev->user_limits_lock); + drm_WARN_ON(&vdev->drm, atomic_read(&limits->db_count)); + hash_del(&limits->hash_node); + kfree(limits); +} + +static void ivpu_user_limits_put(struct ivpu_device *vdev, struct ivpu_user_limits *limits) +{ + guard(mutex)(&vdev->user_limits_lock); + kref_put(&limits->ref, ivpu_user_limits_release); +} + struct ivpu_file_priv *ivpu_file_priv_get(struct ivpu_file_priv *file_priv) { struct ivpu_device *vdev = file_priv->vdev; @@ -110,6 +177,7 @@ static void file_priv_release(struct kref *ref) mutex_unlock(&vdev->context_list_lock); pm_runtime_put_autosuspend(vdev->drm.dev); + ivpu_user_limits_put(vdev, file_priv->user_limits); mutex_destroy(&file_priv->ms_lock); mutex_destroy(&file_priv->lock); kfree(file_priv); @@ -169,7 +237,7 @@ static int ivpu_get_param_ioctl(struct drm_device *dev, void *data, struct drm_f args->value = ivpu_hw_dpu_max_freq_get(vdev); break; case DRM_IVPU_PARAM_NUM_CONTEXTS: - args->value = ivpu_get_context_count(vdev); + args->value = file_priv->user_limits->max_ctx_count; break; case DRM_IVPU_PARAM_CONTEXT_BASE_ADDRESS: args->value = vdev->hw->ranges.user.start; @@ -231,22 +299,30 @@ static int ivpu_open(struct drm_device *dev, struct drm_file *file) { struct ivpu_device *vdev = to_ivpu_device(dev); struct ivpu_file_priv *file_priv; + struct ivpu_user_limits *limits; u32 ctx_id; int idx, ret; if (!drm_dev_enter(dev, &idx)) return -ENODEV; + limits = ivpu_user_limits_get(vdev); + if (IS_ERR(limits)) { + ret = PTR_ERR(limits); + goto err_dev_exit; + } + file_priv = kzalloc_obj(*file_priv); if (!file_priv) { ret = -ENOMEM; - goto err_dev_exit; + goto err_user_limits_put; } INIT_LIST_HEAD(&file_priv->ms_instance_list); file_priv->vdev = vdev; file_priv->bound = true; + file_priv->user_limits = limits; kref_init(&file_priv->ref); mutex_init(&file_priv->lock); mutex_init(&file_priv->ms_lock); @@ -284,6 +360,8 @@ static int ivpu_open(struct drm_device *dev, struct drm_file *file) mutex_destroy(&file_priv->ms_lock); mutex_destroy(&file_priv->lock); kfree(file_priv); +err_user_limits_put: + ivpu_user_limits_put(vdev, limits); err_dev_exit: drm_dev_exit(idx); return ret; @@ -343,8 +421,7 @@ static int ivpu_wait_for_ready(struct ivpu_device *vdev) ivpu_ipc_consumer_del(vdev, &cons); if (!ret && ipc_hdr.data_addr != IVPU_IPC_BOOT_MSG_DATA_ADDR) { - ivpu_err(vdev, "Invalid NPU ready message: 0x%x\n", - ipc_hdr.data_addr); + ivpu_err(vdev, "Invalid NPU ready message: 0x%x\n", ipc_hdr.data_addr); return -EIO; } @@ -453,7 +530,7 @@ int ivpu_shutdown(struct ivpu_device *vdev) } static const struct file_operations ivpu_fops = { - .owner = THIS_MODULE, + .owner = THIS_MODULE, DRM_ACCEL_FOPS, #ifdef CONFIG_PROC_FS .show_fdinfo = drm_show_fdinfo, @@ -592,6 +669,7 @@ static int ivpu_dev_init(struct ivpu_device *vdev) xa_init_flags(&vdev->submitted_jobs_xa, XA_FLAGS_ALLOC1); xa_init_flags(&vdev->db_xa, XA_FLAGS_ALLOC1); INIT_LIST_HEAD(&vdev->bo_list); + hash_init(vdev->user_limits); vdev->db_limit.min = IVPU_MIN_DB; vdev->db_limit.max = IVPU_MAX_DB; @@ -600,6 +678,10 @@ static int ivpu_dev_init(struct ivpu_device *vdev) if (ret) goto err_xa_destroy; + ret = drmm_mutex_init(&vdev->drm, &vdev->user_limits_lock); + if (ret) + goto err_xa_destroy; + ret = drmm_mutex_init(&vdev->drm, &vdev->submitted_jobs_lock); if (ret) goto err_xa_destroy; @@ -717,7 +799,7 @@ static struct pci_device_id ivpu_pci_ids[] = { { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_PTL_P) }, { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_WCL) }, { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_NVL) }, - { } + {} }; MODULE_DEVICE_TABLE(pci, ivpu_pci_ids); diff --git a/drivers/accel/ivpu/ivpu_drv.h b/drivers/accel/ivpu/ivpu_drv.h index 5b34b6f50e69..6378e23e0c97 100644 --- a/drivers/accel/ivpu/ivpu_drv.h +++ b/drivers/accel/ivpu/ivpu_drv.h @@ -12,6 +12,7 @@ #include #include +#include #include #include #include @@ -43,7 +44,7 @@ /* SSID 1 is used by the VPU to represent reserved context */ #define IVPU_RESERVED_CONTEXT_MMU_SSID 1 #define IVPU_USER_CONTEXT_MIN_SSID 2 -#define IVPU_USER_CONTEXT_MAX_SSID (IVPU_USER_CONTEXT_MIN_SSID + 63) +#define IVPU_USER_CONTEXT_MAX_SSID (IVPU_USER_CONTEXT_MIN_SSID + 128) #define IVPU_MIN_DB 1 #define IVPU_MAX_DB 255 @@ -51,9 +52,6 @@ #define IVPU_JOB_ID_JOB_MASK GENMASK(7, 0) #define IVPU_JOB_ID_CONTEXT_MASK GENMASK(31, 8) -#define IVPU_NUM_PRIORITIES 4 -#define IVPU_NUM_CMDQS_PER_CTX (IVPU_NUM_PRIORITIES) - #define IVPU_CMDQ_MIN_ID 1 #define IVPU_CMDQ_MAX_ID 255 @@ -123,6 +121,16 @@ struct ivpu_fw_info; struct ivpu_ipc_info; struct ivpu_pm_info; +struct ivpu_user_limits { + struct hlist_node hash_node; + struct ivpu_device *vdev; + struct kref ref; + u32 max_ctx_count; + u32 max_db_count; + u32 uid; + atomic_t db_count; +}; + struct ivpu_device { struct drm_device drm; void __iomem *regb; @@ -142,6 +150,8 @@ struct ivpu_device { struct mutex context_list_lock; /* Protects user context addition/removal */ struct xarray context_xa; struct xa_limit context_xa_limit; + DECLARE_HASHTABLE(user_limits, 8); + struct mutex user_limits_lock; /* Protects user_limits */ struct xarray db_xa; struct xa_limit db_limit; @@ -189,6 +199,7 @@ struct ivpu_file_priv { struct list_head ms_instance_list; struct ivpu_bo *ms_info_bo; struct xa_limit job_limit; + struct ivpu_user_limits *user_limits; u32 job_id_next; struct xa_limit cmdq_limit; u32 cmdq_id_next; @@ -286,6 +297,13 @@ static inline u32 ivpu_get_context_count(struct ivpu_device *vdev) return (ctx_limit.max - ctx_limit.min + 1); } +static inline u32 ivpu_get_doorbell_count(struct ivpu_device *vdev) +{ + struct xa_limit db_limit = vdev->db_limit; + + return (db_limit.max - db_limit.min + 1); +} + static inline u32 ivpu_get_platform(struct ivpu_device *vdev) { WARN_ON_ONCE(vdev->platform == IVPU_PLATFORM_INVALID); diff --git a/drivers/accel/ivpu/ivpu_job.c b/drivers/accel/ivpu/ivpu_job.c index fe02b7bd465b..f0154dfa6ddc 100644 --- a/drivers/accel/ivpu/ivpu_job.c +++ b/drivers/accel/ivpu/ivpu_job.c @@ -173,7 +173,7 @@ static struct ivpu_cmdq *ivpu_cmdq_create(struct ivpu_file_priv *file_priv, u8 p ret = xa_alloc_cyclic(&file_priv->cmdq_xa, &cmdq->id, cmdq, file_priv->cmdq_limit, &file_priv->cmdq_id_next, GFP_KERNEL); if (ret < 0) { - ivpu_err(vdev, "Failed to allocate command queue ID: %d\n", ret); + ivpu_dbg(vdev, IOCTL, "Failed to allocate command queue ID: %d\n", ret); goto err_free_cmdq; } @@ -215,14 +215,22 @@ static int ivpu_hws_cmdq_init(struct ivpu_file_priv *file_priv, struct ivpu_cmdq static int ivpu_register_db(struct ivpu_file_priv *file_priv, struct ivpu_cmdq *cmdq) { + struct ivpu_user_limits *limits = file_priv->user_limits; struct ivpu_device *vdev = file_priv->vdev; int ret; + if (atomic_inc_return(&limits->db_count) > limits->max_db_count) { + ivpu_dbg(vdev, IOCTL, "Maximum number of %u doorbells for uid %u reached\n", + limits->max_db_count, limits->uid); + ret = -EBUSY; + goto err_dec_db_count; + } + ret = xa_alloc_cyclic(&vdev->db_xa, &cmdq->db_id, NULL, vdev->db_limit, &vdev->db_next, GFP_KERNEL); if (ret < 0) { - ivpu_err(vdev, "Failed to allocate doorbell ID: %d\n", ret); - return ret; + ivpu_dbg(vdev, IOCTL, "Failed to allocate doorbell ID: %d\n", ret); + goto err_dec_db_count; } if (vdev->fw->sched_mode == VPU_SCHEDULING_MODE_HW) @@ -231,15 +239,18 @@ static int ivpu_register_db(struct ivpu_file_priv *file_priv, struct ivpu_cmdq * else ret = ivpu_jsm_register_db(vdev, file_priv->ctx.id, cmdq->db_id, cmdq->mem->vpu_addr, ivpu_bo_size(cmdq->mem)); - - if (!ret) { - ivpu_dbg(vdev, JOB, "DB %d registered to cmdq %d ctx %d priority %d\n", - cmdq->db_id, cmdq->id, file_priv->ctx.id, cmdq->priority); - } else { + if (ret) { xa_erase(&vdev->db_xa, cmdq->db_id); cmdq->db_id = 0; + goto err_dec_db_count; } + ivpu_dbg(vdev, JOB, "DB %d registered to cmdq %d ctx %d priority %d\n", + cmdq->db_id, cmdq->id, file_priv->ctx.id, cmdq->priority); + return 0; + +err_dec_db_count: + atomic_dec(&limits->db_count); return ret; } @@ -298,6 +309,7 @@ static int ivpu_cmdq_unregister(struct ivpu_file_priv *file_priv, struct ivpu_cm } xa_erase(&file_priv->vdev->db_xa, cmdq->db_id); + atomic_dec(&file_priv->user_limits->db_count); cmdq->db_id = 0; return 0; @@ -313,6 +325,7 @@ static inline u8 ivpu_job_to_jsm_priority(u8 priority) static void ivpu_cmdq_destroy(struct ivpu_file_priv *file_priv, struct ivpu_cmdq *cmdq) { + lockdep_assert_held(&file_priv->lock); ivpu_cmdq_unregister(file_priv, cmdq); xa_erase(&file_priv->cmdq_xa, cmdq->id); ivpu_cmdq_free(file_priv, cmdq); @@ -380,8 +393,11 @@ static void ivpu_cmdq_reset(struct ivpu_file_priv *file_priv) mutex_lock(&file_priv->lock); xa_for_each(&file_priv->cmdq_xa, cmdq_id, cmdq) { - xa_erase(&file_priv->vdev->db_xa, cmdq->db_id); - cmdq->db_id = 0; + if (cmdq->db_id) { + xa_erase(&file_priv->vdev->db_xa, cmdq->db_id); + atomic_dec(&file_priv->user_limits->db_count); + cmdq->db_id = 0; + } } mutex_unlock(&file_priv->lock); From 2befa6407d5c8b543be32c2276d396db395d9d02 Mon Sep 17 00:00:00 2001 From: Marek Vasut Date: Thu, 15 Jan 2026 03:39:42 +0100 Subject: [PATCH 18/23] dt-bindings: display: bridge: waveshare,dsi2dpi: Document 1..4 DSI lane support Describe 1..4 DSI lanes as supported. Internally, this bridge is an ChipOne ICN6211 which loads its register configuration from a dedicated storage and its I2C does not seem to be accessible. The ICN6211 supports up to 4 DSI lanes, so this is a hard limit for this bridge. The lane configuration is preconfigured in the bridge for each of the WaveShare panels. Signed-off-by: Marek Vasut Reviewed-by: Rob Herring (Arm) Link: https://patch.msgid.link/20260115024004.660986-1-marek.vasut+renesas@mailbox.org Signed-off-by: Luca Ceresoli --- .../devicetree/bindings/display/bridge/waveshare,dsi2dpi.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Documentation/devicetree/bindings/display/bridge/waveshare,dsi2dpi.yaml b/Documentation/devicetree/bindings/display/bridge/waveshare,dsi2dpi.yaml index 5e8498c8303d..3820dd7e11af 100644 --- a/Documentation/devicetree/bindings/display/bridge/waveshare,dsi2dpi.yaml +++ b/Documentation/devicetree/bindings/display/bridge/waveshare,dsi2dpi.yaml @@ -40,9 +40,12 @@ properties: properties: data-lanes: description: array of physical DSI data lane indexes. + minItems: 1 items: - const: 1 - const: 2 + - const: 3 + - const: 4 required: - data-lanes From fca11428425e92bf21d4a7f5865708c5e64430e4 Mon Sep 17 00:00:00 2001 From: Marek Vasut Date: Thu, 15 Jan 2026 03:39:43 +0100 Subject: [PATCH 19/23] drm/bridge: waveshare-dsi: Add support for 1..4 DSI data lanes Parse the data lane count out of DT. Limit the supported data lanes to 1..4 which is the maximum available DSI pairs on the connector of any known panels which may use this bridge. Internally, this bridge is an ChipOne ICN6211 which loads its register configuration from a dedicated storage and its I2C does not seem to be accessible. The ICN6211 also supports up to 4 DSI lanes, so this is a hard limit. To avoid any breakage on old DTs where the parsing of data lanes from DT may fail, fall back to the original hard-coded value of 2 lanes and warn user. The lane configuration is preconfigured in the bridge for each of the WaveShare panels. The 13.3" DSI panel works with 4-lane configuration, others seem to use 2-lane configuration. This is a hardware property, so the actual count should come from DT. Reviewed-by: Joseph Guo Signed-off-by: Marek Vasut Reviewed-by: Luca Ceresoli Link: https://patch.msgid.link/20260115024004.660986-2-marek.vasut+renesas@mailbox.org Signed-off-by: Luca Ceresoli --- drivers/gpu/drm/bridge/waveshare-dsi.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/bridge/waveshare-dsi.c b/drivers/gpu/drm/bridge/waveshare-dsi.c index 9254446f5495..0497c7ecbc7a 100644 --- a/drivers/gpu/drm/bridge/waveshare-dsi.c +++ b/drivers/gpu/drm/bridge/waveshare-dsi.c @@ -66,7 +66,12 @@ static int ws_bridge_attach_dsi(struct ws_bridge *ws) dsi->mode_flags = MIPI_DSI_MODE_VIDEO_HSE | MIPI_DSI_MODE_VIDEO | MIPI_DSI_CLOCK_NON_CONTINUOUS; dsi->format = MIPI_DSI_FMT_RGB888; - dsi->lanes = 2; + dsi->lanes = drm_of_get_data_lanes_count_ep(dev->of_node, 0, 0, 1, 4); + if (dsi->lanes < 0) { + dev_warn(dev, "Invalid or missing DSI lane count %d, falling back to 2 lanes\n", + dsi->lanes); + dsi->lanes = 2; /* Old DT backward compatibility */ + } ret = devm_mipi_dsi_attach(dev, dsi); if (ret < 0) From 713ec926d8256d5989043ec95620b88a79924aa3 Mon Sep 17 00:00:00 2001 From: Matt Coster Date: Fri, 27 Feb 2026 14:12:47 +0000 Subject: [PATCH 20/23] drm/imagination: Check for NULL struct dev_pm_domain_list While dev_pm_domain_detach_list() itself contains the necessary NULL check, the access to struct dev_pm_domain_list->num_pds does not and thus faults on devices with <=1 power domains (where the struct dev_pm_domain_list machinery is skipped for simplicity). This can be reproduced on AM625, which produces the following log[1]: [ 10.820056] powervr fd00000.gpu: Direct firmware load for powervr/rogue_33.15.11.3_v1.fw failed with error -2 [ 10.831903] powervr fd00000.gpu: [drm] *ERROR* failed to load firmware powervr/rogue_33.15.11.3_v1.fw (err=-2) ... [ 10.844023] Unable to handle kernel NULL pointer dereference at virtual address 0000000000000018 ... [ 11.090162] Call trace: [ 11.092600] pvr_power_domains_fini+0x18/0xa0 [powervr] (P) [ 11.098218] pvr_probe+0x100/0x14c [powervr] [ 11.102505] platform_probe+0x5c/0xa4 Fixes: e19cc5ab347e3 ("drm/imagination: Use dev_pm_domain_attach_list()") Reported-by: Mark Brown Closes: https://lore.kernel.org/r/c353fdef-9ccd-4a11-a527-ab4a792d8e70@sirena.org.uk/ [1] Tested-by: Mark Brown Reviewed-by: Alessio Belle Link: https://patch.msgid.link/20260227-single-domain-power-fixes-v1-1-d37ba0825f7c@imgtec.com Signed-off-by: Matt Coster --- drivers/gpu/drm/imagination/pvr_power.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/imagination/pvr_power.c b/drivers/gpu/drm/imagination/pvr_power.c index 006a72ed5064..be8018085b2d 100644 --- a/drivers/gpu/drm/imagination/pvr_power.c +++ b/drivers/gpu/drm/imagination/pvr_power.c @@ -668,14 +668,16 @@ void pvr_power_domains_fini(struct pvr_device *pvr_dev) { struct pvr_device_power *pvr_power = &pvr_dev->power; - int i = (int)pvr_power->domains->num_pds - 1; + if (!pvr_power->domains) + goto out; - while (--i >= 0) + for (int i = (int)pvr_power->domains->num_pds - 2; i >= 0; --i) device_link_del(pvr_power->domain_links[i]); dev_pm_domain_detach_list(pvr_power->domains); kfree(pvr_power->domain_links); +out: *pvr_power = (struct pvr_device_power){ 0 }; } From 218ea788a69308008c233381605a4b18cc140a5e Mon Sep 17 00:00:00 2001 From: Matt Coster Date: Fri, 27 Feb 2026 14:12:48 +0000 Subject: [PATCH 21/23] drm/imagination: Detach pm domains if linking fails There's a missing call to dev_pm_domain_detach_list() in the error path of pvr_power_domains_init(); if creating the second stage of device links fails then the struct dev_pm_domain_list will be left dangling. Fixes: e19cc5ab347e3 ("drm/imagination: Use dev_pm_domain_attach_list()") Reviewed-by: Alessio Belle Link: https://patch.msgid.link/20260227-single-domain-power-fixes-v1-2-d37ba0825f7c@imgtec.com Signed-off-by: Matt Coster --- drivers/gpu/drm/imagination/pvr_power.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/gpu/drm/imagination/pvr_power.c b/drivers/gpu/drm/imagination/pvr_power.c index be8018085b2d..5a1fda685f2c 100644 --- a/drivers/gpu/drm/imagination/pvr_power.c +++ b/drivers/gpu/drm/imagination/pvr_power.c @@ -661,6 +661,8 @@ int pvr_power_domains_init(struct pvr_device *pvr_dev) while (--i >= 0) device_link_del(domain_links[i]); + dev_pm_domain_detach_list(domains); + return err; } From 144d47f9053634a9587c4ce217c8d7672934f38d Mon Sep 17 00:00:00 2001 From: Matt Coster Date: Fri, 27 Feb 2026 14:12:49 +0000 Subject: [PATCH 22/23] drm/imagination: Ensure struct pvr_device->power is initialized When pvr_power_domains_init() handles <=1 power domains, the content of struct pvr_device->power was previously left uninitialized. Fixes: e19cc5ab347e3 ("drm/imagination: Use dev_pm_domain_attach_list()") Reviewed-by: Alessio Belle Link: https://patch.msgid.link/20260227-single-domain-power-fixes-v1-3-d37ba0825f7c@imgtec.com Signed-off-by: Matt Coster --- drivers/gpu/drm/imagination/pvr_power.c | 44 ++++++++++++++++--------- 1 file changed, 29 insertions(+), 15 deletions(-) diff --git a/drivers/gpu/drm/imagination/pvr_power.c b/drivers/gpu/drm/imagination/pvr_power.c index 5a1fda685f2c..7a8765c0c1ed 100644 --- a/drivers/gpu/drm/imagination/pvr_power.c +++ b/drivers/gpu/drm/imagination/pvr_power.c @@ -598,8 +598,8 @@ int pvr_power_domains_init(struct pvr_device *pvr_dev) struct drm_device *drm_dev = from_pvr_device(pvr_dev); struct device *dev = drm_dev->dev; - struct device_link **domain_links __free(kfree) = NULL; struct dev_pm_domain_list *domains = NULL; + struct device_link **domain_links = NULL; int domain_count; int link_count; @@ -608,23 +608,30 @@ int pvr_power_domains_init(struct pvr_device *pvr_dev) domain_count = of_count_phandle_with_args(dev->of_node, "power-domains", "#power-domain-cells"); - if (domain_count < 0) - return domain_count; + if (domain_count < 0) { + err = domain_count; + goto out; + } - if (domain_count <= 1) - return 0; + if (domain_count <= 1) { + err = 0; + goto out; + } if (domain_count > ARRAY_SIZE(ROGUE_PD_NAMES)) { drm_err(drm_dev, "%s() only supports %zu domains on Rogue", __func__, ARRAY_SIZE(ROGUE_PD_NAMES)); - return -EOPNOTSUPP; + err = -EOPNOTSUPP; + goto out; } link_count = domain_count - 1; domain_links = kzalloc_objs(*domain_links, link_count); - if (!domain_links) - return -ENOMEM; + if (!domain_links) { + err = -ENOMEM; + goto out; + } const struct dev_pm_domain_attach_data pd_attach_data = { .pd_names = ROGUE_PD_NAMES, @@ -634,7 +641,7 @@ int pvr_power_domains_init(struct pvr_device *pvr_dev) err = dev_pm_domain_attach_list(dev, &pd_attach_data, &domains); if (err < 0) - return err; + goto err_free_links; for (i = 0; i < link_count; i++) { struct device_link *link; @@ -650,18 +657,25 @@ int pvr_power_domains_init(struct pvr_device *pvr_dev) domain_links[i] = link; } - pvr_dev->power = (struct pvr_device_power){ - .domains = domains, - .domain_links = no_free_ptr(domain_links), - }; - - return 0; + err = 0; + goto out; err_unlink: while (--i >= 0) device_link_del(domain_links[i]); dev_pm_domain_detach_list(domains); + domains = NULL; + +err_free_links: + kfree(domain_links); + domain_links = NULL; + +out: + pvr_dev->power = (struct pvr_device_power){ + .domains = domains, + .domain_links = domain_links, + }; return err; } From d2e20c8951e4bb5f4a828aed39813599980353b6 Mon Sep 17 00:00:00 2001 From: Thomas Zimmermann Date: Fri, 27 Feb 2026 14:31:03 +0100 Subject: [PATCH 23/23] drm/gud: Test for imported buffers with drm_gem_is_imported() Instead of testing import_attach for imported GEM buffers, invoke drm_gem_is_imported() to do the test. The test itself does not change. Signed-off-by: Thomas Zimmermann Cc: Ruben Wauters Acked-by: Ruben Wauters Link: https://patch.msgid.link/20260227133113.235940-4-tzimmermann@suse.de --- drivers/gpu/drm/gud/gud_pipe.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/gud/gud_pipe.c b/drivers/gpu/drm/gud/gud_pipe.c index 4b77be94348d..11e7441de63b 100644 --- a/drivers/gpu/drm/gud/gud_pipe.c +++ b/drivers/gpu/drm/gud/gud_pipe.c @@ -447,7 +447,7 @@ static void gud_fb_handle_damage(struct gud_device *gdrm, struct drm_framebuffer } /* Imported buffers are assumed to be WriteCombined with uncached reads */ - gud_flush_damage(gdrm, fb, src, !fb->obj[0]->import_attach, damage); + gud_flush_damage(gdrm, fb, src, !drm_gem_is_imported(fb->obj[0]), damage); } int gud_plane_atomic_check(struct drm_plane *plane,