1 /* 2 * This file and its contents are supplied under the terms of the 3 * Common Development and Distribution License ("CDDL"), version 1.0. 4 * You may only use this file in accordance with the terms of version 5 * 1.0 of the CDDL. 6 * 7 * A full copy of the text of the CDDL should have accompanied this 8 * source. A copy of the CDDL is also available via the Internet at 9 * http://www.illumos.org/license/CDDL. 10 * 11 * Copyright 2023 Oxide Computer Company 12 */ 13 14 #include <stdio.h> 15 #include <stdlib.h> 16 #include <errno.h> 17 #include <fcntl.h> 18 #include <unistd.h> 19 #include <err.h> 20 #include <assert.h> 21 #include <signal.h> 22 #include <sys/types.h> 23 24 #include <sys/vmm_dev.h> 25 26 const char *prog_name; 27 28 static void 29 usage(int exitcode) 30 { 31 assert(prog_name != NULL); 32 fprintf(stderr, 33 "Usage: %s [-a add] [-r remove] [-q]\n" 34 "\t-a <SZ> add SZ MiB to the reservoir\n" 35 "\t-r <SZ> remove SZ MiB from the reservoir\n" 36 "\t-s <SZ> set reservoir to SZ MiB, if possible\n" 37 "\t-c <SZ> use SZ MiB chunks when performing resize ops\n" 38 "\t-q query reservoir state\n", prog_name); 39 exit(exitcode); 40 } 41 42 static bool 43 parse_size(const char *arg, size_t *resp) 44 { 45 size_t res; 46 47 errno = 0; 48 res = strtoul(arg, NULL, 0); 49 if (errno != 0) { 50 return (false); 51 } 52 53 *resp = (res * 1024 * 1024); 54 return (true); 55 } 56 57 static size_t 58 query_size(int fd) 59 { 60 struct vmm_resv_query data; 61 62 int res = ioctl(fd, VMM_RESV_QUERY, &data); 63 if (res != 0) { 64 err(EXIT_FAILURE, "Could not query reservoir sizing"); 65 } 66 67 return (data.vrq_free_sz + data.vrq_alloc_sz); 68 } 69 70 static void 71 do_add(int fd, size_t sz, size_t chunk) 72 { 73 const size_t cur = query_size(fd); 74 struct vmm_resv_target target = { 75 .vrt_target_sz = cur + sz, 76 .vrt_chunk_sz = MIN(chunk, sz), 77 }; 78 79 if (ioctl(fd, VMM_RESV_SET_TARGET, &target) != 0) { 80 err(EXIT_FAILURE, "Could not add %zu bytes to reservoir", sz); 81 } 82 } 83 84 static void 85 do_remove(int fd, size_t sz, size_t chunk) 86 { 87 const size_t cur = query_size(fd); 88 if (cur == 0) { 89 /* Reservoir is already empty */ 90 return; 91 } 92 93 const size_t clamped_sz = MIN(sz, cur); 94 struct vmm_resv_target target = { 95 .vrt_target_sz = cur - clamped_sz, 96 .vrt_chunk_sz = MIN(chunk, sz), 97 }; 98 99 if (ioctl(fd, VMM_RESV_SET_TARGET, &target) != 0) { 100 err(EXIT_FAILURE, "Could not remove %zu bytes from reservoir", 101 clamped_sz); 102 } 103 } 104 105 106 bool caught_siginfo = false; 107 108 static void 109 siginfo_handler(int sig, siginfo_t *sip, void *ucp) 110 { 111 caught_siginfo = true; 112 } 113 114 static void 115 do_set_target(int fd, size_t sz, size_t chunk) 116 { 117 struct vmm_resv_target target = { 118 .vrt_target_sz = sz, 119 .vrt_chunk_sz = chunk, 120 }; 121 122 struct sigaction sa = { 123 .sa_sigaction = siginfo_handler, 124 .sa_flags = SA_SIGINFO, 125 }; 126 if (sigaction(SIGINFO, &sa, NULL) != 0) { 127 err(EXIT_FAILURE, "Could not configure SIGINFO handler"); 128 } 129 130 do { 131 if (ioctl(fd, VMM_RESV_SET_TARGET, &target) != 0) { 132 if (errno != EINTR) { 133 err(EXIT_FAILURE, 134 "Could not set reservoir size to %zu bytes", 135 sz); 136 } 137 138 if (caught_siginfo) { 139 caught_siginfo = false; 140 (void) printf("Reservoir size: %zu MiB\n", 141 target.vrt_result_sz / (1024 * 1024)); 142 } 143 } 144 } while (target.vrt_result_sz != sz); 145 } 146 147 static void 148 do_query(int fd) 149 { 150 struct vmm_resv_query data; 151 int res; 152 153 res = ioctl(fd, VMM_RESV_QUERY, &data); 154 if (res != 0) { 155 perror("Could not query reservoir info"); 156 return; 157 } 158 159 printf("Free KiB:\t%zu\n" 160 "Allocated KiB:\t%zu\n" 161 "Transient Allocated KiB:\t%zu\n" 162 "Size limit KiB:\t%zu\n", 163 data.vrq_free_sz / 1024, 164 data.vrq_alloc_sz / 1024, 165 data.vrq_alloc_transient_sz / 1024, 166 data.vrq_limit / 1024); 167 } 168 169 int 170 main(int argc, char *argv[]) 171 { 172 char c; 173 const char *opt_a = NULL, *opt_r = NULL, *opt_s = NULL; 174 bool opt_q = false; 175 int fd; 176 177 prog_name = argv[0]; 178 179 uint_t resize_opts = 0; 180 size_t chunk_sz = 0; 181 while ((c = getopt(argc, argv, "a:r:s:c:qh")) != -1) { 182 switch (c) { 183 case 'a': 184 if (opt_a == NULL) { 185 resize_opts++; 186 opt_a = optarg; 187 } 188 break; 189 case 'r': 190 if (opt_r == NULL) { 191 resize_opts++; 192 opt_r = optarg; 193 } 194 break; 195 case 's': 196 if (opt_s == NULL) { 197 resize_opts++; 198 opt_s = optarg; 199 } 200 break; 201 case 'c': 202 if (!parse_size(optarg, &chunk_sz)) { 203 warn("Invalid chunk size %s", optarg); 204 usage(EXIT_FAILURE); 205 } 206 break; 207 case 'q': 208 opt_q = true; 209 break; 210 case 'h': 211 usage(EXIT_SUCCESS); 212 break; 213 default: 214 usage(EXIT_FAILURE); 215 break; 216 } 217 } 218 219 if (optind < argc || 220 (resize_opts == 0 && !opt_q) || (resize_opts > 1)) { 221 usage(EXIT_FAILURE); 222 } 223 224 fd = open(VMM_CTL_DEV, O_EXCL | O_RDWR); 225 if (fd < 0) { 226 perror("Could not open vmmctl"); 227 usage(EXIT_FAILURE); 228 } 229 230 if (opt_a != NULL) { 231 size_t sz; 232 233 if (!parse_size(opt_a, &sz)) { 234 warn("Invalid size %s", opt_a); 235 usage(EXIT_FAILURE); 236 } 237 238 do_add(fd, sz, chunk_sz); 239 } else if (opt_r != NULL) { 240 size_t sz; 241 242 if (!parse_size(opt_r, &sz)) { 243 warn("Invalid size %s", opt_r); 244 usage(EXIT_FAILURE); 245 } 246 do_remove(fd, sz, chunk_sz); 247 } else if (opt_s != NULL) { 248 size_t sz; 249 250 if (!parse_size(opt_s, &sz)) { 251 warn("Invalid size %s", opt_s); 252 usage(EXIT_FAILURE); 253 } 254 do_set_target(fd, sz, chunk_sz); 255 } else if (opt_q) { 256 do_query(fd); 257 } 258 259 (void) close(fd); 260 return (0); 261 } 262