| 1 | /* SPDX-License-Identifier: GPL-2.0 OR MIT */ |
| 2 | |
| 3 | /* |
| 4 | * Copyright (c) 2006-2009 VMware, Inc., Palo Alto, CA., USA |
| 5 | * Copyright 2020 Advanced Micro Devices, Inc. |
| 6 | * |
| 7 | * Permission is hereby granted, free of charge, to any person obtaining a |
| 8 | * copy of this software and associated documentation files (the "Software"), |
| 9 | * to deal in the Software without restriction, including without limitation |
| 10 | * the rights to use, copy, modify, merge, publish, distribute, sublicense, |
| 11 | * and/or sell copies of the Software, and to permit persons to whom the |
| 12 | * Software is furnished to do so, subject to the following conditions: |
| 13 | * |
| 14 | * The above copyright notice and this permission notice shall be included in |
| 15 | * all copies or substantial portions of the Software. |
| 16 | * |
| 17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
| 18 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
| 19 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL |
| 20 | * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR |
| 21 | * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, |
| 22 | * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR |
| 23 | * OTHER DEALINGS IN THE SOFTWARE. |
| 24 | * |
| 25 | * Authors: Christian König |
| 26 | */ |
| 27 | |
| 28 | #define pr_fmt(fmt) "[TTM DEVICE] " fmt |
| 29 | |
| 30 | #include <linux/debugfs.h> |
| 31 | #include <linux/export.h> |
| 32 | #include <linux/mm.h> |
| 33 | |
| 34 | #include <drm/ttm/ttm_allocation.h> |
| 35 | #include <drm/ttm/ttm_bo.h> |
| 36 | #include <drm/ttm/ttm_device.h> |
| 37 | #include <drm/ttm/ttm_tt.h> |
| 38 | #include <drm/ttm/ttm_placement.h> |
| 39 | |
| 40 | #include "ttm_module.h" |
| 41 | #include "ttm_bo_internal.h" |
| 42 | |
| 43 | /* |
| 44 | * ttm_global_mutex - protecting the global state |
| 45 | */ |
| 46 | static DEFINE_MUTEX(ttm_global_mutex); |
| 47 | static unsigned ttm_glob_use_count; |
| 48 | struct ttm_global ttm_glob; |
| 49 | EXPORT_SYMBOL(ttm_glob); |
| 50 | |
| 51 | struct dentry *ttm_debugfs_root; |
| 52 | |
| 53 | static void ttm_global_release(void) |
| 54 | { |
| 55 | struct ttm_global *glob = &ttm_glob; |
| 56 | |
| 57 | mutex_lock(&ttm_global_mutex); |
| 58 | if (--ttm_glob_use_count > 0) |
| 59 | goto out; |
| 60 | |
| 61 | ttm_pool_mgr_fini(); |
| 62 | debugfs_remove(dentry: ttm_debugfs_root); |
| 63 | |
| 64 | __free_page(glob->dummy_read_page); |
| 65 | memset(glob, 0, sizeof(*glob)); |
| 66 | out: |
| 67 | mutex_unlock(lock: &ttm_global_mutex); |
| 68 | } |
| 69 | |
| 70 | static int ttm_global_init(void) |
| 71 | { |
| 72 | struct ttm_global *glob = &ttm_glob; |
| 73 | unsigned long num_pages, num_dma32; |
| 74 | struct sysinfo si; |
| 75 | int ret = 0; |
| 76 | |
| 77 | mutex_lock(&ttm_global_mutex); |
| 78 | if (++ttm_glob_use_count > 1) |
| 79 | goto out; |
| 80 | |
| 81 | si_meminfo(val: &si); |
| 82 | |
| 83 | ttm_debugfs_root = debugfs_create_dir(name: "ttm" , NULL); |
| 84 | if (IS_ERR(ptr: ttm_debugfs_root)) { |
| 85 | ttm_debugfs_root = NULL; |
| 86 | } |
| 87 | |
| 88 | /* Limit the number of pages in the pool to about 50% of the total |
| 89 | * system memory. |
| 90 | */ |
| 91 | num_pages = ((u64)si.totalram * si.mem_unit) >> PAGE_SHIFT; |
| 92 | num_pages /= 2; |
| 93 | |
| 94 | /* But for DMA32 we limit ourself to only use 2GiB maximum. */ |
| 95 | num_dma32 = (u64)(si.totalram - si.totalhigh) * si.mem_unit |
| 96 | >> PAGE_SHIFT; |
| 97 | num_dma32 = min(num_dma32, 2UL << (30 - PAGE_SHIFT)); |
| 98 | |
| 99 | ttm_pool_mgr_init(num_pages); |
| 100 | ttm_tt_mgr_init(num_pages, num_dma32_pages: num_dma32); |
| 101 | |
| 102 | glob->dummy_read_page = alloc_page(__GFP_ZERO | GFP_DMA32 | |
| 103 | __GFP_NOWARN); |
| 104 | |
| 105 | /* Retry without GFP_DMA32 for platforms DMA32 is not available */ |
| 106 | if (unlikely(glob->dummy_read_page == NULL)) { |
| 107 | glob->dummy_read_page = alloc_page(__GFP_ZERO); |
| 108 | if (unlikely(glob->dummy_read_page == NULL)) { |
| 109 | ret = -ENOMEM; |
| 110 | goto out; |
| 111 | } |
| 112 | pr_warn("Using GFP_DMA32 fallback for dummy_read_page\n" ); |
| 113 | } |
| 114 | |
| 115 | INIT_LIST_HEAD(list: &glob->device_list); |
| 116 | atomic_set(v: &glob->bo_count, i: 0); |
| 117 | |
| 118 | debugfs_create_atomic_t(name: "buffer_objects" , mode: 0444, parent: ttm_debugfs_root, |
| 119 | value: &glob->bo_count); |
| 120 | out: |
| 121 | if (ret && ttm_debugfs_root) |
| 122 | debugfs_remove(dentry: ttm_debugfs_root); |
| 123 | if (ret) |
| 124 | --ttm_glob_use_count; |
| 125 | mutex_unlock(lock: &ttm_global_mutex); |
| 126 | return ret; |
| 127 | } |
| 128 | |
| 129 | /** |
| 130 | * ttm_device_prepare_hibernation - move GTT BOs to shmem for hibernation. |
| 131 | * |
| 132 | * @bdev: A pointer to a struct ttm_device to prepare hibernation for. |
| 133 | * |
| 134 | * Return: 0 on success, negative number on failure. |
| 135 | */ |
| 136 | int ttm_device_prepare_hibernation(struct ttm_device *bdev) |
| 137 | { |
| 138 | struct ttm_operation_ctx ctx = { |
| 139 | .interruptible = false, |
| 140 | .no_wait_gpu = false, |
| 141 | }; |
| 142 | int ret; |
| 143 | |
| 144 | do { |
| 145 | ret = ttm_device_swapout(bdev, ctx: &ctx, GFP_KERNEL); |
| 146 | } while (ret > 0); |
| 147 | return ret; |
| 148 | } |
| 149 | EXPORT_SYMBOL(ttm_device_prepare_hibernation); |
| 150 | |
| 151 | /* |
| 152 | * A buffer object shrink method that tries to swap out the first |
| 153 | * buffer object on the global::swap_lru list. |
| 154 | */ |
| 155 | int ttm_global_swapout(struct ttm_operation_ctx *ctx, gfp_t gfp_flags) |
| 156 | { |
| 157 | struct ttm_global *glob = &ttm_glob; |
| 158 | struct ttm_device *bdev; |
| 159 | int ret = 0; |
| 160 | |
| 161 | mutex_lock(&ttm_global_mutex); |
| 162 | list_for_each_entry(bdev, &glob->device_list, device_list) { |
| 163 | ret = ttm_device_swapout(bdev, ctx, gfp_flags); |
| 164 | if (ret > 0) { |
| 165 | list_move_tail(list: &bdev->device_list, head: &glob->device_list); |
| 166 | break; |
| 167 | } |
| 168 | } |
| 169 | mutex_unlock(lock: &ttm_global_mutex); |
| 170 | return ret; |
| 171 | } |
| 172 | |
| 173 | int ttm_device_swapout(struct ttm_device *bdev, struct ttm_operation_ctx *ctx, |
| 174 | gfp_t gfp_flags) |
| 175 | { |
| 176 | struct ttm_resource_manager *man; |
| 177 | unsigned i; |
| 178 | s64 lret; |
| 179 | |
| 180 | for (i = TTM_PL_SYSTEM; i < TTM_NUM_MEM_TYPES; ++i) { |
| 181 | man = ttm_manager_type(bdev, mem_type: i); |
| 182 | if (!man || !man->use_tt) |
| 183 | continue; |
| 184 | |
| 185 | lret = ttm_bo_swapout(bdev, ctx, man, gfp_flags, target: 1); |
| 186 | /* Can be both positive (num_pages) and negative (error) */ |
| 187 | if (lret) |
| 188 | return lret; |
| 189 | } |
| 190 | return 0; |
| 191 | } |
| 192 | EXPORT_SYMBOL(ttm_device_swapout); |
| 193 | |
| 194 | /** |
| 195 | * ttm_device_init |
| 196 | * |
| 197 | * @bdev: A pointer to a struct ttm_device to initialize. |
| 198 | * @funcs: Function table for the device. |
| 199 | * @dev: The core kernel device pointer for DMA mappings and allocations. |
| 200 | * @mapping: The address space to use for this bo. |
| 201 | * @vma_manager: A pointer to a vma manager. |
| 202 | * @alloc_flags: TTM_ALLOCATION_* flags. |
| 203 | * |
| 204 | * Initializes a struct ttm_device: |
| 205 | * Returns: |
| 206 | * !0: Failure. |
| 207 | */ |
| 208 | int ttm_device_init(struct ttm_device *bdev, const struct ttm_device_funcs *funcs, |
| 209 | struct device *dev, struct address_space *mapping, |
| 210 | struct drm_vma_offset_manager *vma_manager, |
| 211 | unsigned int alloc_flags) |
| 212 | { |
| 213 | struct ttm_global *glob = &ttm_glob; |
| 214 | int ret, nid; |
| 215 | |
| 216 | if (WARN_ON(vma_manager == NULL)) |
| 217 | return -EINVAL; |
| 218 | |
| 219 | ret = ttm_global_init(); |
| 220 | if (ret) |
| 221 | return ret; |
| 222 | |
| 223 | bdev->wq = alloc_workqueue("ttm" , |
| 224 | WQ_MEM_RECLAIM | WQ_HIGHPRI | WQ_UNBOUND, 16); |
| 225 | if (!bdev->wq) { |
| 226 | ttm_global_release(); |
| 227 | return -ENOMEM; |
| 228 | } |
| 229 | |
| 230 | bdev->alloc_flags = alloc_flags; |
| 231 | bdev->funcs = funcs; |
| 232 | |
| 233 | ttm_sys_man_init(bdev); |
| 234 | |
| 235 | if (dev) |
| 236 | nid = dev_to_node(dev); |
| 237 | else |
| 238 | nid = NUMA_NO_NODE; |
| 239 | |
| 240 | ttm_pool_init(pool: &bdev->pool, dev, nid, alloc_flags); |
| 241 | |
| 242 | bdev->vma_manager = vma_manager; |
| 243 | spin_lock_init(&bdev->lru_lock); |
| 244 | INIT_LIST_HEAD(list: &bdev->unevictable); |
| 245 | bdev->dev_mapping = mapping; |
| 246 | mutex_lock(&ttm_global_mutex); |
| 247 | list_add_tail(new: &bdev->device_list, head: &glob->device_list); |
| 248 | mutex_unlock(lock: &ttm_global_mutex); |
| 249 | |
| 250 | return 0; |
| 251 | } |
| 252 | EXPORT_SYMBOL(ttm_device_init); |
| 253 | |
| 254 | void ttm_device_fini(struct ttm_device *bdev) |
| 255 | { |
| 256 | struct ttm_resource_manager *man; |
| 257 | unsigned i; |
| 258 | |
| 259 | mutex_lock(&ttm_global_mutex); |
| 260 | list_del(entry: &bdev->device_list); |
| 261 | mutex_unlock(lock: &ttm_global_mutex); |
| 262 | |
| 263 | drain_workqueue(wq: bdev->wq); |
| 264 | destroy_workqueue(wq: bdev->wq); |
| 265 | |
| 266 | man = ttm_manager_type(bdev, TTM_PL_SYSTEM); |
| 267 | ttm_resource_manager_set_used(man, used: false); |
| 268 | ttm_set_driver_manager(bdev, TTM_PL_SYSTEM, NULL); |
| 269 | |
| 270 | spin_lock(lock: &bdev->lru_lock); |
| 271 | for (i = 0; i < TTM_MAX_BO_PRIORITY; ++i) |
| 272 | if (list_empty(head: &man->lru[0])) |
| 273 | pr_debug("Swap list %d was clean\n" , i); |
| 274 | spin_unlock(lock: &bdev->lru_lock); |
| 275 | |
| 276 | ttm_pool_fini(pool: &bdev->pool); |
| 277 | ttm_global_release(); |
| 278 | } |
| 279 | EXPORT_SYMBOL(ttm_device_fini); |
| 280 | |
| 281 | static void ttm_device_clear_lru_dma_mappings(struct ttm_device *bdev, |
| 282 | struct list_head *list) |
| 283 | { |
| 284 | struct ttm_resource *res; |
| 285 | |
| 286 | spin_lock(lock: &bdev->lru_lock); |
| 287 | while ((res = ttm_lru_first_res_or_null(head: list))) { |
| 288 | struct ttm_buffer_object *bo = res->bo; |
| 289 | |
| 290 | /* Take ref against racing releases once lru_lock is unlocked */ |
| 291 | if (!ttm_bo_get_unless_zero(bo)) |
| 292 | continue; |
| 293 | |
| 294 | list_del_init(entry: &bo->resource->lru.link); |
| 295 | spin_unlock(lock: &bdev->lru_lock); |
| 296 | |
| 297 | if (bo->ttm) |
| 298 | ttm_tt_unpopulate(bdev: bo->bdev, ttm: bo->ttm); |
| 299 | |
| 300 | ttm_bo_put(bo); |
| 301 | spin_lock(lock: &bdev->lru_lock); |
| 302 | } |
| 303 | spin_unlock(lock: &bdev->lru_lock); |
| 304 | } |
| 305 | |
| 306 | void ttm_device_clear_dma_mappings(struct ttm_device *bdev) |
| 307 | { |
| 308 | struct ttm_resource_manager *man; |
| 309 | unsigned int i, j; |
| 310 | |
| 311 | ttm_device_clear_lru_dma_mappings(bdev, list: &bdev->unevictable); |
| 312 | |
| 313 | for (i = TTM_PL_SYSTEM; i < TTM_NUM_MEM_TYPES; ++i) { |
| 314 | man = ttm_manager_type(bdev, mem_type: i); |
| 315 | if (!man || !man->use_tt) |
| 316 | continue; |
| 317 | |
| 318 | for (j = 0; j < TTM_MAX_BO_PRIORITY; ++j) |
| 319 | ttm_device_clear_lru_dma_mappings(bdev, list: &man->lru[j]); |
| 320 | } |
| 321 | } |
| 322 | EXPORT_SYMBOL(ttm_device_clear_dma_mappings); |
| 323 | |