1 // SPDX-License-Identifier: GPL-2.0 2 /* 3 * Test cases for the drm_framebuffer functions 4 * 5 * Copyright (c) 2022 Maíra Canal <mairacanal@riseup.net> 6 */ 7 8 #include <kunit/device.h> 9 #include <kunit/test.h> 10 11 #include <drm/drm_device.h> 12 #include <drm/drm_drv.h> 13 #include <drm/drm_mode.h> 14 #include <drm/drm_framebuffer.h> 15 #include <drm/drm_fourcc.h> 16 #include <drm/drm_kunit_helpers.h> 17 #include <drm/drm_print.h> 18 19 #include "../drm_crtc_internal.h" 20 21 #define MIN_WIDTH 4 22 #define MAX_WIDTH 4096 23 #define MIN_HEIGHT 4 24 #define MAX_HEIGHT 4096 25 26 #define DRM_MODE_FB_INVALID BIT(2) 27 28 struct drm_framebuffer_test { 29 int buffer_created; 30 struct drm_mode_fb_cmd2 cmd; 31 const char *name; 32 }; 33 34 static const struct drm_framebuffer_test drm_framebuffer_create_cases[] = { 35 { .buffer_created = 1, .name = "ABGR8888 normal sizes", 36 .cmd = { .width = 600, .height = 600, .pixel_format = DRM_FORMAT_ABGR8888, 37 .handles = { 1, 0, 0 }, .pitches = { 4 * 600, 0, 0 }, 38 } 39 }, 40 { .buffer_created = 1, .name = "ABGR8888 max sizes", 41 .cmd = { .width = MAX_WIDTH, .height = MAX_HEIGHT, .pixel_format = DRM_FORMAT_ABGR8888, 42 .handles = { 1, 0, 0 }, .pitches = { 4 * MAX_WIDTH, 0, 0 }, 43 } 44 }, 45 { .buffer_created = 1, .name = "ABGR8888 pitch greater than min required", 46 .cmd = { .width = MAX_WIDTH, .height = MAX_HEIGHT, .pixel_format = DRM_FORMAT_ABGR8888, 47 .handles = { 1, 0, 0 }, .pitches = { 4 * MAX_WIDTH + 1, 0, 0 }, 48 } 49 }, 50 { .buffer_created = 0, .name = "ABGR8888 pitch less than min required", 51 .cmd = { .width = MAX_WIDTH, .height = MAX_HEIGHT, .pixel_format = DRM_FORMAT_ABGR8888, 52 .handles = { 1, 0, 0 }, .pitches = { 4 * MAX_WIDTH - 1, 0, 0 }, 53 } 54 }, 55 { .buffer_created = 0, .name = "ABGR8888 Invalid width", 56 .cmd = { .width = MAX_WIDTH + 1, .height = MAX_HEIGHT, .pixel_format = DRM_FORMAT_ABGR8888, 57 .handles = { 1, 0, 0 }, .pitches = { 4 * (MAX_WIDTH + 1), 0, 0 }, 58 } 59 }, 60 { .buffer_created = 0, .name = "ABGR8888 Invalid buffer handle", 61 .cmd = { .width = MAX_WIDTH, .height = MAX_HEIGHT, .pixel_format = DRM_FORMAT_ABGR8888, 62 .handles = { 0, 0, 0 }, .pitches = { 4 * MAX_WIDTH, 0, 0 }, 63 } 64 }, 65 { .buffer_created = 0, .name = "No pixel format", 66 .cmd = { .width = MAX_WIDTH, .height = MAX_HEIGHT, .pixel_format = 0, 67 .handles = { 1, 0, 0 }, .pitches = { 4 * MAX_WIDTH, 0, 0 }, 68 } 69 }, 70 { .buffer_created = 0, .name = "ABGR8888 Width 0", 71 .cmd = { .width = 0, .height = MAX_HEIGHT, .pixel_format = DRM_FORMAT_ABGR8888, 72 .handles = { 1, 0, 0 }, .pitches = { 4 * MAX_WIDTH, 0, 0 }, 73 } 74 }, 75 { .buffer_created = 0, .name = "ABGR8888 Height 0", 76 .cmd = { .width = MAX_WIDTH, .height = 0, .pixel_format = DRM_FORMAT_ABGR8888, 77 .handles = { 1, 0, 0 }, .pitches = { 4 * MAX_WIDTH, 0, 0 }, 78 } 79 }, 80 { .buffer_created = 0, .name = "ABGR8888 Out of bound height * pitch combination", 81 .cmd = { .width = MAX_WIDTH, .height = MAX_HEIGHT, .pixel_format = DRM_FORMAT_ABGR8888, 82 .handles = { 1, 0, 0 }, .offsets = { UINT_MAX - 1, 0, 0 }, 83 .pitches = { 4 * MAX_WIDTH, 0, 0 }, 84 } 85 }, 86 { .buffer_created = 1, .name = "ABGR8888 Large buffer offset", 87 .cmd = { .width = MAX_WIDTH, .height = MAX_HEIGHT, .pixel_format = DRM_FORMAT_ABGR8888, 88 .handles = { 1, 0, 0 }, .offsets = { UINT_MAX / 2, 0, 0 }, 89 .pitches = { 4 * MAX_WIDTH, 0, 0 }, 90 } 91 }, 92 93 /* 94 * All entries in members that represents per-plane values (@modifier, @handles, 95 * @pitches and @offsets) must be zero when unused. 96 */ 97 { .buffer_created = 0, .name = "ABGR8888 Buffer offset for inexistent plane", 98 .cmd = { .width = MAX_WIDTH, .height = MAX_HEIGHT, .pixel_format = DRM_FORMAT_ABGR8888, 99 .handles = { 1, 0, 0 }, .offsets = { UINT_MAX / 2, UINT_MAX / 2, 0 }, 100 .pitches = { 4 * MAX_WIDTH, 0, 0 }, .flags = DRM_MODE_FB_MODIFIERS, 101 } 102 }, 103 104 { .buffer_created = 0, .name = "ABGR8888 Invalid flag", 105 .cmd = { .width = MAX_WIDTH, .height = MAX_HEIGHT, .pixel_format = DRM_FORMAT_ABGR8888, 106 .handles = { 1, 0, 0 }, .offsets = { UINT_MAX / 2, 0, 0 }, 107 .pitches = { 4 * MAX_WIDTH, 0, 0 }, .flags = DRM_MODE_FB_INVALID, 108 } 109 }, 110 { .buffer_created = 1, .name = "ABGR8888 Set DRM_MODE_FB_MODIFIERS without modifiers", 111 .cmd = { .width = MAX_WIDTH, .height = MAX_HEIGHT, .pixel_format = DRM_FORMAT_ABGR8888, 112 .handles = { 1, 0, 0 }, .offsets = { UINT_MAX / 2, 0, 0 }, 113 .pitches = { 4 * MAX_WIDTH, 0, 0 }, .flags = DRM_MODE_FB_MODIFIERS, 114 } 115 }, 116 { .buffer_created = 1, .name = "ABGR8888 Valid buffer modifier", 117 .cmd = { .width = MAX_WIDTH, .height = MAX_HEIGHT, .pixel_format = DRM_FORMAT_ABGR8888, 118 .handles = { 1, 0, 0 }, .offsets = { UINT_MAX / 2, 0, 0 }, 119 .pitches = { 4 * MAX_WIDTH, 0, 0 }, .flags = DRM_MODE_FB_MODIFIERS, 120 .modifier = { AFBC_FORMAT_MOD_YTR, 0, 0 }, 121 } 122 }, 123 { .buffer_created = 0, 124 .name = "ABGR8888 Invalid buffer modifier(DRM_FORMAT_MOD_SAMSUNG_64_32_TILE)", 125 .cmd = { .width = MAX_WIDTH, .height = MAX_HEIGHT, .pixel_format = DRM_FORMAT_ABGR8888, 126 .handles = { 1, 0, 0 }, .offsets = { UINT_MAX / 2, 0, 0 }, 127 .pitches = { 4 * MAX_WIDTH, 0, 0 }, .flags = DRM_MODE_FB_MODIFIERS, 128 .modifier = { DRM_FORMAT_MOD_SAMSUNG_64_32_TILE, 0, 0 }, 129 } 130 }, 131 { .buffer_created = 1, .name = "ABGR8888 Extra pitches without DRM_MODE_FB_MODIFIERS", 132 .cmd = { .width = MAX_WIDTH, .height = MAX_HEIGHT, .pixel_format = DRM_FORMAT_ABGR8888, 133 .handles = { 1, 0, 0 }, .offsets = { UINT_MAX / 2, 0, 0 }, 134 .pitches = { 4 * MAX_WIDTH, 4 * MAX_WIDTH, 0 }, 135 } 136 }, 137 { .buffer_created = 0, .name = "ABGR8888 Extra pitches with DRM_MODE_FB_MODIFIERS", 138 .cmd = { .width = MAX_WIDTH, .height = MAX_HEIGHT, .pixel_format = DRM_FORMAT_ABGR8888, 139 .handles = { 1, 0, 0 }, .flags = DRM_MODE_FB_MODIFIERS, 140 .pitches = { 4 * MAX_WIDTH, 4 * MAX_WIDTH, 0 }, 141 } 142 }, 143 { .buffer_created = 1, .name = "NV12 Normal sizes", 144 .cmd = { .width = 600, .height = 600, .pixel_format = DRM_FORMAT_NV12, 145 .handles = { 1, 1, 0 }, .pitches = { 600, 600, 0 }, 146 } 147 }, 148 { .buffer_created = 1, .name = "NV12 Max sizes", 149 .cmd = { .width = MAX_WIDTH, .height = MAX_HEIGHT, .pixel_format = DRM_FORMAT_NV12, 150 .handles = { 1, 1, 0 }, .pitches = { MAX_WIDTH, MAX_WIDTH, 0 }, 151 } 152 }, 153 { .buffer_created = 0, .name = "NV12 Invalid pitch", 154 .cmd = { .width = MAX_WIDTH, .height = MAX_HEIGHT, .pixel_format = DRM_FORMAT_NV12, 155 .handles = { 1, 1, 0 }, .pitches = { MAX_WIDTH, MAX_WIDTH - 1, 0 }, 156 } 157 }, 158 { .buffer_created = 0, .name = "NV12 Invalid modifier/missing DRM_MODE_FB_MODIFIERS flag", 159 .cmd = { .width = MAX_WIDTH, .height = MAX_HEIGHT, .pixel_format = DRM_FORMAT_NV12, 160 .handles = { 1, 1, 0 }, .modifier = { DRM_FORMAT_MOD_SAMSUNG_64_32_TILE, 0, 0 }, 161 .pitches = { MAX_WIDTH, MAX_WIDTH, 0 }, 162 } 163 }, 164 { .buffer_created = 0, .name = "NV12 different modifier per-plane", 165 .cmd = { .width = MAX_WIDTH, .height = MAX_HEIGHT, .pixel_format = DRM_FORMAT_NV12, 166 .handles = { 1, 1, 0 }, .flags = DRM_MODE_FB_MODIFIERS, 167 .modifier = { DRM_FORMAT_MOD_SAMSUNG_64_32_TILE, 0, 0 }, 168 .pitches = { MAX_WIDTH, MAX_WIDTH, 0 }, 169 } 170 }, 171 { .buffer_created = 1, .name = "NV12 with DRM_FORMAT_MOD_SAMSUNG_64_32_TILE", 172 .cmd = { .width = MAX_WIDTH, .height = MAX_HEIGHT, .pixel_format = DRM_FORMAT_NV12, 173 .handles = { 1, 1, 0 }, .flags = DRM_MODE_FB_MODIFIERS, 174 .modifier = { DRM_FORMAT_MOD_SAMSUNG_64_32_TILE, 175 DRM_FORMAT_MOD_SAMSUNG_64_32_TILE, 0 }, 176 .pitches = { MAX_WIDTH, MAX_WIDTH, 0 }, 177 } 178 }, 179 { .buffer_created = 0, .name = "NV12 Valid modifiers without DRM_MODE_FB_MODIFIERS", 180 .cmd = { .width = MAX_WIDTH, .height = MAX_HEIGHT, .pixel_format = DRM_FORMAT_NV12, 181 .handles = { 1, 1, 0 }, .modifier = { DRM_FORMAT_MOD_SAMSUNG_64_32_TILE, 182 DRM_FORMAT_MOD_SAMSUNG_64_32_TILE, 0 }, 183 .pitches = { MAX_WIDTH, MAX_WIDTH, 0 }, 184 } 185 }, 186 { .buffer_created = 0, .name = "NV12 Modifier for inexistent plane", 187 .cmd = { .width = MAX_WIDTH, .height = MAX_HEIGHT, .pixel_format = DRM_FORMAT_NV12, 188 .handles = { 1, 1, 0 }, .flags = DRM_MODE_FB_MODIFIERS, 189 .modifier = { DRM_FORMAT_MOD_SAMSUNG_64_32_TILE, DRM_FORMAT_MOD_SAMSUNG_64_32_TILE, 190 DRM_FORMAT_MOD_SAMSUNG_64_32_TILE }, 191 .pitches = { MAX_WIDTH, MAX_WIDTH, 0 }, 192 } 193 }, 194 { .buffer_created = 0, .name = "NV12 Handle for inexistent plane", 195 .cmd = { .width = MAX_WIDTH, .height = MAX_HEIGHT, .pixel_format = DRM_FORMAT_NV12, 196 .handles = { 1, 1, 1 }, .flags = DRM_MODE_FB_MODIFIERS, 197 .pitches = { MAX_WIDTH, MAX_WIDTH, 0 }, 198 } 199 }, 200 { .buffer_created = 1, .name = "NV12 Handle for inexistent plane without DRM_MODE_FB_MODIFIERS", 201 .cmd = { .width = 600, .height = 600, .pixel_format = DRM_FORMAT_NV12, 202 .handles = { 1, 1, 1 }, .pitches = { 600, 600, 600 }, 203 } 204 }, 205 { .buffer_created = 1, .name = "YVU420 DRM_MODE_FB_MODIFIERS set without modifier", 206 .cmd = { .width = 600, .height = 600, .pixel_format = DRM_FORMAT_YVU420, 207 .handles = { 1, 1, 1 }, .flags = DRM_MODE_FB_MODIFIERS, 208 .pitches = { 600, 300, 300 }, 209 } 210 }, 211 { .buffer_created = 1, .name = "YVU420 Normal sizes", 212 .cmd = { .width = 600, .height = 600, .pixel_format = DRM_FORMAT_YVU420, 213 .handles = { 1, 1, 1 }, .pitches = { 600, 300, 300 }, 214 } 215 }, 216 { .buffer_created = 1, .name = "YVU420 Max sizes", 217 .cmd = { .width = MAX_WIDTH, .height = MAX_HEIGHT, .pixel_format = DRM_FORMAT_YVU420, 218 .handles = { 1, 1, 1 }, .pitches = { MAX_WIDTH, DIV_ROUND_UP(MAX_WIDTH, 2), 219 DIV_ROUND_UP(MAX_WIDTH, 2) }, 220 } 221 }, 222 { .buffer_created = 0, .name = "YVU420 Invalid pitch", 223 .cmd = { .width = MAX_WIDTH, .height = MAX_HEIGHT, .pixel_format = DRM_FORMAT_YVU420, 224 .handles = { 1, 1, 1 }, .pitches = { MAX_WIDTH, DIV_ROUND_UP(MAX_WIDTH, 2) - 1, 225 DIV_ROUND_UP(MAX_WIDTH, 2) }, 226 } 227 }, 228 { .buffer_created = 1, .name = "YVU420 Different pitches", 229 .cmd = { .width = MAX_WIDTH, .height = MAX_HEIGHT, .pixel_format = DRM_FORMAT_YVU420, 230 .handles = { 1, 1, 1 }, .pitches = { MAX_WIDTH, DIV_ROUND_UP(MAX_WIDTH, 2) + 1, 231 DIV_ROUND_UP(MAX_WIDTH, 2) + 7 }, 232 } 233 }, 234 { .buffer_created = 1, .name = "YVU420 Different buffer offsets/pitches", 235 .cmd = { .width = MAX_WIDTH, .height = MAX_HEIGHT, .pixel_format = DRM_FORMAT_YVU420, 236 .handles = { 1, 1, 1 }, .offsets = { MAX_WIDTH, MAX_WIDTH + 237 MAX_WIDTH * MAX_HEIGHT, MAX_WIDTH + 2 * MAX_WIDTH * MAX_HEIGHT }, 238 .pitches = { MAX_WIDTH, DIV_ROUND_UP(MAX_WIDTH, 2) + 1, 239 DIV_ROUND_UP(MAX_WIDTH, 2) + 7 }, 240 } 241 }, 242 { .buffer_created = 0, 243 .name = "YVU420 Modifier set just for plane 0, without DRM_MODE_FB_MODIFIERS", 244 .cmd = { .width = MAX_WIDTH, .height = MAX_HEIGHT, .pixel_format = DRM_FORMAT_YVU420, 245 .handles = { 1, 1, 1 }, .modifier = { AFBC_FORMAT_MOD_SPARSE, 0, 0 }, 246 .pitches = { MAX_WIDTH, DIV_ROUND_UP(MAX_WIDTH, 2), DIV_ROUND_UP(MAX_WIDTH, 2) }, 247 } 248 }, 249 { .buffer_created = 0, 250 .name = "YVU420 Modifier set just for planes 0, 1, without DRM_MODE_FB_MODIFIERS", 251 .cmd = { .width = MAX_WIDTH, .height = MAX_HEIGHT, .pixel_format = DRM_FORMAT_YVU420, 252 .handles = { 1, 1, 1 }, 253 .modifier = { AFBC_FORMAT_MOD_SPARSE, AFBC_FORMAT_MOD_SPARSE, 0 }, 254 .pitches = { MAX_WIDTH, DIV_ROUND_UP(MAX_WIDTH, 2), DIV_ROUND_UP(MAX_WIDTH, 2) }, 255 } 256 }, 257 { .buffer_created = 0, 258 .name = "YVU420 Modifier set just for plane 0, 1, with DRM_MODE_FB_MODIFIERS", 259 .cmd = { .width = MAX_WIDTH, .height = MAX_HEIGHT, .pixel_format = DRM_FORMAT_YVU420, 260 .handles = { 1, 1, 1 }, .flags = DRM_MODE_FB_MODIFIERS, 261 .modifier = { AFBC_FORMAT_MOD_SPARSE, AFBC_FORMAT_MOD_SPARSE, 0 }, 262 .pitches = { MAX_WIDTH, DIV_ROUND_UP(MAX_WIDTH, 2), DIV_ROUND_UP(MAX_WIDTH, 2) }, 263 } 264 }, 265 { .buffer_created = 1, .name = "YVU420 Valid modifier", 266 .cmd = { .width = MAX_WIDTH, .height = MAX_HEIGHT, .pixel_format = DRM_FORMAT_YVU420, 267 .handles = { 1, 1, 1 }, .flags = DRM_MODE_FB_MODIFIERS, 268 .modifier = { AFBC_FORMAT_MOD_SPARSE, AFBC_FORMAT_MOD_SPARSE, 269 AFBC_FORMAT_MOD_SPARSE }, 270 .pitches = { MAX_WIDTH, DIV_ROUND_UP(MAX_WIDTH, 2), DIV_ROUND_UP(MAX_WIDTH, 2) }, 271 } 272 }, 273 { .buffer_created = 0, .name = "YVU420 Different modifiers per plane", 274 .cmd = { .width = MAX_WIDTH, .height = MAX_HEIGHT, .pixel_format = DRM_FORMAT_YVU420, 275 .handles = { 1, 1, 1 }, .flags = DRM_MODE_FB_MODIFIERS, 276 .modifier = { AFBC_FORMAT_MOD_SPARSE, AFBC_FORMAT_MOD_SPARSE | AFBC_FORMAT_MOD_YTR, 277 AFBC_FORMAT_MOD_SPARSE }, 278 .pitches = { MAX_WIDTH, DIV_ROUND_UP(MAX_WIDTH, 2), DIV_ROUND_UP(MAX_WIDTH, 2) }, 279 } 280 }, 281 { .buffer_created = 0, .name = "YVU420 Modifier for inexistent plane", 282 .cmd = { .width = MAX_WIDTH, .height = MAX_HEIGHT, .pixel_format = DRM_FORMAT_YVU420, 283 .handles = { 1, 1, 1 }, .flags = DRM_MODE_FB_MODIFIERS, 284 .modifier = { AFBC_FORMAT_MOD_SPARSE, AFBC_FORMAT_MOD_SPARSE, 285 AFBC_FORMAT_MOD_SPARSE, AFBC_FORMAT_MOD_SPARSE }, 286 .pitches = { MAX_WIDTH, DIV_ROUND_UP(MAX_WIDTH, 2), DIV_ROUND_UP(MAX_WIDTH, 2) }, 287 } 288 }, 289 { .buffer_created = 0, .name = "YUV420_10BIT Invalid modifier(DRM_FORMAT_MOD_LINEAR)", 290 .cmd = { .width = MAX_WIDTH, .height = MAX_HEIGHT, .pixel_format = DRM_FORMAT_YUV420_10BIT, 291 .handles = { 1, 0, 0 }, .flags = DRM_MODE_FB_MODIFIERS, 292 .modifier = { DRM_FORMAT_MOD_LINEAR, 0, 0 }, 293 .pitches = { MAX_WIDTH, 0, 0 }, 294 } 295 }, 296 { .buffer_created = 1, .name = "X0L2 Normal sizes", 297 .cmd = { .width = 600, .height = 600, .pixel_format = DRM_FORMAT_X0L2, 298 .handles = { 1, 0, 0 }, .pitches = { 1200, 0, 0 } 299 } 300 }, 301 { .buffer_created = 1, .name = "X0L2 Max sizes", 302 .cmd = { .width = MAX_WIDTH, .height = MAX_HEIGHT, .pixel_format = DRM_FORMAT_X0L2, 303 .handles = { 1, 0, 0 }, .pitches = { 2 * MAX_WIDTH, 0, 0 } 304 } 305 }, 306 { .buffer_created = 0, .name = "X0L2 Invalid pitch", 307 .cmd = { .width = MAX_WIDTH, .height = MAX_HEIGHT, .pixel_format = DRM_FORMAT_X0L2, 308 .handles = { 1, 0, 0 }, .pitches = { 2 * MAX_WIDTH - 1, 0, 0 } 309 } 310 }, 311 { .buffer_created = 1, .name = "X0L2 Pitch greater than minimum required", 312 .cmd = { .width = MAX_WIDTH, .height = MAX_HEIGHT, .pixel_format = DRM_FORMAT_X0L2, 313 .handles = { 1, 0, 0 }, .pitches = { 2 * MAX_WIDTH + 1, 0, 0 } 314 } 315 }, 316 { .buffer_created = 0, .name = "X0L2 Handle for inexistent plane", 317 .cmd = { .width = MAX_WIDTH, .height = MAX_HEIGHT, .pixel_format = DRM_FORMAT_X0L2, 318 .handles = { 1, 1, 0 }, .flags = DRM_MODE_FB_MODIFIERS, 319 .pitches = { 2 * MAX_WIDTH + 1, 0, 0 } 320 } 321 }, 322 { .buffer_created = 1, 323 .name = "X0L2 Offset for inexistent plane, without DRM_MODE_FB_MODIFIERS set", 324 .cmd = { .width = MAX_WIDTH, .height = MAX_HEIGHT, .pixel_format = DRM_FORMAT_X0L2, 325 .handles = { 1, 0, 0 }, .offsets = { 0, 0, 3 }, 326 .pitches = { 2 * MAX_WIDTH + 1, 0, 0 } 327 } 328 }, 329 { .buffer_created = 0, .name = "X0L2 Modifier without DRM_MODE_FB_MODIFIERS set", 330 .cmd = { .width = MAX_WIDTH, .height = MAX_HEIGHT, .pixel_format = DRM_FORMAT_X0L2, 331 .handles = { 1, 0, 0 }, .pitches = { 2 * MAX_WIDTH + 1, 0, 0 }, 332 .modifier = { AFBC_FORMAT_MOD_SPARSE, 0, 0 }, 333 } 334 }, 335 { .buffer_created = 1, .name = "X0L2 Valid modifier", 336 .cmd = { .width = MAX_WIDTH, .height = MAX_HEIGHT, .pixel_format = DRM_FORMAT_X0L2, 337 .handles = { 1, 0, 0 }, .pitches = { 2 * MAX_WIDTH + 1, 0, 0 }, 338 .modifier = { AFBC_FORMAT_MOD_SPARSE, 0, 0 }, .flags = DRM_MODE_FB_MODIFIERS, 339 } 340 }, 341 { .buffer_created = 0, .name = "X0L2 Modifier for inexistent plane", 342 .cmd = { .width = MAX_WIDTH, .height = MAX_HEIGHT, 343 .pixel_format = DRM_FORMAT_X0L2, .handles = { 1, 0, 0 }, 344 .pitches = { 2 * MAX_WIDTH + 1, 0, 0 }, 345 .modifier = { AFBC_FORMAT_MOD_SPARSE, AFBC_FORMAT_MOD_SPARSE, 0 }, 346 .flags = DRM_MODE_FB_MODIFIERS, 347 } 348 }, 349 }; 350 351 /* 352 * This struct is intended to provide a way to mocked functions communicate 353 * with the outer test when it can't be achieved by using its return value. In 354 * this way, the functions that receive the mocked drm_device, for example, can 355 * grab a reference to this and actually return something to be used on some 356 * expectation. 357 */ 358 struct drm_framebuffer_test_priv { 359 struct drm_device dev; 360 bool buffer_created; 361 bool buffer_freed; 362 }; 363 364 static struct drm_framebuffer *fb_create_mock(struct drm_device *dev, 365 struct drm_file *file_priv, 366 const struct drm_mode_fb_cmd2 *mode_cmd) 367 { 368 struct drm_framebuffer_test_priv *priv = container_of(dev, typeof(*priv), dev); 369 370 priv->buffer_created = true; 371 return ERR_PTR(-EINVAL); 372 } 373 374 static struct drm_mode_config_funcs mock_config_funcs = { 375 .fb_create = fb_create_mock, 376 }; 377 378 static int drm_framebuffer_test_init(struct kunit *test) 379 { 380 struct device *parent; 381 struct drm_framebuffer_test_priv *priv; 382 struct drm_device *dev; 383 384 parent = drm_kunit_helper_alloc_device(test); 385 KUNIT_ASSERT_NOT_ERR_OR_NULL(test, parent); 386 387 priv = drm_kunit_helper_alloc_drm_device(test, parent, typeof(*priv), 388 dev, 0); 389 KUNIT_ASSERT_NOT_ERR_OR_NULL(test, priv); 390 dev = &priv->dev; 391 392 dev->mode_config.min_width = MIN_WIDTH; 393 dev->mode_config.max_width = MAX_WIDTH; 394 dev->mode_config.min_height = MIN_HEIGHT; 395 dev->mode_config.max_height = MAX_HEIGHT; 396 dev->mode_config.funcs = &mock_config_funcs; 397 398 test->priv = priv; 399 return 0; 400 } 401 402 static void drm_test_framebuffer_create(struct kunit *test) 403 { 404 const struct drm_framebuffer_test *params = test->param_value; 405 struct drm_framebuffer_test_priv *priv = test->priv; 406 struct drm_device *dev = &priv->dev; 407 408 priv->buffer_created = false; 409 drm_internal_framebuffer_create(dev, ¶ms->cmd, NULL); 410 KUNIT_EXPECT_EQ(test, params->buffer_created, priv->buffer_created); 411 } 412 413 static void drm_framebuffer_test_to_desc(const struct drm_framebuffer_test *t, char *desc) 414 { 415 strscpy(desc, t->name, KUNIT_PARAM_DESC_SIZE); 416 } 417 418 KUNIT_ARRAY_PARAM(drm_framebuffer_create, drm_framebuffer_create_cases, 419 drm_framebuffer_test_to_desc); 420 421 /* Tries to create a framebuffer with modifiers without drm_device supporting it */ 422 static void drm_test_framebuffer_modifiers_not_supported(struct kunit *test) 423 { 424 struct drm_framebuffer_test_priv *priv = test->priv; 425 struct drm_device *dev = &priv->dev; 426 struct drm_framebuffer *fb; 427 428 /* A valid cmd with modifier */ 429 struct drm_mode_fb_cmd2 cmd = { 430 .width = MAX_WIDTH, .height = MAX_HEIGHT, 431 .pixel_format = DRM_FORMAT_ABGR8888, .handles = { 1, 0, 0 }, 432 .offsets = { UINT_MAX / 2, 0, 0 }, .pitches = { 4 * MAX_WIDTH, 0, 0 }, 433 .flags = DRM_MODE_FB_MODIFIERS, 434 }; 435 436 priv->buffer_created = false; 437 dev->mode_config.fb_modifiers_not_supported = 1; 438 439 fb = drm_internal_framebuffer_create(dev, &cmd, NULL); 440 KUNIT_EXPECT_EQ(test, priv->buffer_created, false); 441 KUNIT_EXPECT_EQ(test, PTR_ERR(fb), -EINVAL); 442 } 443 444 /* Parameters for testing drm_framebuffer_check_src_coords function */ 445 struct drm_framebuffer_check_src_coords_case { 446 const char *name; 447 const int expect; 448 const unsigned int fb_size; 449 const uint32_t src_x; 450 const uint32_t src_y; 451 452 /* Deltas to be applied on source */ 453 const uint32_t dsrc_w; 454 const uint32_t dsrc_h; 455 }; 456 457 static const struct drm_framebuffer_check_src_coords_case 458 drm_framebuffer_check_src_coords_cases[] = { 459 { .name = "Success: source fits into fb", 460 .expect = 0, 461 }, 462 { .name = "Fail: overflowing fb with x-axis coordinate", 463 .expect = -ENOSPC, .src_x = 1, .fb_size = UINT_MAX, 464 }, 465 { .name = "Fail: overflowing fb with y-axis coordinate", 466 .expect = -ENOSPC, .src_y = 1, .fb_size = UINT_MAX, 467 }, 468 { .name = "Fail: overflowing fb with source width", 469 .expect = -ENOSPC, .dsrc_w = 1, .fb_size = UINT_MAX - 1, 470 }, 471 { .name = "Fail: overflowing fb with source height", 472 .expect = -ENOSPC, .dsrc_h = 1, .fb_size = UINT_MAX - 1, 473 }, 474 }; 475 476 static void drm_test_framebuffer_check_src_coords(struct kunit *test) 477 { 478 const struct drm_framebuffer_check_src_coords_case *params = test->param_value; 479 const uint32_t src_x = params->src_x; 480 const uint32_t src_y = params->src_y; 481 const uint32_t src_w = (params->fb_size << 16) + params->dsrc_w; 482 const uint32_t src_h = (params->fb_size << 16) + params->dsrc_h; 483 const struct drm_framebuffer fb = { 484 .width = params->fb_size, 485 .height = params->fb_size 486 }; 487 int ret; 488 489 ret = drm_framebuffer_check_src_coords(src_x, src_y, src_w, src_h, &fb); 490 KUNIT_EXPECT_EQ(test, ret, params->expect); 491 } 492 493 static void 494 check_src_coords_test_to_desc(const struct drm_framebuffer_check_src_coords_case *t, 495 char *desc) 496 { 497 strscpy(desc, t->name, KUNIT_PARAM_DESC_SIZE); 498 } 499 500 KUNIT_ARRAY_PARAM(check_src_coords, drm_framebuffer_check_src_coords_cases, 501 check_src_coords_test_to_desc); 502 503 /* 504 * Test if drm_framebuffer_cleanup() really pops out the framebuffer object 505 * from device's fb_list and decrement the number of framebuffers for that 506 * device, which is the only things it does. 507 */ 508 static void drm_test_framebuffer_cleanup(struct kunit *test) 509 { 510 struct drm_framebuffer_test_priv *priv = test->priv; 511 struct drm_device *dev = &priv->dev; 512 struct list_head *fb_list = &dev->mode_config.fb_list; 513 struct drm_format_info format = { }; 514 struct drm_framebuffer fb1 = { .dev = dev, .format = &format }; 515 struct drm_framebuffer fb2 = { .dev = dev, .format = &format }; 516 517 /* This will result on [fb_list] -> fb2 -> fb1 */ 518 drm_framebuffer_init(dev, &fb1, NULL); 519 drm_framebuffer_init(dev, &fb2, NULL); 520 521 drm_framebuffer_cleanup(&fb1); 522 523 /* Now fb2 is the only one element on fb_list */ 524 KUNIT_ASSERT_TRUE(test, list_is_singular(&fb2.head)); 525 KUNIT_ASSERT_EQ(test, dev->mode_config.num_fb, 1); 526 527 drm_framebuffer_cleanup(&fb2); 528 529 /* Now fb_list is empty */ 530 KUNIT_ASSERT_TRUE(test, list_empty(fb_list)); 531 KUNIT_ASSERT_EQ(test, dev->mode_config.num_fb, 0); 532 } 533 534 /* 535 * Initialize a framebuffer, lookup its id and test if the returned reference 536 * matches. 537 */ 538 static void drm_test_framebuffer_lookup(struct kunit *test) 539 { 540 struct drm_framebuffer_test_priv *priv = test->priv; 541 struct drm_device *dev = &priv->dev; 542 struct drm_format_info format = { }; 543 struct drm_framebuffer expected_fb = { .dev = dev, .format = &format }; 544 struct drm_framebuffer *returned_fb; 545 uint32_t id = 0; 546 int ret; 547 548 ret = drm_framebuffer_init(dev, &expected_fb, NULL); 549 KUNIT_ASSERT_EQ(test, ret, 0); 550 id = expected_fb.base.id; 551 552 /* Looking for expected_fb */ 553 returned_fb = drm_framebuffer_lookup(dev, NULL, id); 554 KUNIT_EXPECT_PTR_EQ(test, returned_fb, &expected_fb); 555 drm_framebuffer_put(returned_fb); 556 557 drm_framebuffer_cleanup(&expected_fb); 558 } 559 560 /* Try to lookup an id that is not linked to a framebuffer */ 561 static void drm_test_framebuffer_lookup_inexistent(struct kunit *test) 562 { 563 struct drm_framebuffer_test_priv *priv = test->priv; 564 struct drm_device *dev = &priv->dev; 565 struct drm_framebuffer *fb; 566 uint32_t id = 0; 567 568 /* Looking for an inexistent framebuffer */ 569 fb = drm_framebuffer_lookup(dev, NULL, id); 570 KUNIT_EXPECT_NULL(test, fb); 571 } 572 573 /* 574 * Test if drm_framebuffer_init initializes the framebuffer successfully, 575 * asserting that its modeset object struct and its refcount are correctly 576 * set and that strictly one framebuffer is initialized. 577 */ 578 static void drm_test_framebuffer_init(struct kunit *test) 579 { 580 struct drm_framebuffer_test_priv *priv = test->priv; 581 struct drm_device *dev = &priv->dev; 582 struct drm_format_info format = { }; 583 struct drm_framebuffer fb1 = { .dev = dev, .format = &format }; 584 struct drm_framebuffer_funcs funcs = { }; 585 int ret; 586 587 ret = drm_framebuffer_init(dev, &fb1, &funcs); 588 KUNIT_ASSERT_EQ(test, ret, 0); 589 590 /* Check if fb->funcs is actually set to the drm_framebuffer_funcs passed on */ 591 KUNIT_EXPECT_PTR_EQ(test, fb1.funcs, &funcs); 592 593 /* The fb->comm must be set to the current running process */ 594 KUNIT_EXPECT_STREQ(test, fb1.comm, current->comm); 595 596 /* The fb->base must be successfully initialized */ 597 KUNIT_EXPECT_NE(test, fb1.base.id, 0); 598 KUNIT_EXPECT_EQ(test, fb1.base.type, DRM_MODE_OBJECT_FB); 599 KUNIT_EXPECT_EQ(test, kref_read(&fb1.base.refcount), 1); 600 KUNIT_EXPECT_PTR_EQ(test, fb1.base.free_cb, &drm_framebuffer_free); 601 602 /* There must be just that one fb initialized */ 603 KUNIT_EXPECT_EQ(test, dev->mode_config.num_fb, 1); 604 KUNIT_EXPECT_PTR_EQ(test, dev->mode_config.fb_list.prev, &fb1.head); 605 KUNIT_EXPECT_PTR_EQ(test, dev->mode_config.fb_list.next, &fb1.head); 606 607 drm_framebuffer_cleanup(&fb1); 608 } 609 610 /* Try to init a framebuffer without setting its format */ 611 static void drm_test_framebuffer_init_bad_format(struct kunit *test) 612 { 613 struct drm_framebuffer_test_priv *priv = test->priv; 614 struct drm_device *dev = &priv->dev; 615 struct drm_framebuffer fb1 = { .dev = dev, .format = NULL }; 616 struct drm_framebuffer_funcs funcs = { }; 617 int ret; 618 619 /* Fails if fb.format isn't set */ 620 ret = drm_framebuffer_init(dev, &fb1, &funcs); 621 KUNIT_EXPECT_EQ(test, ret, -EINVAL); 622 } 623 624 /* 625 * Test calling drm_framebuffer_init() passing a framebuffer linked to a 626 * different drm_device parent from the one passed on the first argument, which 627 * must fail. 628 */ 629 static void drm_test_framebuffer_init_dev_mismatch(struct kunit *test) 630 { 631 struct drm_framebuffer_test_priv *priv = test->priv; 632 struct drm_device *right_dev = &priv->dev; 633 struct drm_device *wrong_dev; 634 struct device *wrong_dev_parent; 635 struct drm_format_info format = { }; 636 struct drm_framebuffer fb1 = { .dev = right_dev, .format = &format }; 637 struct drm_framebuffer_funcs funcs = { }; 638 int ret; 639 640 wrong_dev_parent = kunit_device_register(test, "drm-kunit-wrong-device-mock"); 641 KUNIT_ASSERT_NOT_ERR_OR_NULL(test, wrong_dev_parent); 642 643 wrong_dev = __drm_kunit_helper_alloc_drm_device(test, wrong_dev_parent, 644 sizeof(struct drm_device), 645 0, 0); 646 KUNIT_ASSERT_NOT_ERR_OR_NULL(test, wrong_dev); 647 648 /* Fails if fb->dev doesn't point to the drm_device passed on first arg */ 649 ret = drm_framebuffer_init(wrong_dev, &fb1, &funcs); 650 KUNIT_EXPECT_EQ(test, ret, -EINVAL); 651 } 652 653 static void destroy_free_mock(struct drm_framebuffer *fb) 654 { 655 struct drm_framebuffer_test_priv *priv = container_of(fb->dev, typeof(*priv), dev); 656 657 priv->buffer_freed = true; 658 } 659 660 static struct drm_framebuffer_funcs framebuffer_funcs_free_mock = { 661 .destroy = destroy_free_mock, 662 }; 663 664 /* 665 * In summary, the drm_framebuffer_free() function must implicitly call 666 * fb->funcs->destroy() and garantee that the framebufer object is unregistered 667 * from the drm_device idr pool. 668 */ 669 static void drm_test_framebuffer_free(struct kunit *test) 670 { 671 struct drm_framebuffer_test_priv *priv = test->priv; 672 struct drm_device *dev = &priv->dev; 673 struct drm_mode_object *obj; 674 struct drm_framebuffer fb = { 675 .dev = dev, 676 .funcs = &framebuffer_funcs_free_mock, 677 }; 678 int id, ret; 679 680 priv->buffer_freed = false; 681 682 /* 683 * Mock a framebuffer that was not unregistered at the moment of the 684 * drm_framebuffer_free() call. 685 */ 686 ret = drm_mode_object_add(dev, &fb.base, DRM_MODE_OBJECT_FB); 687 KUNIT_ASSERT_EQ(test, ret, 0); 688 id = fb.base.id; 689 690 drm_framebuffer_free(&fb.base.refcount); 691 692 /* The framebuffer object must be unregistered */ 693 obj = drm_mode_object_find(dev, NULL, id, DRM_MODE_OBJECT_FB); 694 KUNIT_EXPECT_PTR_EQ(test, obj, NULL); 695 KUNIT_EXPECT_EQ(test, fb.base.id, 0); 696 697 /* Test if fb->funcs->destroy() was called */ 698 KUNIT_EXPECT_EQ(test, priv->buffer_freed, true); 699 } 700 701 static struct kunit_case drm_framebuffer_tests[] = { 702 KUNIT_CASE_PARAM(drm_test_framebuffer_check_src_coords, check_src_coords_gen_params), 703 KUNIT_CASE(drm_test_framebuffer_cleanup), 704 KUNIT_CASE_PARAM(drm_test_framebuffer_create, drm_framebuffer_create_gen_params), 705 KUNIT_CASE(drm_test_framebuffer_free), 706 KUNIT_CASE(drm_test_framebuffer_init), 707 KUNIT_CASE(drm_test_framebuffer_init_bad_format), 708 KUNIT_CASE(drm_test_framebuffer_init_dev_mismatch), 709 KUNIT_CASE(drm_test_framebuffer_lookup), 710 KUNIT_CASE(drm_test_framebuffer_lookup_inexistent), 711 KUNIT_CASE(drm_test_framebuffer_modifiers_not_supported), 712 { } 713 }; 714 715 static struct kunit_suite drm_framebuffer_test_suite = { 716 .name = "drm_framebuffer", 717 .init = drm_framebuffer_test_init, 718 .test_cases = drm_framebuffer_tests, 719 }; 720 721 kunit_test_suite(drm_framebuffer_test_suite); 722 723 MODULE_DESCRIPTION("Test cases for the drm_framebuffer functions"); 724 MODULE_LICENSE("GPL"); 725