vga: add edid support, qxl bugfixes.

-----BEGIN PGP SIGNATURE-----
 Version: GnuPG v2.0.22 (GNU/Linux)
 
 iQIcBAABAgAGBQJbrILgAAoJEEy22O7T6HE4y9UP/isFQYYHjotebzYR8yNX9ukD
 JjtQt2yIMPSb6k/93UK+MvKxrURH/jNXLGYC68YY74YJGx612cEhjiDdUdW6yf1T
 Qs3Y8Qz5EEQ7yO0Sv4uPp3IoZTMCRrnAri8J8r5N1z7Exm6CMlCQwmTuxA+UYOEA
 JJ6Lz4h9zw8eyM+wLUPc4jkbw9juyEhV3yhzXrH3iELanxVxano6umn/SkTTcLXh
 qydCvHyHYnLF6tUJQSssIBjQuUySiA6yCzzM1cinjYqu8Dm37kFPpgWg/uPclqbe
 cQ0CPeyGdFU+ZQpfWRwQiFe498U3QLG/fTdO82XMgoItZa29Vr47fO1WyOly8SGs
 bcGNrzcgToyRasLXHoGQrRpr7PK+0PCUJrPmrpejkviAaX6R4sBZ4xuuirW69UQR
 AgG7BxbpgjCl+A8+sjJgjn1vTR9bT0sb8DG875j58osAVje3ZFf7Ln6I3CTQrrRf
 wrjldNT0/nOV4WK1QAPE085aEihFzO1MHDaoSDT+AkNv0idrJjGxx/HjCpY8mF4u
 YfKBjhxCDmvgVtT+mG9akv7VDfyReD+iqhoDA1hovWamH7E/QMgvl5rNpie7r0Qf
 914mcojOxIKG8OlbBhWVkpvTTCh1Qfzlgb7jffCwU+1RUZ/9lC41aIXmyuUoB2Ld
 G4qtPXaDJzpdZKaUieD9
 =P3hQ
 -----END PGP SIGNATURE-----

Merge remote-tracking branch 'remotes/kraxel/tags/vga-20180927-pull-request' into staging

vga: add edid support, qxl bugfixes.

# gpg: Signature made Thu 27 Sep 2018 08:12:32 BST
# gpg:                using RSA key 4CB6D8EED3E87138
# gpg: Good signature from "Gerd Hoffmann (work) <kraxel@redhat.com>"
# gpg:                 aka "Gerd Hoffmann <gerd@kraxel.org>"
# gpg:                 aka "Gerd Hoffmann (private) <kraxel@gmail.com>"
# Primary key fingerprint: A032 8CFF B93A 17A7 9901  FE7D 4CB6 D8EE D3E8 7138

* remotes/kraxel/tags/vga-20180927-pull-request:
  qxl: support mono cursors with inverted colors
  qxl: use guest_monitor_config for local renderer.
  display/stdvga: add edid support.
  display/edid: add DEFINE_EDID_PROPERTIES
  display/edid: add region helper.
  display/edid: add qemu_edid_size()
  display/edid: add edid generator to qemu.

Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
This commit is contained in:
Peter Maydell 2018-10-01 11:33:16 +01:00
commit cc28dce2ec
16 changed files with 745 additions and 17 deletions

View File

@ -1403,6 +1403,13 @@ S: Odd Fixes
W: https://www.kraxel.org/blog/2014/10/qemu-using-cirrus-considered-harmful/ W: https://www.kraxel.org/blog/2014/10/qemu-using-cirrus-considered-harmful/
F: hw/display/cirrus* F: hw/display/cirrus*
EDID Generator
M: Gerd Hoffmann <kraxel@redhat.com>
S: Maintained
F: hw/display/edid*
F: include/hw/display/edid.h
F: qemu-edid.c
Subsystems Subsystems
---------- ----------
Audio Audio

View File

@ -543,6 +543,8 @@ qemu-bridge-helper$(EXESUF): qemu-bridge-helper.o $(COMMON_LDADDS)
qemu-keymap$(EXESUF): qemu-keymap.o ui/input-keymap.o $(COMMON_LDADDS) qemu-keymap$(EXESUF): qemu-keymap.o ui/input-keymap.o $(COMMON_LDADDS)
qemu-edid$(EXESUF): qemu-edid.o hw/display/edid-generate.o $(COMMON_LDADDS)
fsdev/virtfs-proxy-helper$(EXESUF): fsdev/virtfs-proxy-helper.o fsdev/9p-marshal.o fsdev/9p-iov-marshal.o $(COMMON_LDADDS) fsdev/virtfs-proxy-helper$(EXESUF): fsdev/virtfs-proxy-helper.o fsdev/9p-marshal.o fsdev/9p-iov-marshal.o $(COMMON_LDADDS)
fsdev/virtfs-proxy-helper$(EXESUF): LIBS += -lcap fsdev/virtfs-proxy-helper$(EXESUF): LIBS += -lcap

2
configure vendored
View File

@ -5714,7 +5714,7 @@ fi
tools="" tools=""
if test "$want_tools" = "yes" ; then if test "$want_tools" = "yes" ; then
tools="qemu-img\$(EXESUF) qemu-io\$(EXESUF) $tools" tools="qemu-img\$(EXESUF) qemu-io\$(EXESUF) qemu-edid\$(EXESUF) $tools"
if [ "$linux" = "yes" -o "$bsd" = "yes" -o "$solaris" = "yes" ] ; then if [ "$linux" = "yes" -o "$bsd" = "yes" -o "$solaris" = "yes" ] ; then
tools="qemu-nbd\$(EXESUF) $tools" tools="qemu-nbd\$(EXESUF) $tools"
fi fi

View File

@ -61,7 +61,7 @@ MMIO area spec
Likewise applies to the pci variant only for obvious reasons. Likewise applies to the pci variant only for obvious reasons.
0000 - 03ff : reserved, for possible virtio extension. 0000 - 03ff : edid data blob.
0400 - 041f : vga ioports (0x3c0 -> 0x3df), remapped 1:1. 0400 - 041f : vga ioports (0x3c0 -> 0x3df), remapped 1:1.
word access is supported, bytes are written word access is supported, bytes are written
in little endia order (aka index port first), in little endia order (aka index port first),

View File

@ -1,3 +1,5 @@
common-obj-y += edid-generate.o
common-obj-$(CONFIG_FW_CFG_DMA) += ramfb.o common-obj-$(CONFIG_FW_CFG_DMA) += ramfb.o
common-obj-$(CONFIG_FW_CFG_DMA) += ramfb-standalone.o common-obj-$(CONFIG_FW_CFG_DMA) += ramfb-standalone.o
@ -13,6 +15,7 @@ common-obj-$(CONFIG_XEN) += xenfb.o
common-obj-$(CONFIG_VGA_PCI) += vga-pci.o common-obj-$(CONFIG_VGA_PCI) += vga-pci.o
common-obj-$(CONFIG_VGA_PCI) += bochs-display.o common-obj-$(CONFIG_VGA_PCI) += bochs-display.o
common-obj-$(CONFIG_VGA_PCI) += edid-region.o
common-obj-$(CONFIG_VGA_ISA) += vga-isa.o common-obj-$(CONFIG_VGA_ISA) += vga-isa.o
common-obj-$(CONFIG_VGA_ISA_MM) += vga-isa-mm.o common-obj-$(CONFIG_VGA_ISA_MM) += vga-isa-mm.o
common-obj-$(CONFIG_VMWARE_VGA) += vmware_vga.o common-obj-$(CONFIG_VMWARE_VGA) += vmware_vga.o

439
hw/display/edid-generate.c Normal file
View File

@ -0,0 +1,439 @@
/*
* QEMU EDID generator.
*
* This work is licensed under the terms of the GNU GPL, version 2 or later.
* See the COPYING file in the top-level directory.
*/
#include "qemu/osdep.h"
#include "qemu-common.h"
#include "qemu/bswap.h"
#include "hw/display/edid.h"
static const struct edid_mode {
uint32_t xres;
uint32_t yres;
uint32_t byte;
uint32_t xtra3;
uint32_t bit;
uint32_t dta;
} modes[] = {
/* dea/dta extension timings (all @ 50 Hz) */
{ .xres = 5120, .yres = 2160, .dta = 125 },
{ .xres = 4096, .yres = 2160, .dta = 101 },
{ .xres = 3840, .yres = 2160, .dta = 96 },
{ .xres = 2560, .yres = 1080, .dta = 89 },
{ .xres = 2048, .yres = 1152 },
{ .xres = 1920, .yres = 1080, .dta = 31 },
/* additional standard timings 3 (all @ 60Hz) */
{ .xres = 1920, .yres = 1440, .xtra3 = 11, .bit = 5 },
{ .xres = 1920, .yres = 1200, .xtra3 = 10, .bit = 0 },
{ .xres = 1856, .yres = 1392, .xtra3 = 10, .bit = 3 },
{ .xres = 1792, .yres = 1344, .xtra3 = 10, .bit = 5 },
{ .xres = 1600, .yres = 1200, .xtra3 = 9, .bit = 2 },
{ .xres = 1680, .yres = 1050, .xtra3 = 9, .bit = 5 },
{ .xres = 1440, .yres = 1050, .xtra3 = 8, .bit = 1 },
{ .xres = 1440, .yres = 900, .xtra3 = 8, .bit = 5 },
{ .xres = 1360, .yres = 768, .xtra3 = 8, .bit = 7 },
{ .xres = 1280, .yres = 1024, .xtra3 = 7, .bit = 1 },
{ .xres = 1280, .yres = 960, .xtra3 = 7, .bit = 3 },
{ .xres = 1280, .yres = 768, .xtra3 = 7, .bit = 6 },
/* established timings (all @ 60Hz) */
{ .xres = 1024, .yres = 768, .byte = 36, .bit = 3 },
{ .xres = 800, .yres = 600, .byte = 35, .bit = 0 },
{ .xres = 640, .yres = 480, .byte = 35, .bit = 5 },
};
static void edid_ext_dta(uint8_t *dta)
{
dta[0] = 0x02;
dta[1] = 0x03;
dta[2] = 0x05;
dta[3] = 0x00;
/* video data block */
dta[4] = 0x40;
}
static void edid_ext_dta_mode(uint8_t *dta, uint8_t nr)
{
dta[dta[2]] = nr;
dta[2]++;
dta[4]++;
}
static int edid_std_mode(uint8_t *mode, uint32_t xres, uint32_t yres)
{
uint32_t aspect;
if (xres == 0 || yres == 0) {
mode[0] = 0x01;
mode[1] = 0x01;
return 0;
} else if (xres * 10 == yres * 16) {
aspect = 0;
} else if (xres * 3 == yres * 4) {
aspect = 1;
} else if (xres * 4 == yres * 5) {
aspect = 2;
} else if (xres * 9 == yres * 16) {
aspect = 3;
} else {
return -1;
}
if ((xres / 8) - 31 > 255) {
return -1;
}
mode[0] = (xres / 8) - 31;
mode[1] = ((aspect << 6) | (60 - 60));
return 0;
}
static void edid_fill_modes(uint8_t *edid, uint8_t *xtra3, uint8_t *dta,
uint32_t maxx, uint32_t maxy)
{
const struct edid_mode *mode;
int std = 38;
int rc, i;
for (i = 0; i < ARRAY_SIZE(modes); i++) {
mode = modes + i;
if ((maxx && mode->xres > maxx) ||
(maxy && mode->yres > maxy)) {
continue;
}
if (mode->byte) {
edid[mode->byte] |= (1 << mode->bit);
} else if (mode->xtra3 && xtra3) {
xtra3[mode->xtra3] |= (1 << mode->bit);
} else if (std < 54) {
rc = edid_std_mode(edid + std, mode->xres, mode->yres);
if (rc == 0) {
std += 2;
}
}
if (dta && mode->dta) {
edid_ext_dta_mode(dta, mode->dta);
}
}
while (std < 54) {
edid_std_mode(edid + std, 0, 0);
std += 2;
}
}
static void edid_checksum(uint8_t *edid)
{
uint32_t sum = 0;
int i;
for (i = 0; i < 127; i++) {
sum += edid[i];
}
sum &= 0xff;
if (sum) {
edid[127] = 0x100 - sum;
}
}
static void edid_desc_type(uint8_t *desc, uint8_t type)
{
desc[0] = 0;
desc[1] = 0;
desc[2] = 0;
desc[3] = type;
desc[4] = 0;
}
static void edid_desc_text(uint8_t *desc, uint8_t type,
const char *text)
{
size_t len;
edid_desc_type(desc, type);
memset(desc + 5, ' ', 13);
len = strlen(text);
if (len > 12) {
len = 12;
}
strncpy((char *)(desc + 5), text, len);
desc[5 + len] = '\n';
}
static void edid_desc_ranges(uint8_t *desc)
{
edid_desc_type(desc, 0xfd);
/* vertical (50 -> 125 Hz) */
desc[5] = 50;
desc[6] = 125;
/* horizontal (30 -> 160 kHz) */
desc[7] = 30;
desc[8] = 160;
/* max dot clock (1200 MHz) */
desc[9] = 1200 / 10;
/* no extended timing information */
desc[10] = 0x01;
/* padding */
desc[11] = '\n';
memset(desc + 12, ' ', 6);
}
/* additional standard timings 3 */
static void edid_desc_xtra3_std(uint8_t *desc)
{
edid_desc_type(desc, 0xf7);
desc[5] = 10;
}
static void edid_desc_dummy(uint8_t *desc)
{
edid_desc_type(desc, 0x10);
}
static void edid_desc_timing(uint8_t *desc,
uint32_t xres, uint32_t yres,
uint32_t dpi)
{
/* physical display size */
uint32_t xmm = xres * dpi / 254;
uint32_t ymm = yres * dpi / 254;
/* pull some realistic looking timings out of thin air */
uint32_t xfront = xres * 25 / 100;
uint32_t xsync = xres * 3 / 100;
uint32_t xblank = xres * 35 / 100;
uint32_t yfront = yres * 5 / 1000;
uint32_t ysync = yres * 5 / 1000;
uint32_t yblank = yres * 35 / 1000;
uint32_t clock = 75 * (xres + xblank) * (yres + yblank);
*(uint32_t *)(desc) = cpu_to_le32(clock / 10000);
desc[2] = xres & 0xff;
desc[3] = xblank & 0xff;
desc[4] = (((xres & 0xf00) >> 4) |
((xblank & 0xf00) >> 8));
desc[5] = yres & 0xff;
desc[6] = yblank & 0xff;
desc[7] = (((yres & 0xf00) >> 4) |
((yblank & 0xf00) >> 8));
desc[8] = xfront & 0xff;
desc[9] = xsync & 0xff;
desc[10] = (((yfront & 0x00f) << 4) |
((ysync & 0x00f) << 0));
desc[11] = (((xfront & 0x300) >> 2) |
((xsync & 0x300) >> 4) |
((yfront & 0x030) >> 2) |
((ysync & 0x030) >> 4));
desc[12] = xmm & 0xff;
desc[13] = ymm & 0xff;
desc[14] = (((xmm & 0xf00) >> 4) |
((ymm & 0xf00) >> 8));
desc[17] = 0x18;
}
static uint32_t edid_to_10bit(float value)
{
return (uint32_t)(value * 1024 + 0.5);
}
static void edid_colorspace(uint8_t *edid,
float rx, float ry,
float gx, float gy,
float bx, float by,
float wx, float wy)
{
uint32_t red_x = edid_to_10bit(rx);
uint32_t red_y = edid_to_10bit(ry);
uint32_t green_x = edid_to_10bit(gx);
uint32_t green_y = edid_to_10bit(gy);
uint32_t blue_x = edid_to_10bit(bx);
uint32_t blue_y = edid_to_10bit(by);
uint32_t white_x = edid_to_10bit(wx);
uint32_t white_y = edid_to_10bit(wy);
edid[25] = (((red_x & 0x03) << 6) |
((red_y & 0x03) << 4) |
((green_x & 0x03) << 2) |
((green_y & 0x03) << 0));
edid[26] = (((blue_x & 0x03) << 6) |
((blue_y & 0x03) << 4) |
((white_x & 0x03) << 2) |
((white_y & 0x03) << 0));
edid[27] = red_x >> 2;
edid[28] = red_y >> 2;
edid[29] = green_x >> 2;
edid[30] = green_y >> 2;
edid[31] = blue_x >> 2;
edid[32] = blue_y >> 2;
edid[33] = white_x >> 2;
edid[34] = white_y >> 2;
}
void qemu_edid_generate(uint8_t *edid, size_t size,
qemu_edid_info *info)
{
uint32_t desc = 54;
uint8_t *xtra3 = NULL;
uint8_t *dta = NULL;
/* =============== set defaults =============== */
if (!info->vendor || strlen(info->vendor) != 3) {
info->vendor = "EMU";
}
if (!info->name) {
info->name = "QEMU Monitor";
}
if (!info->dpi) {
info->dpi = 100;
}
if (!info->prefx) {
info->prefx = 1024;
}
if (!info->prefy) {
info->prefy = 768;
}
/* =============== extensions =============== */
if (size >= 256) {
dta = edid + 128;
edid[126]++;
edid_ext_dta(dta);
}
/* =============== header information =============== */
/* fixed */
edid[0] = 0x00;
edid[1] = 0xff;
edid[2] = 0xff;
edid[3] = 0xff;
edid[4] = 0xff;
edid[5] = 0xff;
edid[6] = 0xff;
edid[7] = 0x00;
/* manufacturer id, product code, serial number */
uint16_t vendor_id = ((((info->vendor[0] - '@') & 0x1f) << 10) |
(((info->vendor[1] - '@') & 0x1f) << 5) |
(((info->vendor[2] - '@') & 0x1f) << 0));
uint16_t model_nr = 0x1234;
uint32_t serial_nr = info->serial ? atoi(info->serial) : 0;
*(uint16_t *)(edid + 8) = cpu_to_be16(vendor_id);
*(uint16_t *)(edid + 10) = cpu_to_le16(model_nr);
*(uint32_t *)(edid + 12) = cpu_to_le32(serial_nr);
/* manufacture week and year */
edid[16] = 42;
edid[17] = 2014 - 1990;
/* edid version */
edid[18] = 1;
edid[19] = 4;
/* =============== basic display parameters =============== */
/* video input: digital, 8bpc, displayport */
edid[20] = 0xa5;
/* screen size: undefined */
edid[21] = info->prefx * info->dpi / 2540;
edid[22] = info->prefy * info->dpi / 2540;
/* display gamma: 2.2 */
edid[23] = 220 - 100;
/* supported features bitmap: std sRGB, preferred timing */
edid[24] = 0x06;
/* =============== chromaticity coordinates =============== */
/* standard sRGB colorspace */
edid_colorspace(edid,
0.6400, 0.3300, /* red */
0.3000, 0.6000, /* green */
0.1500, 0.0600, /* blue */
0.3127, 0.3290); /* white point */
/* =============== established timing bitmap =============== */
/* =============== standard timing information =============== */
/* both filled by edid_fill_modes() */
/* =============== descriptor blocks =============== */
edid_desc_timing(edid + desc, info->prefx, info->prefy, info->dpi);
desc += 18;
edid_desc_ranges(edid + desc);
desc += 18;
if (info->name) {
edid_desc_text(edid + desc, 0xfc, info->name);
desc += 18;
}
if (info->serial) {
edid_desc_text(edid + desc, 0xff, info->serial);
desc += 18;
}
if (desc < 126) {
xtra3 = edid + desc;
edid_desc_xtra3_std(xtra3);
desc += 18;
}
while (desc < 126) {
edid_desc_dummy(edid + desc);
desc += 18;
}
/* =============== finish up =============== */
edid_fill_modes(edid, xtra3, dta, info->maxx, info->maxy);
edid_checksum(edid);
if (dta) {
edid_checksum(dta);
}
}
size_t qemu_edid_size(uint8_t *edid)
{
uint32_t exts;
if (edid[0] != 0x00 ||
edid[1] != 0xff) {
/* doesn't look like a valid edid block */
return 0;
}
exts = edid[126];
return 128 * (exts + 1);
}

33
hw/display/edid-region.c Normal file
View File

@ -0,0 +1,33 @@
#include "qemu/osdep.h"
#include "qemu-common.h"
#include "hw/display/edid.h"
static uint64_t edid_region_read(void *ptr, hwaddr addr, unsigned size)
{
uint8_t *edid = ptr;
return edid[addr];
}
static void edid_region_write(void *ptr, hwaddr addr,
uint64_t val, unsigned size)
{
/* read only */
}
static const MemoryRegionOps edid_region_ops = {
.read = edid_region_read,
.write = edid_region_write,
.valid.min_access_size = 1,
.valid.max_access_size = 4,
.impl.min_access_size = 1,
.impl.max_access_size = 1,
.endianness = DEVICE_LITTLE_ENDIAN,
};
void qemu_edid_region_io(MemoryRegion *region, Object *owner,
uint8_t *edid, size_t size)
{
memory_region_init_io(region, owner, &edid_region_ops,
edid, "edid", size);
}

View File

@ -98,6 +98,8 @@ static void qxl_render_update_area_unlocked(PCIQXLDevice *qxl)
{ {
VGACommonState *vga = &qxl->vga; VGACommonState *vga = &qxl->vga;
DisplaySurface *surface; DisplaySurface *surface;
int width = qxl->guest_head0_width ?: qxl->guest_primary.surface.width;
int height = qxl->guest_head0_height ?: qxl->guest_primary.surface.height;
int i; int i;
if (qxl->guest_primary.resized) { if (qxl->guest_primary.resized) {
@ -111,8 +113,8 @@ static void qxl_render_update_area_unlocked(PCIQXLDevice *qxl)
qxl_set_rect_to_surface(qxl, &qxl->dirty[0]); qxl_set_rect_to_surface(qxl, &qxl->dirty[0]);
qxl->num_dirty_rects = 1; qxl->num_dirty_rects = 1;
trace_qxl_render_guest_primary_resized( trace_qxl_render_guest_primary_resized(
qxl->guest_primary.surface.width, width,
qxl->guest_primary.surface.height, height,
qxl->guest_primary.qxl_stride, qxl->guest_primary.qxl_stride,
qxl->guest_primary.bytes_pp, qxl->guest_primary.bytes_pp,
qxl->guest_primary.bits_pp); qxl->guest_primary.bits_pp);
@ -120,15 +122,15 @@ static void qxl_render_update_area_unlocked(PCIQXLDevice *qxl)
pixman_format_code_t format = pixman_format_code_t format =
qemu_default_pixman_format(qxl->guest_primary.bits_pp, true); qemu_default_pixman_format(qxl->guest_primary.bits_pp, true);
surface = qemu_create_displaysurface_from surface = qemu_create_displaysurface_from
(qxl->guest_primary.surface.width, (width,
qxl->guest_primary.surface.height, height,
format, format,
qxl->guest_primary.abs_stride, qxl->guest_primary.abs_stride,
qxl->guest_primary.data); qxl->guest_primary.data);
} else { } else {
surface = qemu_create_displaysurface surface = qemu_create_displaysurface
(qxl->guest_primary.surface.width, (width,
qxl->guest_primary.surface.height); height);
} }
dpy_gfx_replace_surface(vga->con, surface); dpy_gfx_replace_surface(vga->con, surface);
} }
@ -144,8 +146,8 @@ static void qxl_render_update_area_unlocked(PCIQXLDevice *qxl)
qxl->dirty[i].top < 0 || qxl->dirty[i].top < 0 ||
qxl->dirty[i].left > qxl->dirty[i].right || qxl->dirty[i].left > qxl->dirty[i].right ||
qxl->dirty[i].top > qxl->dirty[i].bottom || qxl->dirty[i].top > qxl->dirty[i].bottom ||
qxl->dirty[i].right > qxl->guest_primary.surface.width || qxl->dirty[i].right > width ||
qxl->dirty[i].bottom > qxl->guest_primary.surface.height) { qxl->dirty[i].bottom > height) {
continue; continue;
} }
qxl_blit(qxl, qxl->dirty+i); qxl_blit(qxl, qxl->dirty+i);
@ -234,12 +236,28 @@ static QEMUCursor *qxl_cursor(PCIQXLDevice *qxl, QXLCursor *cursor,
uint32_t group_id) uint32_t group_id)
{ {
QEMUCursor *c; QEMUCursor *c;
uint8_t *and_mask, *xor_mask;
size_t size; size_t size;
c = cursor_alloc(cursor->header.width, cursor->header.height); c = cursor_alloc(cursor->header.width, cursor->header.height);
c->hot_x = cursor->header.hot_spot_x; c->hot_x = cursor->header.hot_spot_x;
c->hot_y = cursor->header.hot_spot_y; c->hot_y = cursor->header.hot_spot_y;
switch (cursor->header.type) { switch (cursor->header.type) {
case SPICE_CURSOR_TYPE_MONO:
/* Assume that the full cursor is available in a single chunk. */
size = 2 * cursor_get_mono_bpl(c) * c->height;
if (size != cursor->data_size) {
fprintf(stderr, "%s: bad monochrome cursor %ux%u with size %u\n",
__func__, c->width, c->height, cursor->data_size);
goto fail;
}
and_mask = cursor->chunk.data;
xor_mask = and_mask + cursor_get_mono_bpl(c) * c->height;
cursor_set_mono(c, 0xffffff, 0x000000, xor_mask, 1, and_mask);
if (qxl->debug > 2) {
cursor_print_ascii_art(c, "qxl/mono");
}
break;
case SPICE_CURSOR_TYPE_ALPHA: case SPICE_CURSOR_TYPE_ALPHA:
size = sizeof(uint32_t) * cursor->header.width * cursor->header.height; size = sizeof(uint32_t) * cursor->header.width * cursor->header.height;
qxl_unpack_chunks(c->data, size, qxl, &cursor->chunk, group_id); qxl_unpack_chunks(c->data, size, qxl, &cursor->chunk, group_id);

View File

@ -259,6 +259,8 @@ static void qxl_spice_destroy_surfaces(PCIQXLDevice *qxl, qxl_async_io async)
static void qxl_spice_monitors_config_async(PCIQXLDevice *qxl, int replay) static void qxl_spice_monitors_config_async(PCIQXLDevice *qxl, int replay)
{ {
QXLMonitorsConfig *cfg;
trace_qxl_spice_monitors_config(qxl->id); trace_qxl_spice_monitors_config(qxl->id);
if (replay) { if (replay) {
/* /*
@ -286,6 +288,16 @@ static void qxl_spice_monitors_config_async(PCIQXLDevice *qxl, int replay)
(uintptr_t)qxl_cookie_new(QXL_COOKIE_TYPE_IO, (uintptr_t)qxl_cookie_new(QXL_COOKIE_TYPE_IO,
QXL_IO_MONITORS_CONFIG_ASYNC)); QXL_IO_MONITORS_CONFIG_ASYNC));
} }
cfg = qxl_phys2virt(qxl, qxl->guest_monitors_config, MEMSLOT_GROUP_GUEST);
if (cfg->count == 1) {
qxl->guest_primary.resized = 1;
qxl->guest_head0_width = cfg->heads[0].width;
qxl->guest_head0_height = cfg->heads[0].height;
} else {
qxl->guest_head0_width = 0;
qxl->guest_head0_height = 0;
}
} }
void qxl_spice_reset_image_cache(PCIQXLDevice *qxl) void qxl_spice_reset_image_cache(PCIQXLDevice *qxl)

View File

@ -78,6 +78,8 @@ typedef struct PCIQXLDevice {
QXLPHYSICAL guest_cursor; QXLPHYSICAL guest_cursor;
QXLPHYSICAL guest_monitors_config; QXLPHYSICAL guest_monitors_config;
uint32_t guest_head0_width;
uint32_t guest_head0_height;
QemuMutex track_lock; QemuMutex track_lock;

View File

@ -30,18 +30,22 @@
#include "ui/pixel_ops.h" #include "ui/pixel_ops.h"
#include "qemu/timer.h" #include "qemu/timer.h"
#include "hw/loader.h" #include "hw/loader.h"
#include "hw/display/edid.h"
enum vga_pci_flags { enum vga_pci_flags {
PCI_VGA_FLAG_ENABLE_MMIO = 1, PCI_VGA_FLAG_ENABLE_MMIO = 1,
PCI_VGA_FLAG_ENABLE_QEXT = 2, PCI_VGA_FLAG_ENABLE_QEXT = 2,
PCI_VGA_FLAG_ENABLE_EDID = 3,
}; };
typedef struct PCIVGAState { typedef struct PCIVGAState {
PCIDevice dev; PCIDevice dev;
VGACommonState vga; VGACommonState vga;
uint32_t flags; uint32_t flags;
qemu_edid_info edid_info;
MemoryRegion mmio; MemoryRegion mmio;
MemoryRegion mrs[3]; MemoryRegion mrs[4];
uint8_t edid[256];
} PCIVGAState; } PCIVGAState;
#define TYPE_PCI_VGA "pci-vga" #define TYPE_PCI_VGA "pci-vga"
@ -195,8 +199,10 @@ void pci_std_vga_mmio_region_init(VGACommonState *s,
Object *owner, Object *owner,
MemoryRegion *parent, MemoryRegion *parent,
MemoryRegion *subs, MemoryRegion *subs,
bool qext) bool qext, bool edid)
{ {
PCIVGAState *d = container_of(s, PCIVGAState, vga);
memory_region_init_io(&subs[0], owner, &pci_vga_ioport_ops, s, memory_region_init_io(&subs[0], owner, &pci_vga_ioport_ops, s,
"vga ioports remapped", PCI_VGA_IOPORT_SIZE); "vga ioports remapped", PCI_VGA_IOPORT_SIZE);
memory_region_add_subregion(parent, PCI_VGA_IOPORT_OFFSET, memory_region_add_subregion(parent, PCI_VGA_IOPORT_OFFSET,
@ -213,6 +219,12 @@ void pci_std_vga_mmio_region_init(VGACommonState *s,
memory_region_add_subregion(parent, PCI_VGA_QEXT_OFFSET, memory_region_add_subregion(parent, PCI_VGA_QEXT_OFFSET,
&subs[2]); &subs[2]);
} }
if (edid) {
qemu_edid_generate(d->edid, sizeof(d->edid), &d->edid_info);
qemu_edid_region_io(&subs[3], owner, d->edid, sizeof(d->edid));
memory_region_add_subregion(parent, 0, &subs[3]);
}
} }
static void pci_std_vga_realize(PCIDevice *dev, Error **errp) static void pci_std_vga_realize(PCIDevice *dev, Error **errp)
@ -220,6 +232,7 @@ static void pci_std_vga_realize(PCIDevice *dev, Error **errp)
PCIVGAState *d = PCI_VGA(dev); PCIVGAState *d = PCI_VGA(dev);
VGACommonState *s = &d->vga; VGACommonState *s = &d->vga;
bool qext = false; bool qext = false;
bool edid = false;
/* vga + console init */ /* vga + console init */
vga_common_init(s, OBJECT(dev)); vga_common_init(s, OBJECT(dev));
@ -240,7 +253,11 @@ static void pci_std_vga_realize(PCIDevice *dev, Error **errp)
qext = true; qext = true;
pci_set_byte(&d->dev.config[PCI_REVISION_ID], 2); pci_set_byte(&d->dev.config[PCI_REVISION_ID], 2);
} }
pci_std_vga_mmio_region_init(s, OBJECT(dev), &d->mmio, d->mrs, qext); if (d->flags & (1 << PCI_VGA_FLAG_ENABLE_EDID)) {
edid = true;
}
pci_std_vga_mmio_region_init(s, OBJECT(dev), &d->mmio, d->mrs,
qext, edid);
pci_register_bar(&d->dev, 2, PCI_BASE_ADDRESS_SPACE_MEMORY, &d->mmio); pci_register_bar(&d->dev, 2, PCI_BASE_ADDRESS_SPACE_MEMORY, &d->mmio);
} }
@ -263,6 +280,7 @@ static void pci_secondary_vga_realize(PCIDevice *dev, Error **errp)
PCIVGAState *d = PCI_VGA(dev); PCIVGAState *d = PCI_VGA(dev);
VGACommonState *s = &d->vga; VGACommonState *s = &d->vga;
bool qext = false; bool qext = false;
bool edid = false;
/* vga + console init */ /* vga + console init */
vga_common_init(s, OBJECT(dev)); vga_common_init(s, OBJECT(dev));
@ -276,7 +294,10 @@ static void pci_secondary_vga_realize(PCIDevice *dev, Error **errp)
qext = true; qext = true;
pci_set_byte(&d->dev.config[PCI_REVISION_ID], 2); pci_set_byte(&d->dev.config[PCI_REVISION_ID], 2);
} }
pci_std_vga_mmio_region_init(s, OBJECT(dev), &d->mmio, d->mrs, qext); if (d->flags & (1 << PCI_VGA_FLAG_ENABLE_EDID)) {
edid = true;
}
pci_std_vga_mmio_region_init(s, OBJECT(dev), &d->mmio, d->mrs, qext, edid);
pci_register_bar(&d->dev, 0, PCI_BASE_ADDRESS_MEM_PREFETCH, &s->vram); pci_register_bar(&d->dev, 0, PCI_BASE_ADDRESS_MEM_PREFETCH, &s->vram);
pci_register_bar(&d->dev, 2, PCI_BASE_ADDRESS_SPACE_MEMORY, &d->mmio); pci_register_bar(&d->dev, 2, PCI_BASE_ADDRESS_SPACE_MEMORY, &d->mmio);
@ -308,6 +329,9 @@ static Property vga_pci_properties[] = {
DEFINE_PROP_BIT("mmio", PCIVGAState, flags, PCI_VGA_FLAG_ENABLE_MMIO, true), DEFINE_PROP_BIT("mmio", PCIVGAState, flags, PCI_VGA_FLAG_ENABLE_MMIO, true),
DEFINE_PROP_BIT("qemu-extended-regs", DEFINE_PROP_BIT("qemu-extended-regs",
PCIVGAState, flags, PCI_VGA_FLAG_ENABLE_QEXT, true), PCIVGAState, flags, PCI_VGA_FLAG_ENABLE_QEXT, true),
DEFINE_PROP_BIT("edid",
PCIVGAState, flags, PCI_VGA_FLAG_ENABLE_EDID, false),
DEFINE_EDID_PROPERTIES(PCIVGAState, edid_info),
DEFINE_PROP_BOOL("global-vmstate", PCIVGAState, vga.global_vmstate, false), DEFINE_PROP_BOOL("global-vmstate", PCIVGAState, vga.global_vmstate, false),
DEFINE_PROP_END_OF_LIST(), DEFINE_PROP_END_OF_LIST(),
}; };
@ -316,6 +340,9 @@ static Property secondary_pci_properties[] = {
DEFINE_PROP_UINT32("vgamem_mb", PCIVGAState, vga.vram_size_mb, 16), DEFINE_PROP_UINT32("vgamem_mb", PCIVGAState, vga.vram_size_mb, 16),
DEFINE_PROP_BIT("qemu-extended-regs", DEFINE_PROP_BIT("qemu-extended-regs",
PCIVGAState, flags, PCI_VGA_FLAG_ENABLE_QEXT, true), PCIVGAState, flags, PCI_VGA_FLAG_ENABLE_QEXT, true),
DEFINE_PROP_BIT("edid",
PCIVGAState, flags, PCI_VGA_FLAG_ENABLE_EDID, false),
DEFINE_EDID_PROPERTIES(PCIVGAState, edid_info),
DEFINE_PROP_END_OF_LIST(), DEFINE_PROP_END_OF_LIST(),
}; };

View File

@ -197,6 +197,6 @@ void pci_std_vga_mmio_region_init(VGACommonState *s,
Object *owner, Object *owner,
MemoryRegion *parent, MemoryRegion *parent,
MemoryRegion *subs, MemoryRegion *subs,
bool qext); bool qext, bool edid);
#endif #endif

View File

@ -153,7 +153,7 @@ static void virtio_vga_realize(VirtIOPCIProxy *vpci_dev, Error **errp)
/* add stdvga mmio regions */ /* add stdvga mmio regions */
pci_std_vga_mmio_region_init(vga, OBJECT(vvga), &vpci_dev->modern_bar, pci_std_vga_mmio_region_init(vga, OBJECT(vvga), &vpci_dev->modern_bar,
vvga->vga_mrs, true); vvga->vga_mrs, true, false);
vga->con = g->scanout[0].con; vga->con = g->scanout[0].con;
graphic_console_set_hwops(vga->con, &virtio_vga_ops, vvga); graphic_console_set_hwops(vga->con, &virtio_vga_ops, vvga);

27
include/hw/display/edid.h Normal file
View File

@ -0,0 +1,27 @@
#ifndef EDID_H
#define EDID_H
#include "hw/hw.h"
typedef struct qemu_edid_info {
const char *vendor;
const char *name;
const char *serial;
uint32_t dpi;
uint32_t prefx;
uint32_t prefy;
uint32_t maxx;
uint32_t maxy;
} qemu_edid_info;
void qemu_edid_generate(uint8_t *edid, size_t size,
qemu_edid_info *info);
size_t qemu_edid_size(uint8_t *edid);
void qemu_edid_region_io(MemoryRegion *region, Object *owner,
uint8_t *edid, size_t size);
#define DEFINE_EDID_PROPERTIES(_state, _edid_info) \
DEFINE_PROP_UINT32("xres", _state, _edid_info.prefx, 0), \
DEFINE_PROP_UINT32("yres", _state, _edid_info.prefy, 0)
#endif /* EDID_H */

120
qemu-edid.c Normal file
View File

@ -0,0 +1,120 @@
/*
* QEMU EDID test tool.
*
* This work is licensed under the terms of the GNU GPL, version 2 or later.
* See the COPYING file in the top-level directory.
*/
#include "qemu/osdep.h"
#include "qemu-common.h"
#include "qemu/bswap.h"
#include "qemu/cutils.h"
#include "hw/display/edid.h"
static qemu_edid_info info;
static void usage(FILE *out)
{
fprintf(out,
"\n"
"This is a test tool for the qemu edid generator.\n"
"\n"
"Typically you'll pipe the output into edid-decode\n"
"to check if the generator works correctly.\n"
"\n"
"usage: qemu-edid <options>\n"
"options:\n"
" -h print this text\n"
" -o <file> set output file (stdout by default)\n"
" -v <vendor> set monitor vendor (three letters)\n"
" -n <name> set monitor name\n"
" -s <serial> set monitor serial\n"
" -d <dpi> set display resolution\n"
" -x <prefx> set preferred width\n"
" -y <prefy> set preferred height\n"
" -X <maxx> set maximum width\n"
" -Y <maxy> set maximum height\n"
"\n");
}
int main(int argc, char *argv[])
{
FILE *outfile = NULL;
uint8_t blob[256];
int rc;
for (;;) {
rc = getopt(argc, argv, "ho:x:y:X:Y:d:v:n:s:");
if (rc == -1) {
break;
}
switch (rc) {
case 'o':
if (outfile) {
fprintf(stderr, "outfile specified twice\n");
exit(1);
}
outfile = fopen(optarg, "w");
if (outfile == NULL) {
fprintf(stderr, "open %s: %s\n", optarg, strerror(errno));
exit(1);
}
break;
case 'x':
if (qemu_strtoui(optarg, NULL, 10, &info.prefx) < 0) {
fprintf(stderr, "not a number: %s\n", optarg);
exit(1);
}
break;
case 'y':
if (qemu_strtoui(optarg, NULL, 10, &info.prefy) < 0) {
fprintf(stderr, "not a number: %s\n", optarg);
exit(1);
}
break;
case 'X':
if (qemu_strtoui(optarg, NULL, 10, &info.maxx) < 0) {
fprintf(stderr, "not a number: %s\n", optarg);
exit(1);
}
break;
case 'Y':
if (qemu_strtoui(optarg, NULL, 10, &info.maxy) < 0) {
fprintf(stderr, "not a number: %s\n", optarg);
exit(1);
}
break;
case 'd':
if (qemu_strtoui(optarg, NULL, 10, &info.dpi) < 0) {
fprintf(stderr, "not a number: %s\n", optarg);
exit(1);
}
break;
case 'v':
info.vendor = optarg;
break;
case 'n':
info.name = optarg;
break;
case 's':
info.serial = optarg;
break;
case 'h':
usage(stdout);
exit(0);
default:
usage(stderr);
exit(1);
}
}
if (outfile == NULL) {
outfile = stdout;
}
memset(blob, 0, sizeof(blob));
qemu_edid_generate(blob, sizeof(blob), &info);
fwrite(blob, sizeof(blob), 1, outfile);
fflush(outfile);
exit(0);
}

View File

@ -128,13 +128,25 @@ void cursor_set_mono(QEMUCursor *c,
uint32_t *data = c->data; uint32_t *data = c->data;
uint8_t bit; uint8_t bit;
int x,y,bpl; int x,y,bpl;
bool expand_bitmap_only = image == mask;
bool has_inverted_colors = false;
const uint32_t inverted = 0x80000000;
/*
* Converts a monochrome bitmap with XOR mask 'image' and AND mask 'mask':
* https://docs.microsoft.com/en-us/windows-hardware/drivers/display/drawing-monochrome-pointers
*/
bpl = cursor_get_mono_bpl(c); bpl = cursor_get_mono_bpl(c);
for (y = 0; y < c->height; y++) { for (y = 0; y < c->height; y++) {
bit = 0x80; bit = 0x80;
for (x = 0; x < c->width; x++, data++) { for (x = 0; x < c->width; x++, data++) {
if (transparent && mask[x/8] & bit) { if (transparent && mask[x/8] & bit) {
*data = 0x00000000; if (!expand_bitmap_only && image[x / 8] & bit) {
*data = inverted;
has_inverted_colors = true;
} else {
*data = 0x00000000;
}
} else if (!transparent && !(mask[x/8] & bit)) { } else if (!transparent && !(mask[x/8] & bit)) {
*data = 0x00000000; *data = 0x00000000;
} else if (image[x/8] & bit) { } else if (image[x/8] & bit) {
@ -150,6 +162,32 @@ void cursor_set_mono(QEMUCursor *c,
mask += bpl; mask += bpl;
image += bpl; image += bpl;
} }
/*
* If there are any pixels with inverted colors, create an outline (fill
* transparent neighbors with the background color) and use the foreground
* color as "inverted" color.
*/
if (has_inverted_colors) {
data = c->data;
for (y = 0; y < c->height; y++) {
for (x = 0; x < c->width; x++, data++) {
if (*data == 0 /* transparent */ &&
((x > 0 && data[-1] == inverted) ||
(x + 1 < c->width && data[1] == inverted) ||
(y > 0 && data[-c->width] == inverted) ||
(y + 1 < c->height && data[c->width] == inverted))) {
*data = 0xff000000 | background;
}
}
}
data = c->data;
for (x = 0; x < c->width * c->height; x++, data++) {
if (*data == inverted) {
*data = 0xff000000 | foreground;
}
}
}
} }
void cursor_get_mono_image(QEMUCursor *c, int foreground, uint8_t *image) void cursor_get_mono_image(QEMUCursor *c, int foreground, uint8_t *image)