File Index
Files and directories
Sign in
FILE

vkms_config.c

File preview with syntax highlighting.

linux-6.19.14/drivers/gpu/drm/vkms/vkms_config.c C
  1// SPDX-License-Identifier: GPL-2.0+
  2
  3#include <linux/slab.h>
  4
  5#include <drm/drm_print.h>
  6#include <drm/drm_debugfs.h>
  7#include <kunit/visibility.h>
  8
  9#include "vkms_config.h"
 10
 11struct vkms_config *vkms_config_create(const char *dev_name)
 12{
 13	struct vkms_config *config;
 14
 15	config = kzalloc(sizeof(*config), GFP_KERNEL);
 16	if (!config)
 17		return ERR_PTR(-ENOMEM);
 18
 19	config->dev_name = kstrdup_const(dev_name, GFP_KERNEL);
 20	if (!config->dev_name) {
 21		kfree(config);
 22		return ERR_PTR(-ENOMEM);
 23	}
 24
 25	INIT_LIST_HEAD(&config->planes);
 26	INIT_LIST_HEAD(&config->crtcs);
 27	INIT_LIST_HEAD(&config->encoders);
 28	INIT_LIST_HEAD(&config->connectors);
 29
 30	return config;
 31}
 32EXPORT_SYMBOL_IF_KUNIT(vkms_config_create);
 33
 34struct vkms_config *vkms_config_default_create(bool enable_cursor,
 35					       bool enable_writeback,
 36					       bool enable_overlay,
 37					       bool enable_plane_pipeline)
 38{
 39	struct vkms_config *config;
 40	struct vkms_config_plane *plane_cfg;
 41	struct vkms_config_crtc *crtc_cfg;
 42	struct vkms_config_encoder *encoder_cfg;
 43	struct vkms_config_connector *connector_cfg;
 44	int n;
 45
 46	config = vkms_config_create(DEFAULT_DEVICE_NAME);
 47	if (IS_ERR(config))
 48		return config;
 49
 50	plane_cfg = vkms_config_create_plane(config);
 51	if (IS_ERR(plane_cfg))
 52		goto err_alloc;
 53	vkms_config_plane_set_type(plane_cfg, DRM_PLANE_TYPE_PRIMARY);
 54
 55	crtc_cfg = vkms_config_create_crtc(config);
 56	if (IS_ERR(crtc_cfg))
 57		goto err_alloc;
 58	vkms_config_crtc_set_writeback(crtc_cfg, enable_writeback);
 59
 60	if (vkms_config_plane_attach_crtc(plane_cfg, crtc_cfg))
 61		goto err_alloc;
 62	vkms_config_plane_set_default_pipeline(plane_cfg, enable_plane_pipeline);
 63
 64	if (enable_overlay) {
 65		for (n = 0; n < NUM_OVERLAY_PLANES; n++) {
 66			plane_cfg = vkms_config_create_plane(config);
 67			if (IS_ERR(plane_cfg))
 68				goto err_alloc;
 69
 70			vkms_config_plane_set_type(plane_cfg,
 71						   DRM_PLANE_TYPE_OVERLAY);
 72			vkms_config_plane_set_default_pipeline(plane_cfg, enable_plane_pipeline);
 73
 74			if (vkms_config_plane_attach_crtc(plane_cfg, crtc_cfg))
 75				goto err_alloc;
 76		}
 77	}
 78
 79	if (enable_cursor) {
 80		plane_cfg = vkms_config_create_plane(config);
 81		if (IS_ERR(plane_cfg))
 82			goto err_alloc;
 83
 84		vkms_config_plane_set_type(plane_cfg, DRM_PLANE_TYPE_CURSOR);
 85		vkms_config_plane_set_default_pipeline(plane_cfg, enable_plane_pipeline);
 86
 87		if (vkms_config_plane_attach_crtc(plane_cfg, crtc_cfg))
 88			goto err_alloc;
 89	}
 90
 91	encoder_cfg = vkms_config_create_encoder(config);
 92	if (IS_ERR(encoder_cfg))
 93		goto err_alloc;
 94
 95	if (vkms_config_encoder_attach_crtc(encoder_cfg, crtc_cfg))
 96		goto err_alloc;
 97
 98	connector_cfg = vkms_config_create_connector(config);
 99	if (IS_ERR(connector_cfg))
100		goto err_alloc;
101
102	if (vkms_config_connector_attach_encoder(connector_cfg, encoder_cfg))
103		goto err_alloc;
104
105	return config;
106
107err_alloc:
108	vkms_config_destroy(config);
109	return ERR_PTR(-ENOMEM);
110}
111EXPORT_SYMBOL_IF_KUNIT(vkms_config_default_create);
112
113void vkms_config_destroy(struct vkms_config *config)
114{
115	struct vkms_config_plane *plane_cfg, *plane_tmp;
116	struct vkms_config_crtc *crtc_cfg, *crtc_tmp;
117	struct vkms_config_encoder *encoder_cfg, *encoder_tmp;
118	struct vkms_config_connector *connector_cfg, *connector_tmp;
119
120	list_for_each_entry_safe(plane_cfg, plane_tmp, &config->planes, link)
121		vkms_config_destroy_plane(plane_cfg);
122
123	list_for_each_entry_safe(crtc_cfg, crtc_tmp, &config->crtcs, link)
124		vkms_config_destroy_crtc(config, crtc_cfg);
125
126	list_for_each_entry_safe(encoder_cfg, encoder_tmp, &config->encoders, link)
127		vkms_config_destroy_encoder(config, encoder_cfg);
128
129	list_for_each_entry_safe(connector_cfg, connector_tmp, &config->connectors, link)
130		vkms_config_destroy_connector(connector_cfg);
131
132	kfree_const(config->dev_name);
133	kfree(config);
134}
135EXPORT_SYMBOL_IF_KUNIT(vkms_config_destroy);
136
137static bool valid_plane_number(const struct vkms_config *config)
138{
139	struct drm_device *dev = config->dev ? &config->dev->drm : NULL;
140	size_t n_planes;
141
142	n_planes = list_count_nodes((struct list_head *)&config->planes);
143	if (n_planes <= 0 || n_planes >= 32) {
144		drm_info(dev, "The number of planes must be between 1 and 31\n");
145		return false;
146	}
147
148	return true;
149}
150
151static bool valid_planes_for_crtc(const struct vkms_config *config,
152				  struct vkms_config_crtc *crtc_cfg)
153{
154	struct drm_device *dev = config->dev ? &config->dev->drm : NULL;
155	struct vkms_config_plane *plane_cfg;
156	bool has_primary_plane = false;
157	bool has_cursor_plane = false;
158
159	vkms_config_for_each_plane(config, plane_cfg) {
160		struct vkms_config_crtc *possible_crtc;
161		unsigned long idx = 0;
162		enum drm_plane_type type;
163
164		type = vkms_config_plane_get_type(plane_cfg);
165
166		vkms_config_plane_for_each_possible_crtc(plane_cfg, idx, possible_crtc) {
167			if (possible_crtc != crtc_cfg)
168				continue;
169
170			if (type == DRM_PLANE_TYPE_PRIMARY) {
171				if (has_primary_plane) {
172					drm_info(dev, "Multiple primary planes\n");
173					return false;
174				}
175
176				has_primary_plane = true;
177			} else if (type == DRM_PLANE_TYPE_CURSOR) {
178				if (has_cursor_plane) {
179					drm_info(dev, "Multiple cursor planes\n");
180					return false;
181				}
182
183				has_cursor_plane = true;
184			}
185		}
186	}
187
188	if (!has_primary_plane) {
189		drm_info(dev, "Primary plane not found\n");
190		return false;
191	}
192
193	return true;
194}
195
196static bool valid_plane_possible_crtcs(const struct vkms_config *config)
197{
198	struct drm_device *dev = config->dev ? &config->dev->drm : NULL;
199	struct vkms_config_plane *plane_cfg;
200
201	vkms_config_for_each_plane(config, plane_cfg) {
202		if (xa_empty(&plane_cfg->possible_crtcs)) {
203			drm_info(dev, "All planes must have at least one possible CRTC\n");
204			return false;
205		}
206	}
207
208	return true;
209}
210
211static bool valid_crtc_number(const struct vkms_config *config)
212{
213	struct drm_device *dev = config->dev ? &config->dev->drm : NULL;
214	size_t n_crtcs;
215
216	n_crtcs = list_count_nodes((struct list_head *)&config->crtcs);
217	if (n_crtcs <= 0 || n_crtcs >= 32) {
218		drm_info(dev, "The number of CRTCs must be between 1 and 31\n");
219		return false;
220	}
221
222	return true;
223}
224
225static bool valid_encoder_number(const struct vkms_config *config)
226{
227	struct drm_device *dev = config->dev ? &config->dev->drm : NULL;
228	size_t n_encoders;
229
230	n_encoders = list_count_nodes((struct list_head *)&config->encoders);
231	if (n_encoders <= 0 || n_encoders >= 32) {
232		drm_info(dev, "The number of encoders must be between 1 and 31\n");
233		return false;
234	}
235
236	return true;
237}
238
239static bool valid_encoder_possible_crtcs(const struct vkms_config *config)
240{
241	struct drm_device *dev = config->dev ? &config->dev->drm : NULL;
242	struct vkms_config_crtc *crtc_cfg;
243	struct vkms_config_encoder *encoder_cfg;
244
245	vkms_config_for_each_encoder(config, encoder_cfg) {
246		if (xa_empty(&encoder_cfg->possible_crtcs)) {
247			drm_info(dev, "All encoders must have at least one possible CRTC\n");
248			return false;
249		}
250	}
251
252	vkms_config_for_each_crtc(config, crtc_cfg) {
253		bool crtc_has_encoder = false;
254
255		vkms_config_for_each_encoder(config, encoder_cfg) {
256			struct vkms_config_crtc *possible_crtc;
257			unsigned long idx = 0;
258
259			vkms_config_encoder_for_each_possible_crtc(encoder_cfg,
260								   idx, possible_crtc) {
261				if (possible_crtc == crtc_cfg)
262					crtc_has_encoder = true;
263			}
264		}
265
266		if (!crtc_has_encoder) {
267			drm_info(dev, "All CRTCs must have at least one possible encoder\n");
268			return false;
269		}
270	}
271
272	return true;
273}
274
275static bool valid_connector_number(const struct vkms_config *config)
276{
277	struct drm_device *dev = config->dev ? &config->dev->drm : NULL;
278	size_t n_connectors;
279
280	n_connectors = list_count_nodes((struct list_head *)&config->connectors);
281	if (n_connectors <= 0 || n_connectors >= 32) {
282		drm_info(dev, "The number of connectors must be between 1 and 31\n");
283		return false;
284	}
285
286	return true;
287}
288
289static bool valid_connector_possible_encoders(const struct vkms_config *config)
290{
291	struct drm_device *dev = config->dev ? &config->dev->drm : NULL;
292	struct vkms_config_connector *connector_cfg;
293
294	vkms_config_for_each_connector(config, connector_cfg) {
295		if (xa_empty(&connector_cfg->possible_encoders)) {
296			drm_info(dev,
297				 "All connectors must have at least one possible encoder\n");
298			return false;
299		}
300	}
301
302	return true;
303}
304
305bool vkms_config_is_valid(const struct vkms_config *config)
306{
307	struct vkms_config_crtc *crtc_cfg;
308
309	if (!valid_plane_number(config))
310		return false;
311
312	if (!valid_crtc_number(config))
313		return false;
314
315	if (!valid_encoder_number(config))
316		return false;
317
318	if (!valid_connector_number(config))
319		return false;
320
321	if (!valid_plane_possible_crtcs(config))
322		return false;
323
324	vkms_config_for_each_crtc(config, crtc_cfg) {
325		if (!valid_planes_for_crtc(config, crtc_cfg))
326			return false;
327	}
328
329	if (!valid_encoder_possible_crtcs(config))
330		return false;
331
332	if (!valid_connector_possible_encoders(config))
333		return false;
334
335	return true;
336}
337EXPORT_SYMBOL_IF_KUNIT(vkms_config_is_valid);
338
339static int vkms_config_show(struct seq_file *m, void *data)
340{
341	struct drm_debugfs_entry *entry = m->private;
342	struct drm_device *dev = entry->dev;
343	struct vkms_device *vkmsdev = drm_device_to_vkms_device(dev);
344	const char *dev_name;
345	struct vkms_config_plane *plane_cfg;
346	struct vkms_config_crtc *crtc_cfg;
347	struct vkms_config_encoder *encoder_cfg;
348	struct vkms_config_connector *connector_cfg;
349
350	dev_name = vkms_config_get_device_name((struct vkms_config *)vkmsdev->config);
351	seq_printf(m, "dev_name=%s\n", dev_name);
352
353	vkms_config_for_each_plane(vkmsdev->config, plane_cfg) {
354		seq_puts(m, "plane:\n");
355		seq_printf(m, "\ttype=%d\n",
356			   vkms_config_plane_get_type(plane_cfg));
357	}
358
359	vkms_config_for_each_crtc(vkmsdev->config, crtc_cfg) {
360		seq_puts(m, "crtc:\n");
361		seq_printf(m, "\twriteback=%d\n",
362			   vkms_config_crtc_get_writeback(crtc_cfg));
363	}
364
365	vkms_config_for_each_encoder(vkmsdev->config, encoder_cfg)
366		seq_puts(m, "encoder\n");
367
368	vkms_config_for_each_connector(vkmsdev->config, connector_cfg) {
369		seq_puts(m, "connector:\n");
370		seq_printf(m, "\tstatus=%d\n",
371			   vkms_config_connector_get_status(connector_cfg));
372	}
373
374	return 0;
375}
376
377static const struct drm_debugfs_info vkms_config_debugfs_list[] = {
378	{ "vkms_config", vkms_config_show, 0 },
379};
380
381void vkms_config_register_debugfs(struct vkms_device *vkms_device)
382{
383	drm_debugfs_add_files(&vkms_device->drm, vkms_config_debugfs_list,
384			      ARRAY_SIZE(vkms_config_debugfs_list));
385}
386
387struct vkms_config_plane *vkms_config_create_plane(struct vkms_config *config)
388{
389	struct vkms_config_plane *plane_cfg;
390
391	plane_cfg = kzalloc(sizeof(*plane_cfg), GFP_KERNEL);
392	if (!plane_cfg)
393		return ERR_PTR(-ENOMEM);
394
395	plane_cfg->config = config;
396	plane_cfg->default_pipeline = false;
397	vkms_config_plane_set_type(plane_cfg, DRM_PLANE_TYPE_OVERLAY);
398	xa_init_flags(&plane_cfg->possible_crtcs, XA_FLAGS_ALLOC);
399
400	list_add_tail(&plane_cfg->link, &config->planes);
401
402	return plane_cfg;
403}
404EXPORT_SYMBOL_IF_KUNIT(vkms_config_create_plane);
405
406void vkms_config_destroy_plane(struct vkms_config_plane *plane_cfg)
407{
408	xa_destroy(&plane_cfg->possible_crtcs);
409	list_del(&plane_cfg->link);
410	kfree(plane_cfg);
411}
412EXPORT_SYMBOL_IF_KUNIT(vkms_config_destroy_plane);
413
414int __must_check vkms_config_plane_attach_crtc(struct vkms_config_plane *plane_cfg,
415					       struct vkms_config_crtc *crtc_cfg)
416{
417	struct vkms_config_crtc *possible_crtc;
418	unsigned long idx = 0;
419	u32 crtc_idx = 0;
420
421	if (plane_cfg->config != crtc_cfg->config)
422		return -EINVAL;
423
424	vkms_config_plane_for_each_possible_crtc(plane_cfg, idx, possible_crtc) {
425		if (possible_crtc == crtc_cfg)
426			return -EEXIST;
427	}
428
429	return xa_alloc(&plane_cfg->possible_crtcs, &crtc_idx, crtc_cfg,
430			xa_limit_32b, GFP_KERNEL);
431}
432EXPORT_SYMBOL_IF_KUNIT(vkms_config_plane_attach_crtc);
433
434void vkms_config_plane_detach_crtc(struct vkms_config_plane *plane_cfg,
435				   struct vkms_config_crtc *crtc_cfg)
436{
437	struct vkms_config_crtc *possible_crtc;
438	unsigned long idx = 0;
439
440	vkms_config_plane_for_each_possible_crtc(plane_cfg, idx, possible_crtc) {
441		if (possible_crtc == crtc_cfg)
442			xa_erase(&plane_cfg->possible_crtcs, idx);
443	}
444}
445EXPORT_SYMBOL_IF_KUNIT(vkms_config_plane_detach_crtc);
446
447struct vkms_config_crtc *vkms_config_create_crtc(struct vkms_config *config)
448{
449	struct vkms_config_crtc *crtc_cfg;
450
451	crtc_cfg = kzalloc(sizeof(*crtc_cfg), GFP_KERNEL);
452	if (!crtc_cfg)
453		return ERR_PTR(-ENOMEM);
454
455	crtc_cfg->config = config;
456	vkms_config_crtc_set_writeback(crtc_cfg, false);
457
458	list_add_tail(&crtc_cfg->link, &config->crtcs);
459
460	return crtc_cfg;
461}
462EXPORT_SYMBOL_IF_KUNIT(vkms_config_create_crtc);
463
464void vkms_config_destroy_crtc(struct vkms_config *config,
465			      struct vkms_config_crtc *crtc_cfg)
466{
467	struct vkms_config_plane *plane_cfg;
468	struct vkms_config_encoder *encoder_cfg;
469
470	vkms_config_for_each_plane(config, plane_cfg)
471		vkms_config_plane_detach_crtc(plane_cfg, crtc_cfg);
472
473	vkms_config_for_each_encoder(config, encoder_cfg)
474		vkms_config_encoder_detach_crtc(encoder_cfg, crtc_cfg);
475
476	list_del(&crtc_cfg->link);
477	kfree(crtc_cfg);
478}
479EXPORT_SYMBOL_IF_KUNIT(vkms_config_destroy_crtc);
480
481/**
482 * vkms_config_crtc_get_plane() - Return the first attached plane to a CRTC with
483 * the specific type
484 * @config: Configuration containing the CRTC and the plane
485 * @crtc_cfg: Only find planes attached to this CRTC
486 * @type: Plane type to search
487 *
488 * Returns:
489 * The first plane found attached to @crtc_cfg with the type @type.
490 */
491static struct vkms_config_plane *vkms_config_crtc_get_plane(const struct vkms_config *config,
492							    struct vkms_config_crtc *crtc_cfg,
493							    enum drm_plane_type type)
494{
495	struct vkms_config_plane *plane_cfg;
496	struct vkms_config_crtc *possible_crtc;
497	enum drm_plane_type current_type;
498	unsigned long idx = 0;
499
500	vkms_config_for_each_plane(config, plane_cfg) {
501		current_type = vkms_config_plane_get_type(plane_cfg);
502
503		vkms_config_plane_for_each_possible_crtc(plane_cfg, idx, possible_crtc) {
504			if (possible_crtc == crtc_cfg && current_type == type)
505				return plane_cfg;
506		}
507	}
508
509	return NULL;
510}
511
512struct vkms_config_plane *vkms_config_crtc_primary_plane(const struct vkms_config *config,
513							 struct vkms_config_crtc *crtc_cfg)
514{
515	return vkms_config_crtc_get_plane(config, crtc_cfg, DRM_PLANE_TYPE_PRIMARY);
516}
517EXPORT_SYMBOL_IF_KUNIT(vkms_config_crtc_primary_plane);
518
519struct vkms_config_plane *vkms_config_crtc_cursor_plane(const struct vkms_config *config,
520							struct vkms_config_crtc *crtc_cfg)
521{
522	return vkms_config_crtc_get_plane(config, crtc_cfg, DRM_PLANE_TYPE_CURSOR);
523}
524EXPORT_SYMBOL_IF_KUNIT(vkms_config_crtc_cursor_plane);
525
526struct vkms_config_encoder *vkms_config_create_encoder(struct vkms_config *config)
527{
528	struct vkms_config_encoder *encoder_cfg;
529
530	encoder_cfg = kzalloc(sizeof(*encoder_cfg), GFP_KERNEL);
531	if (!encoder_cfg)
532		return ERR_PTR(-ENOMEM);
533
534	encoder_cfg->config = config;
535	xa_init_flags(&encoder_cfg->possible_crtcs, XA_FLAGS_ALLOC);
536
537	list_add_tail(&encoder_cfg->link, &config->encoders);
538
539	return encoder_cfg;
540}
541EXPORT_SYMBOL_IF_KUNIT(vkms_config_create_encoder);
542
543void vkms_config_destroy_encoder(struct vkms_config *config,
544				 struct vkms_config_encoder *encoder_cfg)
545{
546	struct vkms_config_connector *connector_cfg;
547
548	vkms_config_for_each_connector(config, connector_cfg)
549		vkms_config_connector_detach_encoder(connector_cfg, encoder_cfg);
550
551	xa_destroy(&encoder_cfg->possible_crtcs);
552	list_del(&encoder_cfg->link);
553	kfree(encoder_cfg);
554}
555EXPORT_SYMBOL_IF_KUNIT(vkms_config_destroy_encoder);
556
557int __must_check vkms_config_encoder_attach_crtc(struct vkms_config_encoder *encoder_cfg,
558						 struct vkms_config_crtc *crtc_cfg)
559{
560	struct vkms_config_crtc *possible_crtc;
561	unsigned long idx = 0;
562	u32 crtc_idx = 0;
563
564	if (encoder_cfg->config != crtc_cfg->config)
565		return -EINVAL;
566
567	vkms_config_encoder_for_each_possible_crtc(encoder_cfg, idx, possible_crtc) {
568		if (possible_crtc == crtc_cfg)
569			return -EEXIST;
570	}
571
572	return xa_alloc(&encoder_cfg->possible_crtcs, &crtc_idx, crtc_cfg,
573			xa_limit_32b, GFP_KERNEL);
574}
575EXPORT_SYMBOL_IF_KUNIT(vkms_config_encoder_attach_crtc);
576
577void vkms_config_encoder_detach_crtc(struct vkms_config_encoder *encoder_cfg,
578				     struct vkms_config_crtc *crtc_cfg)
579{
580	struct vkms_config_crtc *possible_crtc;
581	unsigned long idx = 0;
582
583	vkms_config_encoder_for_each_possible_crtc(encoder_cfg, idx, possible_crtc) {
584		if (possible_crtc == crtc_cfg)
585			xa_erase(&encoder_cfg->possible_crtcs, idx);
586	}
587}
588EXPORT_SYMBOL_IF_KUNIT(vkms_config_encoder_detach_crtc);
589
590struct vkms_config_connector *vkms_config_create_connector(struct vkms_config *config)
591{
592	struct vkms_config_connector *connector_cfg;
593
594	connector_cfg = kzalloc(sizeof(*connector_cfg), GFP_KERNEL);
595	if (!connector_cfg)
596		return ERR_PTR(-ENOMEM);
597
598	connector_cfg->config = config;
599	connector_cfg->status = connector_status_connected;
600	xa_init_flags(&connector_cfg->possible_encoders, XA_FLAGS_ALLOC);
601
602	list_add_tail(&connector_cfg->link, &config->connectors);
603
604	return connector_cfg;
605}
606EXPORT_SYMBOL_IF_KUNIT(vkms_config_create_connector);
607
608void vkms_config_destroy_connector(struct vkms_config_connector *connector_cfg)
609{
610	xa_destroy(&connector_cfg->possible_encoders);
611	list_del(&connector_cfg->link);
612	kfree(connector_cfg);
613}
614EXPORT_SYMBOL_IF_KUNIT(vkms_config_destroy_connector);
615
616int __must_check vkms_config_connector_attach_encoder(struct vkms_config_connector *connector_cfg,
617						      struct vkms_config_encoder *encoder_cfg)
618{
619	struct vkms_config_encoder *possible_encoder;
620	unsigned long idx = 0;
621	u32 encoder_idx = 0;
622
623	if (connector_cfg->config != encoder_cfg->config)
624		return -EINVAL;
625
626	vkms_config_connector_for_each_possible_encoder(connector_cfg, idx,
627							possible_encoder) {
628		if (possible_encoder == encoder_cfg)
629			return -EEXIST;
630	}
631
632	return xa_alloc(&connector_cfg->possible_encoders, &encoder_idx,
633			encoder_cfg, xa_limit_32b, GFP_KERNEL);
634}
635EXPORT_SYMBOL_IF_KUNIT(vkms_config_connector_attach_encoder);
636
637void vkms_config_connector_detach_encoder(struct vkms_config_connector *connector_cfg,
638					  struct vkms_config_encoder *encoder_cfg)
639{
640	struct vkms_config_encoder *possible_encoder;
641	unsigned long idx = 0;
642
643	vkms_config_connector_for_each_possible_encoder(connector_cfg, idx,
644							possible_encoder) {
645		if (possible_encoder == encoder_cfg)
646			xa_erase(&connector_cfg->possible_encoders, idx);
647	}
648}
649EXPORT_SYMBOL_IF_KUNIT(vkms_config_connector_detach_encoder);
~/ Read-only file preview.