| 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_bo.h> | 
|---|
| 35 | #include <drm/ttm/ttm_device.h> | 
|---|
| 36 | #include <drm/ttm/ttm_tt.h> | 
|---|
| 37 | #include <drm/ttm/ttm_placement.h> | 
|---|
| 38 |  | 
|---|
| 39 | #include "ttm_module.h" | 
|---|
| 40 | #include "ttm_bo_internal.h" | 
|---|
| 41 |  | 
|---|
| 42 | /* | 
|---|
| 43 | * ttm_global_mutex - protecting the global state | 
|---|
| 44 | */ | 
|---|
| 45 | static DEFINE_MUTEX(ttm_global_mutex); | 
|---|
| 46 | static unsigned ttm_glob_use_count; | 
|---|
| 47 | struct ttm_global ttm_glob; | 
|---|
| 48 | EXPORT_SYMBOL(ttm_glob); | 
|---|
| 49 |  | 
|---|
| 50 | struct dentry *ttm_debugfs_root; | 
|---|
| 51 |  | 
|---|
| 52 | static void ttm_global_release(void) | 
|---|
| 53 | { | 
|---|
| 54 | struct ttm_global *glob = &ttm_glob; | 
|---|
| 55 |  | 
|---|
| 56 | mutex_lock(lock: &ttm_global_mutex); | 
|---|
| 57 | if (--ttm_glob_use_count > 0) | 
|---|
| 58 | goto out; | 
|---|
| 59 |  | 
|---|
| 60 | ttm_pool_mgr_fini(); | 
|---|
| 61 | debugfs_remove(dentry: ttm_debugfs_root); | 
|---|
| 62 |  | 
|---|
| 63 | __free_page(glob->dummy_read_page); | 
|---|
| 64 | memset(s: glob, c: 0, n: sizeof(*glob)); | 
|---|
| 65 | out: | 
|---|
| 66 | mutex_unlock(lock: &ttm_global_mutex); | 
|---|
| 67 | } | 
|---|
| 68 |  | 
|---|
| 69 | static int ttm_global_init(void) | 
|---|
| 70 | { | 
|---|
| 71 | struct ttm_global *glob = &ttm_glob; | 
|---|
| 72 | unsigned long num_pages, num_dma32; | 
|---|
| 73 | struct sysinfo si; | 
|---|
| 74 | int ret = 0; | 
|---|
| 75 |  | 
|---|
| 76 | mutex_lock(lock: &ttm_global_mutex); | 
|---|
| 77 | if (++ttm_glob_use_count > 1) | 
|---|
| 78 | goto out; | 
|---|
| 79 |  | 
|---|
| 80 | si_meminfo(val: &si); | 
|---|
| 81 |  | 
|---|
| 82 | ttm_debugfs_root = debugfs_create_dir(name: "ttm", NULL); | 
|---|
| 83 | if (IS_ERR(ptr: ttm_debugfs_root)) { | 
|---|
| 84 | ttm_debugfs_root = NULL; | 
|---|
| 85 | } | 
|---|
| 86 |  | 
|---|
| 87 | /* Limit the number of pages in the pool to about 50% of the total | 
|---|
| 88 | * system memory. | 
|---|
| 89 | */ | 
|---|
| 90 | num_pages = ((u64)si.totalram * si.mem_unit) >> PAGE_SHIFT; | 
|---|
| 91 | num_pages /= 2; | 
|---|
| 92 |  | 
|---|
| 93 | /* But for DMA32 we limit ourself to only use 2GiB maximum. */ | 
|---|
| 94 | num_dma32 = (u64)(si.totalram - si.totalhigh) * si.mem_unit | 
|---|
| 95 | >> PAGE_SHIFT; | 
|---|
| 96 | num_dma32 = min(num_dma32, 2UL << (30 - PAGE_SHIFT)); | 
|---|
| 97 |  | 
|---|
| 98 | ttm_pool_mgr_init(num_pages); | 
|---|
| 99 | ttm_tt_mgr_init(num_pages, num_dma32_pages: num_dma32); | 
|---|
| 100 |  | 
|---|
| 101 | glob->dummy_read_page = alloc_page(__GFP_ZERO | GFP_DMA32 | | 
|---|
| 102 | __GFP_NOWARN); | 
|---|
| 103 |  | 
|---|
| 104 | /* Retry without GFP_DMA32 for platforms DMA32 is not available */ | 
|---|
| 105 | if (unlikely(glob->dummy_read_page == NULL)) { | 
|---|
| 106 | glob->dummy_read_page = alloc_page(__GFP_ZERO); | 
|---|
| 107 | if (unlikely(glob->dummy_read_page == NULL)) { | 
|---|
| 108 | ret = -ENOMEM; | 
|---|
| 109 | goto out; | 
|---|
| 110 | } | 
|---|
| 111 | pr_warn( "Using GFP_DMA32 fallback for dummy_read_page\n"); | 
|---|
| 112 | } | 
|---|
| 113 |  | 
|---|
| 114 | INIT_LIST_HEAD(list: &glob->device_list); | 
|---|
| 115 | atomic_set(v: &glob->bo_count, i: 0); | 
|---|
| 116 |  | 
|---|
| 117 | debugfs_create_atomic_t(name: "buffer_objects", mode: 0444, parent: ttm_debugfs_root, | 
|---|
| 118 | value: &glob->bo_count); | 
|---|
| 119 | out: | 
|---|
| 120 | if (ret && ttm_debugfs_root) | 
|---|
| 121 | debugfs_remove(dentry: ttm_debugfs_root); | 
|---|
| 122 | if (ret) | 
|---|
| 123 | --ttm_glob_use_count; | 
|---|
| 124 | mutex_unlock(lock: &ttm_global_mutex); | 
|---|
| 125 | return ret; | 
|---|
| 126 | } | 
|---|
| 127 |  | 
|---|
| 128 | /** | 
|---|
| 129 | * ttm_device_prepare_hibernation - move GTT BOs to shmem for hibernation. | 
|---|
| 130 | * | 
|---|
| 131 | * @bdev: A pointer to a struct ttm_device to prepare hibernation for. | 
|---|
| 132 | * | 
|---|
| 133 | * Return: 0 on success, negative number on failure. | 
|---|
| 134 | */ | 
|---|
| 135 | int ttm_device_prepare_hibernation(struct ttm_device *bdev) | 
|---|
| 136 | { | 
|---|
| 137 | struct ttm_operation_ctx ctx = { | 
|---|
| 138 | .interruptible = false, | 
|---|
| 139 | .no_wait_gpu = false, | 
|---|
| 140 | }; | 
|---|
| 141 | int ret; | 
|---|
| 142 |  | 
|---|
| 143 | do { | 
|---|
| 144 | ret = ttm_device_swapout(bdev, ctx: &ctx, GFP_KERNEL); | 
|---|
| 145 | } while (ret > 0); | 
|---|
| 146 | return ret; | 
|---|
| 147 | } | 
|---|
| 148 | EXPORT_SYMBOL(ttm_device_prepare_hibernation); | 
|---|
| 149 |  | 
|---|
| 150 | /* | 
|---|
| 151 | * A buffer object shrink method that tries to swap out the first | 
|---|
| 152 | * buffer object on the global::swap_lru list. | 
|---|
| 153 | */ | 
|---|
| 154 | int ttm_global_swapout(struct ttm_operation_ctx *ctx, gfp_t gfp_flags) | 
|---|
| 155 | { | 
|---|
| 156 | struct ttm_global *glob = &ttm_glob; | 
|---|
| 157 | struct ttm_device *bdev; | 
|---|
| 158 | int ret = 0; | 
|---|
| 159 |  | 
|---|
| 160 | mutex_lock(lock: &ttm_global_mutex); | 
|---|
| 161 | list_for_each_entry(bdev, &glob->device_list, device_list) { | 
|---|
| 162 | ret = ttm_device_swapout(bdev, ctx, gfp_flags); | 
|---|
| 163 | if (ret > 0) { | 
|---|
| 164 | list_move_tail(list: &bdev->device_list, head: &glob->device_list); | 
|---|
| 165 | break; | 
|---|
| 166 | } | 
|---|
| 167 | } | 
|---|
| 168 | mutex_unlock(lock: &ttm_global_mutex); | 
|---|
| 169 | return ret; | 
|---|
| 170 | } | 
|---|
| 171 |  | 
|---|
| 172 | int ttm_device_swapout(struct ttm_device *bdev, struct ttm_operation_ctx *ctx, | 
|---|
| 173 | gfp_t gfp_flags) | 
|---|
| 174 | { | 
|---|
| 175 | struct ttm_resource_manager *man; | 
|---|
| 176 | unsigned i; | 
|---|
| 177 | s64 lret; | 
|---|
| 178 |  | 
|---|
| 179 | for (i = TTM_PL_SYSTEM; i < TTM_NUM_MEM_TYPES; ++i) { | 
|---|
| 180 | man = ttm_manager_type(bdev, mem_type: i); | 
|---|
| 181 | if (!man || !man->use_tt) | 
|---|
| 182 | continue; | 
|---|
| 183 |  | 
|---|
| 184 | lret = ttm_bo_swapout(bdev, ctx, man, gfp_flags, target: 1); | 
|---|
| 185 | /* Can be both positive (num_pages) and negative (error) */ | 
|---|
| 186 | if (lret) | 
|---|
| 187 | return lret; | 
|---|
| 188 | } | 
|---|
| 189 | return 0; | 
|---|
| 190 | } | 
|---|
| 191 | EXPORT_SYMBOL(ttm_device_swapout); | 
|---|
| 192 |  | 
|---|
| 193 | /** | 
|---|
| 194 | * ttm_device_init | 
|---|
| 195 | * | 
|---|
| 196 | * @bdev: A pointer to a struct ttm_device to initialize. | 
|---|
| 197 | * @funcs: Function table for the device. | 
|---|
| 198 | * @dev: The core kernel device pointer for DMA mappings and allocations. | 
|---|
| 199 | * @mapping: The address space to use for this bo. | 
|---|
| 200 | * @vma_manager: A pointer to a vma manager. | 
|---|
| 201 | * @use_dma_alloc: If coherent DMA allocation API should be used. | 
|---|
| 202 | * @use_dma32: If we should use GFP_DMA32 for device memory allocations. | 
|---|
| 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 | bool use_dma_alloc, bool use_dma32) | 
|---|
| 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->funcs = funcs; | 
|---|
| 231 |  | 
|---|
| 232 | ttm_sys_man_init(bdev); | 
|---|
| 233 |  | 
|---|
| 234 | if (dev) | 
|---|
| 235 | nid = dev_to_node(dev); | 
|---|
| 236 | else | 
|---|
| 237 | nid = NUMA_NO_NODE; | 
|---|
| 238 |  | 
|---|
| 239 | ttm_pool_init(pool: &bdev->pool, dev, nid, use_dma_alloc, use_dma32); | 
|---|
| 240 |  | 
|---|
| 241 | bdev->vma_manager = vma_manager; | 
|---|
| 242 | spin_lock_init(&bdev->lru_lock); | 
|---|
| 243 | INIT_LIST_HEAD(list: &bdev->unevictable); | 
|---|
| 244 | bdev->dev_mapping = mapping; | 
|---|
| 245 | mutex_lock(lock: &ttm_global_mutex); | 
|---|
| 246 | list_add_tail(new: &bdev->device_list, head: &glob->device_list); | 
|---|
| 247 | mutex_unlock(lock: &ttm_global_mutex); | 
|---|
| 248 |  | 
|---|
| 249 | return 0; | 
|---|
| 250 | } | 
|---|
| 251 | EXPORT_SYMBOL(ttm_device_init); | 
|---|
| 252 |  | 
|---|
| 253 | void ttm_device_fini(struct ttm_device *bdev) | 
|---|
| 254 | { | 
|---|
| 255 | struct ttm_resource_manager *man; | 
|---|
| 256 | unsigned i; | 
|---|
| 257 |  | 
|---|
| 258 | mutex_lock(lock: &ttm_global_mutex); | 
|---|
| 259 | list_del(entry: &bdev->device_list); | 
|---|
| 260 | mutex_unlock(lock: &ttm_global_mutex); | 
|---|
| 261 |  | 
|---|
| 262 | drain_workqueue(wq: bdev->wq); | 
|---|
| 263 | destroy_workqueue(wq: bdev->wq); | 
|---|
| 264 |  | 
|---|
| 265 | man = ttm_manager_type(bdev, TTM_PL_SYSTEM); | 
|---|
| 266 | ttm_resource_manager_set_used(man, used: false); | 
|---|
| 267 | ttm_set_driver_manager(bdev, TTM_PL_SYSTEM, NULL); | 
|---|
| 268 |  | 
|---|
| 269 | spin_lock(lock: &bdev->lru_lock); | 
|---|
| 270 | for (i = 0; i < TTM_MAX_BO_PRIORITY; ++i) | 
|---|
| 271 | if (list_empty(head: &man->lru[0])) | 
|---|
| 272 | pr_debug( "Swap list %d was clean\n", i); | 
|---|
| 273 | spin_unlock(lock: &bdev->lru_lock); | 
|---|
| 274 |  | 
|---|
| 275 | ttm_pool_fini(pool: &bdev->pool); | 
|---|
| 276 | ttm_global_release(); | 
|---|
| 277 | } | 
|---|
| 278 | EXPORT_SYMBOL(ttm_device_fini); | 
|---|
| 279 |  | 
|---|
| 280 | static void ttm_device_clear_lru_dma_mappings(struct ttm_device *bdev, | 
|---|
| 281 | struct list_head *list) | 
|---|
| 282 | { | 
|---|
| 283 | struct ttm_resource *res; | 
|---|
| 284 |  | 
|---|
| 285 | spin_lock(lock: &bdev->lru_lock); | 
|---|
| 286 | while ((res = ttm_lru_first_res_or_null(head: list))) { | 
|---|
| 287 | struct ttm_buffer_object *bo = res->bo; | 
|---|
| 288 |  | 
|---|
| 289 | /* Take ref against racing releases once lru_lock is unlocked */ | 
|---|
| 290 | if (!ttm_bo_get_unless_zero(bo)) | 
|---|
| 291 | continue; | 
|---|
| 292 |  | 
|---|
| 293 | list_del_init(entry: &bo->resource->lru.link); | 
|---|
| 294 | spin_unlock(lock: &bdev->lru_lock); | 
|---|
| 295 |  | 
|---|
| 296 | if (bo->ttm) | 
|---|
| 297 | ttm_tt_unpopulate(bdev: bo->bdev, ttm: bo->ttm); | 
|---|
| 298 |  | 
|---|
| 299 | ttm_bo_put(bo); | 
|---|
| 300 | spin_lock(lock: &bdev->lru_lock); | 
|---|
| 301 | } | 
|---|
| 302 | spin_unlock(lock: &bdev->lru_lock); | 
|---|
| 303 | } | 
|---|
| 304 |  | 
|---|
| 305 | void ttm_device_clear_dma_mappings(struct ttm_device *bdev) | 
|---|
| 306 | { | 
|---|
| 307 | struct ttm_resource_manager *man; | 
|---|
| 308 | unsigned int i, j; | 
|---|
| 309 |  | 
|---|
| 310 | ttm_device_clear_lru_dma_mappings(bdev, list: &bdev->unevictable); | 
|---|
| 311 |  | 
|---|
| 312 | for (i = TTM_PL_SYSTEM; i < TTM_NUM_MEM_TYPES; ++i) { | 
|---|
| 313 | man = ttm_manager_type(bdev, mem_type: i); | 
|---|
| 314 | if (!man || !man->use_tt) | 
|---|
| 315 | continue; | 
|---|
| 316 |  | 
|---|
| 317 | for (j = 0; j < TTM_MAX_BO_PRIORITY; ++j) | 
|---|
| 318 | ttm_device_clear_lru_dma_mappings(bdev, list: &man->lru[j]); | 
|---|
| 319 | } | 
|---|
| 320 | } | 
|---|
| 321 | EXPORT_SYMBOL(ttm_device_clear_dma_mappings); | 
|---|
| 322 |  | 
|---|