Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

Merge tag 'cgroup-dmem-drm-v2' of git://git.kernel.org/pub/scm/linux/kernel/git/mripard/linux into drm-next

DMEM cgroup pull request

This introduces a new cgroup controller to limit the device memory.
Notable users would be DRM, dma-buf heaps, or v4l2.

This pull request is based on the series developped by Maarten
Lankhorst, Friedrich Vock, and I:
https://lore.kernel.org/all/20241204134410.1161769-1-dev@lankhorst.se/

Signed-off-by: Dave Airlie <airlied@redhat.com>

From: Maxime Ripard <mripard@redhat.com>
Link: https://patchwork.freedesktop.org/patch/msgid/20250110-cryptic-warm-mandrill-b71f5d@houat

+1194 -32
+51 -7
Documentation/admin-guide/cgroup-v2.rst
··· 64 64 5-6. Device 65 65 5-7. RDMA 66 66 5-7-1. RDMA Interface Files 67 - 5-8. HugeTLB 68 - 5.8-1. HugeTLB Interface Files 69 - 5-9. Misc 70 - 5.9-1 Miscellaneous cgroup Interface Files 71 - 5.9-2 Migration and Ownership 72 - 5-10. Others 73 - 5-10-1. perf_event 67 + 5-8. DMEM 68 + 5-9. HugeTLB 69 + 5.9-1. HugeTLB Interface Files 70 + 5-10. Misc 71 + 5.10-1 Miscellaneous cgroup Interface Files 72 + 5.10-2 Migration and Ownership 73 + 5-11. Others 74 + 5-11-1. perf_event 74 75 5-N. Non-normative information 75 76 5-N-1. CPU controller root cgroup process behaviour 76 77 5-N-2. IO controller root cgroup process behaviour ··· 2626 2625 2627 2626 mlx4_0 hca_handle=1 hca_object=20 2628 2627 ocrdma1 hca_handle=1 hca_object=23 2628 + 2629 + DMEM 2630 + ---- 2631 + 2632 + The "dmem" controller regulates the distribution and accounting of 2633 + device memory regions. Because each memory region may have its own page size, 2634 + which does not have to be equal to the system page size, the units are always bytes. 2635 + 2636 + DMEM Interface Files 2637 + ~~~~~~~~~~~~~~~~~~~~ 2638 + 2639 + dmem.max, dmem.min, dmem.low 2640 + A readwrite nested-keyed file that exists for all the cgroups 2641 + except root that describes current configured resource limit 2642 + for a region. 2643 + 2644 + An example for xe follows:: 2645 + 2646 + drm/0000:03:00.0/vram0 1073741824 2647 + drm/0000:03:00.0/stolen max 2648 + 2649 + The semantics are the same as for the memory cgroup controller, and are 2650 + calculated in the same way. 2651 + 2652 + dmem.capacity 2653 + A read-only file that describes maximum region capacity. 2654 + It only exists on the root cgroup. Not all memory can be 2655 + allocated by cgroups, as the kernel reserves some for 2656 + internal use. 2657 + 2658 + An example for xe follows:: 2659 + 2660 + drm/0000:03:00.0/vram0 8514437120 2661 + drm/0000:03:00.0/stolen 67108864 2662 + 2663 + dmem.current 2664 + A read-only file that describes current resource usage. 2665 + It exists for all the cgroup except root. 2666 + 2667 + An example for xe follows:: 2668 + 2669 + drm/0000:03:00.0/vram0 12550144 2670 + drm/0000:03:00.0/stolen 8650752 2629 2671 2630 2672 HugeTLB 2631 2673 -------
+9
Documentation/core-api/cgroup.rst
··· 1 + ================== 2 + Cgroup Kernel APIs 3 + ================== 4 + 5 + Device Memory Cgroup API (dmemcg) 6 + ========================= 7 + .. kernel-doc:: kernel/cgroup/dmem.c 8 + :export: 9 +
+1
Documentation/core-api/index.rst
··· 109 109 dma-isa-lpc 110 110 swiotlb 111 111 mm-api 112 + cgroup 112 113 genalloc 113 114 pin_user_pages 114 115 boot-time-mm
+54
Documentation/gpu/drm-compute.rst
··· 1 + ================================== 2 + Long running workloads and compute 3 + ================================== 4 + 5 + Long running workloads (compute) are workloads that will not complete in 10 6 + seconds. (The time let the user wait before he reaches for the power button). 7 + This means that other techniques need to be used to manage those workloads, 8 + that cannot use fences. 9 + 10 + Some hardware may schedule compute jobs, and have no way to pre-empt them, or 11 + have their memory swapped out from them. Or they simply want their workload 12 + not to be preempted or swapped out at all. 13 + 14 + This means that it differs from what is described in driver-api/dma-buf.rst. 15 + 16 + As with normal compute jobs, dma-fence may not be used at all. In this case, 17 + not even to force preemption. The driver with is simply forced to unmap a BO 18 + from the long compute job's address space on unbind immediately, not even 19 + waiting for the workload to complete. Effectively this terminates the workload 20 + when there is no hardware support to recover. 21 + 22 + Since this is undesirable, there need to be mitigations to prevent a workload 23 + from being terminated. There are several possible approach, all with their 24 + advantages and drawbacks. 25 + 26 + The first approach you will likely try is to pin all buffers used by compute. 27 + This guarantees that the job will run uninterrupted, but also allows a very 28 + denial of service attack by pinning as much memory as possible, hogging the 29 + all GPU memory, and possibly a huge chunk of CPU memory. 30 + 31 + A second approach that will work slightly better on its own is adding an option 32 + not to evict when creating a new job (any kind). If all of userspace opts in 33 + to this flag, it would prevent cooperating userspace from forced terminating 34 + older compute jobs to start a new one. 35 + 36 + If job preemption and recoverable pagefaults are not available, those are the 37 + only approaches possible. So even with those, you want a separate way of 38 + controlling resources. The standard kernel way of doing so is cgroups. 39 + 40 + This creates a third option, using cgroups to prevent eviction. Both GPU and 41 + driver-allocated CPU memory would be accounted to the correct cgroup, and 42 + eviction would be made cgroup aware. This allows the GPU to be partitioned 43 + into cgroups, that will allow jobs to run next to each other without 44 + interference. 45 + 46 + The interface to the cgroup would be similar to the current CPU memory 47 + interface, with similar semantics for min/low/high/max, if eviction can 48 + be made cgroup aware. 49 + 50 + What should be noted is that each memory region (tiled memory for example) 51 + should have its own accounting. 52 + 53 + The key is set to the regionid set by the driver, for example "tile0". 54 + For the value of $card, we use drmGetUnique().
+32
drivers/gpu/drm/drm_drv.c
··· 26 26 * DEALINGS IN THE SOFTWARE. 27 27 */ 28 28 29 + #include <linux/cgroup_dmem.h> 29 30 #include <linux/debugfs.h> 30 31 #include <linux/fs.h> 31 32 #include <linux/module.h> ··· 820 819 kref_put(&dev->ref, drm_dev_release); 821 820 } 822 821 EXPORT_SYMBOL(drm_dev_put); 822 + 823 + static void drmm_cg_unregister_region(struct drm_device *dev, void *arg) 824 + { 825 + dmem_cgroup_unregister_region(arg); 826 + } 827 + 828 + /** 829 + * drmm_cgroup_register_region - Register a region of a DRM device to cgroups 830 + * @dev: device for region 831 + * @region_name: Region name for registering 832 + * @size: Size of region in bytes 833 + * 834 + * This decreases the ref-count of @dev by one. The device is destroyed if the 835 + * ref-count drops to zero. 836 + */ 837 + struct dmem_cgroup_region *drmm_cgroup_register_region(struct drm_device *dev, const char *region_name, u64 size) 838 + { 839 + struct dmem_cgroup_region *region; 840 + int ret; 841 + 842 + region = dmem_cgroup_register_region(size, "drm/%s/%s", dev->unique, region_name); 843 + if (IS_ERR_OR_NULL(region)) 844 + return region; 845 + 846 + ret = drmm_add_action_or_reset(dev, drmm_cg_unregister_region, region); 847 + if (ret) 848 + return ERR_PTR(ret); 849 + 850 + return region; 851 + } 852 + EXPORT_SYMBOL_GPL(drmm_cgroup_register_region); 823 853 824 854 static int create_compat_control_link(struct drm_device *dev) 825 855 {
+9 -9
drivers/gpu/drm/ttm/tests/ttm_bo_test.c
··· 258 258 bo = ttm_bo_kunit_init(test, test->priv, BO_SIZE, NULL); 259 259 bo->priority = bo_prio; 260 260 261 - err = ttm_resource_alloc(bo, place, &res1); 261 + err = ttm_resource_alloc(bo, place, &res1, NULL); 262 262 KUNIT_ASSERT_EQ(test, err, 0); 263 263 264 264 bo->resource = res1; 265 265 266 266 /* Add a dummy resource to populate LRU */ 267 - ttm_resource_alloc(bo, place, &res2); 267 + ttm_resource_alloc(bo, place, &res2, NULL); 268 268 269 269 dma_resv_lock(bo->base.resv, NULL); 270 270 ttm_bo_unreserve(bo); ··· 300 300 dma_resv_lock(bo->base.resv, NULL); 301 301 ttm_bo_pin(bo); 302 302 303 - err = ttm_resource_alloc(bo, place, &res1); 303 + err = ttm_resource_alloc(bo, place, &res1, NULL); 304 304 KUNIT_ASSERT_EQ(test, err, 0); 305 305 bo->resource = res1; 306 306 307 307 /* Add a dummy resource to the pinned list */ 308 - err = ttm_resource_alloc(bo, place, &res2); 308 + err = ttm_resource_alloc(bo, place, &res2, NULL); 309 309 KUNIT_ASSERT_EQ(test, err, 0); 310 310 KUNIT_ASSERT_EQ(test, 311 311 list_is_last(&res2->lru.link, &priv->ttm_dev->unevictable), 1); ··· 355 355 ttm_bo_set_bulk_move(bo1, &lru_bulk_move); 356 356 dma_resv_unlock(bo1->base.resv); 357 357 358 - err = ttm_resource_alloc(bo1, place, &res1); 358 + err = ttm_resource_alloc(bo1, place, &res1, NULL); 359 359 KUNIT_ASSERT_EQ(test, err, 0); 360 360 bo1->resource = res1; 361 361 ··· 363 363 ttm_bo_set_bulk_move(bo2, &lru_bulk_move); 364 364 dma_resv_unlock(bo2->base.resv); 365 365 366 - err = ttm_resource_alloc(bo2, place, &res2); 366 + err = ttm_resource_alloc(bo2, place, &res2, NULL); 367 367 KUNIT_ASSERT_EQ(test, err, 0); 368 368 bo2->resource = res2; 369 369 ··· 401 401 bo = ttm_bo_kunit_init(test, test->priv, BO_SIZE, NULL); 402 402 bo->type = ttm_bo_type_device; 403 403 404 - err = ttm_resource_alloc(bo, place, &res); 404 + err = ttm_resource_alloc(bo, place, &res, NULL); 405 405 KUNIT_ASSERT_EQ(test, err, 0); 406 406 bo->resource = res; 407 407 ··· 518 518 519 519 bo = ttm_bo_kunit_init(test, test->priv, BO_SIZE, NULL); 520 520 521 - err = ttm_resource_alloc(bo, place, &res); 521 + err = ttm_resource_alloc(bo, place, &res, NULL); 522 522 KUNIT_ASSERT_EQ(test, err, 0); 523 523 bo->resource = res; 524 524 ··· 569 569 570 570 bo = ttm_bo_kunit_init(test, test->priv, BO_SIZE, NULL); 571 571 572 - err = ttm_resource_alloc(bo, place, &res); 572 + err = ttm_resource_alloc(bo, place, &res, NULL); 573 573 KUNIT_ASSERT_EQ(test, err, 0); 574 574 bo->resource = res; 575 575
+2 -2
drivers/gpu/drm/ttm/tests/ttm_bo_validate_test.c
··· 542 542 bo->ttm = old_tt; 543 543 } 544 544 545 - err = ttm_resource_alloc(bo, place, &bo->resource); 545 + err = ttm_resource_alloc(bo, place, &bo->resource, NULL); 546 546 KUNIT_EXPECT_EQ(test, err, 0); 547 547 KUNIT_ASSERT_EQ(test, man->usage, size); 548 548 ··· 603 603 bo = ttm_bo_kunit_init(test, test->priv, size, NULL); 604 604 bo->type = params->bo_type; 605 605 606 - err = ttm_resource_alloc(bo, place, &bo->resource); 606 + err = ttm_resource_alloc(bo, place, &bo->resource, NULL); 607 607 KUNIT_EXPECT_EQ(test, err, 0); 608 608 609 609 placement = kunit_kzalloc(test, sizeof(*placement), GFP_KERNEL);
+1 -1
drivers/gpu/drm/ttm/tests/ttm_resource_test.c
··· 302 302 res = kunit_kzalloc(test, sizeof(*res), GFP_KERNEL); 303 303 KUNIT_ASSERT_NOT_NULL(test, res); 304 304 305 - ttm_resource_alloc(bo, place, &res); 305 + ttm_resource_alloc(bo, place, &res, NULL); 306 306 307 307 man = ttm_manager_type(priv->devs->ttm_dev, mem_type); 308 308 man->func->free(man, res);
+45 -7
drivers/gpu/drm/ttm/ttm_bo.c
··· 42 42 #include <linux/file.h> 43 43 #include <linux/module.h> 44 44 #include <linux/atomic.h> 45 + #include <linux/cgroup_dmem.h> 45 46 #include <linux/dma-resv.h> 46 47 47 48 #include "ttm_module.h" ··· 500 499 struct ttm_resource **res; 501 500 /** @evicted: Number of successful evictions. */ 502 501 unsigned long evicted; 502 + 503 + /** @limit_pool: Which pool limit we should test against */ 504 + struct dmem_cgroup_pool_state *limit_pool; 505 + /** @try_low: Whether we should attempt to evict BO's with low watermark threshold */ 506 + bool try_low; 507 + /** @hit_low: If we cannot evict a bo when @try_low is false (first pass) */ 508 + bool hit_low; 503 509 }; 504 510 505 511 static s64 ttm_bo_evict_cb(struct ttm_lru_walk *walk, struct ttm_buffer_object *bo) ··· 514 506 struct ttm_bo_evict_walk *evict_walk = 515 507 container_of(walk, typeof(*evict_walk), walk); 516 508 s64 lret; 509 + 510 + if (!dmem_cgroup_state_evict_valuable(evict_walk->limit_pool, bo->resource->css, 511 + evict_walk->try_low, &evict_walk->hit_low)) 512 + return 0; 517 513 518 514 if (bo->pin_count || !bo->bdev->funcs->eviction_valuable(bo, evict_walk->place)) 519 515 return 0; ··· 536 524 evict_walk->evicted++; 537 525 if (evict_walk->res) 538 526 lret = ttm_resource_alloc(evict_walk->evictor, evict_walk->place, 539 - evict_walk->res); 527 + evict_walk->res, NULL); 540 528 if (lret == 0) 541 529 return 1; 542 530 out: ··· 557 545 struct ttm_buffer_object *evictor, 558 546 struct ttm_operation_ctx *ctx, 559 547 struct ww_acquire_ctx *ticket, 560 - struct ttm_resource **res) 548 + struct ttm_resource **res, 549 + struct dmem_cgroup_pool_state *limit_pool) 561 550 { 562 551 struct ttm_bo_evict_walk evict_walk = { 563 552 .walk = { ··· 569 556 .place = place, 570 557 .evictor = evictor, 571 558 .res = res, 559 + .limit_pool = limit_pool, 572 560 }; 573 561 s64 lret; 574 562 575 563 evict_walk.walk.trylock_only = true; 576 564 lret = ttm_lru_walk_for_evict(&evict_walk.walk, bdev, man, 1); 565 + 566 + /* One more attempt if we hit low limit? */ 567 + if (!lret && evict_walk.hit_low) { 568 + evict_walk.try_low = true; 569 + lret = ttm_lru_walk_for_evict(&evict_walk.walk, bdev, man, 1); 570 + } 577 571 if (lret || !ticket) 578 572 goto out; 579 573 574 + /* Reset low limit */ 575 + evict_walk.try_low = evict_walk.hit_low = false; 580 576 /* If ticket-locking, repeat while making progress. */ 581 577 evict_walk.walk.trylock_only = false; 578 + 579 + retry: 582 580 do { 583 581 /* The walk may clear the evict_walk.walk.ticket field */ 584 582 evict_walk.walk.ticket = ticket; 585 583 evict_walk.evicted = 0; 586 584 lret = ttm_lru_walk_for_evict(&evict_walk.walk, bdev, man, 1); 587 585 } while (!lret && evict_walk.evicted); 586 + 587 + /* We hit the low limit? Try once more */ 588 + if (!lret && evict_walk.hit_low && !evict_walk.try_low) { 589 + evict_walk.try_low = true; 590 + goto retry; 591 + } 588 592 out: 589 593 if (lret < 0) 590 594 return lret; ··· 719 689 720 690 for (i = 0; i < placement->num_placement; ++i) { 721 691 const struct ttm_place *place = &placement->placement[i]; 692 + struct dmem_cgroup_pool_state *limit_pool = NULL; 722 693 struct ttm_resource_manager *man; 723 694 bool may_evict; 724 695 ··· 732 701 continue; 733 702 734 703 may_evict = (force_space && place->mem_type != TTM_PL_SYSTEM); 735 - ret = ttm_resource_alloc(bo, place, res); 704 + ret = ttm_resource_alloc(bo, place, res, force_space ? &limit_pool : NULL); 736 705 if (ret) { 737 - if (ret != -ENOSPC) 706 + if (ret != -ENOSPC && ret != -EAGAIN) { 707 + dmem_cgroup_pool_state_put(limit_pool); 738 708 return ret; 739 - if (!may_evict) 709 + } 710 + if (!may_evict) { 711 + dmem_cgroup_pool_state_put(limit_pool); 740 712 continue; 713 + } 741 714 742 715 ret = ttm_bo_evict_alloc(bdev, man, place, bo, ctx, 743 - ticket, res); 716 + ticket, res, limit_pool); 717 + dmem_cgroup_pool_state_put(limit_pool); 744 718 if (ret == -EBUSY) 745 719 continue; 746 720 if (ret) ··· 1092 1056 struct ttm_lru_walk walk; 1093 1057 /** @gfp_flags: The gfp flags to use for ttm_tt_swapout() */ 1094 1058 gfp_t gfp_flags; 1059 + 1060 + bool hit_low, evict_low; 1095 1061 }; 1096 1062 1097 1063 static s64 ··· 1144 1106 1145 1107 memset(&hop, 0, sizeof(hop)); 1146 1108 place.mem_type = TTM_PL_SYSTEM; 1147 - ret = ttm_resource_alloc(bo, &place, &evict_mem); 1109 + ret = ttm_resource_alloc(bo, &place, &evict_mem, NULL); 1148 1110 if (ret) 1149 1111 goto out; 1150 1112
+21 -2
drivers/gpu/drm/ttm/ttm_resource.c
··· 26 26 #include <linux/io-mapping.h> 27 27 #include <linux/iosys-map.h> 28 28 #include <linux/scatterlist.h> 29 + #include <linux/cgroup_dmem.h> 29 30 30 31 #include <drm/ttm/ttm_bo.h> 31 32 #include <drm/ttm/ttm_placement.h> ··· 351 350 352 351 int ttm_resource_alloc(struct ttm_buffer_object *bo, 353 352 const struct ttm_place *place, 354 - struct ttm_resource **res_ptr) 353 + struct ttm_resource **res_ptr, 354 + struct dmem_cgroup_pool_state **ret_limit_pool) 355 355 { 356 356 struct ttm_resource_manager *man = 357 357 ttm_manager_type(bo->bdev, place->mem_type); 358 + struct dmem_cgroup_pool_state *pool = NULL; 358 359 int ret; 359 360 361 + if (man->cg) { 362 + ret = dmem_cgroup_try_charge(man->cg, bo->base.size, &pool, ret_limit_pool); 363 + if (ret) 364 + return ret; 365 + } 366 + 360 367 ret = man->func->alloc(man, bo, place, res_ptr); 361 - if (ret) 368 + if (ret) { 369 + if (pool) 370 + dmem_cgroup_uncharge(pool, bo->base.size); 362 371 return ret; 372 + } 373 + 374 + (*res_ptr)->css = pool; 363 375 364 376 spin_lock(&bo->bdev->lru_lock); 365 377 ttm_resource_add_bulk_move(*res_ptr, bo); ··· 384 370 void ttm_resource_free(struct ttm_buffer_object *bo, struct ttm_resource **res) 385 371 { 386 372 struct ttm_resource_manager *man; 373 + struct dmem_cgroup_pool_state *pool; 387 374 388 375 if (!*res) 389 376 return; ··· 392 377 spin_lock(&bo->bdev->lru_lock); 393 378 ttm_resource_del_bulk_move(*res, bo); 394 379 spin_unlock(&bo->bdev->lru_lock); 380 + 381 + pool = (*res)->css; 395 382 man = ttm_manager_type(bo->bdev, (*res)->mem_type); 396 383 man->func->free(man, *res); 397 384 *res = NULL; 385 + if (man->cg) 386 + dmem_cgroup_uncharge(pool, bo->base.size); 398 387 } 399 388 EXPORT_SYMBOL(ttm_resource_free); 400 389
+8
drivers/gpu/drm/xe/xe_ttm_vram_mgr.c
··· 5 5 */ 6 6 7 7 #include <drm/drm_managed.h> 8 + #include <drm/drm_drv.h> 8 9 9 10 #include <drm/ttm/ttm_placement.h> 10 11 #include <drm/ttm/ttm_range_manager.h> ··· 311 310 { 312 311 struct ttm_resource_manager *man = &mgr->manager; 313 312 int err; 313 + 314 + if (mem_type != XE_PL_STOLEN) { 315 + const char *name = mem_type == XE_PL_VRAM0 ? "vram0" : "vram1"; 316 + man->cg = drmm_cgroup_register_region(&xe->drm, name, size); 317 + if (IS_ERR(man->cg)) 318 + return PTR_ERR(man->cg); 319 + } 314 320 315 321 man->func = &xe_ttm_vram_mgr_func; 316 322 mgr->mem_type = mem_type;
+5
include/drm/drm_drv.h
··· 34 34 35 35 #include <drm/drm_device.h> 36 36 37 + struct dmem_cgroup_region; 37 38 struct drm_fb_helper; 38 39 struct drm_fb_helper_surface_size; 39 40 struct drm_file; ··· 436 435 void *__devm_drm_dev_alloc(struct device *parent, 437 436 const struct drm_driver *driver, 438 437 size_t size, size_t offset); 438 + 439 + struct dmem_cgroup_region * 440 + drmm_cgroup_register_region(struct drm_device *dev, 441 + const char *region_name, u64 size); 439 442 440 443 /** 441 444 * devm_drm_dev_alloc - Resource managed allocation of a &drm_device instance
+11 -1
include/drm/ttm/ttm_resource.h
··· 38 38 #define TTM_MAX_BO_PRIORITY 4U 39 39 #define TTM_NUM_MEM_TYPES 8 40 40 41 + struct dmem_cgroup_device; 41 42 struct ttm_device; 42 43 struct ttm_resource_manager; 43 44 struct ttm_resource; ··· 212 211 * bdev->lru_lock. 213 212 */ 214 213 uint64_t usage; 214 + 215 + /** 216 + * @cg: &dmem_cgroup_region used for memory accounting, if not NULL. 217 + */ 218 + struct dmem_cgroup_region *cg; 215 219 }; 216 220 217 221 /** ··· 245 239 * @placement: Placement flags. 246 240 * @bus: Placement on io bus accessible to the CPU 247 241 * @bo: weak reference to the BO, protected by ttm_device::lru_lock 242 + * @css: cgroup state this resource is charged to 248 243 * 249 244 * Structure indicating the placement and space resources used by a 250 245 * buffer object. ··· 257 250 uint32_t placement; 258 251 struct ttm_bus_placement bus; 259 252 struct ttm_buffer_object *bo; 253 + 254 + struct dmem_cgroup_pool_state *css; 260 255 261 256 /** 262 257 * @lru: Least recently used list, see &ttm_resource_manager.lru ··· 441 432 442 433 int ttm_resource_alloc(struct ttm_buffer_object *bo, 443 434 const struct ttm_place *place, 444 - struct ttm_resource **res); 435 + struct ttm_resource **res, 436 + struct dmem_cgroup_pool_state **ret_limit_pool); 445 437 void ttm_resource_free(struct ttm_buffer_object *bo, struct ttm_resource **res); 446 438 bool ttm_resource_intersects(struct ttm_device *bdev, 447 439 struct ttm_resource *res,
+66
include/linux/cgroup_dmem.h
··· 1 + /* SPDX-License-Identifier: MIT */ 2 + /* 3 + * Copyright © 2023-2024 Intel Corporation 4 + */ 5 + 6 + #ifndef _CGROUP_DMEM_H 7 + #define _CGROUP_DMEM_H 8 + 9 + #include <linux/types.h> 10 + #include <linux/llist.h> 11 + 12 + struct dmem_cgroup_pool_state; 13 + 14 + /* Opaque definition of a cgroup region, used internally */ 15 + struct dmem_cgroup_region; 16 + 17 + #if IS_ENABLED(CONFIG_CGROUP_DMEM) 18 + struct dmem_cgroup_region *dmem_cgroup_register_region(u64 size, const char *name_fmt, ...) __printf(2,3); 19 + void dmem_cgroup_unregister_region(struct dmem_cgroup_region *region); 20 + int dmem_cgroup_try_charge(struct dmem_cgroup_region *region, u64 size, 21 + struct dmem_cgroup_pool_state **ret_pool, 22 + struct dmem_cgroup_pool_state **ret_limit_pool); 23 + void dmem_cgroup_uncharge(struct dmem_cgroup_pool_state *pool, u64 size); 24 + bool dmem_cgroup_state_evict_valuable(struct dmem_cgroup_pool_state *limit_pool, 25 + struct dmem_cgroup_pool_state *test_pool, 26 + bool ignore_low, bool *ret_hit_low); 27 + 28 + void dmem_cgroup_pool_state_put(struct dmem_cgroup_pool_state *pool); 29 + #else 30 + static inline __printf(2,3) struct dmem_cgroup_region * 31 + dmem_cgroup_register_region(u64 size, const char *name_fmt, ...) 32 + { 33 + return NULL; 34 + } 35 + 36 + static inline void dmem_cgroup_unregister_region(struct dmem_cgroup_region *region) 37 + { } 38 + 39 + static inline int dmem_cgroup_try_charge(struct dmem_cgroup_region *region, u64 size, 40 + struct dmem_cgroup_pool_state **ret_pool, 41 + struct dmem_cgroup_pool_state **ret_limit_pool) 42 + { 43 + *ret_pool = NULL; 44 + 45 + if (ret_limit_pool) 46 + *ret_limit_pool = NULL; 47 + 48 + return 0; 49 + } 50 + 51 + static inline void dmem_cgroup_uncharge(struct dmem_cgroup_pool_state *pool, u64 size) 52 + { } 53 + 54 + static inline 55 + bool dmem_cgroup_state_evict_valuable(struct dmem_cgroup_pool_state *limit_pool, 56 + struct dmem_cgroup_pool_state *test_pool, 57 + bool ignore_low, bool *ret_hit_low) 58 + { 59 + return true; 60 + } 61 + 62 + static inline void dmem_cgroup_pool_state_put(struct dmem_cgroup_pool_state *pool) 63 + { } 64 + 65 + #endif 66 + #endif /* _CGROUP_DMEM_H */
+4
include/linux/cgroup_subsys.h
··· 65 65 SUBSYS(misc) 66 66 #endif 67 67 68 + #if IS_ENABLED(CONFIG_CGROUP_DMEM) 69 + SUBSYS(dmem) 70 + #endif 71 + 68 72 /* 69 73 * The following subsystems are not supported on the default hierarchy. 70 74 */
+1 -1
include/linux/page_counter.h
··· 96 96 counter->watermark = usage; 97 97 } 98 98 99 - #ifdef CONFIG_MEMCG 99 + #if IS_ENABLED(CONFIG_MEMCG) || IS_ENABLED(CONFIG_CGROUP_DMEM) 100 100 void page_counter_calculate_protection(struct page_counter *root, 101 101 struct page_counter *counter, 102 102 bool recursive_protection);
+10
init/Kconfig
··· 1128 1128 1129 1129 config CGROUP_RDMA 1130 1130 bool "RDMA controller" 1131 + select PAGE_COUNTER 1131 1132 help 1132 1133 Provides enforcement of RDMA resources defined by IB stack. 1133 1134 It is fairly easy for consumers to exhaust RDMA resources, which ··· 1136 1135 RDMA controller is designed to stop this from happening. 1137 1136 Attaching processes with active RDMA resources to the cgroup 1138 1137 hierarchy is allowed even if can cross the hierarchy's limit. 1138 + 1139 + config CGROUP_DMEM 1140 + bool "Device memory controller (DMEM)" 1141 + help 1142 + The DMEM controller allows compatible devices to restrict device 1143 + memory usage based on the cgroup hierarchy. 1144 + 1145 + As an example, it allows you to restrict VRAM usage for applications 1146 + in the DRM subsystem. 1139 1147 1140 1148 config CGROUP_FREEZER 1141 1149 bool "Freezer controller"
+1
kernel/cgroup/Makefile
··· 7 7 obj-$(CONFIG_CPUSETS) += cpuset.o 8 8 obj-$(CONFIG_CPUSETS_V1) += cpuset-v1.o 9 9 obj-$(CONFIG_CGROUP_MISC) += misc.o 10 + obj-$(CONFIG_CGROUP_DMEM) += dmem.o 10 11 obj-$(CONFIG_CGROUP_DEBUG) += debug.o
+861
kernel/cgroup/dmem.c
··· 1 + // SPDX-License-Identifier: GPL-2.0 2 + /* 3 + * Copyright 2023-2024 Intel Corporation (Maarten Lankhorst <dev@lankhorst.se>) 4 + * Copyright 2024 Red Hat (Maxime Ripard <mripard@kernel.org>) 5 + * Partially based on the rdma and misc controllers, which bear the following copyrights: 6 + * 7 + * Copyright 2020 Google LLC 8 + * Copyright (C) 2016 Parav Pandit <pandit.parav@gmail.com> 9 + */ 10 + 11 + #include <linux/cgroup.h> 12 + #include <linux/cgroup_dmem.h> 13 + #include <linux/list.h> 14 + #include <linux/mutex.h> 15 + #include <linux/page_counter.h> 16 + #include <linux/parser.h> 17 + #include <linux/slab.h> 18 + 19 + struct dmem_cgroup_region { 20 + /** 21 + * @ref: References keeping the region alive. 22 + * Keeps the region reference alive after a succesful RCU lookup. 23 + */ 24 + struct kref ref; 25 + 26 + /** @rcu: RCU head for freeing */ 27 + struct rcu_head rcu; 28 + 29 + /** 30 + * @region_node: Linked into &dmem_cgroup_regions list. 31 + * Protected by RCU and global spinlock. 32 + */ 33 + struct list_head region_node; 34 + 35 + /** 36 + * @pools: List of pools linked to this region. 37 + * Protected by global spinlock only 38 + */ 39 + struct list_head pools; 40 + 41 + /** @size: Size of region, in bytes */ 42 + u64 size; 43 + 44 + /** @name: Name describing the node, set by dmem_cgroup_register_region */ 45 + char *name; 46 + 47 + /** 48 + * @unregistered: Whether the region is unregistered by its caller. 49 + * No new pools should be added to the region afterwards. 50 + */ 51 + bool unregistered; 52 + }; 53 + 54 + struct dmemcg_state { 55 + struct cgroup_subsys_state css; 56 + 57 + struct list_head pools; 58 + }; 59 + 60 + struct dmem_cgroup_pool_state { 61 + struct dmem_cgroup_region *region; 62 + struct dmemcg_state *cs; 63 + 64 + /* css node, RCU protected against region teardown */ 65 + struct list_head css_node; 66 + 67 + /* dev node, no RCU protection required */ 68 + struct list_head region_node; 69 + 70 + struct rcu_head rcu; 71 + 72 + struct page_counter cnt; 73 + 74 + bool inited; 75 + }; 76 + 77 + /* 78 + * 3 operations require locking protection: 79 + * - Registering and unregistering region to/from list, requires global lock. 80 + * - Adding a dmem_cgroup_pool_state to a CSS, removing when CSS is freed. 81 + * - Adding a dmem_cgroup_pool_state to a region list. 82 + * 83 + * Since for the most common operations RCU provides enough protection, I 84 + * do not think more granular locking makes sense. Most protection is offered 85 + * by RCU and the lockless operating page_counter. 86 + */ 87 + static DEFINE_SPINLOCK(dmemcg_lock); 88 + static LIST_HEAD(dmem_cgroup_regions); 89 + 90 + static inline struct dmemcg_state * 91 + css_to_dmemcs(struct cgroup_subsys_state *css) 92 + { 93 + return container_of(css, struct dmemcg_state, css); 94 + } 95 + 96 + static inline struct dmemcg_state *get_current_dmemcs(void) 97 + { 98 + return css_to_dmemcs(task_get_css(current, dmem_cgrp_id)); 99 + } 100 + 101 + static struct dmemcg_state *parent_dmemcs(struct dmemcg_state *cg) 102 + { 103 + return cg->css.parent ? css_to_dmemcs(cg->css.parent) : NULL; 104 + } 105 + 106 + static void free_cg_pool(struct dmem_cgroup_pool_state *pool) 107 + { 108 + list_del(&pool->region_node); 109 + kfree(pool); 110 + } 111 + 112 + static void 113 + set_resource_min(struct dmem_cgroup_pool_state *pool, u64 val) 114 + { 115 + page_counter_set_min(&pool->cnt, val); 116 + } 117 + 118 + static void 119 + set_resource_low(struct dmem_cgroup_pool_state *pool, u64 val) 120 + { 121 + page_counter_set_low(&pool->cnt, val); 122 + } 123 + 124 + static void 125 + set_resource_max(struct dmem_cgroup_pool_state *pool, u64 val) 126 + { 127 + page_counter_set_max(&pool->cnt, val); 128 + } 129 + 130 + static u64 get_resource_low(struct dmem_cgroup_pool_state *pool) 131 + { 132 + return pool ? READ_ONCE(pool->cnt.low) : 0; 133 + } 134 + 135 + static u64 get_resource_min(struct dmem_cgroup_pool_state *pool) 136 + { 137 + return pool ? READ_ONCE(pool->cnt.min) : 0; 138 + } 139 + 140 + static u64 get_resource_max(struct dmem_cgroup_pool_state *pool) 141 + { 142 + return pool ? READ_ONCE(pool->cnt.max) : PAGE_COUNTER_MAX; 143 + } 144 + 145 + static u64 get_resource_current(struct dmem_cgroup_pool_state *pool) 146 + { 147 + return pool ? page_counter_read(&pool->cnt) : 0; 148 + } 149 + 150 + static void reset_all_resource_limits(struct dmem_cgroup_pool_state *rpool) 151 + { 152 + set_resource_min(rpool, 0); 153 + set_resource_low(rpool, 0); 154 + set_resource_max(rpool, PAGE_COUNTER_MAX); 155 + } 156 + 157 + static void dmemcs_offline(struct cgroup_subsys_state *css) 158 + { 159 + struct dmemcg_state *dmemcs = css_to_dmemcs(css); 160 + struct dmem_cgroup_pool_state *pool; 161 + 162 + rcu_read_lock(); 163 + list_for_each_entry_rcu(pool, &dmemcs->pools, css_node) 164 + reset_all_resource_limits(pool); 165 + rcu_read_unlock(); 166 + } 167 + 168 + static void dmemcs_free(struct cgroup_subsys_state *css) 169 + { 170 + struct dmemcg_state *dmemcs = css_to_dmemcs(css); 171 + struct dmem_cgroup_pool_state *pool, *next; 172 + 173 + spin_lock(&dmemcg_lock); 174 + list_for_each_entry_safe(pool, next, &dmemcs->pools, css_node) { 175 + /* 176 + *The pool is dead and all references are 0, 177 + * no need for RCU protection with list_del_rcu or freeing. 178 + */ 179 + list_del(&pool->css_node); 180 + free_cg_pool(pool); 181 + } 182 + spin_unlock(&dmemcg_lock); 183 + 184 + kfree(dmemcs); 185 + } 186 + 187 + static struct cgroup_subsys_state * 188 + dmemcs_alloc(struct cgroup_subsys_state *parent_css) 189 + { 190 + struct dmemcg_state *dmemcs = kzalloc(sizeof(*dmemcs), GFP_KERNEL); 191 + if (!dmemcs) 192 + return ERR_PTR(-ENOMEM); 193 + 194 + INIT_LIST_HEAD(&dmemcs->pools); 195 + return &dmemcs->css; 196 + } 197 + 198 + static struct dmem_cgroup_pool_state * 199 + find_cg_pool_locked(struct dmemcg_state *dmemcs, struct dmem_cgroup_region *region) 200 + { 201 + struct dmem_cgroup_pool_state *pool; 202 + 203 + list_for_each_entry_rcu(pool, &dmemcs->pools, css_node, spin_is_locked(&dmemcg_lock)) 204 + if (pool->region == region) 205 + return pool; 206 + 207 + return NULL; 208 + } 209 + 210 + static struct dmem_cgroup_pool_state *pool_parent(struct dmem_cgroup_pool_state *pool) 211 + { 212 + if (!pool->cnt.parent) 213 + return NULL; 214 + 215 + return container_of(pool->cnt.parent, typeof(*pool), cnt); 216 + } 217 + 218 + static void 219 + dmem_cgroup_calculate_protection(struct dmem_cgroup_pool_state *limit_pool, 220 + struct dmem_cgroup_pool_state *test_pool) 221 + { 222 + struct page_counter *climit; 223 + struct cgroup_subsys_state *css, *next_css; 224 + struct dmemcg_state *dmemcg_iter; 225 + struct dmem_cgroup_pool_state *pool, *parent_pool; 226 + bool found_descendant; 227 + 228 + climit = &limit_pool->cnt; 229 + 230 + rcu_read_lock(); 231 + parent_pool = pool = limit_pool; 232 + css = &limit_pool->cs->css; 233 + 234 + /* 235 + * This logic is roughly equivalent to css_foreach_descendant_pre, 236 + * except we also track the parent pool to find out which pool we need 237 + * to calculate protection values for. 238 + * 239 + * We can stop the traversal once we find test_pool among the 240 + * descendants since we don't really care about any others. 241 + */ 242 + while (pool != test_pool) { 243 + next_css = css_next_child(NULL, css); 244 + if (next_css) { 245 + parent_pool = pool; 246 + } else { 247 + while (css != &limit_pool->cs->css) { 248 + next_css = css_next_child(css, css->parent); 249 + if (next_css) 250 + break; 251 + css = css->parent; 252 + parent_pool = pool_parent(parent_pool); 253 + } 254 + /* 255 + * We can only hit this when test_pool is not a 256 + * descendant of limit_pool. 257 + */ 258 + if (WARN_ON_ONCE(css == &limit_pool->cs->css)) 259 + break; 260 + } 261 + css = next_css; 262 + 263 + found_descendant = false; 264 + dmemcg_iter = container_of(css, struct dmemcg_state, css); 265 + 266 + list_for_each_entry_rcu(pool, &dmemcg_iter->pools, css_node) { 267 + if (pool_parent(pool) == parent_pool) { 268 + found_descendant = true; 269 + break; 270 + } 271 + } 272 + if (!found_descendant) 273 + continue; 274 + 275 + page_counter_calculate_protection( 276 + climit, &pool->cnt, true); 277 + } 278 + rcu_read_unlock(); 279 + } 280 + 281 + /** 282 + * dmem_cgroup_state_evict_valuable() - Check if we should evict from test_pool 283 + * @dev: &dmem_cgroup_region 284 + * @index: The index number of the region being tested. 285 + * @limit_pool: The pool for which we hit limits 286 + * @test_pool: The pool for which to test 287 + * @ignore_low: Whether we have to respect low watermarks. 288 + * @ret_hit_low: Pointer to whether it makes sense to consider low watermark. 289 + * 290 + * This function returns true if we can evict from @test_pool, false if not. 291 + * When returning false and @ignore_low is false, @ret_hit_low may 292 + * be set to true to indicate this function can be retried with @ignore_low 293 + * set to true. 294 + * 295 + * Return: bool 296 + */ 297 + bool dmem_cgroup_state_evict_valuable(struct dmem_cgroup_pool_state *limit_pool, 298 + struct dmem_cgroup_pool_state *test_pool, 299 + bool ignore_low, bool *ret_hit_low) 300 + { 301 + struct dmem_cgroup_pool_state *pool = test_pool; 302 + struct page_counter *climit, *ctest; 303 + u64 used, min, low; 304 + 305 + /* Can always evict from current pool, despite limits */ 306 + if (limit_pool == test_pool) 307 + return true; 308 + 309 + if (limit_pool) { 310 + if (!parent_dmemcs(limit_pool->cs)) 311 + return true; 312 + 313 + for (pool = test_pool; pool && limit_pool != pool; pool = pool_parent(pool)) 314 + {} 315 + 316 + if (!pool) 317 + return false; 318 + } else { 319 + /* 320 + * If there is no cgroup limiting memory usage, use the root 321 + * cgroup instead for limit calculations. 322 + */ 323 + for (limit_pool = test_pool; pool_parent(limit_pool); limit_pool = pool_parent(limit_pool)) 324 + {} 325 + } 326 + 327 + climit = &limit_pool->cnt; 328 + ctest = &test_pool->cnt; 329 + 330 + dmem_cgroup_calculate_protection(limit_pool, test_pool); 331 + 332 + used = page_counter_read(ctest); 333 + min = READ_ONCE(ctest->emin); 334 + 335 + if (used <= min) 336 + return false; 337 + 338 + if (!ignore_low) { 339 + low = READ_ONCE(ctest->elow); 340 + if (used > low) 341 + return true; 342 + 343 + *ret_hit_low = true; 344 + return false; 345 + } 346 + return true; 347 + } 348 + EXPORT_SYMBOL_GPL(dmem_cgroup_state_evict_valuable); 349 + 350 + static struct dmem_cgroup_pool_state * 351 + alloc_pool_single(struct dmemcg_state *dmemcs, struct dmem_cgroup_region *region, 352 + struct dmem_cgroup_pool_state **allocpool) 353 + { 354 + struct dmemcg_state *parent = parent_dmemcs(dmemcs); 355 + struct dmem_cgroup_pool_state *pool, *ppool = NULL; 356 + 357 + if (!*allocpool) { 358 + pool = kzalloc(sizeof(*pool), GFP_NOWAIT); 359 + if (!pool) 360 + return ERR_PTR(-ENOMEM); 361 + } else { 362 + pool = *allocpool; 363 + *allocpool = NULL; 364 + } 365 + 366 + pool->region = region; 367 + pool->cs = dmemcs; 368 + 369 + if (parent) 370 + ppool = find_cg_pool_locked(parent, region); 371 + 372 + page_counter_init(&pool->cnt, 373 + ppool ? &ppool->cnt : NULL, true); 374 + reset_all_resource_limits(pool); 375 + 376 + list_add_tail_rcu(&pool->css_node, &dmemcs->pools); 377 + list_add_tail(&pool->region_node, &region->pools); 378 + 379 + if (!parent) 380 + pool->inited = true; 381 + else 382 + pool->inited = ppool ? ppool->inited : false; 383 + return pool; 384 + } 385 + 386 + static struct dmem_cgroup_pool_state * 387 + get_cg_pool_locked(struct dmemcg_state *dmemcs, struct dmem_cgroup_region *region, 388 + struct dmem_cgroup_pool_state **allocpool) 389 + { 390 + struct dmem_cgroup_pool_state *pool, *ppool, *retpool; 391 + struct dmemcg_state *p, *pp; 392 + 393 + /* 394 + * Recursively create pool, we may not initialize yet on 395 + * recursion, this is done as a separate step. 396 + */ 397 + for (p = dmemcs; p; p = parent_dmemcs(p)) { 398 + pool = find_cg_pool_locked(p, region); 399 + if (!pool) 400 + pool = alloc_pool_single(p, region, allocpool); 401 + 402 + if (IS_ERR(pool)) 403 + return pool; 404 + 405 + if (p == dmemcs && pool->inited) 406 + return pool; 407 + 408 + if (pool->inited) 409 + break; 410 + } 411 + 412 + retpool = pool = find_cg_pool_locked(dmemcs, region); 413 + for (p = dmemcs, pp = parent_dmemcs(dmemcs); pp; p = pp, pp = parent_dmemcs(p)) { 414 + if (pool->inited) 415 + break; 416 + 417 + /* ppool was created if it didn't exist by above loop. */ 418 + ppool = find_cg_pool_locked(pp, region); 419 + 420 + /* Fix up parent links, mark as inited. */ 421 + pool->cnt.parent = &ppool->cnt; 422 + pool->inited = true; 423 + 424 + pool = ppool; 425 + } 426 + 427 + return retpool; 428 + } 429 + 430 + static void dmemcg_free_rcu(struct rcu_head *rcu) 431 + { 432 + struct dmem_cgroup_region *region = container_of(rcu, typeof(*region), rcu); 433 + struct dmem_cgroup_pool_state *pool, *next; 434 + 435 + list_for_each_entry_safe(pool, next, &region->pools, region_node) 436 + free_cg_pool(pool); 437 + kfree(region->name); 438 + kfree(region); 439 + } 440 + 441 + static void dmemcg_free_region(struct kref *ref) 442 + { 443 + struct dmem_cgroup_region *cgregion = container_of(ref, typeof(*cgregion), ref); 444 + 445 + call_rcu(&cgregion->rcu, dmemcg_free_rcu); 446 + } 447 + 448 + /** 449 + * dmem_cgroup_unregister_region() - Unregister a previously registered region. 450 + * @region: The region to unregister. 451 + * 452 + * This function undoes dmem_cgroup_register_region. 453 + */ 454 + void dmem_cgroup_unregister_region(struct dmem_cgroup_region *region) 455 + { 456 + struct list_head *entry; 457 + 458 + if (!region) 459 + return; 460 + 461 + spin_lock(&dmemcg_lock); 462 + 463 + /* Remove from global region list */ 464 + list_del_rcu(&region->region_node); 465 + 466 + list_for_each_rcu(entry, &region->pools) { 467 + struct dmem_cgroup_pool_state *pool = 468 + container_of(entry, typeof(*pool), region_node); 469 + 470 + list_del_rcu(&pool->css_node); 471 + } 472 + 473 + /* 474 + * Ensure any RCU based lookups fail. Additionally, 475 + * no new pools should be added to the dead region 476 + * by get_cg_pool_unlocked. 477 + */ 478 + region->unregistered = true; 479 + spin_unlock(&dmemcg_lock); 480 + 481 + kref_put(&region->ref, dmemcg_free_region); 482 + } 483 + EXPORT_SYMBOL_GPL(dmem_cgroup_unregister_region); 484 + 485 + /** 486 + * dmem_cgroup_register_region() - Register a regions for dev cgroup. 487 + * @size: Size of region to register, in bytes. 488 + * @fmt: Region parameters to register 489 + * 490 + * This function registers a node in the dmem cgroup with the 491 + * name given. After calling this function, the region can be 492 + * used for allocations. 493 + * 494 + * Return: NULL or a struct on success, PTR_ERR on failure. 495 + */ 496 + struct dmem_cgroup_region *dmem_cgroup_register_region(u64 size, const char *fmt, ...) 497 + { 498 + struct dmem_cgroup_region *ret; 499 + char *region_name; 500 + va_list ap; 501 + 502 + if (!size) 503 + return NULL; 504 + 505 + va_start(ap, fmt); 506 + region_name = kvasprintf(GFP_KERNEL, fmt, ap); 507 + va_end(ap); 508 + if (!region_name) 509 + return ERR_PTR(-ENOMEM); 510 + 511 + ret = kzalloc(sizeof(*ret), GFP_KERNEL); 512 + if (!ret) { 513 + kfree(region_name); 514 + return ERR_PTR(-ENOMEM); 515 + } 516 + 517 + INIT_LIST_HEAD(&ret->pools); 518 + ret->name = region_name; 519 + ret->size = size; 520 + kref_init(&ret->ref); 521 + 522 + spin_lock(&dmemcg_lock); 523 + list_add_tail_rcu(&ret->region_node, &dmem_cgroup_regions); 524 + spin_unlock(&dmemcg_lock); 525 + 526 + return ret; 527 + } 528 + EXPORT_SYMBOL_GPL(dmem_cgroup_register_region); 529 + 530 + static struct dmem_cgroup_region *dmemcg_get_region_by_name(const char *name) 531 + { 532 + struct dmem_cgroup_region *region; 533 + 534 + list_for_each_entry_rcu(region, &dmem_cgroup_regions, region_node, spin_is_locked(&dmemcg_lock)) 535 + if (!strcmp(name, region->name) && 536 + kref_get_unless_zero(&region->ref)) 537 + return region; 538 + 539 + return NULL; 540 + } 541 + 542 + /** 543 + * dmem_cgroup_pool_state_put() - Drop a reference to a dmem_cgroup_pool_state 544 + * @pool: &dmem_cgroup_pool_state 545 + * 546 + * Called to drop a reference to the limiting pool returned by 547 + * dmem_cgroup_try_charge(). 548 + */ 549 + void dmem_cgroup_pool_state_put(struct dmem_cgroup_pool_state *pool) 550 + { 551 + if (pool) 552 + css_put(&pool->cs->css); 553 + } 554 + EXPORT_SYMBOL_GPL(dmem_cgroup_pool_state_put); 555 + 556 + static struct dmem_cgroup_pool_state * 557 + get_cg_pool_unlocked(struct dmemcg_state *cg, struct dmem_cgroup_region *region) 558 + { 559 + struct dmem_cgroup_pool_state *pool, *allocpool = NULL; 560 + 561 + /* fastpath lookup? */ 562 + rcu_read_lock(); 563 + pool = find_cg_pool_locked(cg, region); 564 + if (pool && !READ_ONCE(pool->inited)) 565 + pool = NULL; 566 + rcu_read_unlock(); 567 + 568 + while (!pool) { 569 + spin_lock(&dmemcg_lock); 570 + if (!region->unregistered) 571 + pool = get_cg_pool_locked(cg, region, &allocpool); 572 + else 573 + pool = ERR_PTR(-ENODEV); 574 + spin_unlock(&dmemcg_lock); 575 + 576 + if (pool == ERR_PTR(-ENOMEM)) { 577 + pool = NULL; 578 + if (WARN_ON(allocpool)) 579 + continue; 580 + 581 + allocpool = kzalloc(sizeof(*allocpool), GFP_KERNEL); 582 + if (allocpool) { 583 + pool = NULL; 584 + continue; 585 + } 586 + } 587 + } 588 + 589 + kfree(allocpool); 590 + return pool; 591 + } 592 + 593 + /** 594 + * dmem_cgroup_uncharge() - Uncharge a pool. 595 + * @pool: Pool to uncharge. 596 + * @size: Size to uncharge. 597 + * 598 + * Undoes the effects of dmem_cgroup_try_charge. 599 + * Must be called with the returned pool as argument, 600 + * and same @index and @size. 601 + */ 602 + void dmem_cgroup_uncharge(struct dmem_cgroup_pool_state *pool, u64 size) 603 + { 604 + if (!pool) 605 + return; 606 + 607 + page_counter_uncharge(&pool->cnt, size); 608 + css_put(&pool->cs->css); 609 + } 610 + EXPORT_SYMBOL_GPL(dmem_cgroup_uncharge); 611 + 612 + /** 613 + * dmem_cgroup_try_charge() - Try charging a new allocation to a region. 614 + * @dev: Device to charge 615 + * @size: Size (in bytes) to charge. 616 + * @ret_pool: On succesfull allocation, the pool that is charged. 617 + * @ret_limit_pool: On a failed allocation, the limiting pool. 618 + * 619 + * This function charges the current pool for @dev with region at @index for a 620 + * size of @size bytes. 621 + * 622 + * If the function succeeds, @ret_pool is set, which must be passed to 623 + * dmem_cgroup_uncharge() when undoing the allocation. 624 + * 625 + * When this function fails with -EAGAIN and @ret_limit_pool is non-null, it 626 + * will be set to the pool for which the limit is hit. This can be used for 627 + * eviction as argument to dmem_cgroup_evict_valuable(). This reference must be freed 628 + * with @dmem_cgroup_pool_state_put(). 629 + * 630 + * Return: 0 on success, -EAGAIN on hitting a limit, or a negative errno on failure. 631 + */ 632 + int dmem_cgroup_try_charge(struct dmem_cgroup_region *region, u64 size, 633 + struct dmem_cgroup_pool_state **ret_pool, 634 + struct dmem_cgroup_pool_state **ret_limit_pool) 635 + { 636 + struct dmemcg_state *cg; 637 + struct dmem_cgroup_pool_state *pool; 638 + struct page_counter *fail; 639 + int ret; 640 + 641 + *ret_pool = NULL; 642 + if (ret_limit_pool) 643 + *ret_limit_pool = NULL; 644 + 645 + /* 646 + * hold on to css, as cgroup can be removed but resource 647 + * accounting happens on css. 648 + */ 649 + cg = get_current_dmemcs(); 650 + 651 + pool = get_cg_pool_unlocked(cg, region); 652 + if (IS_ERR(pool)) { 653 + ret = PTR_ERR(pool); 654 + goto err; 655 + } 656 + 657 + if (!page_counter_try_charge(&pool->cnt, size, &fail)) { 658 + if (ret_limit_pool) { 659 + *ret_limit_pool = container_of(fail, struct dmem_cgroup_pool_state, cnt); 660 + css_get(&(*ret_limit_pool)->cs->css); 661 + } 662 + ret = -EAGAIN; 663 + goto err; 664 + } 665 + 666 + /* On success, reference from get_current_dmemcs is transferred to *ret_pool */ 667 + *ret_pool = pool; 668 + return 0; 669 + 670 + err: 671 + css_put(&cg->css); 672 + return ret; 673 + } 674 + EXPORT_SYMBOL_GPL(dmem_cgroup_try_charge); 675 + 676 + static int dmem_cgroup_region_capacity_show(struct seq_file *sf, void *v) 677 + { 678 + struct dmem_cgroup_region *region; 679 + 680 + rcu_read_lock(); 681 + list_for_each_entry_rcu(region, &dmem_cgroup_regions, region_node) { 682 + seq_puts(sf, region->name); 683 + seq_printf(sf, " %llu\n", region->size); 684 + } 685 + rcu_read_unlock(); 686 + return 0; 687 + } 688 + 689 + static int dmemcg_parse_limit(char *options, struct dmem_cgroup_region *region, 690 + u64 *new_limit) 691 + { 692 + char *end; 693 + 694 + if (!strcmp(options, "max")) { 695 + *new_limit = PAGE_COUNTER_MAX; 696 + return 0; 697 + } 698 + 699 + *new_limit = memparse(options, &end); 700 + if (*end != '\0') 701 + return -EINVAL; 702 + 703 + return 0; 704 + } 705 + 706 + static ssize_t dmemcg_limit_write(struct kernfs_open_file *of, 707 + char *buf, size_t nbytes, loff_t off, 708 + void (*apply)(struct dmem_cgroup_pool_state *, u64)) 709 + { 710 + struct dmemcg_state *dmemcs = css_to_dmemcs(of_css(of)); 711 + int err = 0; 712 + 713 + while (buf && !err) { 714 + struct dmem_cgroup_pool_state *pool = NULL; 715 + char *options, *region_name; 716 + struct dmem_cgroup_region *region; 717 + u64 new_limit; 718 + 719 + options = buf; 720 + buf = strchr(buf, '\n'); 721 + if (buf) 722 + *buf++ = '\0'; 723 + 724 + options = strstrip(options); 725 + 726 + /* eat empty lines */ 727 + if (!options[0]) 728 + continue; 729 + 730 + region_name = strsep(&options, " \t"); 731 + if (!region_name[0]) 732 + continue; 733 + 734 + rcu_read_lock(); 735 + region = dmemcg_get_region_by_name(region_name); 736 + rcu_read_unlock(); 737 + 738 + if (!region) 739 + return -EINVAL; 740 + 741 + err = dmemcg_parse_limit(options, region, &new_limit); 742 + if (err < 0) 743 + goto out_put; 744 + 745 + pool = get_cg_pool_unlocked(dmemcs, region); 746 + if (IS_ERR(pool)) { 747 + err = PTR_ERR(pool); 748 + goto out_put; 749 + } 750 + 751 + /* And commit */ 752 + apply(pool, new_limit); 753 + 754 + out_put: 755 + kref_put(&region->ref, dmemcg_free_region); 756 + } 757 + 758 + 759 + return err ?: nbytes; 760 + } 761 + 762 + static int dmemcg_limit_show(struct seq_file *sf, void *v, 763 + u64 (*fn)(struct dmem_cgroup_pool_state *)) 764 + { 765 + struct dmemcg_state *dmemcs = css_to_dmemcs(seq_css(sf)); 766 + struct dmem_cgroup_region *region; 767 + 768 + rcu_read_lock(); 769 + list_for_each_entry_rcu(region, &dmem_cgroup_regions, region_node) { 770 + struct dmem_cgroup_pool_state *pool = find_cg_pool_locked(dmemcs, region); 771 + u64 val; 772 + 773 + seq_puts(sf, region->name); 774 + 775 + val = fn(pool); 776 + if (val < PAGE_COUNTER_MAX) 777 + seq_printf(sf, " %lld\n", val); 778 + else 779 + seq_puts(sf, " max\n"); 780 + } 781 + rcu_read_unlock(); 782 + 783 + return 0; 784 + } 785 + 786 + static int dmem_cgroup_region_current_show(struct seq_file *sf, void *v) 787 + { 788 + return dmemcg_limit_show(sf, v, get_resource_current); 789 + } 790 + 791 + static int dmem_cgroup_region_min_show(struct seq_file *sf, void *v) 792 + { 793 + return dmemcg_limit_show(sf, v, get_resource_min); 794 + } 795 + 796 + static ssize_t dmem_cgroup_region_min_write(struct kernfs_open_file *of, 797 + char *buf, size_t nbytes, loff_t off) 798 + { 799 + return dmemcg_limit_write(of, buf, nbytes, off, set_resource_min); 800 + } 801 + 802 + static int dmem_cgroup_region_low_show(struct seq_file *sf, void *v) 803 + { 804 + return dmemcg_limit_show(sf, v, get_resource_low); 805 + } 806 + 807 + static ssize_t dmem_cgroup_region_low_write(struct kernfs_open_file *of, 808 + char *buf, size_t nbytes, loff_t off) 809 + { 810 + return dmemcg_limit_write(of, buf, nbytes, off, set_resource_low); 811 + } 812 + 813 + static int dmem_cgroup_region_max_show(struct seq_file *sf, void *v) 814 + { 815 + return dmemcg_limit_show(sf, v, get_resource_max); 816 + } 817 + 818 + static ssize_t dmem_cgroup_region_max_write(struct kernfs_open_file *of, 819 + char *buf, size_t nbytes, loff_t off) 820 + { 821 + return dmemcg_limit_write(of, buf, nbytes, off, set_resource_max); 822 + } 823 + 824 + static struct cftype files[] = { 825 + { 826 + .name = "capacity", 827 + .seq_show = dmem_cgroup_region_capacity_show, 828 + .flags = CFTYPE_ONLY_ON_ROOT, 829 + }, 830 + { 831 + .name = "current", 832 + .seq_show = dmem_cgroup_region_current_show, 833 + }, 834 + { 835 + .name = "min", 836 + .write = dmem_cgroup_region_min_write, 837 + .seq_show = dmem_cgroup_region_min_show, 838 + .flags = CFTYPE_NOT_ON_ROOT, 839 + }, 840 + { 841 + .name = "low", 842 + .write = dmem_cgroup_region_low_write, 843 + .seq_show = dmem_cgroup_region_low_show, 844 + .flags = CFTYPE_NOT_ON_ROOT, 845 + }, 846 + { 847 + .name = "max", 848 + .write = dmem_cgroup_region_max_write, 849 + .seq_show = dmem_cgroup_region_max_show, 850 + .flags = CFTYPE_NOT_ON_ROOT, 851 + }, 852 + { } /* Zero entry terminates. */ 853 + }; 854 + 855 + struct cgroup_subsys dmem_cgrp_subsys = { 856 + .css_alloc = dmemcs_alloc, 857 + .css_free = dmemcs_free, 858 + .css_offline = dmemcs_offline, 859 + .legacy_cftypes = files, 860 + .dfl_cftypes = files, 861 + };
+2 -2
mm/page_counter.c
··· 288 288 } 289 289 290 290 291 - #ifdef CONFIG_MEMCG 291 + #if IS_ENABLED(CONFIG_MEMCG) || IS_ENABLED(CONFIG_CGROUP_DMEM) 292 292 /* 293 293 * This function calculates an individual page counter's effective 294 294 * protection which is derived from its own memory.min/low, its ··· 460 460 atomic_long_read(&parent->children_low_usage), 461 461 recursive_protection)); 462 462 } 463 - #endif /* CONFIG_MEMCG */ 463 + #endif /* CONFIG_MEMCG || CONFIG_CGROUP_DMEM */