[PATCH 3/4] video: implement bochs dispi / QEMU VGA driver for PCI and ISA

Ahmad Fatoum ahmad at a3f.at
Fri Jan 1 17:52:27 EST 2021


All SVGA devices in Qemu, except for Cirrus, support the bochs display
interface, which is very easy to set up. Add a driver for it.

This has been tested with:

 PCI: qemu-system-mips(el) -device VGA + barebox qemu-malta_defconfig
 ISA: qemu-system-x86_64 -device isa-vga + barebox efi_defconfig

Signed-off-by: Ahmad Fatoum <ahmad at a3f.at>
---
 drivers/video/Kconfig           |   2 +
 drivers/video/Makefile          |   2 +-
 drivers/video/bochs/Kconfig     |  22 ++++
 drivers/video/bochs/Makefile    |   3 +
 drivers/video/bochs/bochs_hw.c  | 203 ++++++++++++++++++++++++++++++++
 drivers/video/bochs/bochs_hw.h  |  13 ++
 drivers/video/bochs/bochs_isa.c |  31 +++++
 drivers/video/bochs/bochs_pci.c |  37 ++++++
 8 files changed, 312 insertions(+), 1 deletion(-)
 create mode 100644 drivers/video/bochs/Kconfig
 create mode 100644 drivers/video/bochs/Makefile
 create mode 100644 drivers/video/bochs/bochs_hw.c
 create mode 100644 drivers/video/bochs/bochs_hw.h
 create mode 100644 drivers/video/bochs/bochs_isa.c
 create mode 100644 drivers/video/bochs/bochs_pci.c

diff --git a/drivers/video/Kconfig b/drivers/video/Kconfig
index 56d009529ea4..9ec6ea4248c1 100644
--- a/drivers/video/Kconfig
+++ b/drivers/video/Kconfig
@@ -98,6 +98,8 @@ config DRIVER_VIDEO_BCM283X
 
 source "drivers/video/imx-ipu-v3/Kconfig"
 
+source "drivers/video/bochs/Kconfig"
+
 config DRIVER_VIDEO_SIMPLEFB
 	bool "Simple framebuffer support"
 	depends on OFTREE
diff --git a/drivers/video/Makefile b/drivers/video/Makefile
index 01fabe880920..28d0fe205b83 100644
--- a/drivers/video/Makefile
+++ b/drivers/video/Makefile
@@ -24,4 +24,4 @@ obj-$(CONFIG_DRIVER_VIDEO_IMX_IPUV3) += imx-ipu-v3/
 obj-$(CONFIG_DRIVER_VIDEO_EFI_GOP) += efi_gop.o
 obj-$(CONFIG_DRIVER_VIDEO_FB_SSD1307) += ssd1307fb.o
 obj-$(CONFIG_BACKLIGHT_RAVE_SP)	+= rave-sp-backlight.o
-
+obj-$(CONFIG_DRIVER_VIDEO_BOCHS) += bochs/
diff --git a/drivers/video/bochs/Kconfig b/drivers/video/bochs/Kconfig
new file mode 100644
index 000000000000..e23e68f1126c
--- /dev/null
+++ b/drivers/video/bochs/Kconfig
@@ -0,0 +1,22 @@
+config DRIVER_VIDEO_BOCHS
+	select DRIVER_VIDEO_EDID
+	bool
+
+config DRIVER_VIDEO_BOCHS_PCI
+	bool "bochs dispi / QEMU standard VGA PCI driver"
+	select DRIVER_VIDEO_BOCHS
+	depends on PCI
+	help
+	  Say yes here if you have a PCI VGA display controller that
+	  implements the bochs dispi VBE extension. This is a very simple
+	  interface to drive the graphical output of virtual machines
+	  like bochs, VirtualBox and Qemu (-device VGA).
+
+config DRIVER_VIDEO_BOCHS_ISA
+	bool "bochs dispi / QEMU standard VGA ISA driver"
+	select DRIVER_VIDEO_BOCHS
+	help
+	  Say yes here if you have a ISA (I/O ports) VGA display controller that
+	  implements the bochs dispi VBE extension. This is a very simple
+	  interface to drive the graphical output of virtual machines
+	  like bochs, VirtualBox and Qemu (-device isa-vga).
diff --git a/drivers/video/bochs/Makefile b/drivers/video/bochs/Makefile
new file mode 100644
index 000000000000..78ec1fe0ca81
--- /dev/null
+++ b/drivers/video/bochs/Makefile
@@ -0,0 +1,3 @@
+obj-y += bochs_hw.o
+obj-$(CONFIG_DRIVER_VIDEO_BOCHS_PCI) += bochs_pci.o
+obj-$(CONFIG_DRIVER_VIDEO_BOCHS_ISA) += bochs_isa.o
diff --git a/drivers/video/bochs/bochs_hw.c b/drivers/video/bochs/bochs_hw.c
new file mode 100644
index 000000000000..4f908a60318d
--- /dev/null
+++ b/drivers/video/bochs/bochs_hw.c
@@ -0,0 +1,203 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+// SPDX-FileCopyrightText: Copyright (c) 2020 Ahmad Fatoum, Pengutronix
+/*
+ *  PCI Driver for VGA with the Bochs VBE / QEMU stdvga extensions.
+ */
+
+#include <common.h>
+#include <driver.h>
+#include <linux/pci.h>
+#include <fb.h>
+#include "../edid.h"
+#include "bochs_hw.h"
+
+#define VBE_DISPI_INDEX_ID               0x0
+#define VBE_DISPI_INDEX_XRES             0x1
+#define VBE_DISPI_INDEX_YRES             0x2
+#define VBE_DISPI_INDEX_BPP              0x3
+#define VBE_DISPI_INDEX_ENABLE           0x4
+#define VBE_DISPI_INDEX_BANK             0x5
+#define VBE_DISPI_INDEX_VIRT_WIDTH       0x6
+#define VBE_DISPI_INDEX_VIRT_HEIGHT      0x7
+#define VBE_DISPI_INDEX_X_OFFSET         0x8
+#define VBE_DISPI_INDEX_Y_OFFSET         0x9
+#define VBE_DISPI_INDEX_VIDEO_MEMORY_64K 0xa
+
+#define VBE_DISPI_ENABLED                0x01
+#define VBE_DISPI_GETCAPS                0x02
+#define VBE_DISPI_8BIT_DAC               0x20
+#define VBE_DISPI_LFB_ENABLED            0x40
+#define VBE_DISPI_NOCLEARMEM             0x80
+
+/* Offsets for accessing ioports via PCI BAR1 (MMIO) */
+#define VGA_MMIO_OFFSET (0x400 - 0x3c0)
+#define VBE_MMIO_OFFSET 0x500
+
+struct bochs {
+	struct fb_info fb;
+	void __iomem *fb_map;
+	void __iomem *mmio;
+};
+
+static void bochs_vga_writeb(struct bochs *bochs, u16 ioport, u8 val)
+{
+	if (WARN_ON(ioport < 0x3c0 || ioport > 0x3df))
+		return;
+
+	if (bochs->mmio) {
+		int offset = ioport + VGA_MMIO_OFFSET;
+		writeb(val, bochs->mmio + offset);
+	} else {
+		outb(val, ioport);
+	}
+}
+
+static u16 bochs_dispi_read(struct bochs *bochs, u16 reg)
+{
+	u16 ret = 0;
+
+	if (bochs->mmio) {
+		int offset = VBE_MMIO_OFFSET + (reg << 1);
+		ret = readw(bochs->mmio + offset);
+	} else {
+		outw(reg, VBE_DISPI_IOPORT_INDEX);
+		ret = inw(VBE_DISPI_IOPORT_DATA);
+	}
+	return ret;
+}
+
+static void bochs_dispi_write(struct bochs *bochs, u16 reg, u16 val)
+{
+	if (bochs->mmio) {
+		int offset = VBE_MMIO_OFFSET + (reg << 1);
+		writew(val, bochs->mmio + offset);
+	} else {
+		outw(reg, VBE_DISPI_IOPORT_INDEX);
+		outw(val, VBE_DISPI_IOPORT_DATA);
+	}
+}
+
+static void bochs_fb_enable(struct fb_info *fb)
+{
+	struct bochs *bochs = fb->priv;
+
+	bochs_vga_writeb(bochs, 0x3c0, 0x20); /* unblank */
+
+	bochs_dispi_write(bochs, VBE_DISPI_INDEX_ENABLE, 0);
+
+	bochs_dispi_write(bochs, VBE_DISPI_INDEX_BPP,		fb->bits_per_pixel);
+	bochs_dispi_write(bochs, VBE_DISPI_INDEX_XRES,		fb->xres);
+	bochs_dispi_write(bochs, VBE_DISPI_INDEX_YRES,		fb->yres);
+	bochs_dispi_write(bochs, VBE_DISPI_INDEX_BANK,		0);
+	bochs_dispi_write(bochs, VBE_DISPI_INDEX_VIRT_WIDTH,	fb->xres);
+	bochs_dispi_write(bochs, VBE_DISPI_INDEX_VIRT_HEIGHT,	fb->yres);
+	bochs_dispi_write(bochs, VBE_DISPI_INDEX_X_OFFSET,	0);
+	bochs_dispi_write(bochs, VBE_DISPI_INDEX_Y_OFFSET,	0);
+
+	bochs_dispi_write(bochs, VBE_DISPI_INDEX_ENABLE,
+			  VBE_DISPI_ENABLED | VBE_DISPI_LFB_ENABLED );
+}
+
+static void bochs_fb_disable(struct fb_info *fb)
+{
+	struct bochs *bochs = fb->priv;
+
+	bochs_dispi_write(bochs, VBE_DISPI_INDEX_ENABLE,
+			 bochs_dispi_read(bochs, VBE_DISPI_INDEX_ENABLE) &
+			 ~VBE_DISPI_ENABLED);
+}
+
+static struct fb_ops bochs_fb_ops = {
+	.fb_enable = bochs_fb_enable,
+	.fb_disable = bochs_fb_disable,
+};
+
+static int bochs_hw_load_edid(struct bochs *bochs)
+{
+	u8 *edid;
+	int i;
+
+	edid = xzalloc(EDID_LENGTH);
+
+	for (i = 0; i <= EDID_HEADER_END; i++)
+		edid[i] = readb(bochs->mmio + i);
+
+	/* check header to detect whenever edid support is enabled in qemu */
+	if (!edid_check_header(edid)) {
+		free(edid);
+		return -1;
+	}
+
+	for (i = EDID_HEADER_END + 1; i < EDID_LENGTH; i++)
+		edid[i] = readb(bochs->mmio + i);
+
+	bochs->fb.edid_data = edid;
+	return 0;
+}
+
+static int bochs_hw_read_version(struct bochs *bochs)
+{
+	u16 ver;
+
+	ver = bochs_dispi_read(bochs, VBE_DISPI_INDEX_ID);
+
+	if ((ver & 0xB0C0) != 0xB0C0)
+		return -ENODEV;
+
+	return ver & 0xF;
+}
+
+int bochs_hw_probe(struct device_d *dev, void __iomem *fb_map, void __iomem *mmio)
+{
+	struct bochs *bochs;
+	struct fb_info *fb;
+	int ret;
+
+	bochs = xzalloc(sizeof(*bochs));
+
+	bochs->fb_map	= IOMEM(fb_map);
+	bochs->mmio	= IOMEM(mmio);
+
+	ret = bochs_hw_read_version(bochs);
+	if (ret < 0) {
+		free(bochs);
+		return ret;
+	}
+
+	dev_info(dev, "detected bochs dispi v%u\n", ret);
+
+	fb = &bochs->fb;
+	fb->screen_base = bochs->fb_map;
+
+	fb->bits_per_pixel = 16;
+	fb->red.length = 5;
+	fb->green.length = 6;
+	fb->blue.length = 5;
+	fb->red.offset = 11;
+	fb->green.offset = 5;
+	fb->blue.offset = 0;
+
+	/* EDID is only exposed over PCI */
+	ret = -ENODEV;
+
+	if (mmio) {
+		ret = bochs_hw_load_edid(bochs);
+		if (ret)
+			dev_warn(dev, "couldn't read EDID information\n");
+	}
+
+	if (ret) {
+		fb->mode = xzalloc(sizeof(*fb->mode));
+		fb->modes.modes = fb->mode;
+		fb->modes.num_modes = 1;
+
+		fb->mode->name = "640x480";
+		fb->mode->xres = 640;
+		fb->mode->yres = 480;
+	}
+
+	fb->priv = bochs;
+	fb->fbops = &bochs_fb_ops;
+
+	return register_framebuffer(fb);
+}
diff --git a/drivers/video/bochs/bochs_hw.h b/drivers/video/bochs/bochs_hw.h
new file mode 100644
index 000000000000..36c2cc1cc376
--- /dev/null
+++ b/drivers/video/bochs/bochs_hw.h
@@ -0,0 +1,13 @@
+#ifndef BOCHS_HW_H
+#define BOCHS_HW_H
+
+#include <linux/compiler.h>
+
+#define VBE_DISPI_IOPORT_INDEX           0x01CE
+#define VBE_DISPI_IOPORT_DATA            0x01CF
+
+struct device_d;
+
+int bochs_hw_probe(struct device_d *dev, void *fb_map, void __iomem *mmio);
+
+#endif
diff --git a/drivers/video/bochs/bochs_isa.c b/drivers/video/bochs/bochs_isa.c
new file mode 100644
index 000000000000..7f75803baa0c
--- /dev/null
+++ b/drivers/video/bochs/bochs_isa.c
@@ -0,0 +1,31 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+// SPDX-FileCopyrightText: Copyright (c) 2020 Ahmad Fatoum, Pengutronix
+/*
+ *  ISA Driver for VGA with the Bochs VBE / QEMU stdvga extensions.
+ */
+
+#include <common.h>
+#include <driver.h>
+#include <linux/ioport.h>
+#include "bochs_hw.h"
+
+static int bochs_isa_detect(void)
+{
+	struct device_d *dev;
+	int ret;
+
+	outw(0, VBE_DISPI_IOPORT_INDEX);
+	ret = inw(VBE_DISPI_IOPORT_DATA);
+
+	if ((ret & 0xB0C0) != 0xB0C0)
+		return -ENODEV;
+
+	dev = device_alloc("bochs-dispi", 0);
+
+	ret = platform_device_register(dev);
+	if (ret)
+		return ret;
+
+	return bochs_hw_probe(dev, (void *)0xe0000000, NULL);
+}
+device_initcall(bochs_isa_detect);
diff --git a/drivers/video/bochs/bochs_pci.c b/drivers/video/bochs/bochs_pci.c
new file mode 100644
index 000000000000..39f582029d35
--- /dev/null
+++ b/drivers/video/bochs/bochs_pci.c
@@ -0,0 +1,37 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+// SPDX-FileCopyrightText: Copyright (c) 2020 Ahmad Fatoum, Pengutronix
+/*
+ *  PCI Driver for VGA with the Bochs VBE / QEMU stdvga extensions.
+ */
+
+#include <common.h>
+#include <driver.h>
+#include <linux/pci.h>
+#include "bochs_hw.h"
+
+static int bochs_pci_probe(struct pci_dev *pdev, const struct pci_device_id *ent)
+{
+	void __iomem *fb_map, *mmio;
+	int ret;
+
+	ret = pci_enable_device(pdev);
+	if (ret)
+		return ret;
+
+	fb_map	= pci_iomap(pdev, 0);
+	mmio	= pci_iomap(pdev, 2);
+
+	return bochs_hw_probe(&pdev->dev, fb_map, mmio);
+}
+
+static DEFINE_PCI_DEVICE_TABLE(bochs_pci_tbl) = {
+	/* https://github.com/qemu/qemu/blob/master/docs/specs/standard-vga.txt */
+	{ PCI_DEVICE(0x1234, 0x1111) },
+};
+
+static struct pci_driver bochs_pci_driver = {
+	.name		= "bochs-dispi",
+	.probe		= bochs_pci_probe,
+	.id_table	= bochs_pci_tbl,
+};
+device_pci_driver(bochs_pci_driver);
-- 
2.29.2




More information about the barebox mailing list