Boris Brezillon | e2e2b4c | 2018-02-07 10:09:10 +0100 | [diff] [blame] | 1 | /* |
| 2 | * Copyright © 2017 Broadcom |
| 3 | * |
| 4 | * Permission is hereby granted, free of charge, to any person obtaining a |
| 5 | * copy of this software and associated documentation files (the "Software"), |
| 6 | * to deal in the Software without restriction, including without limitation |
| 7 | * the rights to use, copy, modify, merge, publish, distribute, sublicense, |
| 8 | * and/or sell copies of the Software, and to permit persons to whom the |
| 9 | * Software is furnished to do so, subject to the following conditions: |
| 10 | * |
| 11 | * The above copyright notice and this permission notice (including the next |
| 12 | * paragraph) shall be included in all copies or substantial portions of the |
| 13 | * Software. |
| 14 | * |
| 15 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
| 16 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
| 17 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL |
| 18 | * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
| 19 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING |
| 20 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS |
| 21 | * IN THE SOFTWARE. |
| 22 | */ |
| 23 | |
| 24 | #include "igt.h" |
| 25 | #include "igt_vc4.h" |
| 26 | #include <unistd.h> |
| 27 | #include <signal.h> |
| 28 | #include <stdlib.h> |
| 29 | #include <stdio.h> |
| 30 | #include <string.h> |
| 31 | #include <fcntl.h> |
| 32 | #include <inttypes.h> |
| 33 | #include <errno.h> |
| 34 | #include <sys/stat.h> |
| 35 | #include <sys/ioctl.h> |
| 36 | #include "vc4_drm.h" |
| 37 | |
| 38 | struct igt_vc4_bo { |
| 39 | struct igt_list node; |
| 40 | int handle; |
| 41 | void *map; |
| 42 | size_t size; |
| 43 | }; |
| 44 | |
| 45 | static jmp_buf jmp; |
| 46 | |
| 47 | static void __attribute__((noreturn)) sigtrap(int sig) |
| 48 | { |
| 49 | longjmp(jmp, sig); |
| 50 | } |
| 51 | |
| 52 | static void igt_vc4_alloc_mmap_max_bo(int fd, struct igt_list *list, |
| 53 | size_t size) |
| 54 | { |
| 55 | struct igt_vc4_bo *bo; |
| 56 | struct drm_vc4_create_bo create = { |
| 57 | .size = size, |
| 58 | }; |
| 59 | |
| 60 | while (true) { |
| 61 | if (igt_ioctl(fd, DRM_IOCTL_VC4_CREATE_BO, &create)) |
| 62 | break; |
| 63 | |
| 64 | bo = malloc(sizeof(*bo)); |
| 65 | igt_assert(bo); |
| 66 | bo->handle = create.handle; |
| 67 | bo->size = create.size; |
| 68 | bo->map = igt_vc4_mmap_bo(fd, bo->handle, bo->size, |
| 69 | PROT_READ | PROT_WRITE); |
| 70 | igt_list_add_tail(&bo->node, list); |
| 71 | } |
| 72 | } |
| 73 | |
| 74 | static void igt_vc4_unmap_free_bo_pool(int fd, struct igt_list *list) |
| 75 | { |
| 76 | struct igt_vc4_bo *bo; |
| 77 | |
| 78 | while (!igt_list_empty(list)) { |
| 79 | bo = igt_list_first_entry(list, bo, node); |
| 80 | igt_assert(bo); |
| 81 | igt_list_del(&bo->node); |
| 82 | munmap(bo->map, bo->size); |
| 83 | gem_close(fd, bo->handle); |
| 84 | free(bo); |
| 85 | } |
| 86 | } |
| 87 | |
| 88 | static void igt_vc4_trigger_purge(int fd) |
| 89 | { |
| 90 | struct igt_list list; |
| 91 | |
| 92 | igt_list_init(&list); |
| 93 | |
| 94 | /* Try to allocate as much as we can to trigger a purge. */ |
| 95 | igt_vc4_alloc_mmap_max_bo(fd, &list, 64 * 1024); |
| 96 | igt_assert(!igt_list_empty(&list)); |
| 97 | igt_vc4_unmap_free_bo_pool(fd, &list); |
| 98 | } |
| 99 | |
| 100 | static void igt_vc4_purgeable_subtest_prepare(int fd, struct igt_list *list) |
| 101 | { |
| 102 | igt_vc4_unmap_free_bo_pool(fd, list); |
| 103 | igt_vc4_alloc_mmap_max_bo(fd, list, 64 * 1024); |
| 104 | igt_assert(!igt_list_empty(list)); |
| 105 | } |
| 106 | |
| 107 | igt_main |
| 108 | { |
| 109 | struct igt_vc4_bo *bo; |
| 110 | struct igt_list list; |
| 111 | uint32_t *map; |
| 112 | int fd, ret; |
| 113 | |
| 114 | igt_fixture { |
| 115 | uint64_t val = 0; |
| 116 | |
| 117 | fd = drm_open_driver(DRIVER_VC4); |
| 118 | igt_vc4_get_param(fd, DRM_VC4_PARAM_SUPPORTS_MADVISE, &val); |
| 119 | igt_require(val); |
| 120 | igt_list_init(&list); |
| 121 | } |
| 122 | |
| 123 | igt_subtest("mark-willneed") { |
| 124 | igt_vc4_purgeable_subtest_prepare(fd, &list); |
| 125 | igt_list_for_each(bo, &list, node) |
| 126 | igt_assert(igt_vc4_purgeable_bo(fd, bo->handle, |
| 127 | false)); |
| 128 | } |
| 129 | |
| 130 | igt_subtest("mark-purgeable") { |
| 131 | igt_vc4_purgeable_subtest_prepare(fd, &list); |
| 132 | igt_list_for_each(bo, &list, node) |
| 133 | igt_vc4_purgeable_bo(fd, bo->handle, true); |
| 134 | |
| 135 | igt_list_for_each(bo, &list, node) |
| 136 | igt_vc4_purgeable_bo(fd, bo->handle, false); |
| 137 | } |
| 138 | |
| 139 | igt_subtest("mark-purgeable-twice") { |
| 140 | igt_vc4_purgeable_subtest_prepare(fd, &list); |
| 141 | bo = igt_list_first_entry(&list, bo, node); |
| 142 | igt_vc4_purgeable_bo(fd, bo->handle, true); |
| 143 | igt_vc4_purgeable_bo(fd, bo->handle, true); |
| 144 | igt_vc4_purgeable_bo(fd, bo->handle, false); |
| 145 | } |
| 146 | |
| 147 | igt_subtest("mark-unpurgeable-twice") { |
| 148 | igt_vc4_purgeable_subtest_prepare(fd, &list); |
| 149 | bo = igt_list_first_entry(&list, bo, node); |
| 150 | igt_vc4_purgeable_bo(fd, bo->handle, true); |
| 151 | igt_vc4_purgeable_bo(fd, bo->handle, false); |
| 152 | igt_vc4_purgeable_bo(fd, bo->handle, false); |
| 153 | } |
| 154 | |
| 155 | igt_subtest("access-purgeable-bo-mem") { |
| 156 | igt_vc4_purgeable_subtest_prepare(fd, &list); |
| 157 | bo = igt_list_first_entry(&list, bo, node); |
| 158 | map = (uint32_t *)bo->map; |
| 159 | |
| 160 | /* Mark the BO as purgeable, but do not try to allocate a new |
| 161 | * BO. This should leave the BO in a non-purged state unless |
| 162 | * someone else tries to allocated a new BO. |
| 163 | */ |
| 164 | igt_vc4_purgeable_bo(fd, bo->handle, true); |
| 165 | |
| 166 | /* Accessing a purgeable BO should generate a SIGBUS event if |
| 167 | * the BO has been purged by the system in the meantime. |
| 168 | */ |
| 169 | signal(SIGSEGV, sigtrap); |
| 170 | signal(SIGBUS, sigtrap); |
| 171 | ret = setjmp(jmp); |
| 172 | if (!ret) |
| 173 | *map = 0xdeadbeef; |
| 174 | else |
| 175 | igt_assert(ret == SIGBUS); |
| 176 | signal(SIGBUS, SIG_DFL); |
| 177 | signal(SIGSEGV, SIG_DFL); |
| 178 | } |
| 179 | |
| 180 | igt_subtest("access-purged-bo-mem") { |
| 181 | igt_vc4_purgeable_subtest_prepare(fd, &list); |
| 182 | |
| 183 | /* Mark the first BO in our list as purgeable and try to |
| 184 | * allocate a new one. This should trigger a purge and render |
| 185 | * the first BO inaccessible. |
| 186 | */ |
| 187 | bo = igt_list_first_entry(&list, bo, node); |
| 188 | map = (uint32_t *)bo->map; |
| 189 | igt_vc4_purgeable_bo(fd, bo->handle, true); |
| 190 | |
| 191 | /* Trigger a purge. */ |
| 192 | igt_vc4_trigger_purge(fd); |
| 193 | |
| 194 | /* Accessing a purged BO should generate a SIGBUS event. */ |
| 195 | signal(SIGSEGV, sigtrap); |
| 196 | signal(SIGBUS, sigtrap); |
| 197 | ret = setjmp(jmp); |
| 198 | if (!ret) |
| 199 | *map = 0; |
| 200 | igt_assert(ret == SIGBUS); |
| 201 | signal(SIGBUS, SIG_DFL); |
| 202 | signal(SIGSEGV, SIG_DFL); |
| 203 | igt_vc4_purgeable_bo(fd, bo->handle, false); |
| 204 | } |
| 205 | |
| 206 | igt_subtest("mark-unpurgeable-check-retained") { |
| 207 | igt_vc4_purgeable_subtest_prepare(fd, &list); |
| 208 | igt_list_for_each(bo, &list, node) { |
| 209 | map = (uint32_t *)bo->map; |
| 210 | *map = 0xdeadbeef; |
| 211 | igt_vc4_purgeable_bo(fd, bo->handle, true); |
| 212 | } |
| 213 | |
| 214 | igt_list_for_each(bo, &list, node) { |
| 215 | map = (uint32_t *)bo->map; |
| 216 | if (igt_vc4_purgeable_bo(fd, bo->handle, false)) |
| 217 | igt_assert(*map == 0xdeadbeef); |
| 218 | } |
| 219 | } |
| 220 | |
| 221 | igt_subtest("mark-unpurgeable-purged") { |
| 222 | igt_vc4_purgeable_subtest_prepare(fd, &list); |
| 223 | |
| 224 | igt_list_for_each(bo, &list, node) |
| 225 | igt_vc4_purgeable_bo(fd, bo->handle, true); |
| 226 | |
| 227 | /* Trigger a purge. */ |
| 228 | igt_vc4_trigger_purge(fd); |
| 229 | |
| 230 | bo = igt_list_first_entry(&list, bo, node); |
| 231 | map = (uint32_t *)bo->map; |
| 232 | |
| 233 | igt_assert(!igt_vc4_purgeable_bo(fd, bo->handle, false)); |
| 234 | |
| 235 | /* Purged BOs are unusable and any access to their |
| 236 | * mmap-ed region should trigger a SIGBUS. |
| 237 | */ |
| 238 | signal(SIGSEGV, sigtrap); |
| 239 | signal(SIGBUS, sigtrap); |
| 240 | ret = setjmp(jmp); |
| 241 | if (!ret) |
| 242 | *map = 0; |
| 243 | igt_assert(ret == SIGBUS); |
| 244 | signal(SIGBUS, SIG_DFL); |
| 245 | signal(SIGSEGV, SIG_DFL); |
| 246 | } |
| 247 | |
| 248 | igt_subtest("free-purged-bo") { |
| 249 | igt_vc4_purgeable_subtest_prepare(fd, &list); |
| 250 | bo = igt_list_first_entry(&list, bo, node); |
| 251 | igt_vc4_purgeable_bo(fd, bo->handle, true); |
| 252 | |
| 253 | /* Trigger a purge. */ |
| 254 | igt_vc4_trigger_purge(fd); |
| 255 | |
| 256 | igt_list_del(&bo->node); |
| 257 | munmap(bo->map, bo->size); |
| 258 | gem_close(fd, bo->handle); |
| 259 | free(bo); |
| 260 | } |
| 261 | |
| 262 | igt_fixture |
| 263 | close(fd); |
| 264 | } |