Files
linux/drivers/gpu/drm/drm_mode_config.c
Maarten Lankhorst 6bee098b91 drm: Fix use-after-free on framebuffers and property blobs when calling drm_dev_unplug
When trying to do a rather aggressive test of igt's "xe_module_load
--r reload" with a full desktop environment and game running I noticed
a few OOPSes when dereferencing freed pointers, related to
framebuffers and property blobs after the compositor exits.

Solve this by guarding the freeing in drm_file with drm_dev_enter/exit,
and immediately put the references from struct drm_file objects during
drm_dev_unplug().

Related warnings for framebuffers on the subtest:
[  739.713076] ------------[ cut here ]------------
               WARN_ON(!list_empty(&dev->mode_config.fb_list))
[  739.713079] WARNING: drivers/gpu/drm/drm_mode_config.c:584 at drm_mode_config_cleanup+0x30b/0x320 [drm], CPU#12: xe_module_load/13145
....
[  739.713328] Call Trace:
[  739.713330]  <TASK>
[  739.713335]  ? intel_pmdemand_destroy_state+0x11/0x20 [xe]
[  739.713574]  ? intel_atomic_global_obj_cleanup+0xe4/0x1a0 [xe]
[  739.713794]  intel_display_driver_remove_noirq+0x51/0xb0 [xe]
[  739.714041]  xe_display_fini_early+0x33/0x50 [xe]
[  739.714284]  devm_action_release+0xf/0x20
[  739.714294]  devres_release_all+0xad/0xf0
[  739.714301]  device_unbind_cleanup+0x12/0xa0
[  739.714305]  device_release_driver_internal+0x1b7/0x210
[  739.714311]  device_driver_detach+0x14/0x20
[  739.714315]  unbind_store+0xa6/0xb0
[  739.714319]  drv_attr_store+0x21/0x30
[  739.714322]  sysfs_kf_write+0x48/0x60
[  739.714328]  kernfs_fop_write_iter+0x16b/0x240
[  739.714333]  vfs_write+0x266/0x520
[  739.714341]  ksys_write+0x72/0xe0
[  739.714345]  __x64_sys_write+0x19/0x20
[  739.714347]  x64_sys_call+0xa15/0xa30
[  739.714355]  do_syscall_64+0xd8/0xab0
[  739.714361]  entry_SYSCALL_64_after_hwframe+0x4b/0x53

and

[  739.714459] ------------[ cut here ]------------
[  739.714461] xe 0000:67:00.0: [drm] drm_WARN_ON(!list_empty(&fb->filp_head))
[  739.714464] WARNING: drivers/gpu/drm/drm_framebuffer.c:833 at drm_framebuffer_free+0x6c/0x90 [drm], CPU#12: xe_module_load/13145
[  739.714715] RIP: 0010:drm_framebuffer_free+0x7a/0x90 [drm]
...
[  739.714869] Call Trace:
[  739.714871]  <TASK>
[  739.714876]  drm_mode_config_cleanup+0x26a/0x320 [drm]
[  739.714998]  ? __drm_printfn_seq_file+0x20/0x20 [drm]
[  739.715115]  ? drm_mode_config_cleanup+0x207/0x320 [drm]
[  739.715235]  intel_display_driver_remove_noirq+0x51/0xb0 [xe]
[  739.715576]  xe_display_fini_early+0x33/0x50 [xe]
[  739.715821]  devm_action_release+0xf/0x20
[  739.715828]  devres_release_all+0xad/0xf0
[  739.715843]  device_unbind_cleanup+0x12/0xa0
[  739.715850]  device_release_driver_internal+0x1b7/0x210
[  739.715856]  device_driver_detach+0x14/0x20
[  739.715860]  unbind_store+0xa6/0xb0
[  739.715865]  drv_attr_store+0x21/0x30
[  739.715868]  sysfs_kf_write+0x48/0x60
[  739.715873]  kernfs_fop_write_iter+0x16b/0x240
[  739.715878]  vfs_write+0x266/0x520
[  739.715886]  ksys_write+0x72/0xe0
[  739.715890]  __x64_sys_write+0x19/0x20
[  739.715893]  x64_sys_call+0xa15/0xa30
[  739.715900]  do_syscall_64+0xd8/0xab0
[  739.715905]  entry_SYSCALL_64_after_hwframe+0x4b/0x53

and then finally file close blows up:

[  743.186530] Oops: general protection fault, probably for non-canonical address 0xdead000000000122: 0000 [#1] SMP
[  743.186535] CPU: 3 UID: 1000 PID: 3453 Comm: kwin_wayland Tainted: G        W           7.0.0-rc1-valkyria+ #110 PREEMPT_{RT,(lazy)}
[  743.186537] Tainted: [W]=WARN
[  743.186538] Hardware name: Gigabyte Technology Co., Ltd. X299 AORUS Gaming 3/X299 AORUS Gaming 3-CF, BIOS F8n 12/06/2021
[  743.186539] RIP: 0010:drm_framebuffer_cleanup+0x55/0xc0 [drm]
[  743.186588] Code: d8 72 73 0f b6 42 05 ff c3 39 c3 72 e8 49 8d bd 50 07 00 00 31 f6 e8 3a 80 d3 e1 49 8b 44 24 10 49 8d 7c 24 08 49 8b 54 24 08 <48> 3b 38 0f 85 95 7f 02 00 48 3b 7a 08 0f 85 8b 7f 02 00 48 89 42
[  743.186589] RSP: 0018:ffffc900085e3cf8 EFLAGS: 00010202
[  743.186591] RAX: dead000000000122 RBX: 0000000000000001 RCX: ffffffff8217ed03
[  743.186592] RDX: dead000000000100 RSI: 0000000000000000 RDI: ffff88814675ba08
[  743.186593] RBP: ffffc900085e3d10 R08: 0000000000000000 R09: 0000000000000000
[  743.186593] R10: 0000000000000000 R11: 0000000000000000 R12: ffff88814675ba00
[  743.186594] R13: ffff88810d778000 R14: ffff888119f6dca0 R15: ffff88810c660bb0
[  743.186595] FS:  00007ff377d21280(0000) GS:ffff888cec3f8000(0000) knlGS:0000000000000000
[  743.186596] CS:  0010 DS: 0000 ES: 0000 CR0: 0000000080050033
[  743.186596] CR2: 000055690b55e000 CR3: 0000000113586003 CR4: 00000000003706f0
[  743.186597] Call Trace:
[  743.186598]  <TASK>
[  743.186603]  intel_user_framebuffer_destroy+0x12/0x90 [xe]
[  743.186722]  drm_framebuffer_free+0x3a/0x90 [drm]
[  743.186750]  ? trace_hardirqs_on+0x5f/0x120
[  743.186754]  drm_mode_object_put+0x51/0x70 [drm]
[  743.186786]  drm_fb_release+0x105/0x190 [drm]
[  743.186812]  ? rt_mutex_slowunlock+0x3aa/0x410
[  743.186817]  ? rt_spin_lock+0xea/0x1b0
[  743.186819]  drm_file_free+0x1e0/0x2c0 [drm]
[  743.186843]  drm_release_noglobal+0x91/0xf0 [drm]
[  743.186865]  __fput+0x100/0x2e0
[  743.186869]  fput_close_sync+0x40/0xa0
[  743.186870]  __x64_sys_close+0x3e/0x80
[  743.186873]  x64_sys_call+0xa07/0xa30
[  743.186879]  do_syscall_64+0xd8/0xab0
[  743.186881]  entry_SYSCALL_64_after_hwframe+0x4b/0x53
[  743.186882] RIP: 0033:0x7ff37e567732
[  743.186884] Code: 08 0f 85 a1 38 ff ff 49 89 fb 48 89 f0 48 89 d7 48 89 ce 4c 89 c2 4d 89 ca 4c 8b 44 24 08 4c 8b 4c 24 10 4c 89 5c 24 08 0f 05 <c3> 66 2e 0f 1f 84 00 00 00 00 00 0f 1f 00 f3 0f 1e fa 55 bf 01 00
[  743.186885] RSP: 002b:00007ffc818169a8 EFLAGS: 00000246 ORIG_RAX: 0000000000000003
[  743.186886] RAX: ffffffffffffffda RBX: 00007ffc81816a30 RCX: 00007ff37e567732
[  743.186887] RDX: 0000000000000000 RSI: 0000000000000000 RDI: 0000000000000012
[  743.186888] RBP: 00007ffc818169d0 R08: 0000000000000000 R09: 0000000000000000
[  743.186889] R10: 0000000000000000 R11: 0000000000000246 R12: 000055d60a7996e0
[  743.186889] R13: 00007ffc81816a90 R14: 00007ffc81816a90 R15: 000055d60a782a30
[  743.186892]  </TASK>
[  743.186893] Modules linked in: rfcomm snd_hrtimer xt_CHECKSUM xt_MASQUERADE xt_conntrack ipt_REJECT nf_reject_ipv4 xt_tcpudp xt_addrtype nft_compat x_tables nft_chain_nat nf_nat nf_conntrack nf_defrag_ipv6 nf_defrag_ipv4 nf_tables overlay cfg80211 bnep mtd_intel_dg snd_hda_codec_intelhdmi mtd snd_hda_codec_hdmi nls_utf8 mxm_wmi intel_wmi_thunderbolt gigabyte_wmi wmi_bmof xe drm_gpuvm drm_gpusvm_helper i2c_algo_bit drm_buddy drm_ttm_helper ttm video drm_suballoc_helper gpu_sched drm_client_lib drm_exec drm_display_helper cec drm_kunit_helpers drm_kms_helper kunit x86_pkg_temp_thermal intel_powerclamp coretemp snd_hda_codec_alc882 snd_hda_codec_realtek_lib snd_hda_codec_generic snd_hda_intel snd_soc_avs snd_soc_hda_codec snd_hda_ext_core snd_hda_codec snd_hwdep snd_hda_core snd_intel_dspcfg snd_soc_core snd_compress ac97_bus snd_pcm snd_seq snd_seq_device snd_timer i2c_i801 i2c_mux snd i2c_smbus btusb btrtl btbcm btmtk btintel bluetooth ecdh_generic rfkill ecc mei_me mei ioatdma dca wmi nfsd drm i2c_dev fuse nfnetlink
[  743.186938] ---[ end trace 0000000000000000 ]---

And for property blobs:

void drm_mode_config_cleanup(struct drm_device *dev)
{
...
	list_for_each_entry_safe(blob, bt, &dev->mode_config.property_blob_list,
				 head_global) {
		drm_property_blob_put(blob);
	}

Resulting in:

[  371.072940] BUG: unable to handle page fault for address: 000001ffffffffff
[  371.072944] #PF: supervisor read access in kernel mode
[  371.072945] #PF: error_code(0x0000) - not-present page
[  371.072947] PGD 0 P4D 0
[  371.072950] Oops: Oops: 0000 [#1] SMP
[  371.072953] CPU: 0 UID: 1000 PID: 3693 Comm: kwin_wayland Not tainted 7.0.0-rc1-valkyria+ #111 PREEMPT_{RT,(lazy)}
[  371.072956] Hardware name: Gigabyte Technology Co., Ltd. X299 AORUS Gaming 3/X299 AORUS Gaming 3-CF, BIOS F8n 12/06/2021
[  371.072957] RIP: 0010:drm_property_destroy_user_blobs+0x3b/0x90 [drm]
[  371.073019] Code: 00 00 48 83 ec 10 48 8b 86 30 01 00 00 48 39 c3 74 59 48 89 c2 48 8d 48 c8 48 8b 00 4c 8d 60 c8 eb 04 4c 8d 60 c8 48 8b 71 40 <48> 39 16 0f 85 39 32 01 00 48 3b 50 08 0f 85 2f 32 01 00 48 89 70
[  371.073021] RSP: 0018:ffffc90006a73de8 EFLAGS: 00010293
[  371.073022] RAX: 000001ffffffffff RBX: ffff888118a1a930 RCX: ffff8881b92355c0
[  371.073024] RDX: ffff8881b92355f8 RSI: 000001ffffffffff RDI: ffff888118be4000
[  371.073025] RBP: ffffc90006a73e08 R08: ffff8881009b7300 R09: ffff888cecc5b000
[  371.073026] R10: ffffc90006a73e90 R11: 0000000000000002 R12: 000001ffffffffc7
[  371.073027] R13: ffff888118a1a980 R14: ffff88810b366d20 R15: ffff888118a1a970
[  371.073028] FS:  00007f1faccbb280(0000) GS:ffff888cec2db000(0000) knlGS:0000000000000000
[  371.073029] CS:  0010 DS: 0000 ES: 0000 CR0: 0000000080050033
[  371.073030] CR2: 000001ffffffffff CR3: 000000010655c001 CR4: 00000000003706f0
[  371.073031] Call Trace:
[  371.073033]  <TASK>
[  371.073036]  drm_file_free+0x1df/0x2a0 [drm]
[  371.073077]  drm_release_noglobal+0x7a/0xe0 [drm]
[  371.073113]  __fput+0xe2/0x2b0
[  371.073118]  fput_close_sync+0x40/0xa0
[  371.073119]  __x64_sys_close+0x3e/0x80
[  371.073122]  x64_sys_call+0xa07/0xa30
[  371.073126]  do_syscall_64+0xc0/0x840
[  371.073130]  entry_SYSCALL_64_after_hwframe+0x4b/0x53
[  371.073132] RIP: 0033:0x7f1fb3501732
[  371.073133] Code: 08 0f 85 a1 38 ff ff 49 89 fb 48 89 f0 48 89 d7 48 89 ce 4c 89 c2 4d 89 ca 4c 8b 44 24 08 4c 8b 4c 24 10 4c 89 5c 24 08 0f 05 <c3> 66 2e 0f 1f 84 00 00 00 00 00 0f 1f 00 f3 0f 1e fa 55 bf 01 00
[  371.073135] RSP: 002b:00007ffe8e6f0278 EFLAGS: 00000246 ORIG_RAX: 0000000000000003
[  371.073136] RAX: ffffffffffffffda RBX: 00007ffe8e6f0300 RCX: 00007f1fb3501732
[  371.073137] RDX: 0000000000000000 RSI: 0000000000000000 RDI: 0000000000000012
[  371.073138] RBP: 00007ffe8e6f02a0 R08: 0000000000000000 R09: 0000000000000000
[  371.073139] R10: 0000000000000000 R11: 0000000000000246 R12: 00005585ba46eea0
[  371.073140] R13: 00007ffe8e6f0360 R14: 00007ffe8e6f0360 R15: 00005585ba458a30
[  371.073143]  </TASK>
[  371.073144] Modules linked in: rfcomm snd_hrtimer xt_addrtype xt_CHECKSUM xt_MASQUERADE xt_conntrack ipt_REJECT nf_reject_ipv4 xt_tcpudp nft_compat x_tables nft_chain_nat nf_nat nf_conntrack nf_defrag_ipv6 nf_defrag_ipv4 nf_tables overlay cfg80211 bnep snd_hda_codec_intelhdmi snd_hda_codec_hdmi mtd_intel_dg mtd nls_utf8 wmi_bmof mxm_wmi gigabyte_wmi intel_wmi_thunderbolt xe drm_gpuvm drm_gpusvm_helper i2c_algo_bit drm_buddy drm_ttm_helper ttm video drm_suballoc_helper gpu_sched drm_client_lib drm_exec drm_display_helper cec drm_kunit_helpers drm_kms_helper kunit x86_pkg_temp_thermal intel_powerclamp coretemp snd_hda_codec_alc882 snd_hda_codec_realtek_lib snd_hda_codec_generic snd_hda_intel snd_soc_avs snd_soc_hda_codec snd_hda_ext_core snd_hda_codec snd_hwdep snd_hda_core snd_intel_dspcfg snd_soc_core snd_compress ac97_bus snd_pcm snd_seq snd_seq_device snd_timer i2c_i801 btusb i2c_mux i2c_smbus btrtl snd btbcm btmtk btintel bluetooth ecdh_generic rfkill ecc mei_me mei ioatdma dca wmi nfsd drm i2c_dev fuse nfnetlink
[  371.073198] CR2: 000001ffffffffff
[  371.073199] ---[ end trace 0000000000000000 ]---

Add a guard around file close, and ensure the warnings from drm_mode_config
do not trigger. Fix those by allowing an open reference to the file descriptor
and cleaning up the file linked list entry in drm_mode_config_cleanup().

Cc: <stable@vger.kernel.org> # v4.18+
Fixes: bee330f3d6 ("drm: Use srcu to protect drm_device.unplugged")
Cc: Thomas Hellström <thomas.hellstrom@linux.intel.com>
Reviewed-by: Thomas Hellström <thomas.hellstrom@linux.intel.com>
Link: https://patch.msgid.link/20260313151728.14990-4-dev@lankhorst.se
Signed-off-by: Maarten Lankhorst <dev@lankhorst.se>
2026-03-17 17:49:12 +01:00

732 lines
22 KiB
C

/*
* Copyright (c) 2016 Intel Corporation
*
* Permission to use, copy, modify, distribute, and sell this software and its
* documentation for any purpose is hereby granted without fee, provided that
* the above copyright notice appear in all copies and that both that copyright
* notice and this permission notice appear in supporting documentation, and
* that the name of the copyright holders not be used in advertising or
* publicity pertaining to distribution of the software without specific,
* written prior permission. The copyright holders make no representations
* about the suitability of this software for any purpose. It is provided "as
* is" without express or implied warranty.
*
* THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
* INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO
* EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY SPECIAL, INDIRECT OR
* CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE,
* DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
* TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
* OF THIS SOFTWARE.
*/
#include <linux/export.h>
#include <linux/uaccess.h>
#include <drm/drm_drv.h>
#include <drm/drm_encoder.h>
#include <drm/drm_file.h>
#include <drm/drm_framebuffer.h>
#include <drm/drm_managed.h>
#include <drm/drm_mode_config.h>
#include <drm/drm_print.h>
#include <drm/drm_colorop.h>
#include <linux/dma-resv.h>
#include "drm_crtc_internal.h"
#include "drm_internal.h"
int drm_modeset_register_all(struct drm_device *dev)
{
int ret;
ret = drm_plane_register_all(dev);
if (ret)
goto err_plane;
ret = drm_crtc_register_all(dev);
if (ret)
goto err_crtc;
ret = drm_encoder_register_all(dev);
if (ret)
goto err_encoder;
ret = drm_connector_register_all(dev);
if (ret)
goto err_connector;
return 0;
err_connector:
drm_encoder_unregister_all(dev);
err_encoder:
drm_crtc_unregister_all(dev);
err_crtc:
drm_plane_unregister_all(dev);
err_plane:
return ret;
}
void drm_modeset_unregister_all(struct drm_device *dev)
{
drm_connector_unregister_all(dev);
drm_encoder_unregister_all(dev);
drm_crtc_unregister_all(dev);
drm_plane_unregister_all(dev);
}
/**
* drm_mode_getresources - get graphics configuration
* @dev: drm device for the ioctl
* @data: data pointer for the ioctl
* @file_priv: drm file for the ioctl call
*
* Construct a set of configuration description structures and return
* them to the user, including CRTC, connector and framebuffer configuration.
*
* Called by the user via ioctl.
*
* Returns:
* Zero on success, negative errno on failure.
*/
int drm_mode_getresources(struct drm_device *dev, void *data,
struct drm_file *file_priv)
{
struct drm_mode_card_res *card_res = data;
struct drm_framebuffer *fb;
struct drm_connector *connector;
struct drm_crtc *crtc;
struct drm_encoder *encoder;
int count, ret = 0;
uint32_t __user *fb_id;
uint32_t __user *crtc_id;
uint32_t __user *connector_id;
uint32_t __user *encoder_id;
struct drm_connector_list_iter conn_iter;
if (!drm_core_check_feature(dev, DRIVER_MODESET))
return -EOPNOTSUPP;
mutex_lock(&file_priv->fbs_lock);
count = 0;
fb_id = u64_to_user_ptr(card_res->fb_id_ptr);
list_for_each_entry(fb, &file_priv->fbs, filp_head) {
if (count < card_res->count_fbs &&
put_user(fb->base.id, fb_id + count)) {
mutex_unlock(&file_priv->fbs_lock);
return -EFAULT;
}
count++;
}
card_res->count_fbs = count;
mutex_unlock(&file_priv->fbs_lock);
card_res->max_height = dev->mode_config.max_height;
card_res->min_height = dev->mode_config.min_height;
card_res->max_width = dev->mode_config.max_width;
card_res->min_width = dev->mode_config.min_width;
count = 0;
crtc_id = u64_to_user_ptr(card_res->crtc_id_ptr);
drm_for_each_crtc(crtc, dev) {
if (drm_lease_held(file_priv, crtc->base.id)) {
if (count < card_res->count_crtcs &&
put_user(crtc->base.id, crtc_id + count))
return -EFAULT;
count++;
}
}
card_res->count_crtcs = count;
count = 0;
encoder_id = u64_to_user_ptr(card_res->encoder_id_ptr);
drm_for_each_encoder(encoder, dev) {
if (count < card_res->count_encoders &&
put_user(encoder->base.id, encoder_id + count))
return -EFAULT;
count++;
}
card_res->count_encoders = count;
drm_connector_list_iter_begin(dev, &conn_iter);
count = 0;
connector_id = u64_to_user_ptr(card_res->connector_id_ptr);
/*
* FIXME: the connectors on the list may not be fully initialized yet,
* if the ioctl is called before the connectors are registered. (See
* drm_dev_register()->drm_modeset_register_all() for static and
* drm_connector_dynamic_register() for dynamic connectors.)
* The driver should only get registered after static connectors are
* fully initialized and dynamic connectors should be added to the
* connector list only after fully initializing them.
*/
drm_for_each_connector_iter(connector, &conn_iter) {
/* only expose writeback connectors if userspace understands them */
if (!file_priv->writeback_connectors &&
(connector->connector_type == DRM_MODE_CONNECTOR_WRITEBACK))
continue;
if (drm_lease_held(file_priv, connector->base.id)) {
if (count < card_res->count_connectors &&
put_user(connector->base.id, connector_id + count)) {
drm_connector_list_iter_end(&conn_iter);
return -EFAULT;
}
count++;
}
}
card_res->count_connectors = count;
drm_connector_list_iter_end(&conn_iter);
return ret;
}
/**
* drm_mode_config_reset - call ->reset callbacks
* @dev: drm device
*
* This functions calls all the crtc's, encoder's and connector's ->reset
* callback. Drivers can use this in e.g. their driver load or resume code to
* reset hardware and software state.
*/
void drm_mode_config_reset(struct drm_device *dev)
{
struct drm_crtc *crtc;
struct drm_colorop *colorop;
struct drm_plane *plane;
struct drm_encoder *encoder;
struct drm_connector *connector;
struct drm_connector_list_iter conn_iter;
drm_for_each_colorop(colorop, dev)
drm_colorop_reset(colorop);
drm_for_each_plane(plane, dev)
if (plane->funcs->reset)
plane->funcs->reset(plane);
drm_for_each_crtc(crtc, dev)
if (crtc->funcs->reset)
crtc->funcs->reset(crtc);
drm_for_each_encoder(encoder, dev)
if (encoder->funcs && encoder->funcs->reset)
encoder->funcs->reset(encoder);
drm_connector_list_iter_begin(dev, &conn_iter);
drm_for_each_connector_iter(connector, &conn_iter)
if (connector->funcs->reset)
connector->funcs->reset(connector);
drm_connector_list_iter_end(&conn_iter);
}
EXPORT_SYMBOL(drm_mode_config_reset);
/*
* Global properties
*/
static const struct drm_prop_enum_list drm_plane_type_enum_list[] = {
{ DRM_PLANE_TYPE_OVERLAY, "Overlay" },
{ DRM_PLANE_TYPE_PRIMARY, "Primary" },
{ DRM_PLANE_TYPE_CURSOR, "Cursor" },
};
static int drm_mode_create_standard_properties(struct drm_device *dev)
{
struct drm_property *prop;
int ret;
ret = drm_connector_create_standard_properties(dev);
if (ret)
return ret;
prop = drm_property_create_enum(dev, DRM_MODE_PROP_IMMUTABLE,
"type", drm_plane_type_enum_list,
ARRAY_SIZE(drm_plane_type_enum_list));
if (!prop)
return -ENOMEM;
dev->mode_config.plane_type_property = prop;
prop = drm_property_create_range(dev, DRM_MODE_PROP_ATOMIC,
"SRC_X", 0, UINT_MAX);
if (!prop)
return -ENOMEM;
dev->mode_config.prop_src_x = prop;
prop = drm_property_create_range(dev, DRM_MODE_PROP_ATOMIC,
"SRC_Y", 0, UINT_MAX);
if (!prop)
return -ENOMEM;
dev->mode_config.prop_src_y = prop;
prop = drm_property_create_range(dev, DRM_MODE_PROP_ATOMIC,
"SRC_W", 0, UINT_MAX);
if (!prop)
return -ENOMEM;
dev->mode_config.prop_src_w = prop;
prop = drm_property_create_range(dev, DRM_MODE_PROP_ATOMIC,
"SRC_H", 0, UINT_MAX);
if (!prop)
return -ENOMEM;
dev->mode_config.prop_src_h = prop;
prop = drm_property_create_signed_range(dev, DRM_MODE_PROP_ATOMIC,
"CRTC_X", INT_MIN, INT_MAX);
if (!prop)
return -ENOMEM;
dev->mode_config.prop_crtc_x = prop;
prop = drm_property_create_signed_range(dev, DRM_MODE_PROP_ATOMIC,
"CRTC_Y", INT_MIN, INT_MAX);
if (!prop)
return -ENOMEM;
dev->mode_config.prop_crtc_y = prop;
prop = drm_property_create_range(dev, DRM_MODE_PROP_ATOMIC,
"CRTC_W", 0, INT_MAX);
if (!prop)
return -ENOMEM;
dev->mode_config.prop_crtc_w = prop;
prop = drm_property_create_range(dev, DRM_MODE_PROP_ATOMIC,
"CRTC_H", 0, INT_MAX);
if (!prop)
return -ENOMEM;
dev->mode_config.prop_crtc_h = prop;
prop = drm_property_create_object(dev, DRM_MODE_PROP_ATOMIC,
"FB_ID", DRM_MODE_OBJECT_FB);
if (!prop)
return -ENOMEM;
dev->mode_config.prop_fb_id = prop;
prop = drm_property_create_signed_range(dev, DRM_MODE_PROP_ATOMIC,
"IN_FENCE_FD", -1, INT_MAX);
if (!prop)
return -ENOMEM;
dev->mode_config.prop_in_fence_fd = prop;
prop = drm_property_create_range(dev, DRM_MODE_PROP_ATOMIC,
"OUT_FENCE_PTR", 0, U64_MAX);
if (!prop)
return -ENOMEM;
dev->mode_config.prop_out_fence_ptr = prop;
prop = drm_property_create_object(dev, DRM_MODE_PROP_ATOMIC,
"CRTC_ID", DRM_MODE_OBJECT_CRTC);
if (!prop)
return -ENOMEM;
dev->mode_config.prop_crtc_id = prop;
prop = drm_property_create(dev,
DRM_MODE_PROP_ATOMIC | DRM_MODE_PROP_BLOB,
"FB_DAMAGE_CLIPS", 0);
if (!prop)
return -ENOMEM;
dev->mode_config.prop_fb_damage_clips = prop;
prop = drm_property_create_bool(dev, DRM_MODE_PROP_ATOMIC,
"ACTIVE");
if (!prop)
return -ENOMEM;
dev->mode_config.prop_active = prop;
prop = drm_property_create(dev,
DRM_MODE_PROP_ATOMIC | DRM_MODE_PROP_BLOB,
"MODE_ID", 0);
if (!prop)
return -ENOMEM;
dev->mode_config.prop_mode_id = prop;
prop = drm_property_create_bool(dev, 0,
"VRR_ENABLED");
if (!prop)
return -ENOMEM;
dev->mode_config.prop_vrr_enabled = prop;
prop = drm_property_create(dev,
DRM_MODE_PROP_BLOB,
"DEGAMMA_LUT", 0);
if (!prop)
return -ENOMEM;
dev->mode_config.degamma_lut_property = prop;
prop = drm_property_create_range(dev,
DRM_MODE_PROP_IMMUTABLE,
"DEGAMMA_LUT_SIZE", 0, UINT_MAX);
if (!prop)
return -ENOMEM;
dev->mode_config.degamma_lut_size_property = prop;
prop = drm_property_create(dev,
DRM_MODE_PROP_BLOB,
"CTM", 0);
if (!prop)
return -ENOMEM;
dev->mode_config.ctm_property = prop;
prop = drm_property_create(dev,
DRM_MODE_PROP_BLOB,
"GAMMA_LUT", 0);
if (!prop)
return -ENOMEM;
dev->mode_config.gamma_lut_property = prop;
prop = drm_property_create_range(dev,
DRM_MODE_PROP_IMMUTABLE,
"GAMMA_LUT_SIZE", 0, UINT_MAX);
if (!prop)
return -ENOMEM;
dev->mode_config.gamma_lut_size_property = prop;
prop = drm_property_create(dev,
DRM_MODE_PROP_IMMUTABLE | DRM_MODE_PROP_BLOB,
"IN_FORMATS", 0);
if (!prop)
return -ENOMEM;
dev->mode_config.modifiers_property = prop;
prop = drm_property_create(dev,
DRM_MODE_PROP_IMMUTABLE | DRM_MODE_PROP_BLOB,
"IN_FORMATS_ASYNC", 0);
if (!prop)
return -ENOMEM;
dev->mode_config.async_modifiers_property = prop;
prop = drm_property_create(dev,
DRM_MODE_PROP_IMMUTABLE | DRM_MODE_PROP_BLOB,
"SIZE_HINTS", 0);
if (!prop)
return -ENOMEM;
dev->mode_config.size_hints_property = prop;
return 0;
}
static void drm_mode_config_init_release(struct drm_device *dev, void *ptr)
{
drm_mode_config_cleanup(dev);
}
/**
* drmm_mode_config_init - managed DRM mode_configuration structure
* initialization
* @dev: DRM device
*
* Initialize @dev's mode_config structure, used for tracking the graphics
* configuration of @dev.
*
* Since this initializes the modeset locks, no locking is possible. Which is no
* problem, since this should happen single threaded at init time. It is the
* driver's problem to ensure this guarantee.
*
* Cleanup is automatically handled through registering drm_mode_config_cleanup
* with drmm_add_action().
*
* Returns: 0 on success, negative error value on failure.
*/
int drmm_mode_config_init(struct drm_device *dev)
{
int ret;
mutex_init(&dev->mode_config.mutex);
drm_modeset_lock_init(&dev->mode_config.connection_mutex);
mutex_init(&dev->mode_config.idr_mutex);
mutex_init(&dev->mode_config.fb_lock);
mutex_init(&dev->mode_config.blob_lock);
INIT_LIST_HEAD(&dev->mode_config.fb_list);
INIT_LIST_HEAD(&dev->mode_config.crtc_list);
INIT_LIST_HEAD(&dev->mode_config.connector_list);
INIT_LIST_HEAD(&dev->mode_config.encoder_list);
INIT_LIST_HEAD(&dev->mode_config.property_list);
INIT_LIST_HEAD(&dev->mode_config.property_blob_list);
INIT_LIST_HEAD(&dev->mode_config.plane_list);
INIT_LIST_HEAD(&dev->mode_config.colorop_list);
INIT_LIST_HEAD(&dev->mode_config.privobj_list);
idr_init_base(&dev->mode_config.object_idr, 1);
idr_init_base(&dev->mode_config.tile_idr, 1);
ida_init(&dev->mode_config.connector_ida);
spin_lock_init(&dev->mode_config.connector_list_lock);
init_llist_head(&dev->mode_config.connector_free_list);
INIT_WORK(&dev->mode_config.connector_free_work, drm_connector_free_work_fn);
ret = drm_mode_create_standard_properties(dev);
if (ret) {
drm_mode_config_cleanup(dev);
return ret;
}
/* Just to be sure */
dev->mode_config.num_fb = 0;
dev->mode_config.num_connector = 0;
dev->mode_config.num_crtc = 0;
dev->mode_config.num_encoder = 0;
dev->mode_config.num_total_plane = 0;
dev->mode_config.num_colorop = 0;
if (IS_ENABLED(CONFIG_LOCKDEP)) {
struct drm_modeset_acquire_ctx modeset_ctx;
struct ww_acquire_ctx resv_ctx;
struct dma_resv resv;
int ret;
dma_resv_init(&resv);
drm_modeset_acquire_init(&modeset_ctx, 0);
ret = drm_modeset_lock(&dev->mode_config.connection_mutex,
&modeset_ctx);
if (ret == -EDEADLK)
ret = drm_modeset_backoff(&modeset_ctx);
might_fault();
ww_acquire_init(&resv_ctx, &reservation_ww_class);
ret = dma_resv_lock(&resv, &resv_ctx);
if (ret == -EDEADLK)
dma_resv_lock_slow(&resv, &resv_ctx);
dma_resv_unlock(&resv);
ww_acquire_fini(&resv_ctx);
drm_modeset_drop_locks(&modeset_ctx);
drm_modeset_acquire_fini(&modeset_ctx);
dma_resv_fini(&resv);
}
return drmm_add_action_or_reset(dev, drm_mode_config_init_release,
NULL);
}
EXPORT_SYMBOL(drmm_mode_config_init);
/**
* drm_mode_config_cleanup - free up DRM mode_config info
* @dev: DRM device
*
* Free up all the connectors and CRTCs associated with this DRM device, then
* free up the framebuffers and associated buffer objects.
*
* Note that since this /should/ happen single-threaded at driver/device
* teardown time, no locking is required. It's the driver's job to ensure that
* this guarantee actually holds true.
*
* FIXME: With the managed drmm_mode_config_init() it is no longer necessary for
* drivers to explicitly call this function.
*/
void drm_mode_config_cleanup(struct drm_device *dev)
{
struct drm_connector *connector;
struct drm_connector_list_iter conn_iter;
struct drm_crtc *crtc, *ct;
struct drm_encoder *encoder, *enct;
struct drm_framebuffer *fb, *fbt;
struct drm_property *property, *pt;
struct drm_property_blob *blob, *bt;
struct drm_plane *plane, *plt;
list_for_each_entry_safe(encoder, enct, &dev->mode_config.encoder_list,
head) {
encoder->funcs->destroy(encoder);
}
drm_connector_list_iter_begin(dev, &conn_iter);
drm_for_each_connector_iter(connector, &conn_iter) {
/* drm_connector_list_iter holds an full reference to the
* current connector itself, which means it is inherently safe
* against unreferencing the current connector - but not against
* deleting it right away. */
drm_connector_put(connector);
}
drm_connector_list_iter_end(&conn_iter);
/* connector_iter drops references in a work item. */
flush_work(&dev->mode_config.connector_free_work);
if (WARN_ON(!list_empty(&dev->mode_config.connector_list))) {
drm_connector_list_iter_begin(dev, &conn_iter);
drm_for_each_connector_iter(connector, &conn_iter)
DRM_ERROR("connector %s leaked!\n", connector->name);
drm_connector_list_iter_end(&conn_iter);
}
list_for_each_entry_safe(property, pt, &dev->mode_config.property_list,
head) {
drm_property_destroy(dev, property);
}
list_for_each_entry_safe(plane, plt, &dev->mode_config.plane_list,
head) {
plane->funcs->destroy(plane);
}
list_for_each_entry_safe(crtc, ct, &dev->mode_config.crtc_list, head) {
crtc->funcs->destroy(crtc);
}
list_for_each_entry_safe(blob, bt, &dev->mode_config.property_blob_list,
head_global) {
drm_property_blob_put(blob);
}
/*
* Single-threaded teardown context, so it's not required to grab the
* fb_lock to protect against concurrent fb_list access. Contrary, it
* would actually deadlock with the drm_framebuffer_cleanup function.
*
* Also, if there are any framebuffers left, that's a driver leak now,
* so politely WARN about this.
*/
WARN_ON(!list_empty(&dev->mode_config.fb_list));
list_for_each_entry_safe(fb, fbt, &dev->mode_config.fb_list, head) {
if (list_empty(&fb->filp_head) || drm_framebuffer_read_refcount(fb) > 1) {
struct drm_printer p = drm_dbg_printer(dev, DRM_UT_KMS, "[leaked fb]");
drm_printf(&p, "framebuffer[%u]:\n", fb->base.id);
drm_framebuffer_print_info(&p, 1, fb);
}
list_del_init(&fb->filp_head);
drm_framebuffer_free(&fb->base.refcount);
}
ida_destroy(&dev->mode_config.connector_ida);
idr_destroy(&dev->mode_config.tile_idr);
idr_destroy(&dev->mode_config.object_idr);
drm_modeset_lock_fini(&dev->mode_config.connection_mutex);
}
EXPORT_SYMBOL(drm_mode_config_cleanup);
static u32 full_encoder_mask(struct drm_device *dev)
{
struct drm_encoder *encoder;
u32 encoder_mask = 0;
drm_for_each_encoder(encoder, dev)
encoder_mask |= drm_encoder_mask(encoder);
return encoder_mask;
}
/*
* For some reason we want the encoder itself included in
* possible_clones. Make life easy for drivers by allowing them
* to leave possible_clones unset if no cloning is possible.
*/
static void fixup_encoder_possible_clones(struct drm_encoder *encoder)
{
if (encoder->possible_clones == 0)
encoder->possible_clones = drm_encoder_mask(encoder);
}
static void validate_encoder_possible_clones(struct drm_encoder *encoder)
{
struct drm_device *dev = encoder->dev;
u32 encoder_mask = full_encoder_mask(dev);
struct drm_encoder *other;
drm_for_each_encoder(other, dev) {
WARN(!!(encoder->possible_clones & drm_encoder_mask(other)) !=
!!(other->possible_clones & drm_encoder_mask(encoder)),
"possible_clones mismatch: "
"[ENCODER:%d:%s] mask=0x%x possible_clones=0x%x vs. "
"[ENCODER:%d:%s] mask=0x%x possible_clones=0x%x\n",
encoder->base.id, encoder->name,
drm_encoder_mask(encoder), encoder->possible_clones,
other->base.id, other->name,
drm_encoder_mask(other), other->possible_clones);
}
WARN((encoder->possible_clones & drm_encoder_mask(encoder)) == 0 ||
(encoder->possible_clones & ~encoder_mask) != 0,
"Bogus possible_clones: "
"[ENCODER:%d:%s] possible_clones=0x%x (full encoder mask=0x%x)\n",
encoder->base.id, encoder->name,
encoder->possible_clones, encoder_mask);
}
static u32 full_crtc_mask(struct drm_device *dev)
{
struct drm_crtc *crtc;
u32 crtc_mask = 0;
drm_for_each_crtc(crtc, dev)
crtc_mask |= drm_crtc_mask(crtc);
return crtc_mask;
}
static void validate_encoder_possible_crtcs(struct drm_encoder *encoder)
{
u32 crtc_mask = full_crtc_mask(encoder->dev);
WARN((encoder->possible_crtcs & crtc_mask) == 0 ||
(encoder->possible_crtcs & ~crtc_mask) != 0,
"Bogus possible_crtcs: "
"[ENCODER:%d:%s] possible_crtcs=0x%x (full crtc mask=0x%x)\n",
encoder->base.id, encoder->name,
encoder->possible_crtcs, crtc_mask);
}
void drm_mode_config_validate(struct drm_device *dev)
{
struct drm_encoder *encoder;
struct drm_crtc *crtc;
struct drm_plane *plane;
u32 primary_with_crtc = 0, cursor_with_crtc = 0;
unsigned int num_primary = 0;
if (!drm_core_check_feature(dev, DRIVER_MODESET))
return;
drm_for_each_encoder(encoder, dev)
fixup_encoder_possible_clones(encoder);
drm_for_each_encoder(encoder, dev) {
validate_encoder_possible_clones(encoder);
validate_encoder_possible_crtcs(encoder);
}
drm_for_each_crtc(crtc, dev) {
WARN(!crtc->primary, "Missing primary plane on [CRTC:%d:%s]\n",
crtc->base.id, crtc->name);
WARN(crtc->cursor && crtc->funcs->cursor_set,
"[CRTC:%d:%s] must not have both a cursor plane and a cursor_set func",
crtc->base.id, crtc->name);
WARN(crtc->cursor && crtc->funcs->cursor_set2,
"[CRTC:%d:%s] must not have both a cursor plane and a cursor_set2 func",
crtc->base.id, crtc->name);
WARN(crtc->cursor && crtc->funcs->cursor_move,
"[CRTC:%d:%s] must not have both a cursor plane and a cursor_move func",
crtc->base.id, crtc->name);
if (crtc->primary) {
WARN(!(crtc->primary->possible_crtcs & drm_crtc_mask(crtc)),
"Bogus primary plane possible_crtcs: [PLANE:%d:%s] must be compatible with [CRTC:%d:%s]\n",
crtc->primary->base.id, crtc->primary->name,
crtc->base.id, crtc->name);
WARN(primary_with_crtc & drm_plane_mask(crtc->primary),
"Primary plane [PLANE:%d:%s] used for multiple CRTCs",
crtc->primary->base.id, crtc->primary->name);
primary_with_crtc |= drm_plane_mask(crtc->primary);
}
if (crtc->cursor) {
WARN(!(crtc->cursor->possible_crtcs & drm_crtc_mask(crtc)),
"Bogus cursor plane possible_crtcs: [PLANE:%d:%s] must be compatible with [CRTC:%d:%s]\n",
crtc->cursor->base.id, crtc->cursor->name,
crtc->base.id, crtc->name);
WARN(cursor_with_crtc & drm_plane_mask(crtc->cursor),
"Cursor plane [PLANE:%d:%s] used for multiple CRTCs",
crtc->cursor->base.id, crtc->cursor->name);
cursor_with_crtc |= drm_plane_mask(crtc->cursor);
}
}
drm_for_each_plane(plane, dev) {
if (plane->type == DRM_PLANE_TYPE_PRIMARY)
num_primary++;
}
WARN(num_primary != dev->mode_config.num_crtc,
"Must have as many primary planes as there are CRTCs, but have %u primary planes and %u CRTCs",
num_primary, dev->mode_config.num_crtc);
}