1/*-
2 * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
3 *
4 * Copyright (c) 2015 Nahanni Systems, Inc.
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#include <sys/cdefs.h>
32__FBSDID("$FreeBSD$");
33
34#include <sys/types.h>
35#include <sys/mman.h>
36
37#include <machine/vmm.h>
38#include <machine/vmm_snapshot.h>
39#include <vmmapi.h>
40
41#include <stdio.h>
42#include <stdlib.h>
43#include <string.h>
44
45#include <errno.h>
46#include <unistd.h>
47
48#include "bhyvegc.h"
49#include "bhyverun.h"
50#include "debug.h"
51#include "console.h"
52#include "inout.h"
53#include "pci_emul.h"
54#include "rfb.h"
55#include "vga.h"
56
57/*
58 * bhyve Framebuffer device emulation.
59 * BAR0 points to the current mode information.
60 * BAR1 is the 32-bit framebuffer address.
61 *
62 *  -s <b>,fbuf,wait,vga=on|io|off,rfb=<ip>:port,w=width,h=height
63 */
64
65static int fbuf_debug = 1;
66#define	DEBUG_INFO	1
67#define	DEBUG_VERBOSE	4
68#define	DPRINTF(level, params)  if (level <= fbuf_debug) PRINTLN params
69
70
71#define	KB	(1024UL)
72#define	MB	(1024 * 1024UL)
73
74#define	DMEMSZ	128
75
76#define	FB_SIZE		(16*MB)
77
78#define COLS_MAX	1920
79#define	ROWS_MAX	1200
80
81#define COLS_DEFAULT	1024
82#define ROWS_DEFAULT	768
83
84#define COLS_MIN	640
85#define ROWS_MIN	480
86
87struct pci_fbuf_softc {
88	struct pci_devinst *fsc_pi;
89	struct {
90		uint32_t fbsize;
91		uint16_t width;
92		uint16_t height;
93		uint16_t depth;
94		uint16_t refreshrate;
95		uint8_t  reserved[116];
96	} __packed memregs;
97
98	/* rfb server */
99	char      *rfb_host;
100	char      *rfb_password;
101	int       rfb_port;
102	int       rfb_wait;
103	int       vga_enabled;
104	int	  vga_full;
105
106	uint32_t  fbaddr;
107	char      *fb_base;
108	uint16_t  gc_width;
109	uint16_t  gc_height;
110	void      *vgasc;
111	struct bhyvegc_image *gc_image;
112};
113
114static struct pci_fbuf_softc *fbuf_sc;
115
116#define	PCI_FBUF_MSI_MSGS	 4
117
118static void
119pci_fbuf_usage(char *opt)
120{
121
122	EPRINTLN("Invalid fbuf emulation option \"%s\"", opt);
123	EPRINTLN("fbuf: {wait,}{vga=on|io|off,}rfb=<ip>:port"
124	    "{,w=width}{,h=height}");
125}
126
127static void
128pci_fbuf_write(struct vmctx *ctx, int vcpu, struct pci_devinst *pi,
129	       int baridx, uint64_t offset, int size, uint64_t value)
130{
131	struct pci_fbuf_softc *sc;
132	uint8_t *p;
133
134	assert(baridx == 0);
135
136	sc = pi->pi_arg;
137
138	DPRINTF(DEBUG_VERBOSE,
139	    ("fbuf wr: offset 0x%lx, size: %d, value: 0x%lx",
140	    offset, size, value));
141
142	if (offset + size > DMEMSZ) {
143		printf("fbuf: write too large, offset %ld size %d\n",
144		       offset, size);
145		return;
146	}
147
148	p = (uint8_t *)&sc->memregs + offset;
149
150	switch (size) {
151	case 1:
152		*p = value;
153		break;
154	case 2:
155		*(uint16_t *)p = value;
156		break;
157	case 4:
158		*(uint32_t *)p = value;
159		break;
160	case 8:
161		*(uint64_t *)p = value;
162		break;
163	default:
164		printf("fbuf: write unknown size %d\n", size);
165		break;
166	}
167
168	if (!sc->gc_image->vgamode && sc->memregs.width == 0 &&
169	    sc->memregs.height == 0) {
170		DPRINTF(DEBUG_INFO, ("switching to VGA mode"));
171		sc->gc_image->vgamode = 1;
172		sc->gc_width = 0;
173		sc->gc_height = 0;
174	} else if (sc->gc_image->vgamode && sc->memregs.width != 0 &&
175	    sc->memregs.height != 0) {
176		DPRINTF(DEBUG_INFO, ("switching to VESA mode"));
177		sc->gc_image->vgamode = 0;
178	}
179}
180
181uint64_t
182pci_fbuf_read(struct vmctx *ctx, int vcpu, struct pci_devinst *pi,
183	      int baridx, uint64_t offset, int size)
184{
185	struct pci_fbuf_softc *sc;
186	uint8_t *p;
187	uint64_t value;
188
189	assert(baridx == 0);
190
191	sc = pi->pi_arg;
192
193
194	if (offset + size > DMEMSZ) {
195		printf("fbuf: read too large, offset %ld size %d\n",
196		       offset, size);
197		return (0);
198	}
199
200	p = (uint8_t *)&sc->memregs + offset;
201	value = 0;
202	switch (size) {
203	case 1:
204		value = *p;
205		break;
206	case 2:
207		value = *(uint16_t *)p;
208		break;
209	case 4:
210		value = *(uint32_t *)p;
211		break;
212	case 8:
213		value = *(uint64_t *)p;
214		break;
215	default:
216		printf("fbuf: read unknown size %d\n", size);
217		break;
218	}
219
220	DPRINTF(DEBUG_VERBOSE,
221	    ("fbuf rd: offset 0x%lx, size: %d, value: 0x%lx",
222	     offset, size, value));
223
224	return (value);
225}
226
227static void
228pci_fbuf_baraddr(struct vmctx *ctx, struct pci_devinst *pi, int baridx,
229		 int enabled, uint64_t address)
230{
231	struct pci_fbuf_softc *sc;
232	int prot;
233
234	if (baridx != 1)
235		return;
236
237	sc = pi->pi_arg;
238	if (!enabled && sc->fbaddr != 0) {
239		if (vm_munmap_memseg(ctx, sc->fbaddr, FB_SIZE) != 0)
240			EPRINTLN("pci_fbuf: munmap_memseg failed");
241		sc->fbaddr = 0;
242	} else if (sc->fb_base != NULL && sc->fbaddr == 0) {
243		prot = PROT_READ | PROT_WRITE;
244		if (vm_mmap_memseg(ctx, address, VM_FRAMEBUFFER, 0, FB_SIZE, prot) != 0)
245			EPRINTLN("pci_fbuf: mmap_memseg failed");
246		sc->fbaddr = address;
247	}
248}
249
250
251static int
252pci_fbuf_parse_opts(struct pci_fbuf_softc *sc, char *opts)
253{
254	char	*uopts, *uoptsbak, *xopts, *config;
255	char	*tmpstr;
256	int	ret;
257
258	ret = 0;
259	uoptsbak = uopts = strdup(opts);
260	while ((xopts = strsep(&uopts, ",")) != NULL) {
261		if (strcmp(xopts, "wait") == 0) {
262			sc->rfb_wait = 1;
263			continue;
264		}
265
266		if ((config = strchr(xopts, '=')) == NULL) {
267			pci_fbuf_usage(xopts);
268			ret = -1;
269			goto done;
270		}
271
272		*config++ = '\0';
273
274		DPRINTF(DEBUG_VERBOSE, ("pci_fbuf option %s = %s",
275		   xopts, config));
276
277		if (!strcmp(xopts, "tcp") || !strcmp(xopts, "rfb")) {
278			/*
279			 * IPv4 -- host-ip:port
280			 * IPv6 -- [host-ip%zone]:port
281			 * XXX for now port is mandatory.
282			 */
283			tmpstr = strsep(&config, "]");
284			if (config) {
285				if (tmpstr[0] == '[')
286					tmpstr++;
287				sc->rfb_host = strdup(tmpstr);
288				if (config[0] == ':')
289					config++;
290				else {
291					pci_fbuf_usage(xopts);
292					ret = -1;
293					goto done;
294				}
295				sc->rfb_port = atoi(config);
296			} else {
297				config = tmpstr;
298				tmpstr = strsep(&config, ":");
299				if (!config)
300					sc->rfb_port = atoi(tmpstr);
301				else {
302					sc->rfb_port = atoi(config);
303					sc->rfb_host = strdup(tmpstr);
304				}
305			}
306	        } else if (!strcmp(xopts, "vga")) {
307			if (!strcmp(config, "off")) {
308				sc->vga_enabled = 0;
309			} else if (!strcmp(config, "io")) {
310				sc->vga_enabled = 1;
311				sc->vga_full = 0;
312			} else if (!strcmp(config, "on")) {
313				sc->vga_enabled = 1;
314				sc->vga_full = 1;
315			} else {
316				pci_fbuf_usage(xopts);
317				ret = -1;
318				goto done;
319			}
320	        } else if (!strcmp(xopts, "w")) {
321		        sc->memregs.width = atoi(config);
322			if (sc->memregs.width > COLS_MAX) {
323				pci_fbuf_usage(xopts);
324				ret = -1;
325				goto done;
326			} else if (sc->memregs.width == 0)
327				sc->memregs.width = 1920;
328		} else if (!strcmp(xopts, "h")) {
329			sc->memregs.height = atoi(config);
330			if (sc->memregs.height > ROWS_MAX) {
331				pci_fbuf_usage(xopts);
332				ret = -1;
333				goto done;
334			} else if (sc->memregs.height == 0)
335				sc->memregs.height = 1080;
336		} else if (!strcmp(xopts, "password")) {
337			sc->rfb_password = strdup(config);
338		} else {
339			pci_fbuf_usage(xopts);
340			ret = -1;
341			goto done;
342		}
343	}
344
345done:
346	free(uoptsbak);
347	return (ret);
348}
349
350
351extern void vga_render(struct bhyvegc *gc, void *arg);
352
353void
354pci_fbuf_render(struct bhyvegc *gc, void *arg)
355{
356	struct pci_fbuf_softc *sc;
357
358	sc = arg;
359
360	if (sc->vga_full && sc->gc_image->vgamode) {
361		/* TODO: mode switching to vga and vesa should use the special
362		 *      EFI-bhyve protocol port.
363		 */
364		vga_render(gc, sc->vgasc);
365		return;
366	}
367	if (sc->gc_width != sc->memregs.width ||
368	    sc->gc_height != sc->memregs.height) {
369		bhyvegc_resize(gc, sc->memregs.width, sc->memregs.height);
370		sc->gc_width = sc->memregs.width;
371		sc->gc_height = sc->memregs.height;
372	}
373
374	return;
375}
376
377static int
378pci_fbuf_init(struct vmctx *ctx, struct pci_devinst *pi, char *opts)
379{
380	int error, prot;
381	struct pci_fbuf_softc *sc;
382
383	if (fbuf_sc != NULL) {
384		EPRINTLN("Only one frame buffer device is allowed.");
385		return (-1);
386	}
387
388	sc = calloc(1, sizeof(struct pci_fbuf_softc));
389
390	pi->pi_arg = sc;
391
392	/* initialize config space */
393	pci_set_cfgdata16(pi, PCIR_DEVICE, 0x40FB);
394	pci_set_cfgdata16(pi, PCIR_VENDOR, 0xFB5D);
395	pci_set_cfgdata8(pi, PCIR_CLASS, PCIC_DISPLAY);
396	pci_set_cfgdata8(pi, PCIR_SUBCLASS, PCIS_DISPLAY_VGA);
397
398	error = pci_emul_alloc_bar(pi, 0, PCIBAR_MEM32, DMEMSZ);
399	assert(error == 0);
400
401	error = pci_emul_alloc_bar(pi, 1, PCIBAR_MEM32, FB_SIZE);
402	assert(error == 0);
403
404	error = pci_emul_add_msicap(pi, PCI_FBUF_MSI_MSGS);
405	assert(error == 0);
406
407	sc->fbaddr = pi->pi_bar[1].addr;
408	sc->memregs.fbsize = FB_SIZE;
409	sc->memregs.width  = COLS_DEFAULT;
410	sc->memregs.height = ROWS_DEFAULT;
411	sc->memregs.depth  = 32;
412
413	sc->vga_enabled = 1;
414	sc->vga_full = 0;
415
416	sc->fsc_pi = pi;
417
418	error = pci_fbuf_parse_opts(sc, opts);
419	if (error != 0)
420		goto done;
421
422	/* XXX until VGA rendering is enabled */
423	if (sc->vga_full != 0) {
424		EPRINTLN("pci_fbuf: VGA rendering not enabled");
425		goto done;
426	}
427
428	sc->fb_base = vm_create_devmem(ctx, VM_FRAMEBUFFER, "framebuffer", FB_SIZE);
429	if (sc->fb_base == MAP_FAILED) {
430		error = -1;
431		goto done;
432	}
433	DPRINTF(DEBUG_INFO, ("fbuf frame buffer base: %p [sz %lu]",
434	        sc->fb_base, FB_SIZE));
435
436	/*
437	 * Map the framebuffer into the guest address space.
438	 * XXX This may fail if the BAR is different than a prior
439	 * run. In this case flag the error. This will be fixed
440	 * when a change_memseg api is available.
441	 */
442	prot = PROT_READ | PROT_WRITE;
443	if (vm_mmap_memseg(ctx, sc->fbaddr, VM_FRAMEBUFFER, 0, FB_SIZE, prot) != 0) {
444		EPRINTLN("pci_fbuf: mapseg failed - try deleting VM and restarting");
445		error = -1;
446		goto done;
447	}
448
449	console_init(sc->memregs.width, sc->memregs.height, sc->fb_base);
450	console_fb_register(pci_fbuf_render, sc);
451
452	if (sc->vga_enabled)
453		sc->vgasc = vga_init(!sc->vga_full);
454	sc->gc_image = console_get_image();
455
456	fbuf_sc = sc;
457
458	memset((void *)sc->fb_base, 0, FB_SIZE);
459
460	error = rfb_init(sc->rfb_host, sc->rfb_port, sc->rfb_wait, sc->rfb_password);
461done:
462	if (error)
463		free(sc);
464
465	return (error);
466}
467
468#ifdef BHYVE_SNAPSHOT
469static int
470pci_fbuf_snapshot(struct vm_snapshot_meta *meta)
471{
472	int ret;
473
474	SNAPSHOT_BUF_OR_LEAVE(fbuf_sc->fb_base, FB_SIZE, meta, ret, err);
475
476err:
477	return (ret);
478}
479#endif
480
481struct pci_devemu pci_fbuf = {
482	.pe_emu =	"fbuf",
483	.pe_init =	pci_fbuf_init,
484	.pe_barwrite =	pci_fbuf_write,
485	.pe_barread =	pci_fbuf_read,
486	.pe_baraddr =	pci_fbuf_baraddr,
487#ifdef BHYVE_SNAPSHOT
488	.pe_snapshot =	pci_fbuf_snapshot,
489#endif
490};
491PCI_EMUL_SET(pci_fbuf);
492