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 /* 43 * Parse an input size of MiB to bytes. 44 */ 45 static bool 46 parse_size(const char *arg, size_t *resp) 47 { 48 size_t res; 49 50 errno = 0; 51 res = strtoul(arg, NULL, 0); 52 if (errno != 0) { 53 return (false); 54 } 55 56 *resp = (res * 1024 * 1024); 57 return (true); 58 } 59 60 static size_t 61 query_size(int fd) 62 { 63 struct vmm_resv_query data; 64 65 int res = ioctl(fd, VMM_RESV_QUERY, &data); 66 if (res != 0) { 67 err(EXIT_FAILURE, "Could not query reservoir sizing"); 68 } 69 70 return (data.vrq_free_sz + data.vrq_alloc_sz); 71 } 72 73 static void 74 do_add(int fd, size_t sz, size_t chunk) 75 { 76 const size_t cur = query_size(fd); 77 struct vmm_resv_target target = { 78 .vrt_target_sz = cur + sz, 79 .vrt_chunk_sz = MIN(chunk, sz), 80 }; 81 82 if (ioctl(fd, VMM_RESV_SET_TARGET, &target) != 0) { 83 err(EXIT_FAILURE, "Could not add %zu bytes to reservoir", sz); 84 } 85 } 86 87 static void 88 do_remove(int fd, size_t sz, size_t chunk) 89 { 90 const size_t cur = query_size(fd); 91 if (cur == 0) { 92 /* Reservoir is already empty */ 93 return; 94 } 95 96 const size_t clamped_sz = MIN(sz, cur); 97 struct vmm_resv_target target = { 98 .vrt_target_sz = cur - clamped_sz, 99 .vrt_chunk_sz = MIN(chunk, sz), 100 }; 101 102 if (ioctl(fd, VMM_RESV_SET_TARGET, &target) != 0) { 103 err(EXIT_FAILURE, "Could not remove %zu bytes from reservoir", 104 clamped_sz); 105 } 106 } 107 108 109 bool caught_siginfo = false; 110 111 static void 112 siginfo_handler(int sig, siginfo_t *sip, void *ucp) 113 { 114 caught_siginfo = true; 115 } 116 117 static void 118 do_set_target(int fd, size_t sz, size_t chunk) 119 { 120 struct vmm_resv_target target = { 121 .vrt_target_sz = sz, 122 .vrt_chunk_sz = chunk, 123 }; 124 125 struct sigaction sa = { 126 .sa_sigaction = siginfo_handler, 127 .sa_flags = SA_SIGINFO, 128 }; 129 if (sigaction(SIGINFO, &sa, NULL) != 0) { 130 err(EXIT_FAILURE, "Could not configure SIGINFO handler"); 131 } 132 133 do { 134 if (ioctl(fd, VMM_RESV_SET_TARGET, &target) != 0) { 135 if (errno != EINTR) { 136 err(EXIT_FAILURE, 137 "Could not set reservoir size to %zu bytes", 138 sz); 139 } 140 141 if (caught_siginfo) { 142 caught_siginfo = false; 143 (void) printf("Reservoir size: %zu MiB\n", 144 target.vrt_result_sz / (1024 * 1024)); 145 } 146 } 147 } while (target.vrt_result_sz != sz); 148 } 149 150 static void 151 do_query(int fd) 152 { 153 struct vmm_resv_query data; 154 int res; 155 156 res = ioctl(fd, VMM_RESV_QUERY, &data); 157 if (res != 0) { 158 perror("Could not query reservoir info"); 159 return; 160 } 161 162 printf("Free MiB:\t%zu\n" 163 "Allocated MiB:\t%zu\n" 164 "Transient Allocated MiB:\t%zu\n" 165 "Size limit MiB:\t%zu\n", 166 data.vrq_free_sz / (1024 * 1024), 167 data.vrq_alloc_sz / (1024 * 1024), 168 data.vrq_alloc_transient_sz / (1024 * 1024), 169 data.vrq_limit / (1024 * 1024)); 170 } 171 172 int 173 main(int argc, char *argv[]) 174 { 175 char c; 176 const char *opt_a = NULL, *opt_r = NULL, *opt_s = NULL; 177 bool opt_q = false; 178 int fd; 179 180 prog_name = argv[0]; 181 182 uint_t resize_opts = 0; 183 size_t chunk_sz = 0; 184 while ((c = getopt(argc, argv, "a:r:s:c:qh")) != -1) { 185 switch (c) { 186 case 'a': 187 if (opt_a == NULL) { 188 resize_opts++; 189 opt_a = optarg; 190 } 191 break; 192 case 'r': 193 if (opt_r == NULL) { 194 resize_opts++; 195 opt_r = optarg; 196 } 197 break; 198 case 's': 199 if (opt_s == NULL) { 200 resize_opts++; 201 opt_s = optarg; 202 } 203 break; 204 case 'c': 205 if (!parse_size(optarg, &chunk_sz)) { 206 warn("Invalid chunk size %s", optarg); 207 usage(EXIT_FAILURE); 208 } 209 break; 210 case 'q': 211 opt_q = true; 212 break; 213 case 'h': 214 usage(EXIT_SUCCESS); 215 break; 216 default: 217 usage(EXIT_FAILURE); 218 break; 219 } 220 } 221 222 if (optind < argc || 223 (resize_opts == 0 && !opt_q) || (resize_opts > 1)) { 224 usage(EXIT_FAILURE); 225 } 226 227 fd = open(VMM_CTL_DEV, O_EXCL | O_RDWR); 228 if (fd < 0) { 229 perror("Could not open vmmctl"); 230 usage(EXIT_FAILURE); 231 } 232 233 if (opt_a != NULL) { 234 size_t sz; 235 236 if (!parse_size(opt_a, &sz)) { 237 warn("Invalid size %s", opt_a); 238 usage(EXIT_FAILURE); 239 } 240 241 do_add(fd, sz, chunk_sz); 242 } else if (opt_r != NULL) { 243 size_t sz; 244 245 if (!parse_size(opt_r, &sz)) { 246 warn("Invalid size %s", opt_r); 247 usage(EXIT_FAILURE); 248 } 249 do_remove(fd, sz, chunk_sz); 250 } else if (opt_s != NULL) { 251 size_t sz; 252 253 if (!parse_size(opt_s, &sz)) { 254 warn("Invalid size %s", opt_s); 255 usage(EXIT_FAILURE); 256 } 257 do_set_target(fd, sz, chunk_sz); 258 } else if (opt_q) { 259 do_query(fd); 260 } 261 262 (void) close(fd); 263 return (0); 264 } 265