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