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_format_info *info, 367 const struct drm_mode_fb_cmd2 *mode_cmd) 368 { 369 struct drm_framebuffer_test_priv *priv = container_of(dev, typeof(*priv), dev); 370 371 priv->buffer_created = true; 372 return ERR_PTR(-EINVAL); 373 } 374 375 static struct drm_mode_config_funcs mock_config_funcs = { 376 .fb_create = fb_create_mock, 377 }; 378 379 static int drm_framebuffer_test_init(struct kunit *test) 380 { 381 struct device *parent; 382 struct drm_framebuffer_test_priv *priv; 383 struct drm_device *dev; 384 385 parent = drm_kunit_helper_alloc_device(test); 386 KUNIT_ASSERT_NOT_ERR_OR_NULL(test, parent); 387 388 priv = drm_kunit_helper_alloc_drm_device(test, parent, typeof(*priv), 389 dev, 0); 390 KUNIT_ASSERT_NOT_ERR_OR_NULL(test, priv); 391 dev = &priv->dev; 392 393 dev->mode_config.min_width = MIN_WIDTH; 394 dev->mode_config.max_width = MAX_WIDTH; 395 dev->mode_config.min_height = MIN_HEIGHT; 396 dev->mode_config.max_height = MAX_HEIGHT; 397 dev->mode_config.funcs = &mock_config_funcs; 398 399 test->priv = priv; 400 return 0; 401 } 402 403 static void drm_test_framebuffer_create(struct kunit *test) 404 { 405 const struct drm_framebuffer_test *params = test->param_value; 406 struct drm_framebuffer_test_priv *priv = test->priv; 407 struct drm_device *dev = &priv->dev; 408 409 priv->buffer_created = false; 410 drm_internal_framebuffer_create(dev, ¶ms->cmd, NULL); 411 KUNIT_EXPECT_EQ(test, params->buffer_created, priv->buffer_created); 412 } 413 414 static void drm_framebuffer_test_to_desc(const struct drm_framebuffer_test *t, char *desc) 415 { 416 strscpy(desc, t->name, KUNIT_PARAM_DESC_SIZE); 417 } 418 419 KUNIT_ARRAY_PARAM(drm_framebuffer_create, drm_framebuffer_create_cases, 420 drm_framebuffer_test_to_desc); 421 422 /* Tries to create a framebuffer with modifiers without drm_device supporting it */ 423 static void drm_test_framebuffer_modifiers_not_supported(struct kunit *test) 424 { 425 struct drm_framebuffer_test_priv *priv = test->priv; 426 struct drm_device *dev = &priv->dev; 427 struct drm_framebuffer *fb; 428 429 /* A valid cmd with modifier */ 430 struct drm_mode_fb_cmd2 cmd = { 431 .width = MAX_WIDTH, .height = MAX_HEIGHT, 432 .pixel_format = DRM_FORMAT_ABGR8888, .handles = { 1, 0, 0 }, 433 .offsets = { UINT_MAX / 2, 0, 0 }, .pitches = { 4 * MAX_WIDTH, 0, 0 }, 434 .flags = DRM_MODE_FB_MODIFIERS, 435 }; 436 437 priv->buffer_created = false; 438 dev->mode_config.fb_modifiers_not_supported = 1; 439 440 fb = drm_internal_framebuffer_create(dev, &cmd, NULL); 441 KUNIT_EXPECT_EQ(test, priv->buffer_created, false); 442 KUNIT_EXPECT_EQ(test, PTR_ERR(fb), -EINVAL); 443 } 444 445 /* Parameters for testing drm_framebuffer_check_src_coords function */ 446 struct drm_framebuffer_check_src_coords_case { 447 const char *name; 448 const int expect; 449 const unsigned int fb_size; 450 const uint32_t src_x; 451 const uint32_t src_y; 452 453 /* Deltas to be applied on source */ 454 const uint32_t dsrc_w; 455 const uint32_t dsrc_h; 456 }; 457 458 static const struct drm_framebuffer_check_src_coords_case 459 drm_framebuffer_check_src_coords_cases[] = { 460 { .name = "Success: source fits into fb", 461 .expect = 0, 462 }, 463 { .name = "Fail: overflowing fb with x-axis coordinate", 464 .expect = -ENOSPC, .src_x = 1, .fb_size = UINT_MAX, 465 }, 466 { .name = "Fail: overflowing fb with y-axis coordinate", 467 .expect = -ENOSPC, .src_y = 1, .fb_size = UINT_MAX, 468 }, 469 { .name = "Fail: overflowing fb with source width", 470 .expect = -ENOSPC, .dsrc_w = 1, .fb_size = UINT_MAX - 1, 471 }, 472 { .name = "Fail: overflowing fb with source height", 473 .expect = -ENOSPC, .dsrc_h = 1, .fb_size = UINT_MAX - 1, 474 }, 475 }; 476 477 static void drm_test_framebuffer_check_src_coords(struct kunit *test) 478 { 479 const struct drm_framebuffer_check_src_coords_case *params = test->param_value; 480 const uint32_t src_x = params->src_x; 481 const uint32_t src_y = params->src_y; 482 const uint32_t src_w = (params->fb_size << 16) + params->dsrc_w; 483 const uint32_t src_h = (params->fb_size << 16) + params->dsrc_h; 484 const struct drm_framebuffer fb = { 485 .width = params->fb_size, 486 .height = params->fb_size 487 }; 488 int ret; 489 490 ret = drm_framebuffer_check_src_coords(src_x, src_y, src_w, src_h, &fb); 491 KUNIT_EXPECT_EQ(test, ret, params->expect); 492 } 493 494 static void 495 check_src_coords_test_to_desc(const struct drm_framebuffer_check_src_coords_case *t, 496 char *desc) 497 { 498 strscpy(desc, t->name, KUNIT_PARAM_DESC_SIZE); 499 } 500 501 KUNIT_ARRAY_PARAM(check_src_coords, drm_framebuffer_check_src_coords_cases, 502 check_src_coords_test_to_desc); 503 504 /* 505 * Test if drm_framebuffer_cleanup() really pops out the framebuffer object 506 * from device's fb_list and decrement the number of framebuffers for that 507 * device, which is the only things it does. 508 */ 509 static void drm_test_framebuffer_cleanup(struct kunit *test) 510 { 511 struct drm_framebuffer_test_priv *priv = test->priv; 512 struct drm_device *dev = &priv->dev; 513 struct list_head *fb_list = &dev->mode_config.fb_list; 514 struct drm_format_info format = { }; 515 struct drm_framebuffer fb1 = { .dev = dev, .format = &format }; 516 struct drm_framebuffer fb2 = { .dev = dev, .format = &format }; 517 518 /* This will result on [fb_list] -> fb2 -> fb1 */ 519 drm_framebuffer_init(dev, &fb1, NULL); 520 drm_framebuffer_init(dev, &fb2, NULL); 521 522 drm_framebuffer_cleanup(&fb1); 523 524 /* Now fb2 is the only one element on fb_list */ 525 KUNIT_ASSERT_TRUE(test, list_is_singular(&fb2.head)); 526 KUNIT_ASSERT_EQ(test, dev->mode_config.num_fb, 1); 527 528 drm_framebuffer_cleanup(&fb2); 529 530 /* Now fb_list is empty */ 531 KUNIT_ASSERT_TRUE(test, list_empty(fb_list)); 532 KUNIT_ASSERT_EQ(test, dev->mode_config.num_fb, 0); 533 } 534 535 /* 536 * Initialize a framebuffer, lookup its id and test if the returned reference 537 * matches. 538 */ 539 static void drm_test_framebuffer_lookup(struct kunit *test) 540 { 541 struct drm_framebuffer_test_priv *priv = test->priv; 542 struct drm_device *dev = &priv->dev; 543 struct drm_format_info format = { }; 544 struct drm_framebuffer expected_fb = { .dev = dev, .format = &format }; 545 struct drm_framebuffer *returned_fb; 546 uint32_t id = 0; 547 int ret; 548 549 ret = drm_framebuffer_init(dev, &expected_fb, NULL); 550 KUNIT_ASSERT_EQ(test, ret, 0); 551 id = expected_fb.base.id; 552 553 /* Looking for expected_fb */ 554 returned_fb = drm_framebuffer_lookup(dev, NULL, id); 555 KUNIT_EXPECT_PTR_EQ(test, returned_fb, &expected_fb); 556 drm_framebuffer_put(returned_fb); 557 558 drm_framebuffer_cleanup(&expected_fb); 559 } 560 561 /* Try to lookup an id that is not linked to a framebuffer */ 562 static void drm_test_framebuffer_lookup_inexistent(struct kunit *test) 563 { 564 struct drm_framebuffer_test_priv *priv = test->priv; 565 struct drm_device *dev = &priv->dev; 566 struct drm_framebuffer *fb; 567 uint32_t id = 0; 568 569 /* Looking for an inexistent framebuffer */ 570 fb = drm_framebuffer_lookup(dev, NULL, id); 571 KUNIT_EXPECT_NULL(test, fb); 572 } 573 574 /* 575 * Test if drm_framebuffer_init initializes the framebuffer successfully, 576 * asserting that its modeset object struct and its refcount are correctly 577 * set and that strictly one framebuffer is initialized. 578 */ 579 static void drm_test_framebuffer_init(struct kunit *test) 580 { 581 struct drm_framebuffer_test_priv *priv = test->priv; 582 struct drm_device *dev = &priv->dev; 583 struct drm_format_info format = { }; 584 struct drm_framebuffer fb1 = { .dev = dev, .format = &format }; 585 struct drm_framebuffer_funcs funcs = { }; 586 int ret; 587 588 ret = drm_framebuffer_init(dev, &fb1, &funcs); 589 KUNIT_ASSERT_EQ(test, ret, 0); 590 591 /* Check if fb->funcs is actually set to the drm_framebuffer_funcs passed on */ 592 KUNIT_EXPECT_PTR_EQ(test, fb1.funcs, &funcs); 593 594 /* The fb->comm must be set to the current running process */ 595 KUNIT_EXPECT_STREQ(test, fb1.comm, current->comm); 596 597 /* The fb->base must be successfully initialized */ 598 KUNIT_EXPECT_NE(test, fb1.base.id, 0); 599 KUNIT_EXPECT_EQ(test, fb1.base.type, DRM_MODE_OBJECT_FB); 600 KUNIT_EXPECT_EQ(test, kref_read(&fb1.base.refcount), 1); 601 KUNIT_EXPECT_PTR_EQ(test, fb1.base.free_cb, &drm_framebuffer_free); 602 603 /* There must be just that one fb initialized */ 604 KUNIT_EXPECT_EQ(test, dev->mode_config.num_fb, 1); 605 KUNIT_EXPECT_PTR_EQ(test, dev->mode_config.fb_list.prev, &fb1.head); 606 KUNIT_EXPECT_PTR_EQ(test, dev->mode_config.fb_list.next, &fb1.head); 607 608 drm_framebuffer_cleanup(&fb1); 609 } 610 611 /* Try to init a framebuffer without setting its format */ 612 static void drm_test_framebuffer_init_bad_format(struct kunit *test) 613 { 614 struct drm_framebuffer_test_priv *priv = test->priv; 615 struct drm_device *dev = &priv->dev; 616 struct drm_framebuffer fb1 = { .dev = dev, .format = NULL }; 617 struct drm_framebuffer_funcs funcs = { }; 618 int ret; 619 620 /* Fails if fb.format isn't set */ 621 ret = drm_framebuffer_init(dev, &fb1, &funcs); 622 KUNIT_EXPECT_EQ(test, ret, -EINVAL); 623 } 624 625 /* 626 * Test calling drm_framebuffer_init() passing a framebuffer linked to a 627 * different drm_device parent from the one passed on the first argument, which 628 * must fail. 629 */ 630 static void drm_test_framebuffer_init_dev_mismatch(struct kunit *test) 631 { 632 struct drm_framebuffer_test_priv *priv = test->priv; 633 struct drm_device *right_dev = &priv->dev; 634 struct drm_device *wrong_dev; 635 struct device *wrong_dev_parent; 636 struct drm_format_info format = { }; 637 struct drm_framebuffer fb1 = { .dev = right_dev, .format = &format }; 638 struct drm_framebuffer_funcs funcs = { }; 639 int ret; 640 641 wrong_dev_parent = kunit_device_register(test, "drm-kunit-wrong-device-mock"); 642 KUNIT_ASSERT_NOT_ERR_OR_NULL(test, wrong_dev_parent); 643 644 wrong_dev = __drm_kunit_helper_alloc_drm_device(test, wrong_dev_parent, 645 sizeof(struct drm_device), 646 0, 0); 647 KUNIT_ASSERT_NOT_ERR_OR_NULL(test, wrong_dev); 648 649 /* Fails if fb->dev doesn't point to the drm_device passed on first arg */ 650 ret = drm_framebuffer_init(wrong_dev, &fb1, &funcs); 651 KUNIT_EXPECT_EQ(test, ret, -EINVAL); 652 } 653 654 static void destroy_free_mock(struct drm_framebuffer *fb) 655 { 656 struct drm_framebuffer_test_priv *priv = container_of(fb->dev, typeof(*priv), dev); 657 658 priv->buffer_freed = true; 659 } 660 661 static struct drm_framebuffer_funcs framebuffer_funcs_free_mock = { 662 .destroy = destroy_free_mock, 663 }; 664 665 /* 666 * In summary, the drm_framebuffer_free() function must implicitly call 667 * fb->funcs->destroy() and garantee that the framebufer object is unregistered 668 * from the drm_device idr pool. 669 */ 670 static void drm_test_framebuffer_free(struct kunit *test) 671 { 672 struct drm_framebuffer_test_priv *priv = test->priv; 673 struct drm_device *dev = &priv->dev; 674 struct drm_mode_object *obj; 675 struct drm_framebuffer fb = { 676 .dev = dev, 677 .funcs = &framebuffer_funcs_free_mock, 678 }; 679 int id, ret; 680 681 priv->buffer_freed = false; 682 683 /* 684 * Mock a framebuffer that was not unregistered at the moment of the 685 * drm_framebuffer_free() call. 686 */ 687 ret = drm_mode_object_add(dev, &fb.base, DRM_MODE_OBJECT_FB); 688 KUNIT_ASSERT_EQ(test, ret, 0); 689 id = fb.base.id; 690 691 drm_framebuffer_free(&fb.base.refcount); 692 693 /* The framebuffer object must be unregistered */ 694 obj = drm_mode_object_find(dev, NULL, id, DRM_MODE_OBJECT_FB); 695 KUNIT_EXPECT_PTR_EQ(test, obj, NULL); 696 KUNIT_EXPECT_EQ(test, fb.base.id, 0); 697 698 /* Test if fb->funcs->destroy() was called */ 699 KUNIT_EXPECT_EQ(test, priv->buffer_freed, true); 700 } 701 702 static struct kunit_case drm_framebuffer_tests[] = { 703 KUNIT_CASE_PARAM(drm_test_framebuffer_check_src_coords, check_src_coords_gen_params), 704 KUNIT_CASE(drm_test_framebuffer_cleanup), 705 KUNIT_CASE_PARAM(drm_test_framebuffer_create, drm_framebuffer_create_gen_params), 706 KUNIT_CASE(drm_test_framebuffer_free), 707 KUNIT_CASE(drm_test_framebuffer_init), 708 KUNIT_CASE(drm_test_framebuffer_init_bad_format), 709 KUNIT_CASE(drm_test_framebuffer_init_dev_mismatch), 710 KUNIT_CASE(drm_test_framebuffer_lookup), 711 KUNIT_CASE(drm_test_framebuffer_lookup_inexistent), 712 KUNIT_CASE(drm_test_framebuffer_modifiers_not_supported), 713 { } 714 }; 715 716 static struct kunit_suite drm_framebuffer_test_suite = { 717 .name = "drm_framebuffer", 718 .init = drm_framebuffer_test_init, 719 .test_cases = drm_framebuffer_tests, 720 }; 721 722 kunit_test_suite(drm_framebuffer_test_suite); 723 724 MODULE_DESCRIPTION("Test cases for the drm_framebuffer functions"); 725 MODULE_LICENSE("GPL"); 726