| 1 | // SPDX-License-Identifier: MIT |
| 2 | /* |
| 3 | * Copyright © 2021 Intel Corporation |
| 4 | */ |
| 5 | |
| 6 | #include <linux/slab.h> |
| 7 | |
| 8 | #include <drm/drm_buddy.h> |
| 9 | #include <drm/drm_print.h> |
| 10 | #include <drm/ttm/ttm_placement.h> |
| 11 | #include <drm/ttm/ttm_bo.h> |
| 12 | |
| 13 | #include "i915_ttm_buddy_manager.h" |
| 14 | |
| 15 | #include "i915_gem.h" |
| 16 | |
| 17 | struct i915_ttm_buddy_manager { |
| 18 | struct ttm_resource_manager manager; |
| 19 | struct drm_buddy mm; |
| 20 | struct list_head reserved; |
| 21 | struct mutex lock; |
| 22 | unsigned long visible_size; |
| 23 | unsigned long visible_avail; |
| 24 | unsigned long visible_reserved; |
| 25 | u64 default_page_size; |
| 26 | }; |
| 27 | |
| 28 | static struct i915_ttm_buddy_manager * |
| 29 | to_buddy_manager(struct ttm_resource_manager *man) |
| 30 | { |
| 31 | return container_of(man, struct i915_ttm_buddy_manager, manager); |
| 32 | } |
| 33 | |
| 34 | static int i915_ttm_buddy_man_alloc(struct ttm_resource_manager *man, |
| 35 | struct ttm_buffer_object *bo, |
| 36 | const struct ttm_place *place, |
| 37 | struct ttm_resource **res) |
| 38 | { |
| 39 | struct i915_ttm_buddy_manager *bman = to_buddy_manager(man); |
| 40 | struct i915_ttm_buddy_resource *bman_res; |
| 41 | struct drm_buddy *mm = &bman->mm; |
| 42 | unsigned long n_pages, lpfn; |
| 43 | u64 min_page_size; |
| 44 | u64 size; |
| 45 | int err; |
| 46 | |
| 47 | lpfn = place->lpfn; |
| 48 | if (!lpfn) |
| 49 | lpfn = man->size; |
| 50 | |
| 51 | bman_res = kzalloc(sizeof(*bman_res), GFP_KERNEL); |
| 52 | if (!bman_res) |
| 53 | return -ENOMEM; |
| 54 | |
| 55 | ttm_resource_init(bo, place, res: &bman_res->base); |
| 56 | INIT_LIST_HEAD(list: &bman_res->blocks); |
| 57 | bman_res->mm = mm; |
| 58 | |
| 59 | if (place->flags & TTM_PL_FLAG_TOPDOWN) |
| 60 | bman_res->flags |= DRM_BUDDY_TOPDOWN_ALLOCATION; |
| 61 | |
| 62 | if (place->flags & TTM_PL_FLAG_CONTIGUOUS) |
| 63 | bman_res->flags |= DRM_BUDDY_CONTIGUOUS_ALLOCATION; |
| 64 | |
| 65 | if (place->fpfn || lpfn != man->size) |
| 66 | bman_res->flags |= DRM_BUDDY_RANGE_ALLOCATION; |
| 67 | |
| 68 | GEM_BUG_ON(!bman_res->base.size); |
| 69 | size = bman_res->base.size; |
| 70 | |
| 71 | min_page_size = bman->default_page_size; |
| 72 | if (bo->page_alignment) |
| 73 | min_page_size = bo->page_alignment << PAGE_SHIFT; |
| 74 | |
| 75 | GEM_BUG_ON(min_page_size < mm->chunk_size); |
| 76 | GEM_BUG_ON(!IS_ALIGNED(size, min_page_size)); |
| 77 | |
| 78 | if (size > lpfn << PAGE_SHIFT) { |
| 79 | err = -E2BIG; |
| 80 | goto err_free_res; |
| 81 | } |
| 82 | |
| 83 | n_pages = size >> ilog2(mm->chunk_size); |
| 84 | |
| 85 | mutex_lock(&bman->lock); |
| 86 | if (lpfn <= bman->visible_size && n_pages > bman->visible_avail) { |
| 87 | mutex_unlock(lock: &bman->lock); |
| 88 | err = -ENOSPC; |
| 89 | goto err_free_res; |
| 90 | } |
| 91 | |
| 92 | err = drm_buddy_alloc_blocks(mm, start: (u64)place->fpfn << PAGE_SHIFT, |
| 93 | end: (u64)lpfn << PAGE_SHIFT, |
| 94 | size: (u64)n_pages << PAGE_SHIFT, |
| 95 | min_page_size, |
| 96 | blocks: &bman_res->blocks, |
| 97 | flags: bman_res->flags); |
| 98 | if (unlikely(err)) |
| 99 | goto err_free_blocks; |
| 100 | |
| 101 | if (lpfn <= bman->visible_size) { |
| 102 | bman_res->used_visible_size = PFN_UP(bman_res->base.size); |
| 103 | } else { |
| 104 | struct drm_buddy_block *block; |
| 105 | |
| 106 | list_for_each_entry(block, &bman_res->blocks, link) { |
| 107 | unsigned long start = |
| 108 | drm_buddy_block_offset(block) >> PAGE_SHIFT; |
| 109 | |
| 110 | if (start < bman->visible_size) { |
| 111 | unsigned long end = start + |
| 112 | (drm_buddy_block_size(mm, block) >> PAGE_SHIFT); |
| 113 | |
| 114 | bman_res->used_visible_size += |
| 115 | min(end, bman->visible_size) - start; |
| 116 | } |
| 117 | } |
| 118 | } |
| 119 | |
| 120 | if (bman_res->used_visible_size) |
| 121 | bman->visible_avail -= bman_res->used_visible_size; |
| 122 | |
| 123 | mutex_unlock(lock: &bman->lock); |
| 124 | |
| 125 | *res = &bman_res->base; |
| 126 | return 0; |
| 127 | |
| 128 | err_free_blocks: |
| 129 | drm_buddy_free_list(mm, objects: &bman_res->blocks, flags: 0); |
| 130 | mutex_unlock(lock: &bman->lock); |
| 131 | err_free_res: |
| 132 | ttm_resource_fini(man, res: &bman_res->base); |
| 133 | kfree(objp: bman_res); |
| 134 | return err; |
| 135 | } |
| 136 | |
| 137 | static void i915_ttm_buddy_man_free(struct ttm_resource_manager *man, |
| 138 | struct ttm_resource *res) |
| 139 | { |
| 140 | struct i915_ttm_buddy_resource *bman_res = to_ttm_buddy_resource(res); |
| 141 | struct i915_ttm_buddy_manager *bman = to_buddy_manager(man); |
| 142 | |
| 143 | mutex_lock(&bman->lock); |
| 144 | drm_buddy_free_list(mm: &bman->mm, objects: &bman_res->blocks, flags: 0); |
| 145 | bman->visible_avail += bman_res->used_visible_size; |
| 146 | mutex_unlock(lock: &bman->lock); |
| 147 | |
| 148 | ttm_resource_fini(man, res); |
| 149 | kfree(objp: bman_res); |
| 150 | } |
| 151 | |
| 152 | static bool i915_ttm_buddy_man_intersects(struct ttm_resource_manager *man, |
| 153 | struct ttm_resource *res, |
| 154 | const struct ttm_place *place, |
| 155 | size_t size) |
| 156 | { |
| 157 | struct i915_ttm_buddy_resource *bman_res = to_ttm_buddy_resource(res); |
| 158 | struct i915_ttm_buddy_manager *bman = to_buddy_manager(man); |
| 159 | struct drm_buddy *mm = &bman->mm; |
| 160 | struct drm_buddy_block *block; |
| 161 | |
| 162 | if (!place->fpfn && !place->lpfn) |
| 163 | return true; |
| 164 | |
| 165 | GEM_BUG_ON(!place->lpfn); |
| 166 | |
| 167 | /* |
| 168 | * If we just want something mappable then we can quickly check |
| 169 | * if the current victim resource is using any of the CPU |
| 170 | * visible portion. |
| 171 | */ |
| 172 | if (!place->fpfn && |
| 173 | place->lpfn == i915_ttm_buddy_man_visible_size(man)) |
| 174 | return bman_res->used_visible_size > 0; |
| 175 | |
| 176 | /* Check each drm buddy block individually */ |
| 177 | list_for_each_entry(block, &bman_res->blocks, link) { |
| 178 | unsigned long fpfn = |
| 179 | drm_buddy_block_offset(block) >> PAGE_SHIFT; |
| 180 | unsigned long lpfn = fpfn + |
| 181 | (drm_buddy_block_size(mm, block) >> PAGE_SHIFT); |
| 182 | |
| 183 | if (place->fpfn < lpfn && place->lpfn > fpfn) |
| 184 | return true; |
| 185 | } |
| 186 | |
| 187 | return false; |
| 188 | } |
| 189 | |
| 190 | static bool i915_ttm_buddy_man_compatible(struct ttm_resource_manager *man, |
| 191 | struct ttm_resource *res, |
| 192 | const struct ttm_place *place, |
| 193 | size_t size) |
| 194 | { |
| 195 | struct i915_ttm_buddy_resource *bman_res = to_ttm_buddy_resource(res); |
| 196 | struct i915_ttm_buddy_manager *bman = to_buddy_manager(man); |
| 197 | struct drm_buddy *mm = &bman->mm; |
| 198 | struct drm_buddy_block *block; |
| 199 | |
| 200 | if (!place->fpfn && !place->lpfn) |
| 201 | return true; |
| 202 | |
| 203 | GEM_BUG_ON(!place->lpfn); |
| 204 | |
| 205 | if (!place->fpfn && |
| 206 | place->lpfn == i915_ttm_buddy_man_visible_size(man)) |
| 207 | return bman_res->used_visible_size == PFN_UP(res->size); |
| 208 | |
| 209 | /* Check each drm buddy block individually */ |
| 210 | list_for_each_entry(block, &bman_res->blocks, link) { |
| 211 | unsigned long fpfn = |
| 212 | drm_buddy_block_offset(block) >> PAGE_SHIFT; |
| 213 | unsigned long lpfn = fpfn + |
| 214 | (drm_buddy_block_size(mm, block) >> PAGE_SHIFT); |
| 215 | |
| 216 | if (fpfn < place->fpfn || lpfn > place->lpfn) |
| 217 | return false; |
| 218 | } |
| 219 | |
| 220 | return true; |
| 221 | } |
| 222 | |
| 223 | static void i915_ttm_buddy_man_debug(struct ttm_resource_manager *man, |
| 224 | struct drm_printer *printer) |
| 225 | { |
| 226 | struct i915_ttm_buddy_manager *bman = to_buddy_manager(man); |
| 227 | struct drm_buddy_block *block; |
| 228 | |
| 229 | mutex_lock(&bman->lock); |
| 230 | drm_printf(p: printer, f: "default_page_size: %lluKiB\n" , |
| 231 | bman->default_page_size >> 10); |
| 232 | drm_printf(p: printer, f: "visible_avail: %lluMiB\n" , |
| 233 | (u64)bman->visible_avail << PAGE_SHIFT >> 20); |
| 234 | drm_printf(p: printer, f: "visible_size: %lluMiB\n" , |
| 235 | (u64)bman->visible_size << PAGE_SHIFT >> 20); |
| 236 | drm_printf(p: printer, f: "visible_reserved: %lluMiB\n" , |
| 237 | (u64)bman->visible_reserved << PAGE_SHIFT >> 20); |
| 238 | |
| 239 | drm_buddy_print(mm: &bman->mm, p: printer); |
| 240 | |
| 241 | drm_printf(p: printer, f: "reserved:\n" ); |
| 242 | list_for_each_entry(block, &bman->reserved, link) |
| 243 | drm_buddy_block_print(mm: &bman->mm, block, p: printer); |
| 244 | mutex_unlock(lock: &bman->lock); |
| 245 | } |
| 246 | |
| 247 | static const struct ttm_resource_manager_func i915_ttm_buddy_manager_func = { |
| 248 | .alloc = i915_ttm_buddy_man_alloc, |
| 249 | .free = i915_ttm_buddy_man_free, |
| 250 | .intersects = i915_ttm_buddy_man_intersects, |
| 251 | .compatible = i915_ttm_buddy_man_compatible, |
| 252 | .debug = i915_ttm_buddy_man_debug, |
| 253 | }; |
| 254 | |
| 255 | /** |
| 256 | * i915_ttm_buddy_man_init - Setup buddy allocator based ttm manager |
| 257 | * @bdev: The ttm device |
| 258 | * @type: Memory type we want to manage |
| 259 | * @use_tt: Set use_tt for the manager |
| 260 | * @size: The size in bytes to manage |
| 261 | * @visible_size: The CPU visible size in bytes to manage |
| 262 | * @default_page_size: The default minimum page size in bytes for allocations, |
| 263 | * this must be at least as large as @chunk_size, and can be overridden by |
| 264 | * setting the BO page_alignment, to be larger or smaller as needed. |
| 265 | * @chunk_size: The minimum page size in bytes for our allocations i.e |
| 266 | * order-zero |
| 267 | * |
| 268 | * Note that the starting address is assumed to be zero here, since this |
| 269 | * simplifies keeping the property where allocated blocks having natural |
| 270 | * power-of-two alignment. So long as the real starting address is some large |
| 271 | * power-of-two, or naturally start from zero, then this should be fine. Also |
| 272 | * the &i915_ttm_buddy_man_reserve interface can be used to preserve alignment |
| 273 | * if say there is some unusable range from the start of the region. We can |
| 274 | * revisit this in the future and make the interface accept an actual starting |
| 275 | * offset and let it take care of the rest. |
| 276 | * |
| 277 | * Note that if the @size is not aligned to the @chunk_size then we perform the |
| 278 | * required rounding to get the usable size. The final size in pages can be |
| 279 | * taken from &ttm_resource_manager.size. |
| 280 | * |
| 281 | * Return: 0 on success, negative error code on failure. |
| 282 | */ |
| 283 | int i915_ttm_buddy_man_init(struct ttm_device *bdev, |
| 284 | unsigned int type, bool use_tt, |
| 285 | u64 size, u64 visible_size, u64 default_page_size, |
| 286 | u64 chunk_size) |
| 287 | { |
| 288 | struct ttm_resource_manager *man; |
| 289 | struct i915_ttm_buddy_manager *bman; |
| 290 | int err; |
| 291 | |
| 292 | bman = kzalloc(sizeof(*bman), GFP_KERNEL); |
| 293 | if (!bman) |
| 294 | return -ENOMEM; |
| 295 | |
| 296 | err = drm_buddy_init(mm: &bman->mm, size, chunk_size); |
| 297 | if (err) |
| 298 | goto err_free_bman; |
| 299 | |
| 300 | mutex_init(&bman->lock); |
| 301 | INIT_LIST_HEAD(list: &bman->reserved); |
| 302 | GEM_BUG_ON(default_page_size < chunk_size); |
| 303 | bman->default_page_size = default_page_size; |
| 304 | bman->visible_size = visible_size >> PAGE_SHIFT; |
| 305 | bman->visible_avail = bman->visible_size; |
| 306 | |
| 307 | man = &bman->manager; |
| 308 | man->use_tt = use_tt; |
| 309 | man->func = &i915_ttm_buddy_manager_func; |
| 310 | ttm_resource_manager_init(man, bdev, size: bman->mm.size >> PAGE_SHIFT); |
| 311 | |
| 312 | ttm_resource_manager_set_used(man, used: true); |
| 313 | ttm_set_driver_manager(bdev, type, manager: man); |
| 314 | |
| 315 | return 0; |
| 316 | |
| 317 | err_free_bman: |
| 318 | kfree(objp: bman); |
| 319 | return err; |
| 320 | } |
| 321 | |
| 322 | /** |
| 323 | * i915_ttm_buddy_man_fini - Destroy the buddy allocator ttm manager |
| 324 | * @bdev: The ttm device |
| 325 | * @type: Memory type we want to manage |
| 326 | * |
| 327 | * Note that if we reserved anything with &i915_ttm_buddy_man_reserve, this will |
| 328 | * also be freed for us here. |
| 329 | * |
| 330 | * Return: 0 on success, negative error code on failure. |
| 331 | */ |
| 332 | int i915_ttm_buddy_man_fini(struct ttm_device *bdev, unsigned int type) |
| 333 | { |
| 334 | struct ttm_resource_manager *man = ttm_manager_type(bdev, mem_type: type); |
| 335 | struct i915_ttm_buddy_manager *bman = to_buddy_manager(man); |
| 336 | struct drm_buddy *mm = &bman->mm; |
| 337 | int ret; |
| 338 | |
| 339 | ttm_resource_manager_set_used(man, used: false); |
| 340 | |
| 341 | ret = ttm_resource_manager_evict_all(bdev, man); |
| 342 | if (ret) |
| 343 | return ret; |
| 344 | |
| 345 | ttm_set_driver_manager(bdev, type, NULL); |
| 346 | |
| 347 | mutex_lock(&bman->lock); |
| 348 | drm_buddy_free_list(mm, objects: &bman->reserved, flags: 0); |
| 349 | drm_buddy_fini(mm); |
| 350 | bman->visible_avail += bman->visible_reserved; |
| 351 | WARN_ON_ONCE(bman->visible_avail != bman->visible_size); |
| 352 | mutex_unlock(lock: &bman->lock); |
| 353 | |
| 354 | ttm_resource_manager_cleanup(man); |
| 355 | kfree(objp: bman); |
| 356 | |
| 357 | return 0; |
| 358 | } |
| 359 | |
| 360 | /** |
| 361 | * i915_ttm_buddy_man_reserve - Reserve address range |
| 362 | * @man: The buddy allocator ttm manager |
| 363 | * @start: The offset in bytes, where the region start is assumed to be zero |
| 364 | * @size: The size in bytes |
| 365 | * |
| 366 | * Note that the starting address for the region is always assumed to be zero. |
| 367 | * |
| 368 | * Return: 0 on success, negative error code on failure. |
| 369 | */ |
| 370 | int i915_ttm_buddy_man_reserve(struct ttm_resource_manager *man, |
| 371 | u64 start, u64 size) |
| 372 | { |
| 373 | struct i915_ttm_buddy_manager *bman = to_buddy_manager(man); |
| 374 | struct drm_buddy *mm = &bman->mm; |
| 375 | unsigned long fpfn = start >> PAGE_SHIFT; |
| 376 | unsigned long flags = 0; |
| 377 | int ret; |
| 378 | |
| 379 | flags |= DRM_BUDDY_RANGE_ALLOCATION; |
| 380 | |
| 381 | mutex_lock(&bman->lock); |
| 382 | ret = drm_buddy_alloc_blocks(mm, start, |
| 383 | end: start + size, |
| 384 | size, min_page_size: mm->chunk_size, |
| 385 | blocks: &bman->reserved, |
| 386 | flags); |
| 387 | |
| 388 | if (fpfn < bman->visible_size) { |
| 389 | unsigned long lpfn = fpfn + (size >> PAGE_SHIFT); |
| 390 | unsigned long visible = min(lpfn, bman->visible_size) - fpfn; |
| 391 | |
| 392 | bman->visible_reserved += visible; |
| 393 | bman->visible_avail -= visible; |
| 394 | } |
| 395 | mutex_unlock(lock: &bman->lock); |
| 396 | |
| 397 | return ret; |
| 398 | } |
| 399 | |
| 400 | /** |
| 401 | * i915_ttm_buddy_man_visible_size - Return the size of the CPU visible portion |
| 402 | * in pages. |
| 403 | * @man: The buddy allocator ttm manager |
| 404 | */ |
| 405 | u64 i915_ttm_buddy_man_visible_size(struct ttm_resource_manager *man) |
| 406 | { |
| 407 | struct i915_ttm_buddy_manager *bman = to_buddy_manager(man); |
| 408 | |
| 409 | return bman->visible_size; |
| 410 | } |
| 411 | |
| 412 | /** |
| 413 | * i915_ttm_buddy_man_avail - Query the avail tracking for the manager. |
| 414 | * |
| 415 | * @man: The buddy allocator ttm manager |
| 416 | * @avail: The total available memory in pages for the entire manager. |
| 417 | * @visible_avail: The total available memory in pages for the CPU visible |
| 418 | * portion. Note that this will always give the same value as @avail on |
| 419 | * configurations that don't have a small BAR. |
| 420 | */ |
| 421 | void i915_ttm_buddy_man_avail(struct ttm_resource_manager *man, |
| 422 | u64 *avail, u64 *visible_avail) |
| 423 | { |
| 424 | struct i915_ttm_buddy_manager *bman = to_buddy_manager(man); |
| 425 | |
| 426 | mutex_lock(&bman->lock); |
| 427 | *avail = bman->mm.avail >> PAGE_SHIFT; |
| 428 | *visible_avail = bman->visible_avail; |
| 429 | mutex_unlock(lock: &bman->lock); |
| 430 | } |
| 431 | |
| 432 | #if IS_ENABLED(CONFIG_DRM_I915_SELFTEST) |
| 433 | void i915_ttm_buddy_man_force_visible_size(struct ttm_resource_manager *man, |
| 434 | u64 size) |
| 435 | { |
| 436 | struct i915_ttm_buddy_manager *bman = to_buddy_manager(man); |
| 437 | |
| 438 | bman->visible_size = size; |
| 439 | } |
| 440 | #endif |
| 441 | |