1300829Sgrehan/*-
2300829Sgrehan * Copyright (c) 2015 Nahanni Systems, Inc.
3300829Sgrehan * All rights reserved.
4300829Sgrehan *
5300829Sgrehan * Redistribution and use in source and binary forms, with or without
6300829Sgrehan * modification, are permitted provided that the following conditions
7300829Sgrehan * are met:
8300829Sgrehan * 1. Redistributions of source code must retain the above copyright
9300829Sgrehan *    notice, this list of conditions and the following disclaimer.
10300829Sgrehan * 2. Redistributions in binary form must reproduce the above copyright
11300829Sgrehan *    notice, this list of conditions and the following disclaimer in the
12300829Sgrehan *    documentation and/or other materials provided with the distribution.
13300829Sgrehan *
14300829Sgrehan * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND
15300829Sgrehan * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16300829Sgrehan * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17300829Sgrehan * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
18300829Sgrehan * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19300829Sgrehan * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20300829Sgrehan * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21300829Sgrehan * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22300829Sgrehan * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23300829Sgrehan * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24300829Sgrehan * SUCH DAMAGE.
25300829Sgrehan *
26300829Sgrehan * $FreeBSD: releng/11.0/usr.sbin/bhyve/pci_fbuf.c 302408 2016-07-08 00:04:57Z gjb $
27300829Sgrehan */
28300829Sgrehan
29300829Sgrehan#include <sys/cdefs.h>
30300829Sgrehan__FBSDID("$FreeBSD: releng/11.0/usr.sbin/bhyve/pci_fbuf.c 302408 2016-07-08 00:04:57Z gjb $");
31300829Sgrehan
32300829Sgrehan#include <sys/types.h>
33300829Sgrehan#include <sys/mman.h>
34300829Sgrehan
35300829Sgrehan#include <machine/vmm.h>
36300829Sgrehan#include <vmmapi.h>
37300829Sgrehan
38300829Sgrehan#include <stdio.h>
39300829Sgrehan#include <stdlib.h>
40300829Sgrehan#include <string.h>
41300829Sgrehan
42300829Sgrehan#include <errno.h>
43300829Sgrehan#include <unistd.h>
44300829Sgrehan
45300829Sgrehan#include "bhyvegc.h"
46300829Sgrehan#include "bhyverun.h"
47300829Sgrehan#include "console.h"
48300829Sgrehan#include "inout.h"
49300829Sgrehan#include "pci_emul.h"
50300829Sgrehan#include "rfb.h"
51300829Sgrehan#include "vga.h"
52300829Sgrehan
53300829Sgrehan/*
54300829Sgrehan * bhyve Framebuffer device emulation.
55300829Sgrehan * BAR0 points to the current mode information.
56300829Sgrehan * BAR1 is the 32-bit framebuffer address.
57300829Sgrehan *
58300829Sgrehan *  -s <b>,fbuf,wait,tcp=<ip>:port,w=width,h=height
59300829Sgrehan */
60300829Sgrehan
61300829Sgrehanstatic int fbuf_debug = 1;
62300829Sgrehan#define	DEBUG_INFO	1
63300829Sgrehan#define	DEBUG_VERBOSE	4
64300829Sgrehan#define	DPRINTF(level, params)  if (level <= fbuf_debug) printf params
65300829Sgrehan
66300829Sgrehan
67300829Sgrehan#define	KB	(1024UL)
68300829Sgrehan#define	MB	(1024 * 1024UL)
69300829Sgrehan
70300829Sgrehan#define	DMEMSZ	128
71300829Sgrehan
72300829Sgrehan#define	FB_SIZE		(16*MB)
73300829Sgrehan
74300829Sgrehan#define COLS_MAX	1920
75300829Sgrehan#define	ROWS_MAX	1200
76300829Sgrehan
77300829Sgrehan#define COLS_DEFAULT	1024
78300829Sgrehan#define ROWS_DEFAULT	768
79300829Sgrehan
80300829Sgrehan#define COLS_MIN	640
81300829Sgrehan#define ROWS_MIN	480
82300829Sgrehan
83300829Sgrehanstruct pci_fbuf_softc {
84300829Sgrehan	struct pci_devinst *fsc_pi;
85300829Sgrehan	struct {
86300829Sgrehan		uint32_t fbsize;
87300829Sgrehan		uint16_t width;
88300829Sgrehan		uint16_t height;
89300829Sgrehan		uint16_t depth;
90300829Sgrehan		uint16_t refreshrate;
91300829Sgrehan		uint8_t  reserved[116];
92300829Sgrehan	} __packed memregs;
93300829Sgrehan
94300829Sgrehan	/* rfb server */
95300829Sgrehan	char      *rfb_host;
96300829Sgrehan	int       rfb_port;
97300829Sgrehan	int       rfb_wait;
98300829Sgrehan	int       use_vga;
99300829Sgrehan
100300829Sgrehan	uint32_t  fbaddr;
101300829Sgrehan	char      *fb_base;
102300829Sgrehan	uint16_t  gc_width;
103300829Sgrehan	uint16_t  gc_height;
104300829Sgrehan	void      *vgasc;
105300829Sgrehan	struct bhyvegc_image *gc_image;
106300829Sgrehan};
107300829Sgrehan
108300829Sgrehanstatic struct pci_fbuf_softc *fbuf_sc;
109300829Sgrehan
110300829Sgrehan#define	PCI_FBUF_MSI_MSGS	 4
111300829Sgrehan
112300829Sgrehanstatic void
113300829Sgrehanpci_fbuf_usage(char *opt)
114300829Sgrehan{
115300829Sgrehan
116300829Sgrehan	fprintf(stderr, "Invalid fbuf emulation \"%s\"\r\n", opt);
117300829Sgrehan	fprintf(stderr, "fbuf: {wait,}tcp=<ip>:port\r\n");
118300829Sgrehan}
119300829Sgrehan
120300829Sgrehanstatic void
121300829Sgrehanpci_fbuf_write(struct vmctx *ctx, int vcpu, struct pci_devinst *pi,
122300829Sgrehan	       int baridx, uint64_t offset, int size, uint64_t value)
123300829Sgrehan{
124300829Sgrehan	struct pci_fbuf_softc *sc;
125300829Sgrehan	uint8_t *p;
126300829Sgrehan
127300829Sgrehan	assert(baridx == 0);
128300829Sgrehan
129300829Sgrehan	sc = pi->pi_arg;
130300829Sgrehan
131300829Sgrehan	DPRINTF(DEBUG_VERBOSE,
132300829Sgrehan	    ("fbuf wr: offset 0x%lx, size: %d, value: 0x%lx\n",
133300829Sgrehan	    offset, size, value));
134300829Sgrehan
135300829Sgrehan	if (offset + size > DMEMSZ) {
136300829Sgrehan		printf("fbuf: write too large, offset %ld size %d\n",
137300829Sgrehan		       offset, size);
138300829Sgrehan		return;
139300829Sgrehan	}
140300829Sgrehan
141300829Sgrehan	p = (uint8_t *)&sc->memregs + offset;
142300829Sgrehan
143300829Sgrehan	switch (size) {
144300829Sgrehan	case 1:
145300829Sgrehan		*p = value;
146300829Sgrehan		break;
147300829Sgrehan	case 2:
148300829Sgrehan		*(uint16_t *)p = value;
149300829Sgrehan		break;
150300829Sgrehan	case 4:
151300829Sgrehan		*(uint32_t *)p = value;
152300829Sgrehan		break;
153300829Sgrehan	case 8:
154300829Sgrehan		*(uint64_t *)p = value;
155300829Sgrehan		break;
156300829Sgrehan	default:
157300829Sgrehan		printf("fbuf: write unknown size %d\n", size);
158300829Sgrehan		break;
159300829Sgrehan	}
160300829Sgrehan
161300829Sgrehan	if (!sc->gc_image->vgamode && sc->memregs.width == 0 &&
162300829Sgrehan	    sc->memregs.height == 0) {
163300829Sgrehan		DPRINTF(DEBUG_INFO, ("switching to VGA mode\r\n"));
164300829Sgrehan		sc->gc_image->vgamode = 1;
165300829Sgrehan		sc->gc_width = 0;
166300829Sgrehan		sc->gc_height = 0;
167300829Sgrehan	} else if (sc->gc_image->vgamode && sc->memregs.width != 0 &&
168300829Sgrehan	    sc->memregs.height != 0) {
169300829Sgrehan		DPRINTF(DEBUG_INFO, ("switching to VESA mode\r\n"));
170300829Sgrehan		sc->gc_image->vgamode = 0;
171300829Sgrehan	}
172300829Sgrehan}
173300829Sgrehan
174300829Sgrehanuint64_t
175300829Sgrehanpci_fbuf_read(struct vmctx *ctx, int vcpu, struct pci_devinst *pi,
176300829Sgrehan	      int baridx, uint64_t offset, int size)
177300829Sgrehan{
178300829Sgrehan	struct pci_fbuf_softc *sc;
179300829Sgrehan	uint8_t *p;
180300829Sgrehan	uint64_t value;
181300829Sgrehan
182300829Sgrehan	assert(baridx == 0);
183300829Sgrehan
184300829Sgrehan	sc = pi->pi_arg;
185300829Sgrehan
186300829Sgrehan
187300829Sgrehan	if (offset + size > DMEMSZ) {
188300829Sgrehan		printf("fbuf: read too large, offset %ld size %d\n",
189300829Sgrehan		       offset, size);
190300829Sgrehan		return (0);
191300829Sgrehan	}
192300829Sgrehan
193300829Sgrehan	p = (uint8_t *)&sc->memregs + offset;
194300829Sgrehan	value = 0;
195300829Sgrehan	switch (size) {
196300829Sgrehan	case 1:
197300829Sgrehan		value = *p;
198300829Sgrehan		break;
199300829Sgrehan	case 2:
200300829Sgrehan		value = *(uint16_t *)p;
201300829Sgrehan		break;
202300829Sgrehan	case 4:
203300829Sgrehan		value = *(uint32_t *)p;
204300829Sgrehan		break;
205300829Sgrehan	case 8:
206300829Sgrehan		value = *(uint64_t *)p;
207300829Sgrehan		break;
208300829Sgrehan	default:
209300829Sgrehan		printf("fbuf: read unknown size %d\n", size);
210300829Sgrehan		break;
211300829Sgrehan	}
212300829Sgrehan
213300829Sgrehan	DPRINTF(DEBUG_VERBOSE,
214300829Sgrehan	    ("fbuf rd: offset 0x%lx, size: %d, value: 0x%lx\n",
215300829Sgrehan	     offset, size, value));
216300829Sgrehan
217300829Sgrehan	return (value);
218300829Sgrehan}
219300829Sgrehan
220300829Sgrehanstatic int
221300829Sgrehanpci_fbuf_parse_opts(struct pci_fbuf_softc *sc, char *opts)
222300829Sgrehan{
223300829Sgrehan	char	*uopts, *xopts, *config;
224300829Sgrehan	char	*tmpstr;
225300829Sgrehan	int	ret;
226300829Sgrehan
227300829Sgrehan	ret = 0;
228300829Sgrehan	uopts = strdup(opts);
229300829Sgrehan	for (xopts = strtok(uopts, ",");
230300829Sgrehan	     xopts != NULL;
231300829Sgrehan	     xopts = strtok(NULL, ",")) {
232300829Sgrehan		if (strcmp(xopts, "wait") == 0) {
233300829Sgrehan			sc->rfb_wait = 1;
234300829Sgrehan			continue;
235300829Sgrehan		}
236300829Sgrehan
237300829Sgrehan#if 0 /* notyet */
238300829Sgrehan		if (strcmp(xopts, "vga") == 0) {
239300829Sgrehan			sc->use_vga = 1;
240300829Sgrehan			continue;
241300829Sgrehan		}
242300829Sgrehan#endif
243300829Sgrehan
244300829Sgrehan		if ((config = strchr(xopts, '=')) == NULL) {
245300829Sgrehan			pci_fbuf_usage(xopts);
246300829Sgrehan			ret = -1;
247300829Sgrehan			goto done;
248300829Sgrehan		}
249300829Sgrehan
250300829Sgrehan		*config++ = '\0';
251300829Sgrehan
252300829Sgrehan		DPRINTF(DEBUG_VERBOSE, ("pci_fbuf option %s = %s\r\n",
253300829Sgrehan		   xopts, config));
254300829Sgrehan
255300829Sgrehan		if (!strcmp(xopts, "tcp")) {
256300829Sgrehan			/* parse host-ip:port */
257300829Sgrehan			tmpstr = strsep(&config, ":");
258300829Sgrehan			if (!config)
259300829Sgrehan				sc->rfb_port = atoi(tmpstr);
260300829Sgrehan			else {
261300829Sgrehan				sc->rfb_port = atoi(config);
262300829Sgrehan				sc->rfb_host = tmpstr;
263300829Sgrehan			}
264300829Sgrehan		} else if (!strcmp(xopts, "w")) {
265300829Sgrehan			sc->memregs.width = atoi(config);
266300829Sgrehan			if (sc->memregs.width > COLS_MAX) {
267300829Sgrehan				pci_fbuf_usage(xopts);
268300829Sgrehan				ret = -1;
269300829Sgrehan				goto done;
270300829Sgrehan			} else if (sc->memregs.width == 0)
271300829Sgrehan				sc->memregs.width = 1920;
272300829Sgrehan		} else if (!strcmp(xopts, "h")) {
273300829Sgrehan			sc->memregs.height = atoi(config);
274300829Sgrehan			if (sc->memregs.height > ROWS_MAX) {
275300829Sgrehan				pci_fbuf_usage(xopts);
276300829Sgrehan				ret = -1;
277300829Sgrehan				goto done;
278300829Sgrehan			} else if (sc->memregs.height == 0)
279300829Sgrehan				sc->memregs.height = 1080;
280300829Sgrehan
281300829Sgrehan		} else {
282300829Sgrehan			pci_fbuf_usage(xopts);
283300829Sgrehan			ret = -1;
284300829Sgrehan			goto done;
285300829Sgrehan		}
286300829Sgrehan	}
287300829Sgrehan
288300829Sgrehandone:
289300829Sgrehan	return (ret);
290300829Sgrehan}
291300829Sgrehan
292300829Sgrehan
293300829Sgrehanextern void vga_render(struct bhyvegc *gc, void *arg);
294300829Sgrehan
295300829Sgrehanvoid
296300829Sgrehanpci_fbuf_render(struct bhyvegc *gc, void *arg)
297300829Sgrehan{
298300829Sgrehan	struct pci_fbuf_softc *sc;
299300829Sgrehan
300300829Sgrehan	sc = arg;
301300829Sgrehan
302300829Sgrehan	if (sc->use_vga && sc->gc_image->vgamode) {
303300829Sgrehan		/* TODO: mode switching to vga and vesa should use the special
304300829Sgrehan		 *      EFI-bhyve protocol port.
305300829Sgrehan		 */
306300829Sgrehan		vga_render(gc, sc->vgasc);
307300829Sgrehan		return;
308300829Sgrehan	}
309300829Sgrehan	if (sc->gc_width != sc->memregs.width ||
310300829Sgrehan	    sc->gc_height != sc->memregs.height) {
311300829Sgrehan		bhyvegc_resize(gc, sc->memregs.width, sc->memregs.height);
312300829Sgrehan		sc->gc_width = sc->memregs.width;
313300829Sgrehan		sc->gc_height = sc->memregs.height;
314300829Sgrehan	}
315300829Sgrehan
316300829Sgrehan	return;
317300829Sgrehan}
318300829Sgrehan
319300829Sgrehanstatic int
320300829Sgrehanpci_fbuf_init(struct vmctx *ctx, struct pci_devinst *pi, char *opts)
321300829Sgrehan{
322300829Sgrehan	int error, prot;
323300829Sgrehan	struct pci_fbuf_softc *sc;
324300829Sgrehan
325300829Sgrehan	if (fbuf_sc != NULL) {
326300829Sgrehan		fprintf(stderr, "Only one frame buffer device is allowed.\n");
327300829Sgrehan		return (-1);
328300829Sgrehan	}
329300829Sgrehan
330300829Sgrehan	sc = calloc(1, sizeof(struct pci_fbuf_softc));
331300829Sgrehan
332300829Sgrehan	pi->pi_arg = sc;
333300829Sgrehan
334300829Sgrehan	/* initialize config space */
335300829Sgrehan	pci_set_cfgdata16(pi, PCIR_DEVICE, 0x40FB);
336300829Sgrehan	pci_set_cfgdata16(pi, PCIR_VENDOR, 0xFB5D);
337300829Sgrehan	pci_set_cfgdata8(pi, PCIR_CLASS, PCIC_DISPLAY);
338300829Sgrehan	pci_set_cfgdata8(pi, PCIR_SUBCLASS, PCIS_DISPLAY_VGA);
339300829Sgrehan
340300829Sgrehan	error = pci_emul_alloc_bar(pi, 0, PCIBAR_MEM32, DMEMSZ);
341300829Sgrehan	assert(error == 0);
342300829Sgrehan
343300829Sgrehan	error = pci_emul_alloc_bar(pi, 1, PCIBAR_MEM32, FB_SIZE);
344300829Sgrehan	assert(error == 0);
345300829Sgrehan
346300829Sgrehan	error = pci_emul_add_msicap(pi, PCI_FBUF_MSI_MSGS);
347300829Sgrehan	assert(error == 0);
348300829Sgrehan
349300829Sgrehan	sc->fbaddr = pi->pi_bar[1].addr;
350300829Sgrehan	sc->memregs.fbsize = FB_SIZE;
351300829Sgrehan	sc->memregs.width  = COLS_DEFAULT;
352300829Sgrehan	sc->memregs.height = ROWS_DEFAULT;
353300829Sgrehan	sc->memregs.depth  = 32;
354300829Sgrehan
355300829Sgrehan	sc->fsc_pi = pi;
356300829Sgrehan
357300829Sgrehan	error = pci_fbuf_parse_opts(sc, opts);
358300829Sgrehan	if (error != 0)
359300829Sgrehan		goto done;
360300829Sgrehan
361300829Sgrehan	sc->fb_base = vm_create_devmem(ctx, VM_FRAMEBUFFER, "framebuffer", FB_SIZE);
362300829Sgrehan	if (sc->fb_base == MAP_FAILED) {
363300829Sgrehan		error = -1;
364300829Sgrehan		goto done;
365300829Sgrehan	}
366300829Sgrehan	DPRINTF(DEBUG_INFO, ("fbuf frame buffer base: %p [sz %lu]\r\n",
367300829Sgrehan	        sc->fb_base, FB_SIZE));
368300829Sgrehan
369300829Sgrehan	/*
370300829Sgrehan	 * Map the framebuffer into the guest address space.
371300829Sgrehan	 * XXX This may fail if the BAR is different than a prior
372300829Sgrehan	 * run. In this case flag the error. This will be fixed
373300829Sgrehan	 * when a change_memseg api is available.
374300829Sgrehan	 */
375300829Sgrehan	prot = PROT_READ | PROT_WRITE;
376300829Sgrehan	if (vm_mmap_memseg(ctx, sc->fbaddr, VM_FRAMEBUFFER, 0, FB_SIZE, prot) != 0) {
377300829Sgrehan		fprintf(stderr, "pci_fbuf: mapseg failed - try deleting VM and restarting\n");
378300829Sgrehan		error = -1;
379300829Sgrehan		goto done;
380300829Sgrehan	}
381300829Sgrehan
382300829Sgrehan	console_init(sc->memregs.width, sc->memregs.height, sc->fb_base);
383300829Sgrehan	console_fb_register(pci_fbuf_render, sc);
384300829Sgrehan
385300829Sgrehan	sc->vgasc = vga_init(!sc->use_vga);
386300829Sgrehan	sc->gc_image = console_get_image();
387300829Sgrehan
388300829Sgrehan	fbuf_sc = sc;
389300829Sgrehan
390300829Sgrehan	memset((void *)sc->fb_base, 0, FB_SIZE);
391300829Sgrehan
392300829Sgrehan	error = rfb_init(sc->rfb_host, sc->rfb_port, sc->rfb_wait);
393300829Sgrehandone:
394300829Sgrehan	if (error)
395300829Sgrehan		free(sc);
396300829Sgrehan
397300829Sgrehan	return (error);
398300829Sgrehan}
399300829Sgrehan
400300829Sgrehanstruct pci_devemu pci_fbuf = {
401300829Sgrehan	.pe_emu =	"fbuf",
402300829Sgrehan	.pe_init =	pci_fbuf_init,
403300829Sgrehan	.pe_barwrite =	pci_fbuf_write,
404300829Sgrehan	.pe_barread =	pci_fbuf_read
405300829Sgrehan};
406300829SgrehanPCI_EMUL_SET(pci_fbuf);
407