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 11 struct 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 } 32 EXPORT_SYMBOL_IF_KUNIT(vkms_config_create); 33 34 struct 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 107 err_alloc: 108 vkms_config_destroy(config); 109 return ERR_PTR(-ENOMEM); 110 } 111 EXPORT_SYMBOL_IF_KUNIT(vkms_config_default_create); 112 113 void 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 } 135 EXPORT_SYMBOL_IF_KUNIT(vkms_config_destroy); 136 137 static 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 151 static 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 196 static 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 211 static 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 225 static 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 239 static 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 275 static 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 289 static 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 305 bool 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 } 337 EXPORT_SYMBOL_IF_KUNIT(vkms_config_is_valid); 338 339 static 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 377 static const struct drm_debugfs_info vkms_config_debugfs_list[] = { 378 { "vkms_config", vkms_config_show, 0 }, 379 }; 380 381 void 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 387 struct 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 } 404 EXPORT_SYMBOL_IF_KUNIT(vkms_config_create_plane); 405 406 void 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 } 412 EXPORT_SYMBOL_IF_KUNIT(vkms_config_destroy_plane); 413 414 int __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 } 432 EXPORT_SYMBOL_IF_KUNIT(vkms_config_plane_attach_crtc); 433 434 void 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 } 445 EXPORT_SYMBOL_IF_KUNIT(vkms_config_plane_detach_crtc); 446 447 struct 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 } 462 EXPORT_SYMBOL_IF_KUNIT(vkms_config_create_crtc); 463 464 void 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 } 479 EXPORT_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 */ 491 static 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 512 struct 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 } 517 EXPORT_SYMBOL_IF_KUNIT(vkms_config_crtc_primary_plane); 518 519 struct 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 } 524 EXPORT_SYMBOL_IF_KUNIT(vkms_config_crtc_cursor_plane); 525 526 struct 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 } 541 EXPORT_SYMBOL_IF_KUNIT(vkms_config_create_encoder); 542 543 void 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 } 555 EXPORT_SYMBOL_IF_KUNIT(vkms_config_destroy_encoder); 556 557 int __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 } 575 EXPORT_SYMBOL_IF_KUNIT(vkms_config_encoder_attach_crtc); 576 577 void 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 } 588 EXPORT_SYMBOL_IF_KUNIT(vkms_config_encoder_detach_crtc); 589 590 struct 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 } 606 EXPORT_SYMBOL_IF_KUNIT(vkms_config_create_connector); 607 608 void 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 } 614 EXPORT_SYMBOL_IF_KUNIT(vkms_config_destroy_connector); 615 616 int __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 } 635 EXPORT_SYMBOL_IF_KUNIT(vkms_config_connector_attach_encoder); 636 637 void 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 } 649 EXPORT_SYMBOL_IF_KUNIT(vkms_config_connector_detach_encoder); 650