xref: /illumos-gate/usr/src/cmd/bhyve/fwctl.c (revision 5d9d9091f564c198a760790b0bfa72c44e17912b)
1 /*-
2  * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
3  *
4  * Copyright (c) 2015  Peter Grehan <grehan@freebsd.org>
5  * All rights reserved.
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  * 1. Redistributions of source code must retain the above copyright
11  *    notice, this list of conditions and the following disclaimer.
12  * 2. Redistributions in binary form must reproduce the above copyright
13  *    notice, this list of conditions and the following disclaimer in the
14  *    documentation and/or other materials provided with the distribution.
15  *
16  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND
17  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
20  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
21  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
22  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
23  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
24  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
25  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
26  * SUCH DAMAGE.
27  *
28  * $FreeBSD$
29  */
30 
31 /*
32  * Guest firmware interface. Uses i/o ports x510/x511 as Qemu does,
33  * but with a request/response messaging protocol.
34  */
35 #include <sys/cdefs.h>
36 __FBSDID("$FreeBSD$");
37 
38 #include <sys/param.h>
39 #include <sys/types.h>
40 #include <sys/errno.h>
41 #include <sys/uio.h>
42 
43 #include <assert.h>
44 #include <stdio.h>
45 #include <stdlib.h>
46 #include <string.h>
47 
48 #include "bhyverun.h"
49 #include "inout.h"
50 #include "fwctl.h"
51 
52 /*
53  * Messaging protocol base operations
54  */
55 #define	OP_NULL		1
56 #define	OP_ECHO		2
57 #define	OP_GET		3
58 #define	OP_GET_LEN	4
59 #define	OP_SET		5
60 #define	OP_MAX		OP_SET
61 
62 /* I/O ports */
63 #define	FWCTL_OUT	0x510
64 #define	FWCTL_IN	0x511
65 
66 /*
67  * Back-end state-machine
68  */
69 static enum state {
70 	DORMANT,
71 	IDENT_WAIT,
72 	IDENT_SEND,
73 	REQ,
74 	RESP
75 } be_state = DORMANT;
76 
77 static uint8_t sig[] = { 'B', 'H', 'Y', 'V' };
78 static u_int ident_idx;
79 
80 struct op_info {
81 	int op;
82 	int  (*op_start)(uint32_t len);
83 	void (*op_data)(uint32_t data, uint32_t len);
84 	int  (*op_result)(struct iovec **data);
85 	void (*op_done)(struct iovec *data);
86 };
87 static struct op_info *ops[OP_MAX+1];
88 
89 /* Return 0-padded uint32_t */
90 static uint32_t
91 fwctl_send_rest(uint8_t *data, size_t len)
92 {
93 	union {
94 		uint8_t c[4];
95 		uint32_t w;
96 	} u;
97 	size_t i;
98 
99 	u.w = 0;
100 	for (i = 0; i < len; i++)
101 		u.c[i] = *data++;
102 
103 	return (u.w);
104 }
105 
106 /*
107  * error op dummy proto - drop all data sent and return an error
108 */
109 static int errop_code;
110 
111 static void
112 errop_set(int err)
113 {
114 
115 	errop_code = err;
116 }
117 
118 static int
119 errop_start(uint32_t len __unused)
120 {
121 	errop_code = ENOENT;
122 
123 	/* accept any length */
124 	return (errop_code);
125 }
126 
127 static void
128 errop_data(uint32_t data __unused, uint32_t len __unused)
129 {
130 
131 	/* ignore */
132 }
133 
134 static int
135 errop_result(struct iovec **data)
136 {
137 
138 	/* no data to send back; always successful */
139 	*data = NULL;
140 	return (errop_code);
141 }
142 
143 static void
144 errop_done(struct iovec *data __unused)
145 {
146 
147 	/* assert data is NULL */
148 }
149 
150 static struct op_info errop_info = {
151 	.op_start  = errop_start,
152 	.op_data   = errop_data,
153 	.op_result = errop_result,
154 	.op_done   = errop_done
155 };
156 
157 /* OID search */
158 SET_DECLARE(ctl_set, struct ctl);
159 
160 CTL_NODE("hw.ncpu", &guest_ncpus, sizeof(guest_ncpus));
161 
162 static struct ctl *
163 ctl_locate(const char *str, int maxlen)
164 {
165 	struct ctl *cp, **cpp;
166 
167 	SET_FOREACH(cpp, ctl_set)  {
168 		cp = *cpp;
169 		if (!strncmp(str, cp->c_oid, maxlen))
170 			return (cp);
171 	}
172 
173 	return (NULL);
174 }
175 
176 /* uefi-sysctl get-len */
177 #define FGET_STRSZ	80
178 static struct iovec fget_biov[2];
179 static char fget_str[FGET_STRSZ];
180 static struct {
181 	size_t f_sz;
182 	uint32_t f_data[1024];
183 } fget_buf;
184 static int fget_cnt;
185 static size_t fget_size;
186 
187 static int
188 fget_start(uint32_t len)
189 {
190 
191 	if (len > FGET_STRSZ)
192 		return(E2BIG);
193 
194 	fget_cnt = 0;
195 
196 	return (0);
197 }
198 
199 static void
200 fget_data(uint32_t data, uint32_t len __unused)
201 {
202 
203 	memcpy(&fget_str[fget_cnt], &data, sizeof(data));
204 	fget_cnt += sizeof(uint32_t);
205 }
206 
207 static int
208 fget_result(struct iovec **data, int val)
209 {
210 	struct ctl *cp;
211 	int err;
212 
213 	err = 0;
214 
215 	/* Locate the OID */
216 	cp = ctl_locate(fget_str, fget_cnt);
217 	if (cp == NULL) {
218 		*data = NULL;
219 		err = ENOENT;
220 	} else {
221 		if (val) {
222 			/* For now, copy the len/data into a buffer */
223 			memset(&fget_buf, 0, sizeof(fget_buf));
224 			fget_buf.f_sz = cp->c_len;
225 			memcpy(fget_buf.f_data, cp->c_data, cp->c_len);
226 			fget_biov[0].iov_base = (char *)&fget_buf;
227 			fget_biov[0].iov_len  = sizeof(fget_buf.f_sz) +
228 				cp->c_len;
229 		} else {
230 			fget_size = cp->c_len;
231 			fget_biov[0].iov_base = (char *)&fget_size;
232 			fget_biov[0].iov_len  = sizeof(fget_size);
233 		}
234 
235 		fget_biov[1].iov_base = NULL;
236 		fget_biov[1].iov_len  = 0;
237 		*data = fget_biov;
238 	}
239 
240 	return (err);
241 }
242 
243 static void
244 fget_done(struct iovec *data __unused)
245 {
246 
247 	/* nothing needs to be freed */
248 }
249 
250 static int
251 fget_len_result(struct iovec **data)
252 {
253 	return (fget_result(data, 0));
254 }
255 
256 static int
257 fget_val_result(struct iovec **data)
258 {
259 	return (fget_result(data, 1));
260 }
261 
262 static struct op_info fgetlen_info = {
263 	.op_start  = fget_start,
264 	.op_data   = fget_data,
265 	.op_result = fget_len_result,
266 	.op_done   = fget_done
267 };
268 
269 static struct op_info fgetval_info = {
270 	.op_start  = fget_start,
271 	.op_data   = fget_data,
272 	.op_result = fget_val_result,
273 	.op_done   = fget_done
274 };
275 
276 static struct req_info {
277 	int      req_error;
278 	u_int    req_count;
279 	uint32_t req_size;
280 	uint32_t req_type;
281 	uint32_t req_txid;
282 	struct op_info *req_op;
283 	int	 resp_error;
284 	int	 resp_count;
285 	size_t	 resp_size;
286 	size_t	 resp_off;
287 	struct iovec *resp_biov;
288 } rinfo;
289 
290 static void
291 fwctl_response_done(void)
292 {
293 
294 	(*rinfo.req_op->op_done)(rinfo.resp_biov);
295 
296 	/* reinit the req data struct */
297 	memset(&rinfo, 0, sizeof(rinfo));
298 }
299 
300 static void
301 fwctl_request_done(void)
302 {
303 
304 	rinfo.resp_error = (*rinfo.req_op->op_result)(&rinfo.resp_biov);
305 
306 	/* XXX only a single vector supported at the moment */
307 	rinfo.resp_off = 0;
308 	if (rinfo.resp_biov == NULL) {
309 		rinfo.resp_size = 0;
310 	} else {
311 		rinfo.resp_size = rinfo.resp_biov[0].iov_len;
312 	}
313 }
314 
315 static int
316 fwctl_request_start(void)
317 {
318 	int err;
319 
320 	/* Data size doesn't include header */
321 	rinfo.req_size -= 12;
322 
323 	rinfo.req_op = &errop_info;
324 	if (rinfo.req_type <= OP_MAX && ops[rinfo.req_type] != NULL)
325 		rinfo.req_op = ops[rinfo.req_type];
326 
327 	err = (*rinfo.req_op->op_start)(rinfo.req_size);
328 
329 	if (err) {
330 		errop_set(err);
331 		rinfo.req_op = &errop_info;
332 	}
333 
334 	/* Catch case of zero-length message here */
335 	if (rinfo.req_size == 0) {
336 		fwctl_request_done();
337 		return (1);
338 	}
339 
340 	return (0);
341 }
342 
343 static int
344 fwctl_request_data(uint32_t value)
345 {
346 
347 	/* Make sure remaining size is >= 0 */
348 	if (rinfo.req_size <= sizeof(uint32_t))
349 		rinfo.req_size = 0;
350 	else
351 		rinfo.req_size -= sizeof(uint32_t);
352 
353 	(*rinfo.req_op->op_data)(value, rinfo.req_size);
354 
355 	if (rinfo.req_size < sizeof(uint32_t)) {
356 		fwctl_request_done();
357 		return (1);
358 	}
359 
360 	return (0);
361 }
362 
363 static int
364 fwctl_request(uint32_t value)
365 {
366 
367 	int ret;
368 
369 	ret = 0;
370 
371 	switch (rinfo.req_count) {
372 	case 0:
373 		/* Verify size */
374 		if (value < 12) {
375 			printf("msg size error");
376 			exit(4);
377 		}
378 		rinfo.req_size = value;
379 		rinfo.req_count = 1;
380 		break;
381 	case 1:
382 		rinfo.req_type = value;
383 		rinfo.req_count++;
384 		break;
385 	case 2:
386 		rinfo.req_txid = value;
387 		rinfo.req_count++;
388 		ret = fwctl_request_start();
389 		break;
390 	default:
391 		ret = fwctl_request_data(value);
392 		break;
393 	}
394 
395 	return (ret);
396 }
397 
398 static int
399 fwctl_response(uint32_t *retval)
400 {
401 	uint8_t *dp;
402 	ssize_t remlen;
403 
404 	switch(rinfo.resp_count) {
405 	case 0:
406 		/* 4 x u32 header len + data */
407 		*retval = 4*sizeof(uint32_t) +
408 		    roundup(rinfo.resp_size, sizeof(uint32_t));
409 		rinfo.resp_count++;
410 		break;
411 	case 1:
412 		*retval = rinfo.req_type;
413 		rinfo.resp_count++;
414 		break;
415 	case 2:
416 		*retval = rinfo.req_txid;
417 		rinfo.resp_count++;
418 		break;
419 	case 3:
420 		*retval = rinfo.resp_error;
421 		rinfo.resp_count++;
422 		break;
423 	default:
424 		remlen = rinfo.resp_size - rinfo.resp_off;
425 		dp = (uint8_t *)rinfo.resp_biov->iov_base + rinfo.resp_off;
426 		if (remlen >= (ssize_t)sizeof(uint32_t)) {
427 			memcpy(retval, dp, sizeof(uint32_t));
428 		} else if (remlen > 0) {
429 			*retval = fwctl_send_rest(dp, remlen);
430 		}
431 		rinfo.resp_off += sizeof(uint32_t);
432 		break;
433 	}
434 
435 	if (rinfo.resp_count > 3 &&
436 	    rinfo.resp_off >= rinfo.resp_size) {
437 		fwctl_response_done();
438 		return (1);
439 	}
440 
441 	return (0);
442 }
443 
444 
445 /*
446  * i/o port handling.
447  */
448 static uint8_t
449 fwctl_inb(void)
450 {
451 	uint8_t retval;
452 
453 	retval = 0xff;
454 
455 	switch (be_state) {
456 	case IDENT_SEND:
457 		retval = sig[ident_idx++];
458 		if (ident_idx >= sizeof(sig))
459 			be_state = REQ;
460 		break;
461 	default:
462 		break;
463 	}
464 
465 	return (retval);
466 }
467 
468 static void
469 fwctl_outw(uint16_t val)
470 {
471 	if (be_state == DORMANT) {
472 		return;
473 	}
474 
475 	if (val == 0) {
476 		/*
477 		 * The guest wants to read the signature. It's possible that the
478 		 * guest is unaware of the fwctl state at this moment. For that
479 		 * reason, reset the state machine unconditionally.
480 		 */
481 		be_state = IDENT_SEND;
482 		ident_idx = 0;
483 	}
484 }
485 
486 static uint32_t
487 fwctl_inl(void)
488 {
489 	uint32_t retval;
490 
491 	switch (be_state) {
492 	case RESP:
493 		if (fwctl_response(&retval))
494 			be_state = REQ;
495 		break;
496 	default:
497 		retval = 0xffffffff;
498 		break;
499 	}
500 
501 	return (retval);
502 }
503 
504 static void
505 fwctl_outl(uint32_t val)
506 {
507 
508 	switch (be_state) {
509 	case REQ:
510 		if (fwctl_request(val))
511 			be_state = RESP;
512 	default:
513 		break;
514 	}
515 
516 }
517 
518 static int
519 fwctl_handler(struct vmctx *ctx __unused, int in,
520     int port __unused, int bytes, uint32_t *eax, void *arg __unused)
521 {
522 
523 	if (in) {
524 		if (bytes == 1)
525 			*eax = fwctl_inb();
526 		else if (bytes == 4)
527 			*eax = fwctl_inl();
528 		else
529 			*eax = 0xffff;
530 	} else {
531 		if (bytes == 2)
532 			fwctl_outw(*eax);
533 		else if (bytes == 4)
534 			fwctl_outl(*eax);
535 	}
536 
537 	return (0);
538 }
539 
540 void
541 fwctl_init(void)
542 {
543 	struct inout_port iop;
544 	int error;
545 
546 	bzero(&iop, sizeof(iop));
547 	iop.name = "fwctl_wreg";
548 	iop.port = FWCTL_OUT;
549 	iop.size = 1;
550 	iop.flags = IOPORT_F_INOUT;
551 	iop.handler = fwctl_handler;
552 
553 	error = register_inout(&iop);
554 	assert(error == 0);
555 
556 	bzero(&iop, sizeof(iop));
557 	iop.name = "fwctl_rreg";
558 	iop.port = FWCTL_IN;
559 	iop.size = 1;
560 	iop.flags = IOPORT_F_IN;
561 	iop.handler = fwctl_handler;
562 
563 	error = register_inout(&iop);
564 	assert(error == 0);
565 
566 	ops[OP_GET_LEN] = &fgetlen_info;
567 	ops[OP_GET]     = &fgetval_info;
568 
569 	be_state = IDENT_WAIT;
570 }
571