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
usage(int exitcode)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
parse_size(const char * arg,size_t * resp)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
query_size(int fd)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
do_add(int fd,size_t sz,size_t chunk)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
do_remove(int fd,size_t sz,size_t chunk)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
siginfo_handler(int sig,siginfo_t * sip,void * ucp)112 siginfo_handler(int sig, siginfo_t *sip, void *ucp)
113 {
114 caught_siginfo = true;
115 }
116
117 static void
do_set_target(int fd,size_t sz,size_t chunk)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
do_query(int fd)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
main(int argc,char * argv[])173 main(int argc, char *argv[])
174 {
175 int 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