diff options
Diffstat (limited to 'hw/display')
43 files changed, 25854 insertions, 0 deletions
diff --git a/hw/display/Makefile.objs b/hw/display/Makefile.objs new file mode 100644 index 0000000000..3f7027d46b --- /dev/null +++ b/hw/display/Makefile.objs @@ -0,0 +1,34 @@ +common-obj-$(CONFIG_ADS7846) += ads7846.o +common-obj-$(CONFIG_VGA_CIRRUS) += cirrus_vga.o +common-obj-$(CONFIG_G364FB) += g364fb.o +common-obj-$(CONFIG_JAZZ_LED) += jazz_led.o +common-obj-$(CONFIG_PL110) += pl110.o +common-obj-$(CONFIG_SSD0303) += ssd0303.o +common-obj-$(CONFIG_SSD0323) += ssd0323.o +common-obj-$(CONFIG_XEN_BACKEND) += xenfb.o + +common-obj-$(CONFIG_VGA_PCI) += vga-pci.o +common-obj-$(CONFIG_VGA_ISA) += vga-isa.o +common-obj-$(CONFIG_VGA_ISA_MM) += vga-isa-mm.o +common-obj-$(CONFIG_VMWARE_VGA) += vmware_vga.o + +common-obj-$(CONFIG_BLIZZARD) += blizzard.o +common-obj-$(CONFIG_EXYNOS4) += exynos4210_fimd.o +common-obj-$(CONFIG_FRAMEBUFFER) += framebuffer.o +common-obj-$(CONFIG_MILKYMIST) += milkymist-vgafb.o +common-obj-$(CONFIG_ZAURUS) += tc6393xb.o + +ifeq ($(CONFIG_GLX),y) +common-obj-$(CONFIG_MILKYMIST) += milkymist-tmu2.o +endif + +obj-$(CONFIG_OMAP) += omap_dss.o +obj-$(CONFIG_OMAP) += omap_lcdc.o +obj-$(CONFIG_PXA2XX) += pxa2xx_lcd.o +obj-$(CONFIG_SM501) += sm501.o +obj-$(CONFIG_TCX) += tcx.o + +obj-$(CONFIG_VGA) += vga.o + +common-obj-$(CONFIG_QXL) += qxl-logger.o qxl-render.o +obj-$(CONFIG_QXL) += qxl.o diff --git a/hw/display/ads7846.c b/hw/display/ads7846.c new file mode 100644 index 0000000000..5da3dc5b2c --- /dev/null +++ b/hw/display/ads7846.c @@ -0,0 +1,177 @@ +/* + * TI ADS7846 / TSC2046 chip emulation. + * + * Copyright (c) 2006 Openedhand Ltd. + * Written by Andrzej Zaborowski <balrog@zabor.org> + * + * This code is licensed under the GNU GPL v2. + * + * Contributions after 2012-01-13 are licensed under the terms of the + * GNU GPL, version 2 or (at your option) any later version. + */ + +#include "hw/ssi.h" +#include "ui/console.h" + +typedef struct { + SSISlave ssidev; + qemu_irq interrupt; + + int input[8]; + int pressure; + int noise; + + int cycle; + int output; +} ADS7846State; + +/* Control-byte bitfields */ +#define CB_PD0 (1 << 0) +#define CB_PD1 (1 << 1) +#define CB_SER (1 << 2) +#define CB_MODE (1 << 3) +#define CB_A0 (1 << 4) +#define CB_A1 (1 << 5) +#define CB_A2 (1 << 6) +#define CB_START (1 << 7) + +#define X_AXIS_DMAX 3470 +#define X_AXIS_MIN 290 +#define Y_AXIS_DMAX 3450 +#define Y_AXIS_MIN 200 + +#define ADS_VBAT 2000 +#define ADS_VAUX 2000 +#define ADS_TEMP0 2000 +#define ADS_TEMP1 3000 +#define ADS_XPOS(x, y) (X_AXIS_MIN + ((X_AXIS_DMAX * (x)) >> 15)) +#define ADS_YPOS(x, y) (Y_AXIS_MIN + ((Y_AXIS_DMAX * (y)) >> 15)) +#define ADS_Z1POS(x, y) 600 +#define ADS_Z2POS(x, y) (600 + 6000 / ADS_XPOS(x, y)) + +static void ads7846_int_update(ADS7846State *s) +{ + if (s->interrupt) + qemu_set_irq(s->interrupt, s->pressure == 0); +} + +static uint32_t ads7846_transfer(SSISlave *dev, uint32_t value) +{ + ADS7846State *s = FROM_SSI_SLAVE(ADS7846State, dev); + + switch (s->cycle ++) { + case 0: + if (!(value & CB_START)) { + s->cycle = 0; + break; + } + + s->output = s->input[(value >> 4) & 7]; + + /* Imitate the ADC noise, some drivers expect this. */ + s->noise = (s->noise + 3) & 7; + switch ((value >> 4) & 7) { + case 1: s->output += s->noise ^ 2; break; + case 3: s->output += s->noise ^ 0; break; + case 4: s->output += s->noise ^ 7; break; + case 5: s->output += s->noise ^ 5; break; + } + + if (value & CB_MODE) + s->output >>= 4; /* 8 bits instead of 12 */ + + break; + case 1: + s->cycle = 0; + break; + } + return s->output; +} + +static void ads7846_ts_event(void *opaque, + int x, int y, int z, int buttons_state) +{ + ADS7846State *s = opaque; + + if (buttons_state) { + x = 0x7fff - x; + s->input[1] = ADS_XPOS(x, y); + s->input[3] = ADS_Z1POS(x, y); + s->input[4] = ADS_Z2POS(x, y); + s->input[5] = ADS_YPOS(x, y); + } + + if (s->pressure == !buttons_state) { + s->pressure = !!buttons_state; + + ads7846_int_update(s); + } +} + +static int ads7856_post_load(void *opaque, int version_id) +{ + ADS7846State *s = opaque; + + s->pressure = 0; + ads7846_int_update(s); + return 0; +} + +static const VMStateDescription vmstate_ads7846 = { + .name = "ads7846", + .version_id = 1, + .minimum_version_id = 1, + .minimum_version_id_old = 1, + .post_load = ads7856_post_load, + .fields = (VMStateField[]) { + VMSTATE_SSI_SLAVE(ssidev, ADS7846State), + VMSTATE_INT32_ARRAY(input, ADS7846State, 8), + VMSTATE_INT32(noise, ADS7846State), + VMSTATE_INT32(cycle, ADS7846State), + VMSTATE_INT32(output, ADS7846State), + VMSTATE_END_OF_LIST() + } +}; + +static int ads7846_init(SSISlave *dev) +{ + ADS7846State *s = FROM_SSI_SLAVE(ADS7846State, dev); + + qdev_init_gpio_out(&dev->qdev, &s->interrupt, 1); + + s->input[0] = ADS_TEMP0; /* TEMP0 */ + s->input[2] = ADS_VBAT; /* VBAT */ + s->input[6] = ADS_VAUX; /* VAUX */ + s->input[7] = ADS_TEMP1; /* TEMP1 */ + + /* We want absolute coordinates */ + qemu_add_mouse_event_handler(ads7846_ts_event, s, 1, + "QEMU ADS7846-driven Touchscreen"); + + ads7846_int_update(s); + + vmstate_register(NULL, -1, &vmstate_ads7846, s); + return 0; +} + +static void ads7846_class_init(ObjectClass *klass, void *data) +{ + SSISlaveClass *k = SSI_SLAVE_CLASS(klass); + + k->init = ads7846_init; + k->transfer = ads7846_transfer; +} + +static const TypeInfo ads7846_info = { + .name = "ads7846", + .parent = TYPE_SSI_SLAVE, + .instance_size = sizeof(ADS7846State), + .class_init = ads7846_class_init, +}; + +static void ads7846_register_types(void) +{ + type_register_static(&ads7846_info); +} + +type_init(ads7846_register_types) diff --git a/hw/display/blizzard.c b/hw/display/blizzard.c new file mode 100644 index 0000000000..175c5cdd00 --- /dev/null +++ b/hw/display/blizzard.c @@ -0,0 +1,1004 @@ +/* + * Epson S1D13744/S1D13745 (Blizzard/Hailstorm/Tornado) LCD/TV controller. + * + * Copyright (C) 2008 Nokia Corporation + * Written by Andrzej Zaborowski <andrew@openedhand.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 or + * (at your option) version 3 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, see <http://www.gnu.org/licenses/>. + */ + +#include "qemu-common.h" +#include "ui/console.h" +#include "hw/arm/devices.h" +#include "vga_int.h" +#include "ui/pixel_ops.h" + +typedef void (*blizzard_fn_t)(uint8_t *, const uint8_t *, unsigned int); + +typedef struct { + uint8_t reg; + uint32_t addr; + int swallow; + + int pll; + int pll_range; + int pll_ctrl; + uint8_t pll_mode; + uint8_t clksel; + int memenable; + int memrefresh; + uint8_t timing[3]; + int priority; + + uint8_t lcd_config; + int x; + int y; + int skipx; + int skipy; + uint8_t hndp; + uint8_t vndp; + uint8_t hsync; + uint8_t vsync; + uint8_t pclk; + uint8_t u; + uint8_t v; + uint8_t yrc[2]; + int ix[2]; + int iy[2]; + int ox[2]; + int oy[2]; + + int enable; + int blank; + int bpp; + int invalidate; + int mx[2]; + int my[2]; + uint8_t mode; + uint8_t effect; + uint8_t iformat; + uint8_t source; + QemuConsole *con; + blizzard_fn_t *line_fn_tab[2]; + void *fb; + + uint8_t hssi_config[3]; + uint8_t tv_config; + uint8_t tv_timing[4]; + uint8_t vbi; + uint8_t tv_x; + uint8_t tv_y; + uint8_t tv_test; + uint8_t tv_filter_config; + uint8_t tv_filter_idx; + uint8_t tv_filter_coeff[0x20]; + uint8_t border_r; + uint8_t border_g; + uint8_t border_b; + uint8_t gamma_config; + uint8_t gamma_idx; + uint8_t gamma_lut[0x100]; + uint8_t matrix_ena; + uint8_t matrix_coeff[0x12]; + uint8_t matrix_r; + uint8_t matrix_g; + uint8_t matrix_b; + uint8_t pm; + uint8_t status; + uint8_t rgbgpio_dir; + uint8_t rgbgpio; + uint8_t gpio_dir; + uint8_t gpio; + uint8_t gpio_edge[2]; + uint8_t gpio_irq; + uint8_t gpio_pdown; + + struct { + int x; + int y; + int dx; + int dy; + int len; + int buflen; + void *buf; + void *data; + uint16_t *ptr; + int angle; + int pitch; + blizzard_fn_t line_fn; + } data; +} BlizzardState; + +/* Bytes(!) per pixel */ +static const int blizzard_iformat_bpp[0x10] = { + 0, + 2, /* RGB 5:6:5*/ + 3, /* RGB 6:6:6 mode 1 */ + 3, /* RGB 8:8:8 mode 1 */ + 0, 0, + 4, /* RGB 6:6:6 mode 2 */ + 4, /* RGB 8:8:8 mode 2 */ + 0, /* YUV 4:2:2 */ + 0, /* YUV 4:2:0 */ + 0, 0, 0, 0, 0, 0, +}; + +static inline void blizzard_rgb2yuv(int r, int g, int b, + int *y, int *u, int *v) +{ + *y = 0x10 + ((0x838 * r + 0x1022 * g + 0x322 * b) >> 13); + *u = 0x80 + ((0xe0e * b - 0x04c1 * r - 0x94e * g) >> 13); + *v = 0x80 + ((0xe0e * r - 0x0bc7 * g - 0x247 * b) >> 13); +} + +static void blizzard_window(BlizzardState *s) +{ + DisplaySurface *surface = qemu_console_surface(s->con); + uint8_t *src, *dst; + int bypp[2]; + int bypl[3]; + int y; + blizzard_fn_t fn = s->data.line_fn; + + if (!fn) + return; + if (s->mx[0] > s->data.x) + s->mx[0] = s->data.x; + if (s->my[0] > s->data.y) + s->my[0] = s->data.y; + if (s->mx[1] < s->data.x + s->data.dx) + s->mx[1] = s->data.x + s->data.dx; + if (s->my[1] < s->data.y + s->data.dy) + s->my[1] = s->data.y + s->data.dy; + + bypp[0] = s->bpp; + bypp[1] = surface_bytes_per_pixel(surface); + bypl[0] = bypp[0] * s->data.pitch; + bypl[1] = bypp[1] * s->x; + bypl[2] = bypp[0] * s->data.dx; + + src = s->data.data; + dst = s->fb + bypl[1] * s->data.y + bypp[1] * s->data.x; + for (y = s->data.dy; y > 0; y --, src += bypl[0], dst += bypl[1]) + fn(dst, src, bypl[2]); +} + +static int blizzard_transfer_setup(BlizzardState *s) +{ + if (s->source > 3 || !s->bpp || + s->ix[1] < s->ix[0] || s->iy[1] < s->iy[0]) + return 0; + + s->data.angle = s->effect & 3; + s->data.line_fn = s->line_fn_tab[!!s->data.angle][s->iformat]; + s->data.x = s->ix[0]; + s->data.y = s->iy[0]; + s->data.dx = s->ix[1] - s->ix[0] + 1; + s->data.dy = s->iy[1] - s->iy[0] + 1; + s->data.len = s->bpp * s->data.dx * s->data.dy; + s->data.pitch = s->data.dx; + if (s->data.len > s->data.buflen) { + s->data.buf = g_realloc(s->data.buf, s->data.len); + s->data.buflen = s->data.len; + } + s->data.ptr = s->data.buf; + s->data.data = s->data.buf; + s->data.len /= 2; + return 1; +} + +static void blizzard_reset(BlizzardState *s) +{ + s->reg = 0; + s->swallow = 0; + + s->pll = 9; + s->pll_range = 1; + s->pll_ctrl = 0x14; + s->pll_mode = 0x32; + s->clksel = 0x00; + s->memenable = 0; + s->memrefresh = 0x25c; + s->timing[0] = 0x3f; + s->timing[1] = 0x13; + s->timing[2] = 0x21; + s->priority = 0; + + s->lcd_config = 0x74; + s->x = 8; + s->y = 1; + s->skipx = 0; + s->skipy = 0; + s->hndp = 3; + s->vndp = 2; + s->hsync = 1; + s->vsync = 1; + s->pclk = 0x80; + + s->ix[0] = 0; + s->ix[1] = 0; + s->iy[0] = 0; + s->iy[1] = 0; + s->ox[0] = 0; + s->ox[1] = 0; + s->oy[0] = 0; + s->oy[1] = 0; + + s->yrc[0] = 0x00; + s->yrc[1] = 0x30; + s->u = 0; + s->v = 0; + + s->iformat = 3; + s->source = 0; + s->bpp = blizzard_iformat_bpp[s->iformat]; + + s->hssi_config[0] = 0x00; + s->hssi_config[1] = 0x00; + s->hssi_config[2] = 0x01; + s->tv_config = 0x00; + s->tv_timing[0] = 0x00; + s->tv_timing[1] = 0x00; + s->tv_timing[2] = 0x00; + s->tv_timing[3] = 0x00; + s->vbi = 0x10; + s->tv_x = 0x14; + s->tv_y = 0x03; + s->tv_test = 0x00; + s->tv_filter_config = 0x80; + s->tv_filter_idx = 0x00; + s->border_r = 0x10; + s->border_g = 0x80; + s->border_b = 0x80; + s->gamma_config = 0x00; + s->gamma_idx = 0x00; + s->matrix_ena = 0x00; + memset(&s->matrix_coeff, 0, sizeof(s->matrix_coeff)); + s->matrix_r = 0x00; + s->matrix_g = 0x00; + s->matrix_b = 0x00; + s->pm = 0x02; + s->status = 0x00; + s->rgbgpio_dir = 0x00; + s->gpio_dir = 0x00; + s->gpio_edge[0] = 0x00; + s->gpio_edge[1] = 0x00; + s->gpio_irq = 0x00; + s->gpio_pdown = 0xff; +} + +static inline void blizzard_invalidate_display(void *opaque) { + BlizzardState *s = (BlizzardState *) opaque; + + s->invalidate = 1; +} + +static uint16_t blizzard_reg_read(void *opaque, uint8_t reg) +{ + BlizzardState *s = (BlizzardState *) opaque; + + switch (reg) { + case 0x00: /* Revision Code */ + return 0xa5; + + case 0x02: /* Configuration Readback */ + return 0x83; /* Macrovision OK, CNF[2:0] = 3 */ + + case 0x04: /* PLL M-Divider */ + return (s->pll - 1) | (1 << 7); + case 0x06: /* PLL Lock Range Control */ + return s->pll_range; + case 0x08: /* PLL Lock Synthesis Control 0 */ + return s->pll_ctrl & 0xff; + case 0x0a: /* PLL Lock Synthesis Control 1 */ + return s->pll_ctrl >> 8; + case 0x0c: /* PLL Mode Control 0 */ + return s->pll_mode; + + case 0x0e: /* Clock-Source Select */ + return s->clksel; + + case 0x10: /* Memory Controller Activate */ + case 0x14: /* Memory Controller Bank 0 Status Flag */ + return s->memenable; + + case 0x18: /* Auto-Refresh Interval Setting 0 */ + return s->memrefresh & 0xff; + case 0x1a: /* Auto-Refresh Interval Setting 1 */ + return s->memrefresh >> 8; + + case 0x1c: /* Power-On Sequence Timing Control */ + return s->timing[0]; + case 0x1e: /* Timing Control 0 */ + return s->timing[1]; + case 0x20: /* Timing Control 1 */ + return s->timing[2]; + + case 0x24: /* Arbitration Priority Control */ + return s->priority; + + case 0x28: /* LCD Panel Configuration */ + return s->lcd_config; + + case 0x2a: /* LCD Horizontal Display Width */ + return s->x >> 3; + case 0x2c: /* LCD Horizontal Non-display Period */ + return s->hndp; + case 0x2e: /* LCD Vertical Display Height 0 */ + return s->y & 0xff; + case 0x30: /* LCD Vertical Display Height 1 */ + return s->y >> 8; + case 0x32: /* LCD Vertical Non-display Period */ + return s->vndp; + case 0x34: /* LCD HS Pulse-width */ + return s->hsync; + case 0x36: /* LCd HS Pulse Start Position */ + return s->skipx >> 3; + case 0x38: /* LCD VS Pulse-width */ + return s->vsync; + case 0x3a: /* LCD VS Pulse Start Position */ + return s->skipy; + + case 0x3c: /* PCLK Polarity */ + return s->pclk; + + case 0x3e: /* High-speed Serial Interface Tx Configuration Port 0 */ + return s->hssi_config[0]; + case 0x40: /* High-speed Serial Interface Tx Configuration Port 1 */ + return s->hssi_config[1]; + case 0x42: /* High-speed Serial Interface Tx Mode */ + return s->hssi_config[2]; + case 0x44: /* TV Display Configuration */ + return s->tv_config; + case 0x46 ... 0x4c: /* TV Vertical Blanking Interval Data bits */ + return s->tv_timing[(reg - 0x46) >> 1]; + case 0x4e: /* VBI: Closed Caption / XDS Control / Status */ + return s->vbi; + case 0x50: /* TV Horizontal Start Position */ + return s->tv_x; + case 0x52: /* TV Vertical Start Position */ + return s->tv_y; + case 0x54: /* TV Test Pattern Setting */ + return s->tv_test; + case 0x56: /* TV Filter Setting */ + return s->tv_filter_config; + case 0x58: /* TV Filter Coefficient Index */ + return s->tv_filter_idx; + case 0x5a: /* TV Filter Coefficient Data */ + if (s->tv_filter_idx < 0x20) + return s->tv_filter_coeff[s->tv_filter_idx ++]; + return 0; + + case 0x60: /* Input YUV/RGB Translate Mode 0 */ + return s->yrc[0]; + case 0x62: /* Input YUV/RGB Translate Mode 1 */ + return s->yrc[1]; + case 0x64: /* U Data Fix */ + return s->u; + case 0x66: /* V Data Fix */ + return s->v; + + case 0x68: /* Display Mode */ + return s->mode; + + case 0x6a: /* Special Effects */ + return s->effect; + + case 0x6c: /* Input Window X Start Position 0 */ + return s->ix[0] & 0xff; + case 0x6e: /* Input Window X Start Position 1 */ + return s->ix[0] >> 3; + case 0x70: /* Input Window Y Start Position 0 */ + return s->ix[0] & 0xff; + case 0x72: /* Input Window Y Start Position 1 */ + return s->ix[0] >> 3; + case 0x74: /* Input Window X End Position 0 */ + return s->ix[1] & 0xff; + case 0x76: /* Input Window X End Position 1 */ + return s->ix[1] >> 3; + case 0x78: /* Input Window Y End Position 0 */ + return s->ix[1] & 0xff; + case 0x7a: /* Input Window Y End Position 1 */ + return s->ix[1] >> 3; + case 0x7c: /* Output Window X Start Position 0 */ + return s->ox[0] & 0xff; + case 0x7e: /* Output Window X Start Position 1 */ + return s->ox[0] >> 3; + case 0x80: /* Output Window Y Start Position 0 */ + return s->oy[0] & 0xff; + case 0x82: /* Output Window Y Start Position 1 */ + return s->oy[0] >> 3; + case 0x84: /* Output Window X End Position 0 */ + return s->ox[1] & 0xff; + case 0x86: /* Output Window X End Position 1 */ + return s->ox[1] >> 3; + case 0x88: /* Output Window Y End Position 0 */ + return s->oy[1] & 0xff; + case 0x8a: /* Output Window Y End Position 1 */ + return s->oy[1] >> 3; + + case 0x8c: /* Input Data Format */ + return s->iformat; + case 0x8e: /* Data Source Select */ + return s->source; + case 0x90: /* Display Memory Data Port */ + return 0; + + case 0xa8: /* Border Color 0 */ + return s->border_r; + case 0xaa: /* Border Color 1 */ + return s->border_g; + case 0xac: /* Border Color 2 */ + return s->border_b; + + case 0xb4: /* Gamma Correction Enable */ + return s->gamma_config; + case 0xb6: /* Gamma Correction Table Index */ + return s->gamma_idx; + case 0xb8: /* Gamma Correction Table Data */ + return s->gamma_lut[s->gamma_idx ++]; + + case 0xba: /* 3x3 Matrix Enable */ + return s->matrix_ena; + case 0xbc ... 0xde: /* Coefficient Registers */ + return s->matrix_coeff[(reg - 0xbc) >> 1]; + case 0xe0: /* 3x3 Matrix Red Offset */ + return s->matrix_r; + case 0xe2: /* 3x3 Matrix Green Offset */ + return s->matrix_g; + case 0xe4: /* 3x3 Matrix Blue Offset */ + return s->matrix_b; + + case 0xe6: /* Power-save */ + return s->pm; + case 0xe8: /* Non-display Period Control / Status */ + return s->status | (1 << 5); + case 0xea: /* RGB Interface Control */ + return s->rgbgpio_dir; + case 0xec: /* RGB Interface Status */ + return s->rgbgpio; + case 0xee: /* General-purpose IO Pins Configuration */ + return s->gpio_dir; + case 0xf0: /* General-purpose IO Pins Status / Control */ + return s->gpio; + case 0xf2: /* GPIO Positive Edge Interrupt Trigger */ + return s->gpio_edge[0]; + case 0xf4: /* GPIO Negative Edge Interrupt Trigger */ + return s->gpio_edge[1]; + case 0xf6: /* GPIO Interrupt Status */ + return s->gpio_irq; + case 0xf8: /* GPIO Pull-down Control */ + return s->gpio_pdown; + + default: + fprintf(stderr, "%s: unknown register %02x\n", __FUNCTION__, reg); + return 0; + } +} + +static void blizzard_reg_write(void *opaque, uint8_t reg, uint16_t value) +{ + BlizzardState *s = (BlizzardState *) opaque; + + switch (reg) { + case 0x04: /* PLL M-Divider */ + s->pll = (value & 0x3f) + 1; + break; + case 0x06: /* PLL Lock Range Control */ + s->pll_range = value & 3; + break; + case 0x08: /* PLL Lock Synthesis Control 0 */ + s->pll_ctrl &= 0xf00; + s->pll_ctrl |= (value << 0) & 0x0ff; + break; + case 0x0a: /* PLL Lock Synthesis Control 1 */ + s->pll_ctrl &= 0x0ff; + s->pll_ctrl |= (value << 8) & 0xf00; + break; + case 0x0c: /* PLL Mode Control 0 */ + s->pll_mode = value & 0x77; + if ((value & 3) == 0 || (value & 3) == 3) + fprintf(stderr, "%s: wrong PLL Control bits (%i)\n", + __FUNCTION__, value & 3); + break; + + case 0x0e: /* Clock-Source Select */ + s->clksel = value & 0xff; + break; + + case 0x10: /* Memory Controller Activate */ + s->memenable = value & 1; + break; + case 0x14: /* Memory Controller Bank 0 Status Flag */ + break; + + case 0x18: /* Auto-Refresh Interval Setting 0 */ + s->memrefresh &= 0xf00; + s->memrefresh |= (value << 0) & 0x0ff; + break; + case 0x1a: /* Auto-Refresh Interval Setting 1 */ + s->memrefresh &= 0x0ff; + s->memrefresh |= (value << 8) & 0xf00; + break; + + case 0x1c: /* Power-On Sequence Timing Control */ + s->timing[0] = value & 0x7f; + break; + case 0x1e: /* Timing Control 0 */ + s->timing[1] = value & 0x17; + break; + case 0x20: /* Timing Control 1 */ + s->timing[2] = value & 0x35; + break; + + case 0x24: /* Arbitration Priority Control */ + s->priority = value & 1; + break; + + case 0x28: /* LCD Panel Configuration */ + s->lcd_config = value & 0xff; + if (value & (1 << 7)) + fprintf(stderr, "%s: data swap not supported!\n", __FUNCTION__); + break; + + case 0x2a: /* LCD Horizontal Display Width */ + s->x = value << 3; + break; + case 0x2c: /* LCD Horizontal Non-display Period */ + s->hndp = value & 0xff; + break; + case 0x2e: /* LCD Vertical Display Height 0 */ + s->y &= 0x300; + s->y |= (value << 0) & 0x0ff; + break; + case 0x30: /* LCD Vertical Display Height 1 */ + s->y &= 0x0ff; + s->y |= (value << 8) & 0x300; + break; + case 0x32: /* LCD Vertical Non-display Period */ + s->vndp = value & 0xff; + break; + case 0x34: /* LCD HS Pulse-width */ + s->hsync = value & 0xff; + break; + case 0x36: /* LCD HS Pulse Start Position */ + s->skipx = value & 0xff; + break; + case 0x38: /* LCD VS Pulse-width */ + s->vsync = value & 0xbf; + break; + case 0x3a: /* LCD VS Pulse Start Position */ + s->skipy = value & 0xff; + break; + + case 0x3c: /* PCLK Polarity */ + s->pclk = value & 0x82; + /* Affects calculation of s->hndp, s->hsync and s->skipx. */ + break; + + case 0x3e: /* High-speed Serial Interface Tx Configuration Port 0 */ + s->hssi_config[0] = value; + break; + case 0x40: /* High-speed Serial Interface Tx Configuration Port 1 */ + s->hssi_config[1] = value; + if (((value >> 4) & 3) == 3) + fprintf(stderr, "%s: Illegal active-data-links value\n", + __FUNCTION__); + break; + case 0x42: /* High-speed Serial Interface Tx Mode */ + s->hssi_config[2] = value & 0xbd; + break; + + case 0x44: /* TV Display Configuration */ + s->tv_config = value & 0xfe; + break; + case 0x46 ... 0x4c: /* TV Vertical Blanking Interval Data bits 0 */ + s->tv_timing[(reg - 0x46) >> 1] = value; + break; + case 0x4e: /* VBI: Closed Caption / XDS Control / Status */ + s->vbi = value; + break; + case 0x50: /* TV Horizontal Start Position */ + s->tv_x = value; + break; + case 0x52: /* TV Vertical Start Position */ + s->tv_y = value & 0x7f; + break; + case 0x54: /* TV Test Pattern Setting */ + s->tv_test = value; + break; + case 0x56: /* TV Filter Setting */ + s->tv_filter_config = value & 0xbf; + break; + case 0x58: /* TV Filter Coefficient Index */ + s->tv_filter_idx = value & 0x1f; + break; + case 0x5a: /* TV Filter Coefficient Data */ + if (s->tv_filter_idx < 0x20) + s->tv_filter_coeff[s->tv_filter_idx ++] = value; + break; + + case 0x60: /* Input YUV/RGB Translate Mode 0 */ + s->yrc[0] = value & 0xb0; + break; + case 0x62: /* Input YUV/RGB Translate Mode 1 */ + s->yrc[1] = value & 0x30; + break; + case 0x64: /* U Data Fix */ + s->u = value & 0xff; + break; + case 0x66: /* V Data Fix */ + s->v = value & 0xff; + break; + + case 0x68: /* Display Mode */ + if ((s->mode ^ value) & 3) + s->invalidate = 1; + s->mode = value & 0xb7; + s->enable = value & 1; + s->blank = (value >> 1) & 1; + if (value & (1 << 4)) + fprintf(stderr, "%s: Macrovision enable attempt!\n", __FUNCTION__); + break; + + case 0x6a: /* Special Effects */ + s->effect = value & 0xfb; + break; + + case 0x6c: /* Input Window X Start Position 0 */ + s->ix[0] &= 0x300; + s->ix[0] |= (value << 0) & 0x0ff; + break; + case 0x6e: /* Input Window X Start Position 1 */ + s->ix[0] &= 0x0ff; + s->ix[0] |= (value << 8) & 0x300; + break; + case 0x70: /* Input Window Y Start Position 0 */ + s->iy[0] &= 0x300; + s->iy[0] |= (value << 0) & 0x0ff; + break; + case 0x72: /* Input Window Y Start Position 1 */ + s->iy[0] &= 0x0ff; + s->iy[0] |= (value << 8) & 0x300; + break; + case 0x74: /* Input Window X End Position 0 */ + s->ix[1] &= 0x300; + s->ix[1] |= (value << 0) & 0x0ff; + break; + case 0x76: /* Input Window X End Position 1 */ + s->ix[1] &= 0x0ff; + s->ix[1] |= (value << 8) & 0x300; + break; + case 0x78: /* Input Window Y End Position 0 */ + s->iy[1] &= 0x300; + s->iy[1] |= (value << 0) & 0x0ff; + break; + case 0x7a: /* Input Window Y End Position 1 */ + s->iy[1] &= 0x0ff; + s->iy[1] |= (value << 8) & 0x300; + break; + case 0x7c: /* Output Window X Start Position 0 */ + s->ox[0] &= 0x300; + s->ox[0] |= (value << 0) & 0x0ff; + break; + case 0x7e: /* Output Window X Start Position 1 */ + s->ox[0] &= 0x0ff; + s->ox[0] |= (value << 8) & 0x300; + break; + case 0x80: /* Output Window Y Start Position 0 */ + s->oy[0] &= 0x300; + s->oy[0] |= (value << 0) & 0x0ff; + break; + case 0x82: /* Output Window Y Start Position 1 */ + s->oy[0] &= 0x0ff; + s->oy[0] |= (value << 8) & 0x300; + break; + case 0x84: /* Output Window X End Position 0 */ + s->ox[1] &= 0x300; + s->ox[1] |= (value << 0) & 0x0ff; + break; + case 0x86: /* Output Window X End Position 1 */ + s->ox[1] &= 0x0ff; + s->ox[1] |= (value << 8) & 0x300; + break; + case 0x88: /* Output Window Y End Position 0 */ + s->oy[1] &= 0x300; + s->oy[1] |= (value << 0) & 0x0ff; + break; + case 0x8a: /* Output Window Y End Position 1 */ + s->oy[1] &= 0x0ff; + s->oy[1] |= (value << 8) & 0x300; + break; + + case 0x8c: /* Input Data Format */ + s->iformat = value & 0xf; + s->bpp = blizzard_iformat_bpp[s->iformat]; + if (!s->bpp) + fprintf(stderr, "%s: Illegal or unsupported input format %x\n", + __FUNCTION__, s->iformat); + break; + case 0x8e: /* Data Source Select */ + s->source = value & 7; + /* Currently all windows will be "destructive overlays". */ + if ((!(s->effect & (1 << 3)) && (s->ix[0] != s->ox[0] || + s->iy[0] != s->oy[0] || + s->ix[1] != s->ox[1] || + s->iy[1] != s->oy[1])) || + !((s->ix[1] - s->ix[0]) & (s->iy[1] - s->iy[0]) & + (s->ox[1] - s->ox[0]) & (s->oy[1] - s->oy[0]) & 1)) + fprintf(stderr, "%s: Illegal input/output window positions\n", + __FUNCTION__); + + blizzard_transfer_setup(s); + break; + + case 0x90: /* Display Memory Data Port */ + if (!s->data.len && !blizzard_transfer_setup(s)) + break; + + *s->data.ptr ++ = value; + if (-- s->data.len == 0) + blizzard_window(s); + break; + + case 0xa8: /* Border Color 0 */ + s->border_r = value; + break; + case 0xaa: /* Border Color 1 */ + s->border_g = value; + break; + case 0xac: /* Border Color 2 */ + s->border_b = value; + break; + + case 0xb4: /* Gamma Correction Enable */ + s->gamma_config = value & 0x87; + break; + case 0xb6: /* Gamma Correction Table Index */ + s->gamma_idx = value; + break; + case 0xb8: /* Gamma Correction Table Data */ + s->gamma_lut[s->gamma_idx ++] = value; + break; + + case 0xba: /* 3x3 Matrix Enable */ + s->matrix_ena = value & 1; + break; + case 0xbc ... 0xde: /* Coefficient Registers */ + s->matrix_coeff[(reg - 0xbc) >> 1] = value & ((reg & 2) ? 0x80 : 0xff); + break; + case 0xe0: /* 3x3 Matrix Red Offset */ + s->matrix_r = value; + break; + case 0xe2: /* 3x3 Matrix Green Offset */ + s->matrix_g = value; + break; + case 0xe4: /* 3x3 Matrix Blue Offset */ + s->matrix_b = value; + break; + + case 0xe6: /* Power-save */ + s->pm = value & 0x83; + if (value & s->mode & 1) + fprintf(stderr, "%s: The display must be disabled before entering " + "Standby Mode\n", __FUNCTION__); + break; + case 0xe8: /* Non-display Period Control / Status */ + s->status = value & 0x1b; + break; + case 0xea: /* RGB Interface Control */ + s->rgbgpio_dir = value & 0x8f; + break; + case 0xec: /* RGB Interface Status */ + s->rgbgpio = value & 0xcf; + break; + case 0xee: /* General-purpose IO Pins Configuration */ + s->gpio_dir = value; + break; + case 0xf0: /* General-purpose IO Pins Status / Control */ + s->gpio = value; + break; + case 0xf2: /* GPIO Positive Edge Interrupt Trigger */ + s->gpio_edge[0] = value; + break; + case 0xf4: /* GPIO Negative Edge Interrupt Trigger */ + s->gpio_edge[1] = value; + break; + case 0xf6: /* GPIO Interrupt Status */ + s->gpio_irq &= value; + break; + case 0xf8: /* GPIO Pull-down Control */ + s->gpio_pdown = value; + break; + + default: + fprintf(stderr, "%s: unknown register %02x\n", __FUNCTION__, reg); + break; + } +} + +uint16_t s1d13745_read(void *opaque, int dc) +{ + BlizzardState *s = (BlizzardState *) opaque; + uint16_t value = blizzard_reg_read(s, s->reg); + + if (s->swallow -- > 0) + return 0; + if (dc) + s->reg ++; + + return value; +} + +void s1d13745_write(void *opaque, int dc, uint16_t value) +{ + BlizzardState *s = (BlizzardState *) opaque; + + if (s->swallow -- > 0) + return; + if (dc) { + blizzard_reg_write(s, s->reg, value); + + if (s->reg != 0x90 && s->reg != 0x5a && s->reg != 0xb8) + s->reg += 2; + } else + s->reg = value & 0xff; +} + +void s1d13745_write_block(void *opaque, int dc, + void *buf, size_t len, int pitch) +{ + BlizzardState *s = (BlizzardState *) opaque; + + while (len > 0) { + if (s->reg == 0x90 && dc && + (s->data.len || blizzard_transfer_setup(s)) && + len >= (s->data.len << 1)) { + len -= s->data.len << 1; + s->data.len = 0; + s->data.data = buf; + if (pitch) + s->data.pitch = pitch; + blizzard_window(s); + s->data.data = s->data.buf; + continue; + } + + s1d13745_write(opaque, dc, *(uint16_t *) buf); + len -= 2; + buf += 2; + } +} + +static void blizzard_update_display(void *opaque) +{ + BlizzardState *s = (BlizzardState *) opaque; + DisplaySurface *surface = qemu_console_surface(s->con); + int y, bypp, bypl, bwidth; + uint8_t *src, *dst; + + if (!s->enable) + return; + + if (s->x != surface_width(surface) || s->y != surface_height(surface)) { + s->invalidate = 1; + qemu_console_resize(s->con, s->x, s->y); + surface = qemu_console_surface(s->con); + } + + if (s->invalidate) { + s->invalidate = 0; + + if (s->blank) { + bypp = surface_bytes_per_pixel(surface); + memset(surface_data(surface), 0, bypp * s->x * s->y); + return; + } + + s->mx[0] = 0; + s->mx[1] = s->x; + s->my[0] = 0; + s->my[1] = s->y; + } + + if (s->mx[1] <= s->mx[0]) + return; + + bypp = surface_bytes_per_pixel(surface); + bypl = bypp * s->x; + bwidth = bypp * (s->mx[1] - s->mx[0]); + y = s->my[0]; + src = s->fb + bypl * y + bypp * s->mx[0]; + dst = surface_data(surface) + bypl * y + bypp * s->mx[0]; + for (; y < s->my[1]; y ++, src += bypl, dst += bypl) + memcpy(dst, src, bwidth); + + dpy_gfx_update(s->con, s->mx[0], s->my[0], + s->mx[1] - s->mx[0], y - s->my[0]); + + s->mx[0] = s->x; + s->mx[1] = 0; + s->my[0] = s->y; + s->my[1] = 0; +} + +static void blizzard_screen_dump(void *opaque, const char *filename, + bool cswitch, Error **errp) +{ + BlizzardState *s = (BlizzardState *) opaque; + DisplaySurface *surface = qemu_console_surface(s->con); + + blizzard_update_display(opaque); + if (s && surface_data(surface)) { + ppm_save(filename, surface, errp); + } +} + +#define DEPTH 8 +#include "blizzard_template.h" +#define DEPTH 15 +#include "blizzard_template.h" +#define DEPTH 16 +#include "blizzard_template.h" +#define DEPTH 24 +#include "blizzard_template.h" +#define DEPTH 32 +#include "blizzard_template.h" + +void *s1d13745_init(qemu_irq gpio_int) +{ + BlizzardState *s = (BlizzardState *) g_malloc0(sizeof(*s)); + DisplaySurface *surface; + + s->fb = g_malloc(0x180000); + + s->con = graphic_console_init(blizzard_update_display, + blizzard_invalidate_display, + blizzard_screen_dump, NULL, s); + surface = qemu_console_surface(s->con); + + switch (surface_bits_per_pixel(surface)) { + case 0: + s->line_fn_tab[0] = s->line_fn_tab[1] = + g_malloc0(sizeof(blizzard_fn_t) * 0x10); + break; + case 8: + s->line_fn_tab[0] = blizzard_draw_fn_8; + s->line_fn_tab[1] = blizzard_draw_fn_r_8; + break; + case 15: + s->line_fn_tab[0] = blizzard_draw_fn_15; + s->line_fn_tab[1] = blizzard_draw_fn_r_15; + break; + case 16: + s->line_fn_tab[0] = blizzard_draw_fn_16; + s->line_fn_tab[1] = blizzard_draw_fn_r_16; + break; + case 24: + s->line_fn_tab[0] = blizzard_draw_fn_24; + s->line_fn_tab[1] = blizzard_draw_fn_r_24; + break; + case 32: + s->line_fn_tab[0] = blizzard_draw_fn_32; + s->line_fn_tab[1] = blizzard_draw_fn_r_32; + break; + default: + fprintf(stderr, "%s: Bad color depth\n", __FUNCTION__); + exit(1); + } + + blizzard_reset(s); + + return s; +} diff --git a/hw/display/blizzard_template.h b/hw/display/blizzard_template.h new file mode 100644 index 0000000000..a8a8899478 --- /dev/null +++ b/hw/display/blizzard_template.h @@ -0,0 +1,136 @@ +/* + * QEMU Epson S1D13744/S1D13745 templates + * + * Copyright (C) 2008 Nokia Corporation + * Written by Andrzej Zaborowski <andrew@openedhand.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 or + * (at your option) version 3 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, see <http://www.gnu.org/licenses/>. + */ + +#define SKIP_PIXEL(to) to += deststep +#if DEPTH == 8 +# define PIXEL_TYPE uint8_t +# define COPY_PIXEL(to, from) *to = from; SKIP_PIXEL(to) +# define COPY_PIXEL1(to, from) *to ++ = from +#elif DEPTH == 15 || DEPTH == 16 +# define PIXEL_TYPE uint16_t +# define COPY_PIXEL(to, from) *to = from; SKIP_PIXEL(to) +# define COPY_PIXEL1(to, from) *to ++ = from +#elif DEPTH == 24 +# define PIXEL_TYPE uint8_t +# define COPY_PIXEL(to, from) \ + to[0] = from; to[1] = (from) >> 8; to[2] = (from) >> 16; SKIP_PIXEL(to) +# define COPY_PIXEL1(to, from) \ + *to ++ = from; *to ++ = (from) >> 8; *to ++ = (from) >> 16 +#elif DEPTH == 32 +# define PIXEL_TYPE uint32_t +# define COPY_PIXEL(to, from) *to = from; SKIP_PIXEL(to) +# define COPY_PIXEL1(to, from) *to ++ = from +#else +# error unknown bit depth +#endif + +#ifdef HOST_WORDS_BIGENDIAN +# define SWAP_WORDS 1 +#endif + +static void glue(blizzard_draw_line16_, DEPTH)(PIXEL_TYPE *dest, + const uint16_t *src, unsigned int width) +{ +#if !defined(SWAP_WORDS) && DEPTH == 16 + memcpy(dest, src, width); +#else + uint16_t data; + unsigned int r, g, b; + const uint16_t *end = (const void *) src + width; + while (src < end) { + data = *src ++; + b = (data & 0x1f) << 3; + data >>= 5; + g = (data & 0x3f) << 2; + data >>= 6; + r = (data & 0x1f) << 3; + data >>= 5; + COPY_PIXEL1(dest, glue(rgb_to_pixel, DEPTH)(r, g, b)); + } +#endif +} + +static void glue(blizzard_draw_line24mode1_, DEPTH)(PIXEL_TYPE *dest, + const uint8_t *src, unsigned int width) +{ + /* TODO: check if SDL 24-bit planes are not in the same format and + * if so, use memcpy */ + unsigned int r[2], g[2], b[2]; + const uint8_t *end = src + width; + while (src < end) { + g[0] = *src ++; + r[0] = *src ++; + r[1] = *src ++; + b[0] = *src ++; + COPY_PIXEL1(dest, glue(rgb_to_pixel, DEPTH)(r[0], g[0], b[0])); + b[1] = *src ++; + g[1] = *src ++; + COPY_PIXEL1(dest, glue(rgb_to_pixel, DEPTH)(r[1], g[1], b[1])); + } +} + +static void glue(blizzard_draw_line24mode2_, DEPTH)(PIXEL_TYPE *dest, + const uint8_t *src, unsigned int width) +{ + unsigned int r, g, b; + const uint8_t *end = src + width; + while (src < end) { + r = *src ++; + src ++; + b = *src ++; + g = *src ++; + COPY_PIXEL1(dest, glue(rgb_to_pixel, DEPTH)(r, g, b)); + } +} + +/* No rotation */ +static blizzard_fn_t glue(blizzard_draw_fn_, DEPTH)[0x10] = { + NULL, + /* RGB 5:6:5*/ + (blizzard_fn_t) glue(blizzard_draw_line16_, DEPTH), + /* RGB 6:6:6 mode 1 */ + (blizzard_fn_t) glue(blizzard_draw_line24mode1_, DEPTH), + /* RGB 8:8:8 mode 1 */ + (blizzard_fn_t) glue(blizzard_draw_line24mode1_, DEPTH), + NULL, NULL, + /* RGB 6:6:6 mode 2 */ + (blizzard_fn_t) glue(blizzard_draw_line24mode2_, DEPTH), + /* RGB 8:8:8 mode 2 */ + (blizzard_fn_t) glue(blizzard_draw_line24mode2_, DEPTH), + /* YUV 4:2:2 */ + NULL, + /* YUV 4:2:0 */ + NULL, + NULL, NULL, NULL, NULL, NULL, NULL, +}; + +/* 90deg, 180deg and 270deg rotation */ +static blizzard_fn_t glue(blizzard_draw_fn_r_, DEPTH)[0x10] = { + /* TODO */ + [0 ... 0xf] = NULL, +}; + +#undef DEPTH +#undef SKIP_PIXEL +#undef COPY_PIXEL +#undef COPY_PIXEL1 +#undef PIXEL_TYPE + +#undef SWAP_WORDS diff --git a/hw/display/cirrus_vga.c b/hw/display/cirrus_vga.c new file mode 100644 index 0000000000..bf2181afd8 --- /dev/null +++ b/hw/display/cirrus_vga.c @@ -0,0 +1,3021 @@ +/* + * QEMU Cirrus CLGD 54xx VGA Emulator. + * + * Copyright (c) 2004 Fabrice Bellard + * Copyright (c) 2004 Makoto Suzuki (suzu) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +/* + * Reference: Finn Thogersons' VGADOC4b + * available at http://home.worldonline.dk/~finth/ + */ +#include "hw/hw.h" +#include "hw/pci/pci.h" +#include "ui/console.h" +#include "vga_int.h" +#include "hw/loader.h" + +/* + * TODO: + * - destination write mask support not complete (bits 5..7) + * - optimize linear mappings + * - optimize bitblt functions + */ + +//#define DEBUG_CIRRUS +//#define DEBUG_BITBLT + +/*************************************** + * + * definitions + * + ***************************************/ + +// ID +#define CIRRUS_ID_CLGD5422 (0x23<<2) +#define CIRRUS_ID_CLGD5426 (0x24<<2) +#define CIRRUS_ID_CLGD5424 (0x25<<2) +#define CIRRUS_ID_CLGD5428 (0x26<<2) +#define CIRRUS_ID_CLGD5430 (0x28<<2) +#define CIRRUS_ID_CLGD5434 (0x2A<<2) +#define CIRRUS_ID_CLGD5436 (0x2B<<2) +#define CIRRUS_ID_CLGD5446 (0x2E<<2) + +// sequencer 0x07 +#define CIRRUS_SR7_BPP_VGA 0x00 +#define CIRRUS_SR7_BPP_SVGA 0x01 +#define CIRRUS_SR7_BPP_MASK 0x0e +#define CIRRUS_SR7_BPP_8 0x00 +#define CIRRUS_SR7_BPP_16_DOUBLEVCLK 0x02 +#define CIRRUS_SR7_BPP_24 0x04 +#define CIRRUS_SR7_BPP_16 0x06 +#define CIRRUS_SR7_BPP_32 0x08 +#define CIRRUS_SR7_ISAADDR_MASK 0xe0 + +// sequencer 0x0f +#define CIRRUS_MEMSIZE_512k 0x08 +#define CIRRUS_MEMSIZE_1M 0x10 +#define CIRRUS_MEMSIZE_2M 0x18 +#define CIRRUS_MEMFLAGS_BANKSWITCH 0x80 // bank switching is enabled. + +// sequencer 0x12 +#define CIRRUS_CURSOR_SHOW 0x01 +#define CIRRUS_CURSOR_HIDDENPEL 0x02 +#define CIRRUS_CURSOR_LARGE 0x04 // 64x64 if set, 32x32 if clear + +// sequencer 0x17 +#define CIRRUS_BUSTYPE_VLBFAST 0x10 +#define CIRRUS_BUSTYPE_PCI 0x20 +#define CIRRUS_BUSTYPE_VLBSLOW 0x30 +#define CIRRUS_BUSTYPE_ISA 0x38 +#define CIRRUS_MMIO_ENABLE 0x04 +#define CIRRUS_MMIO_USE_PCIADDR 0x40 // 0xb8000 if cleared. +#define CIRRUS_MEMSIZEEXT_DOUBLE 0x80 + +// control 0x0b +#define CIRRUS_BANKING_DUAL 0x01 +#define CIRRUS_BANKING_GRANULARITY_16K 0x20 // set:16k, clear:4k + +// control 0x30 +#define CIRRUS_BLTMODE_BACKWARDS 0x01 +#define CIRRUS_BLTMODE_MEMSYSDEST 0x02 +#define CIRRUS_BLTMODE_MEMSYSSRC 0x04 +#define CIRRUS_BLTMODE_TRANSPARENTCOMP 0x08 +#define CIRRUS_BLTMODE_PATTERNCOPY 0x40 +#define CIRRUS_BLTMODE_COLOREXPAND 0x80 +#define CIRRUS_BLTMODE_PIXELWIDTHMASK 0x30 +#define CIRRUS_BLTMODE_PIXELWIDTH8 0x00 +#define CIRRUS_BLTMODE_PIXELWIDTH16 0x10 +#define CIRRUS_BLTMODE_PIXELWIDTH24 0x20 +#define CIRRUS_BLTMODE_PIXELWIDTH32 0x30 + +// control 0x31 +#define CIRRUS_BLT_BUSY 0x01 +#define CIRRUS_BLT_START 0x02 +#define CIRRUS_BLT_RESET 0x04 +#define CIRRUS_BLT_FIFOUSED 0x10 +#define CIRRUS_BLT_AUTOSTART 0x80 + +// control 0x32 +#define CIRRUS_ROP_0 0x00 +#define CIRRUS_ROP_SRC_AND_DST 0x05 +#define CIRRUS_ROP_NOP 0x06 +#define CIRRUS_ROP_SRC_AND_NOTDST 0x09 +#define CIRRUS_ROP_NOTDST 0x0b +#define CIRRUS_ROP_SRC 0x0d +#define CIRRUS_ROP_1 0x0e +#define CIRRUS_ROP_NOTSRC_AND_DST 0x50 +#define CIRRUS_ROP_SRC_XOR_DST 0x59 +#define CIRRUS_ROP_SRC_OR_DST 0x6d +#define CIRRUS_ROP_NOTSRC_OR_NOTDST 0x90 +#define CIRRUS_ROP_SRC_NOTXOR_DST 0x95 +#define CIRRUS_ROP_SRC_OR_NOTDST 0xad +#define CIRRUS_ROP_NOTSRC 0xd0 +#define CIRRUS_ROP_NOTSRC_OR_DST 0xd6 +#define CIRRUS_ROP_NOTSRC_AND_NOTDST 0xda + +#define CIRRUS_ROP_NOP_INDEX 2 +#define CIRRUS_ROP_SRC_INDEX 5 + +// control 0x33 +#define CIRRUS_BLTMODEEXT_SOLIDFILL 0x04 +#define CIRRUS_BLTMODEEXT_COLOREXPINV 0x02 +#define CIRRUS_BLTMODEEXT_DWORDGRANULARITY 0x01 + +// memory-mapped IO +#define CIRRUS_MMIO_BLTBGCOLOR 0x00 // dword +#define CIRRUS_MMIO_BLTFGCOLOR 0x04 // dword +#define CIRRUS_MMIO_BLTWIDTH 0x08 // word +#define CIRRUS_MMIO_BLTHEIGHT 0x0a // word +#define CIRRUS_MMIO_BLTDESTPITCH 0x0c // word +#define CIRRUS_MMIO_BLTSRCPITCH 0x0e // word +#define CIRRUS_MMIO_BLTDESTADDR 0x10 // dword +#define CIRRUS_MMIO_BLTSRCADDR 0x14 // dword +#define CIRRUS_MMIO_BLTWRITEMASK 0x17 // byte +#define CIRRUS_MMIO_BLTMODE 0x18 // byte +#define CIRRUS_MMIO_BLTROP 0x1a // byte +#define CIRRUS_MMIO_BLTMODEEXT 0x1b // byte +#define CIRRUS_MMIO_BLTTRANSPARENTCOLOR 0x1c // word? +#define CIRRUS_MMIO_BLTTRANSPARENTCOLORMASK 0x20 // word? +#define CIRRUS_MMIO_LINEARDRAW_START_X 0x24 // word +#define CIRRUS_MMIO_LINEARDRAW_START_Y 0x26 // word +#define CIRRUS_MMIO_LINEARDRAW_END_X 0x28 // word +#define CIRRUS_MMIO_LINEARDRAW_END_Y 0x2a // word +#define CIRRUS_MMIO_LINEARDRAW_LINESTYLE_INC 0x2c // byte +#define CIRRUS_MMIO_LINEARDRAW_LINESTYLE_ROLLOVER 0x2d // byte +#define CIRRUS_MMIO_LINEARDRAW_LINESTYLE_MASK 0x2e // byte +#define CIRRUS_MMIO_LINEARDRAW_LINESTYLE_ACCUM 0x2f // byte +#define CIRRUS_MMIO_BRESENHAM_K1 0x30 // word +#define CIRRUS_MMIO_BRESENHAM_K3 0x32 // word +#define CIRRUS_MMIO_BRESENHAM_ERROR 0x34 // word +#define CIRRUS_MMIO_BRESENHAM_DELTA_MAJOR 0x36 // word +#define CIRRUS_MMIO_BRESENHAM_DIRECTION 0x38 // byte +#define CIRRUS_MMIO_LINEDRAW_MODE 0x39 // byte +#define CIRRUS_MMIO_BLTSTATUS 0x40 // byte + +#define CIRRUS_PNPMMIO_SIZE 0x1000 + +#define BLTUNSAFE(s) \ + ( \ + ( /* check dst is within bounds */ \ + (s)->cirrus_blt_height * ABS((s)->cirrus_blt_dstpitch) \ + + ((s)->cirrus_blt_dstaddr & (s)->cirrus_addr_mask) > \ + (s)->vga.vram_size \ + ) || \ + ( /* check src is within bounds */ \ + (s)->cirrus_blt_height * ABS((s)->cirrus_blt_srcpitch) \ + + ((s)->cirrus_blt_srcaddr & (s)->cirrus_addr_mask) > \ + (s)->vga.vram_size \ + ) \ + ) + +struct CirrusVGAState; +typedef void (*cirrus_bitblt_rop_t) (struct CirrusVGAState *s, + uint8_t * dst, const uint8_t * src, + int dstpitch, int srcpitch, + int bltwidth, int bltheight); +typedef void (*cirrus_fill_t)(struct CirrusVGAState *s, + uint8_t *dst, int dst_pitch, int width, int height); + +typedef struct CirrusVGAState { + VGACommonState vga; + + MemoryRegion cirrus_vga_io; + MemoryRegion cirrus_linear_io; + MemoryRegion cirrus_linear_bitblt_io; + MemoryRegion cirrus_mmio_io; + MemoryRegion pci_bar; + bool linear_vram; /* vga.vram mapped over cirrus_linear_io */ + MemoryRegion low_mem_container; /* container for 0xa0000-0xc0000 */ + MemoryRegion low_mem; /* always mapped, overridden by: */ + MemoryRegion cirrus_bank[2]; /* aliases at 0xa0000-0xb0000 */ + uint32_t cirrus_addr_mask; + uint32_t linear_mmio_mask; + uint8_t cirrus_shadow_gr0; + uint8_t cirrus_shadow_gr1; + uint8_t cirrus_hidden_dac_lockindex; + uint8_t cirrus_hidden_dac_data; + uint32_t cirrus_bank_base[2]; + uint32_t cirrus_bank_limit[2]; + uint8_t cirrus_hidden_palette[48]; + uint32_t hw_cursor_x; + uint32_t hw_cursor_y; + int cirrus_blt_pixelwidth; + int cirrus_blt_width; + int cirrus_blt_height; + int cirrus_blt_dstpitch; + int cirrus_blt_srcpitch; + uint32_t cirrus_blt_fgcol; + uint32_t cirrus_blt_bgcol; + uint32_t cirrus_blt_dstaddr; + uint32_t cirrus_blt_srcaddr; + uint8_t cirrus_blt_mode; + uint8_t cirrus_blt_modeext; + cirrus_bitblt_rop_t cirrus_rop; +#define CIRRUS_BLTBUFSIZE (2048 * 4) /* one line width */ + uint8_t cirrus_bltbuf[CIRRUS_BLTBUFSIZE]; + uint8_t *cirrus_srcptr; + uint8_t *cirrus_srcptr_end; + uint32_t cirrus_srccounter; + /* hwcursor display state */ + int last_hw_cursor_size; + int last_hw_cursor_x; + int last_hw_cursor_y; + int last_hw_cursor_y_start; + int last_hw_cursor_y_end; + int real_vram_size; /* XXX: suppress that */ + int device_id; + int bustype; +} CirrusVGAState; + +typedef struct PCICirrusVGAState { + PCIDevice dev; + CirrusVGAState cirrus_vga; +} PCICirrusVGAState; + +typedef struct ISACirrusVGAState { + ISADevice dev; + CirrusVGAState cirrus_vga; +} ISACirrusVGAState; + +static uint8_t rop_to_index[256]; + +/*************************************** + * + * prototypes. + * + ***************************************/ + + +static void cirrus_bitblt_reset(CirrusVGAState *s); +static void cirrus_update_memory_access(CirrusVGAState *s); + +/*************************************** + * + * raster operations + * + ***************************************/ + +static void cirrus_bitblt_rop_nop(CirrusVGAState *s, + uint8_t *dst,const uint8_t *src, + int dstpitch,int srcpitch, + int bltwidth,int bltheight) +{ +} + +static void cirrus_bitblt_fill_nop(CirrusVGAState *s, + uint8_t *dst, + int dstpitch, int bltwidth,int bltheight) +{ +} + +#define ROP_NAME 0 +#define ROP_FN(d, s) 0 +#include "cirrus_vga_rop.h" + +#define ROP_NAME src_and_dst +#define ROP_FN(d, s) (s) & (d) +#include "cirrus_vga_rop.h" + +#define ROP_NAME src_and_notdst +#define ROP_FN(d, s) (s) & (~(d)) +#include "cirrus_vga_rop.h" + +#define ROP_NAME notdst +#define ROP_FN(d, s) ~(d) +#include "cirrus_vga_rop.h" + +#define ROP_NAME src +#define ROP_FN(d, s) s +#include "cirrus_vga_rop.h" + +#define ROP_NAME 1 +#define ROP_FN(d, s) ~0 +#include "cirrus_vga_rop.h" + +#define ROP_NAME notsrc_and_dst +#define ROP_FN(d, s) (~(s)) & (d) +#include "cirrus_vga_rop.h" + +#define ROP_NAME src_xor_dst +#define ROP_FN(d, s) (s) ^ (d) +#include "cirrus_vga_rop.h" + +#define ROP_NAME src_or_dst +#define ROP_FN(d, s) (s) | (d) +#include "cirrus_vga_rop.h" + +#define ROP_NAME notsrc_or_notdst +#define ROP_FN(d, s) (~(s)) | (~(d)) +#include "cirrus_vga_rop.h" + +#define ROP_NAME src_notxor_dst +#define ROP_FN(d, s) ~((s) ^ (d)) +#include "cirrus_vga_rop.h" + +#define ROP_NAME src_or_notdst +#define ROP_FN(d, s) (s) | (~(d)) +#include "cirrus_vga_rop.h" + +#define ROP_NAME notsrc +#define ROP_FN(d, s) (~(s)) +#include "cirrus_vga_rop.h" + +#define ROP_NAME notsrc_or_dst +#define ROP_FN(d, s) (~(s)) | (d) +#include "cirrus_vga_rop.h" + +#define ROP_NAME notsrc_and_notdst +#define ROP_FN(d, s) (~(s)) & (~(d)) +#include "cirrus_vga_rop.h" + +static const cirrus_bitblt_rop_t cirrus_fwd_rop[16] = { + cirrus_bitblt_rop_fwd_0, + cirrus_bitblt_rop_fwd_src_and_dst, + cirrus_bitblt_rop_nop, + cirrus_bitblt_rop_fwd_src_and_notdst, + cirrus_bitblt_rop_fwd_notdst, + cirrus_bitblt_rop_fwd_src, + cirrus_bitblt_rop_fwd_1, + cirrus_bitblt_rop_fwd_notsrc_and_dst, + cirrus_bitblt_rop_fwd_src_xor_dst, + cirrus_bitblt_rop_fwd_src_or_dst, + cirrus_bitblt_rop_fwd_notsrc_or_notdst, + cirrus_bitblt_rop_fwd_src_notxor_dst, + cirrus_bitblt_rop_fwd_src_or_notdst, + cirrus_bitblt_rop_fwd_notsrc, + cirrus_bitblt_rop_fwd_notsrc_or_dst, + cirrus_bitblt_rop_fwd_notsrc_and_notdst, +}; + +static const cirrus_bitblt_rop_t cirrus_bkwd_rop[16] = { + cirrus_bitblt_rop_bkwd_0, + cirrus_bitblt_rop_bkwd_src_and_dst, + cirrus_bitblt_rop_nop, + cirrus_bitblt_rop_bkwd_src_and_notdst, + cirrus_bitblt_rop_bkwd_notdst, + cirrus_bitblt_rop_bkwd_src, + cirrus_bitblt_rop_bkwd_1, + cirrus_bitblt_rop_bkwd_notsrc_and_dst, + cirrus_bitblt_rop_bkwd_src_xor_dst, + cirrus_bitblt_rop_bkwd_src_or_dst, + cirrus_bitblt_rop_bkwd_notsrc_or_notdst, + cirrus_bitblt_rop_bkwd_src_notxor_dst, + cirrus_bitblt_rop_bkwd_src_or_notdst, + cirrus_bitblt_rop_bkwd_notsrc, + cirrus_bitblt_rop_bkwd_notsrc_or_dst, + cirrus_bitblt_rop_bkwd_notsrc_and_notdst, +}; + +#define TRANSP_ROP(name) {\ + name ## _8,\ + name ## _16,\ + } +#define TRANSP_NOP(func) {\ + func,\ + func,\ + } + +static const cirrus_bitblt_rop_t cirrus_fwd_transp_rop[16][2] = { + TRANSP_ROP(cirrus_bitblt_rop_fwd_transp_0), + TRANSP_ROP(cirrus_bitblt_rop_fwd_transp_src_and_dst), + TRANSP_NOP(cirrus_bitblt_rop_nop), + TRANSP_ROP(cirrus_bitblt_rop_fwd_transp_src_and_notdst), + TRANSP_ROP(cirrus_bitblt_rop_fwd_transp_notdst), + TRANSP_ROP(cirrus_bitblt_rop_fwd_transp_src), + TRANSP_ROP(cirrus_bitblt_rop_fwd_transp_1), + TRANSP_ROP(cirrus_bitblt_rop_fwd_transp_notsrc_and_dst), + TRANSP_ROP(cirrus_bitblt_rop_fwd_transp_src_xor_dst), + TRANSP_ROP(cirrus_bitblt_rop_fwd_transp_src_or_dst), + TRANSP_ROP(cirrus_bitblt_rop_fwd_transp_notsrc_or_notdst), + TRANSP_ROP(cirrus_bitblt_rop_fwd_transp_src_notxor_dst), + TRANSP_ROP(cirrus_bitblt_rop_fwd_transp_src_or_notdst), + TRANSP_ROP(cirrus_bitblt_rop_fwd_transp_notsrc), + TRANSP_ROP(cirrus_bitblt_rop_fwd_transp_notsrc_or_dst), + TRANSP_ROP(cirrus_bitblt_rop_fwd_transp_notsrc_and_notdst), +}; + +static const cirrus_bitblt_rop_t cirrus_bkwd_transp_rop[16][2] = { + TRANSP_ROP(cirrus_bitblt_rop_bkwd_transp_0), + TRANSP_ROP(cirrus_bitblt_rop_bkwd_transp_src_and_dst), + TRANSP_NOP(cirrus_bitblt_rop_nop), + TRANSP_ROP(cirrus_bitblt_rop_bkwd_transp_src_and_notdst), + TRANSP_ROP(cirrus_bitblt_rop_bkwd_transp_notdst), + TRANSP_ROP(cirrus_bitblt_rop_bkwd_transp_src), + TRANSP_ROP(cirrus_bitblt_rop_bkwd_transp_1), + TRANSP_ROP(cirrus_bitblt_rop_bkwd_transp_notsrc_and_dst), + TRANSP_ROP(cirrus_bitblt_rop_bkwd_transp_src_xor_dst), + TRANSP_ROP(cirrus_bitblt_rop_bkwd_transp_src_or_dst), + TRANSP_ROP(cirrus_bitblt_rop_bkwd_transp_notsrc_or_notdst), + TRANSP_ROP(cirrus_bitblt_rop_bkwd_transp_src_notxor_dst), + TRANSP_ROP(cirrus_bitblt_rop_bkwd_transp_src_or_notdst), + TRANSP_ROP(cirrus_bitblt_rop_bkwd_transp_notsrc), + TRANSP_ROP(cirrus_bitblt_rop_bkwd_transp_notsrc_or_dst), + TRANSP_ROP(cirrus_bitblt_rop_bkwd_transp_notsrc_and_notdst), +}; + +#define ROP2(name) {\ + name ## _8,\ + name ## _16,\ + name ## _24,\ + name ## _32,\ + } + +#define ROP_NOP2(func) {\ + func,\ + func,\ + func,\ + func,\ + } + +static const cirrus_bitblt_rop_t cirrus_patternfill[16][4] = { + ROP2(cirrus_patternfill_0), + ROP2(cirrus_patternfill_src_and_dst), + ROP_NOP2(cirrus_bitblt_rop_nop), + ROP2(cirrus_patternfill_src_and_notdst), + ROP2(cirrus_patternfill_notdst), + ROP2(cirrus_patternfill_src), + ROP2(cirrus_patternfill_1), + ROP2(cirrus_patternfill_notsrc_and_dst), + ROP2(cirrus_patternfill_src_xor_dst), + ROP2(cirrus_patternfill_src_or_dst), + ROP2(cirrus_patternfill_notsrc_or_notdst), + ROP2(cirrus_patternfill_src_notxor_dst), + ROP2(cirrus_patternfill_src_or_notdst), + ROP2(cirrus_patternfill_notsrc), + ROP2(cirrus_patternfill_notsrc_or_dst), + ROP2(cirrus_patternfill_notsrc_and_notdst), +}; + +static const cirrus_bitblt_rop_t cirrus_colorexpand_transp[16][4] = { + ROP2(cirrus_colorexpand_transp_0), + ROP2(cirrus_colorexpand_transp_src_and_dst), + ROP_NOP2(cirrus_bitblt_rop_nop), + ROP2(cirrus_colorexpand_transp_src_and_notdst), + ROP2(cirrus_colorexpand_transp_notdst), + ROP2(cirrus_colorexpand_transp_src), + ROP2(cirrus_colorexpand_transp_1), + ROP2(cirrus_colorexpand_transp_notsrc_and_dst), + ROP2(cirrus_colorexpand_transp_src_xor_dst), + ROP2(cirrus_colorexpand_transp_src_or_dst), + ROP2(cirrus_colorexpand_transp_notsrc_or_notdst), + ROP2(cirrus_colorexpand_transp_src_notxor_dst), + ROP2(cirrus_colorexpand_transp_src_or_notdst), + ROP2(cirrus_colorexpand_transp_notsrc), + ROP2(cirrus_colorexpand_transp_notsrc_or_dst), + ROP2(cirrus_colorexpand_transp_notsrc_and_notdst), +}; + +static const cirrus_bitblt_rop_t cirrus_colorexpand[16][4] = { + ROP2(cirrus_colorexpand_0), + ROP2(cirrus_colorexpand_src_and_dst), + ROP_NOP2(cirrus_bitblt_rop_nop), + ROP2(cirrus_colorexpand_src_and_notdst), + ROP2(cirrus_colorexpand_notdst), + ROP2(cirrus_colorexpand_src), + ROP2(cirrus_colorexpand_1), + ROP2(cirrus_colorexpand_notsrc_and_dst), + ROP2(cirrus_colorexpand_src_xor_dst), + ROP2(cirrus_colorexpand_src_or_dst), + ROP2(cirrus_colorexpand_notsrc_or_notdst), + ROP2(cirrus_colorexpand_src_notxor_dst), + ROP2(cirrus_colorexpand_src_or_notdst), + ROP2(cirrus_colorexpand_notsrc), + ROP2(cirrus_colorexpand_notsrc_or_dst), + ROP2(cirrus_colorexpand_notsrc_and_notdst), +}; + +static const cirrus_bitblt_rop_t cirrus_colorexpand_pattern_transp[16][4] = { + ROP2(cirrus_colorexpand_pattern_transp_0), + ROP2(cirrus_colorexpand_pattern_transp_src_and_dst), + ROP_NOP2(cirrus_bitblt_rop_nop), + ROP2(cirrus_colorexpand_pattern_transp_src_and_notdst), + ROP2(cirrus_colorexpand_pattern_transp_notdst), + ROP2(cirrus_colorexpand_pattern_transp_src), + ROP2(cirrus_colorexpand_pattern_transp_1), + ROP2(cirrus_colorexpand_pattern_transp_notsrc_and_dst), + ROP2(cirrus_colorexpand_pattern_transp_src_xor_dst), + ROP2(cirrus_colorexpand_pattern_transp_src_or_dst), + ROP2(cirrus_colorexpand_pattern_transp_notsrc_or_notdst), + ROP2(cirrus_colorexpand_pattern_transp_src_notxor_dst), + ROP2(cirrus_colorexpand_pattern_transp_src_or_notdst), + ROP2(cirrus_colorexpand_pattern_transp_notsrc), + ROP2(cirrus_colorexpand_pattern_transp_notsrc_or_dst), + ROP2(cirrus_colorexpand_pattern_transp_notsrc_and_notdst), +}; + +static const cirrus_bitblt_rop_t cirrus_colorexpand_pattern[16][4] = { + ROP2(cirrus_colorexpand_pattern_0), + ROP2(cirrus_colorexpand_pattern_src_and_dst), + ROP_NOP2(cirrus_bitblt_rop_nop), + ROP2(cirrus_colorexpand_pattern_src_and_notdst), + ROP2(cirrus_colorexpand_pattern_notdst), + ROP2(cirrus_colorexpand_pattern_src), + ROP2(cirrus_colorexpand_pattern_1), + ROP2(cirrus_colorexpand_pattern_notsrc_and_dst), + ROP2(cirrus_colorexpand_pattern_src_xor_dst), + ROP2(cirrus_colorexpand_pattern_src_or_dst), + ROP2(cirrus_colorexpand_pattern_notsrc_or_notdst), + ROP2(cirrus_colorexpand_pattern_src_notxor_dst), + ROP2(cirrus_colorexpand_pattern_src_or_notdst), + ROP2(cirrus_colorexpand_pattern_notsrc), + ROP2(cirrus_colorexpand_pattern_notsrc_or_dst), + ROP2(cirrus_colorexpand_pattern_notsrc_and_notdst), +}; + +static const cirrus_fill_t cirrus_fill[16][4] = { + ROP2(cirrus_fill_0), + ROP2(cirrus_fill_src_and_dst), + ROP_NOP2(cirrus_bitblt_fill_nop), + ROP2(cirrus_fill_src_and_notdst), + ROP2(cirrus_fill_notdst), + ROP2(cirrus_fill_src), + ROP2(cirrus_fill_1), + ROP2(cirrus_fill_notsrc_and_dst), + ROP2(cirrus_fill_src_xor_dst), + ROP2(cirrus_fill_src_or_dst), + ROP2(cirrus_fill_notsrc_or_notdst), + ROP2(cirrus_fill_src_notxor_dst), + ROP2(cirrus_fill_src_or_notdst), + ROP2(cirrus_fill_notsrc), + ROP2(cirrus_fill_notsrc_or_dst), + ROP2(cirrus_fill_notsrc_and_notdst), +}; + +static inline void cirrus_bitblt_fgcol(CirrusVGAState *s) +{ + unsigned int color; + switch (s->cirrus_blt_pixelwidth) { + case 1: + s->cirrus_blt_fgcol = s->cirrus_shadow_gr1; + break; + case 2: + color = s->cirrus_shadow_gr1 | (s->vga.gr[0x11] << 8); + s->cirrus_blt_fgcol = le16_to_cpu(color); + break; + case 3: + s->cirrus_blt_fgcol = s->cirrus_shadow_gr1 | + (s->vga.gr[0x11] << 8) | (s->vga.gr[0x13] << 16); + break; + default: + case 4: + color = s->cirrus_shadow_gr1 | (s->vga.gr[0x11] << 8) | + (s->vga.gr[0x13] << 16) | (s->vga.gr[0x15] << 24); + s->cirrus_blt_fgcol = le32_to_cpu(color); + break; + } +} + +static inline void cirrus_bitblt_bgcol(CirrusVGAState *s) +{ + unsigned int color; + switch (s->cirrus_blt_pixelwidth) { + case 1: + s->cirrus_blt_bgcol = s->cirrus_shadow_gr0; + break; + case 2: + color = s->cirrus_shadow_gr0 | (s->vga.gr[0x10] << 8); + s->cirrus_blt_bgcol = le16_to_cpu(color); + break; + case 3: + s->cirrus_blt_bgcol = s->cirrus_shadow_gr0 | + (s->vga.gr[0x10] << 8) | (s->vga.gr[0x12] << 16); + break; + default: + case 4: + color = s->cirrus_shadow_gr0 | (s->vga.gr[0x10] << 8) | + (s->vga.gr[0x12] << 16) | (s->vga.gr[0x14] << 24); + s->cirrus_blt_bgcol = le32_to_cpu(color); + break; + } +} + +static void cirrus_invalidate_region(CirrusVGAState * s, int off_begin, + int off_pitch, int bytesperline, + int lines) +{ + int y; + int off_cur; + int off_cur_end; + + for (y = 0; y < lines; y++) { + off_cur = off_begin; + off_cur_end = (off_cur + bytesperline) & s->cirrus_addr_mask; + memory_region_set_dirty(&s->vga.vram, off_cur, off_cur_end - off_cur); + off_begin += off_pitch; + } +} + +static int cirrus_bitblt_common_patterncopy(CirrusVGAState * s, + const uint8_t * src) +{ + uint8_t *dst; + + dst = s->vga.vram_ptr + (s->cirrus_blt_dstaddr & s->cirrus_addr_mask); + + if (BLTUNSAFE(s)) + return 0; + + (*s->cirrus_rop) (s, dst, src, + s->cirrus_blt_dstpitch, 0, + s->cirrus_blt_width, s->cirrus_blt_height); + cirrus_invalidate_region(s, s->cirrus_blt_dstaddr, + s->cirrus_blt_dstpitch, s->cirrus_blt_width, + s->cirrus_blt_height); + return 1; +} + +/* fill */ + +static int cirrus_bitblt_solidfill(CirrusVGAState *s, int blt_rop) +{ + cirrus_fill_t rop_func; + + if (BLTUNSAFE(s)) + return 0; + rop_func = cirrus_fill[rop_to_index[blt_rop]][s->cirrus_blt_pixelwidth - 1]; + rop_func(s, s->vga.vram_ptr + (s->cirrus_blt_dstaddr & s->cirrus_addr_mask), + s->cirrus_blt_dstpitch, + s->cirrus_blt_width, s->cirrus_blt_height); + cirrus_invalidate_region(s, s->cirrus_blt_dstaddr, + s->cirrus_blt_dstpitch, s->cirrus_blt_width, + s->cirrus_blt_height); + cirrus_bitblt_reset(s); + return 1; +} + +/*************************************** + * + * bitblt (video-to-video) + * + ***************************************/ + +static int cirrus_bitblt_videotovideo_patterncopy(CirrusVGAState * s) +{ + return cirrus_bitblt_common_patterncopy(s, + s->vga.vram_ptr + ((s->cirrus_blt_srcaddr & ~7) & + s->cirrus_addr_mask)); +} + +static void cirrus_do_copy(CirrusVGAState *s, int dst, int src, int w, int h) +{ + int sx = 0, sy = 0; + int dx = 0, dy = 0; + int depth = 0; + int notify = 0; + + /* make sure to only copy if it's a plain copy ROP */ + if (*s->cirrus_rop == cirrus_bitblt_rop_fwd_src || + *s->cirrus_rop == cirrus_bitblt_rop_bkwd_src) { + + int width, height; + + depth = s->vga.get_bpp(&s->vga) / 8; + s->vga.get_resolution(&s->vga, &width, &height); + + /* extra x, y */ + sx = (src % ABS(s->cirrus_blt_srcpitch)) / depth; + sy = (src / ABS(s->cirrus_blt_srcpitch)); + dx = (dst % ABS(s->cirrus_blt_dstpitch)) / depth; + dy = (dst / ABS(s->cirrus_blt_dstpitch)); + + /* normalize width */ + w /= depth; + + /* if we're doing a backward copy, we have to adjust + our x/y to be the upper left corner (instead of the lower + right corner) */ + if (s->cirrus_blt_dstpitch < 0) { + sx -= (s->cirrus_blt_width / depth) - 1; + dx -= (s->cirrus_blt_width / depth) - 1; + sy -= s->cirrus_blt_height - 1; + dy -= s->cirrus_blt_height - 1; + } + + /* are we in the visible portion of memory? */ + if (sx >= 0 && sy >= 0 && dx >= 0 && dy >= 0 && + (sx + w) <= width && (sy + h) <= height && + (dx + w) <= width && (dy + h) <= height) { + notify = 1; + } + } + + /* we have to flush all pending changes so that the copy + is generated at the appropriate moment in time */ + if (notify) + vga_hw_update(); + + (*s->cirrus_rop) (s, s->vga.vram_ptr + + (s->cirrus_blt_dstaddr & s->cirrus_addr_mask), + s->vga.vram_ptr + + (s->cirrus_blt_srcaddr & s->cirrus_addr_mask), + s->cirrus_blt_dstpitch, s->cirrus_blt_srcpitch, + s->cirrus_blt_width, s->cirrus_blt_height); + + if (notify) { + qemu_console_copy(s->vga.con, + sx, sy, dx, dy, + s->cirrus_blt_width / depth, + s->cirrus_blt_height); + } + + /* we don't have to notify the display that this portion has + changed since qemu_console_copy implies this */ + + cirrus_invalidate_region(s, s->cirrus_blt_dstaddr, + s->cirrus_blt_dstpitch, s->cirrus_blt_width, + s->cirrus_blt_height); +} + +static int cirrus_bitblt_videotovideo_copy(CirrusVGAState * s) +{ + if (BLTUNSAFE(s)) + return 0; + + cirrus_do_copy(s, s->cirrus_blt_dstaddr - s->vga.start_addr, + s->cirrus_blt_srcaddr - s->vga.start_addr, + s->cirrus_blt_width, s->cirrus_blt_height); + + return 1; +} + +/*************************************** + * + * bitblt (cpu-to-video) + * + ***************************************/ + +static void cirrus_bitblt_cputovideo_next(CirrusVGAState * s) +{ + int copy_count; + uint8_t *end_ptr; + + if (s->cirrus_srccounter > 0) { + if (s->cirrus_blt_mode & CIRRUS_BLTMODE_PATTERNCOPY) { + cirrus_bitblt_common_patterncopy(s, s->cirrus_bltbuf); + the_end: + s->cirrus_srccounter = 0; + cirrus_bitblt_reset(s); + } else { + /* at least one scan line */ + do { + (*s->cirrus_rop)(s, s->vga.vram_ptr + + (s->cirrus_blt_dstaddr & s->cirrus_addr_mask), + s->cirrus_bltbuf, 0, 0, s->cirrus_blt_width, 1); + cirrus_invalidate_region(s, s->cirrus_blt_dstaddr, 0, + s->cirrus_blt_width, 1); + s->cirrus_blt_dstaddr += s->cirrus_blt_dstpitch; + s->cirrus_srccounter -= s->cirrus_blt_srcpitch; + if (s->cirrus_srccounter <= 0) + goto the_end; + /* more bytes than needed can be transferred because of + word alignment, so we keep them for the next line */ + /* XXX: keep alignment to speed up transfer */ + end_ptr = s->cirrus_bltbuf + s->cirrus_blt_srcpitch; + copy_count = s->cirrus_srcptr_end - end_ptr; + memmove(s->cirrus_bltbuf, end_ptr, copy_count); + s->cirrus_srcptr = s->cirrus_bltbuf + copy_count; + s->cirrus_srcptr_end = s->cirrus_bltbuf + s->cirrus_blt_srcpitch; + } while (s->cirrus_srcptr >= s->cirrus_srcptr_end); + } + } +} + +/*************************************** + * + * bitblt wrapper + * + ***************************************/ + +static void cirrus_bitblt_reset(CirrusVGAState * s) +{ + int need_update; + + s->vga.gr[0x31] &= + ~(CIRRUS_BLT_START | CIRRUS_BLT_BUSY | CIRRUS_BLT_FIFOUSED); + need_update = s->cirrus_srcptr != &s->cirrus_bltbuf[0] + || s->cirrus_srcptr_end != &s->cirrus_bltbuf[0]; + s->cirrus_srcptr = &s->cirrus_bltbuf[0]; + s->cirrus_srcptr_end = &s->cirrus_bltbuf[0]; + s->cirrus_srccounter = 0; + if (!need_update) + return; + cirrus_update_memory_access(s); +} + +static int cirrus_bitblt_cputovideo(CirrusVGAState * s) +{ + int w; + + s->cirrus_blt_mode &= ~CIRRUS_BLTMODE_MEMSYSSRC; + s->cirrus_srcptr = &s->cirrus_bltbuf[0]; + s->cirrus_srcptr_end = &s->cirrus_bltbuf[0]; + + if (s->cirrus_blt_mode & CIRRUS_BLTMODE_PATTERNCOPY) { + if (s->cirrus_blt_mode & CIRRUS_BLTMODE_COLOREXPAND) { + s->cirrus_blt_srcpitch = 8; + } else { + /* XXX: check for 24 bpp */ + s->cirrus_blt_srcpitch = 8 * 8 * s->cirrus_blt_pixelwidth; + } + s->cirrus_srccounter = s->cirrus_blt_srcpitch; + } else { + if (s->cirrus_blt_mode & CIRRUS_BLTMODE_COLOREXPAND) { + w = s->cirrus_blt_width / s->cirrus_blt_pixelwidth; + if (s->cirrus_blt_modeext & CIRRUS_BLTMODEEXT_DWORDGRANULARITY) + s->cirrus_blt_srcpitch = ((w + 31) >> 5); + else + s->cirrus_blt_srcpitch = ((w + 7) >> 3); + } else { + /* always align input size to 32 bits */ + s->cirrus_blt_srcpitch = (s->cirrus_blt_width + 3) & ~3; + } + s->cirrus_srccounter = s->cirrus_blt_srcpitch * s->cirrus_blt_height; + } + s->cirrus_srcptr = s->cirrus_bltbuf; + s->cirrus_srcptr_end = s->cirrus_bltbuf + s->cirrus_blt_srcpitch; + cirrus_update_memory_access(s); + return 1; +} + +static int cirrus_bitblt_videotocpu(CirrusVGAState * s) +{ + /* XXX */ +#ifdef DEBUG_BITBLT + printf("cirrus: bitblt (video to cpu) is not implemented yet\n"); +#endif + return 0; +} + +static int cirrus_bitblt_videotovideo(CirrusVGAState * s) +{ + int ret; + + if (s->cirrus_blt_mode & CIRRUS_BLTMODE_PATTERNCOPY) { + ret = cirrus_bitblt_videotovideo_patterncopy(s); + } else { + ret = cirrus_bitblt_videotovideo_copy(s); + } + if (ret) + cirrus_bitblt_reset(s); + return ret; +} + +static void cirrus_bitblt_start(CirrusVGAState * s) +{ + uint8_t blt_rop; + + s->vga.gr[0x31] |= CIRRUS_BLT_BUSY; + + s->cirrus_blt_width = (s->vga.gr[0x20] | (s->vga.gr[0x21] << 8)) + 1; + s->cirrus_blt_height = (s->vga.gr[0x22] | (s->vga.gr[0x23] << 8)) + 1; + s->cirrus_blt_dstpitch = (s->vga.gr[0x24] | (s->vga.gr[0x25] << 8)); + s->cirrus_blt_srcpitch = (s->vga.gr[0x26] | (s->vga.gr[0x27] << 8)); + s->cirrus_blt_dstaddr = + (s->vga.gr[0x28] | (s->vga.gr[0x29] << 8) | (s->vga.gr[0x2a] << 16)); + s->cirrus_blt_srcaddr = + (s->vga.gr[0x2c] | (s->vga.gr[0x2d] << 8) | (s->vga.gr[0x2e] << 16)); + s->cirrus_blt_mode = s->vga.gr[0x30]; + s->cirrus_blt_modeext = s->vga.gr[0x33]; + blt_rop = s->vga.gr[0x32]; + +#ifdef DEBUG_BITBLT + printf("rop=0x%02x mode=0x%02x modeext=0x%02x w=%d h=%d dpitch=%d spitch=%d daddr=0x%08x saddr=0x%08x writemask=0x%02x\n", + blt_rop, + s->cirrus_blt_mode, + s->cirrus_blt_modeext, + s->cirrus_blt_width, + s->cirrus_blt_height, + s->cirrus_blt_dstpitch, + s->cirrus_blt_srcpitch, + s->cirrus_blt_dstaddr, + s->cirrus_blt_srcaddr, + s->vga.gr[0x2f]); +#endif + + switch (s->cirrus_blt_mode & CIRRUS_BLTMODE_PIXELWIDTHMASK) { + case CIRRUS_BLTMODE_PIXELWIDTH8: + s->cirrus_blt_pixelwidth = 1; + break; + case CIRRUS_BLTMODE_PIXELWIDTH16: + s->cirrus_blt_pixelwidth = 2; + break; + case CIRRUS_BLTMODE_PIXELWIDTH24: + s->cirrus_blt_pixelwidth = 3; + break; + case CIRRUS_BLTMODE_PIXELWIDTH32: + s->cirrus_blt_pixelwidth = 4; + break; + default: +#ifdef DEBUG_BITBLT + printf("cirrus: bitblt - pixel width is unknown\n"); +#endif + goto bitblt_ignore; + } + s->cirrus_blt_mode &= ~CIRRUS_BLTMODE_PIXELWIDTHMASK; + + if ((s-> + cirrus_blt_mode & (CIRRUS_BLTMODE_MEMSYSSRC | + CIRRUS_BLTMODE_MEMSYSDEST)) + == (CIRRUS_BLTMODE_MEMSYSSRC | CIRRUS_BLTMODE_MEMSYSDEST)) { +#ifdef DEBUG_BITBLT + printf("cirrus: bitblt - memory-to-memory copy is requested\n"); +#endif + goto bitblt_ignore; + } + + if ((s->cirrus_blt_modeext & CIRRUS_BLTMODEEXT_SOLIDFILL) && + (s->cirrus_blt_mode & (CIRRUS_BLTMODE_MEMSYSDEST | + CIRRUS_BLTMODE_TRANSPARENTCOMP | + CIRRUS_BLTMODE_PATTERNCOPY | + CIRRUS_BLTMODE_COLOREXPAND)) == + (CIRRUS_BLTMODE_PATTERNCOPY | CIRRUS_BLTMODE_COLOREXPAND)) { + cirrus_bitblt_fgcol(s); + cirrus_bitblt_solidfill(s, blt_rop); + } else { + if ((s->cirrus_blt_mode & (CIRRUS_BLTMODE_COLOREXPAND | + CIRRUS_BLTMODE_PATTERNCOPY)) == + CIRRUS_BLTMODE_COLOREXPAND) { + + if (s->cirrus_blt_mode & CIRRUS_BLTMODE_TRANSPARENTCOMP) { + if (s->cirrus_blt_modeext & CIRRUS_BLTMODEEXT_COLOREXPINV) + cirrus_bitblt_bgcol(s); + else + cirrus_bitblt_fgcol(s); + s->cirrus_rop = cirrus_colorexpand_transp[rop_to_index[blt_rop]][s->cirrus_blt_pixelwidth - 1]; + } else { + cirrus_bitblt_fgcol(s); + cirrus_bitblt_bgcol(s); + s->cirrus_rop = cirrus_colorexpand[rop_to_index[blt_rop]][s->cirrus_blt_pixelwidth - 1]; + } + } else if (s->cirrus_blt_mode & CIRRUS_BLTMODE_PATTERNCOPY) { + if (s->cirrus_blt_mode & CIRRUS_BLTMODE_COLOREXPAND) { + if (s->cirrus_blt_mode & CIRRUS_BLTMODE_TRANSPARENTCOMP) { + if (s->cirrus_blt_modeext & CIRRUS_BLTMODEEXT_COLOREXPINV) + cirrus_bitblt_bgcol(s); + else + cirrus_bitblt_fgcol(s); + s->cirrus_rop = cirrus_colorexpand_pattern_transp[rop_to_index[blt_rop]][s->cirrus_blt_pixelwidth - 1]; + } else { + cirrus_bitblt_fgcol(s); + cirrus_bitblt_bgcol(s); + s->cirrus_rop = cirrus_colorexpand_pattern[rop_to_index[blt_rop]][s->cirrus_blt_pixelwidth - 1]; + } + } else { + s->cirrus_rop = cirrus_patternfill[rop_to_index[blt_rop]][s->cirrus_blt_pixelwidth - 1]; + } + } else { + if (s->cirrus_blt_mode & CIRRUS_BLTMODE_TRANSPARENTCOMP) { + if (s->cirrus_blt_pixelwidth > 2) { + printf("src transparent without colorexpand must be 8bpp or 16bpp\n"); + goto bitblt_ignore; + } + if (s->cirrus_blt_mode & CIRRUS_BLTMODE_BACKWARDS) { + s->cirrus_blt_dstpitch = -s->cirrus_blt_dstpitch; + s->cirrus_blt_srcpitch = -s->cirrus_blt_srcpitch; + s->cirrus_rop = cirrus_bkwd_transp_rop[rop_to_index[blt_rop]][s->cirrus_blt_pixelwidth - 1]; + } else { + s->cirrus_rop = cirrus_fwd_transp_rop[rop_to_index[blt_rop]][s->cirrus_blt_pixelwidth - 1]; + } + } else { + if (s->cirrus_blt_mode & CIRRUS_BLTMODE_BACKWARDS) { + s->cirrus_blt_dstpitch = -s->cirrus_blt_dstpitch; + s->cirrus_blt_srcpitch = -s->cirrus_blt_srcpitch; + s->cirrus_rop = cirrus_bkwd_rop[rop_to_index[blt_rop]]; + } else { + s->cirrus_rop = cirrus_fwd_rop[rop_to_index[blt_rop]]; + } + } + } + // setup bitblt engine. + if (s->cirrus_blt_mode & CIRRUS_BLTMODE_MEMSYSSRC) { + if (!cirrus_bitblt_cputovideo(s)) + goto bitblt_ignore; + } else if (s->cirrus_blt_mode & CIRRUS_BLTMODE_MEMSYSDEST) { + if (!cirrus_bitblt_videotocpu(s)) + goto bitblt_ignore; + } else { + if (!cirrus_bitblt_videotovideo(s)) + goto bitblt_ignore; + } + } + return; + bitblt_ignore:; + cirrus_bitblt_reset(s); +} + +static void cirrus_write_bitblt(CirrusVGAState * s, unsigned reg_value) +{ + unsigned old_value; + + old_value = s->vga.gr[0x31]; + s->vga.gr[0x31] = reg_value; + + if (((old_value & CIRRUS_BLT_RESET) != 0) && + ((reg_value & CIRRUS_BLT_RESET) == 0)) { + cirrus_bitblt_reset(s); + } else if (((old_value & CIRRUS_BLT_START) == 0) && + ((reg_value & CIRRUS_BLT_START) != 0)) { + cirrus_bitblt_start(s); + } +} + + +/*************************************** + * + * basic parameters + * + ***************************************/ + +static void cirrus_get_offsets(VGACommonState *s1, + uint32_t *pline_offset, + uint32_t *pstart_addr, + uint32_t *pline_compare) +{ + CirrusVGAState * s = container_of(s1, CirrusVGAState, vga); + uint32_t start_addr, line_offset, line_compare; + + line_offset = s->vga.cr[0x13] + | ((s->vga.cr[0x1b] & 0x10) << 4); + line_offset <<= 3; + *pline_offset = line_offset; + + start_addr = (s->vga.cr[0x0c] << 8) + | s->vga.cr[0x0d] + | ((s->vga.cr[0x1b] & 0x01) << 16) + | ((s->vga.cr[0x1b] & 0x0c) << 15) + | ((s->vga.cr[0x1d] & 0x80) << 12); + *pstart_addr = start_addr; + + line_compare = s->vga.cr[0x18] | + ((s->vga.cr[0x07] & 0x10) << 4) | + ((s->vga.cr[0x09] & 0x40) << 3); + *pline_compare = line_compare; +} + +static uint32_t cirrus_get_bpp16_depth(CirrusVGAState * s) +{ + uint32_t ret = 16; + + switch (s->cirrus_hidden_dac_data & 0xf) { + case 0: + ret = 15; + break; /* Sierra HiColor */ + case 1: + ret = 16; + break; /* XGA HiColor */ + default: +#ifdef DEBUG_CIRRUS + printf("cirrus: invalid DAC value %x in 16bpp\n", + (s->cirrus_hidden_dac_data & 0xf)); +#endif + ret = 15; /* XXX */ + break; + } + return ret; +} + +static int cirrus_get_bpp(VGACommonState *s1) +{ + CirrusVGAState * s = container_of(s1, CirrusVGAState, vga); + uint32_t ret = 8; + + if ((s->vga.sr[0x07] & 0x01) != 0) { + /* Cirrus SVGA */ + switch (s->vga.sr[0x07] & CIRRUS_SR7_BPP_MASK) { + case CIRRUS_SR7_BPP_8: + ret = 8; + break; + case CIRRUS_SR7_BPP_16_DOUBLEVCLK: + ret = cirrus_get_bpp16_depth(s); + break; + case CIRRUS_SR7_BPP_24: + ret = 24; + break; + case CIRRUS_SR7_BPP_16: + ret = cirrus_get_bpp16_depth(s); + break; + case CIRRUS_SR7_BPP_32: + ret = 32; + break; + default: +#ifdef DEBUG_CIRRUS + printf("cirrus: unknown bpp - sr7=%x\n", s->vga.sr[0x7]); +#endif + ret = 8; + break; + } + } else { + /* VGA */ + ret = 0; + } + + return ret; +} + +static void cirrus_get_resolution(VGACommonState *s, int *pwidth, int *pheight) +{ + int width, height; + + width = (s->cr[0x01] + 1) * 8; + height = s->cr[0x12] | + ((s->cr[0x07] & 0x02) << 7) | + ((s->cr[0x07] & 0x40) << 3); + height = (height + 1); + /* interlace support */ + if (s->cr[0x1a] & 0x01) + height = height * 2; + *pwidth = width; + *pheight = height; +} + +/*************************************** + * + * bank memory + * + ***************************************/ + +static void cirrus_update_bank_ptr(CirrusVGAState * s, unsigned bank_index) +{ + unsigned offset; + unsigned limit; + + if ((s->vga.gr[0x0b] & 0x01) != 0) /* dual bank */ + offset = s->vga.gr[0x09 + bank_index]; + else /* single bank */ + offset = s->vga.gr[0x09]; + + if ((s->vga.gr[0x0b] & 0x20) != 0) + offset <<= 14; + else + offset <<= 12; + + if (s->real_vram_size <= offset) + limit = 0; + else + limit = s->real_vram_size - offset; + + if (((s->vga.gr[0x0b] & 0x01) == 0) && (bank_index != 0)) { + if (limit > 0x8000) { + offset += 0x8000; + limit -= 0x8000; + } else { + limit = 0; + } + } + + if (limit > 0) { + s->cirrus_bank_base[bank_index] = offset; + s->cirrus_bank_limit[bank_index] = limit; + } else { + s->cirrus_bank_base[bank_index] = 0; + s->cirrus_bank_limit[bank_index] = 0; + } +} + +/*************************************** + * + * I/O access between 0x3c4-0x3c5 + * + ***************************************/ + +static int cirrus_vga_read_sr(CirrusVGAState * s) +{ + switch (s->vga.sr_index) { + case 0x00: // Standard VGA + case 0x01: // Standard VGA + case 0x02: // Standard VGA + case 0x03: // Standard VGA + case 0x04: // Standard VGA + return s->vga.sr[s->vga.sr_index]; + case 0x06: // Unlock Cirrus extensions + return s->vga.sr[s->vga.sr_index]; + case 0x10: + case 0x30: + case 0x50: + case 0x70: // Graphics Cursor X + case 0x90: + case 0xb0: + case 0xd0: + case 0xf0: // Graphics Cursor X + return s->vga.sr[0x10]; + case 0x11: + case 0x31: + case 0x51: + case 0x71: // Graphics Cursor Y + case 0x91: + case 0xb1: + case 0xd1: + case 0xf1: // Graphics Cursor Y + return s->vga.sr[0x11]; + case 0x05: // ??? + case 0x07: // Extended Sequencer Mode + case 0x08: // EEPROM Control + case 0x09: // Scratch Register 0 + case 0x0a: // Scratch Register 1 + case 0x0b: // VCLK 0 + case 0x0c: // VCLK 1 + case 0x0d: // VCLK 2 + case 0x0e: // VCLK 3 + case 0x0f: // DRAM Control + case 0x12: // Graphics Cursor Attribute + case 0x13: // Graphics Cursor Pattern Address + case 0x14: // Scratch Register 2 + case 0x15: // Scratch Register 3 + case 0x16: // Performance Tuning Register + case 0x17: // Configuration Readback and Extended Control + case 0x18: // Signature Generator Control + case 0x19: // Signal Generator Result + case 0x1a: // Signal Generator Result + case 0x1b: // VCLK 0 Denominator & Post + case 0x1c: // VCLK 1 Denominator & Post + case 0x1d: // VCLK 2 Denominator & Post + case 0x1e: // VCLK 3 Denominator & Post + case 0x1f: // BIOS Write Enable and MCLK select +#ifdef DEBUG_CIRRUS + printf("cirrus: handled inport sr_index %02x\n", s->vga.sr_index); +#endif + return s->vga.sr[s->vga.sr_index]; + default: +#ifdef DEBUG_CIRRUS + printf("cirrus: inport sr_index %02x\n", s->vga.sr_index); +#endif + return 0xff; + break; + } +} + +static void cirrus_vga_write_sr(CirrusVGAState * s, uint32_t val) +{ + switch (s->vga.sr_index) { + case 0x00: // Standard VGA + case 0x01: // Standard VGA + case 0x02: // Standard VGA + case 0x03: // Standard VGA + case 0x04: // Standard VGA + s->vga.sr[s->vga.sr_index] = val & sr_mask[s->vga.sr_index]; + if (s->vga.sr_index == 1) + s->vga.update_retrace_info(&s->vga); + break; + case 0x06: // Unlock Cirrus extensions + val &= 0x17; + if (val == 0x12) { + s->vga.sr[s->vga.sr_index] = 0x12; + } else { + s->vga.sr[s->vga.sr_index] = 0x0f; + } + break; + case 0x10: + case 0x30: + case 0x50: + case 0x70: // Graphics Cursor X + case 0x90: + case 0xb0: + case 0xd0: + case 0xf0: // Graphics Cursor X + s->vga.sr[0x10] = val; + s->hw_cursor_x = (val << 3) | (s->vga.sr_index >> 5); + break; + case 0x11: + case 0x31: + case 0x51: + case 0x71: // Graphics Cursor Y + case 0x91: + case 0xb1: + case 0xd1: + case 0xf1: // Graphics Cursor Y + s->vga.sr[0x11] = val; + s->hw_cursor_y = (val << 3) | (s->vga.sr_index >> 5); + break; + case 0x07: // Extended Sequencer Mode + cirrus_update_memory_access(s); + case 0x08: // EEPROM Control + case 0x09: // Scratch Register 0 + case 0x0a: // Scratch Register 1 + case 0x0b: // VCLK 0 + case 0x0c: // VCLK 1 + case 0x0d: // VCLK 2 + case 0x0e: // VCLK 3 + case 0x0f: // DRAM Control + case 0x12: // Graphics Cursor Attribute + case 0x13: // Graphics Cursor Pattern Address + case 0x14: // Scratch Register 2 + case 0x15: // Scratch Register 3 + case 0x16: // Performance Tuning Register + case 0x18: // Signature Generator Control + case 0x19: // Signature Generator Result + case 0x1a: // Signature Generator Result + case 0x1b: // VCLK 0 Denominator & Post + case 0x1c: // VCLK 1 Denominator & Post + case 0x1d: // VCLK 2 Denominator & Post + case 0x1e: // VCLK 3 Denominator & Post + case 0x1f: // BIOS Write Enable and MCLK select + s->vga.sr[s->vga.sr_index] = val; +#ifdef DEBUG_CIRRUS + printf("cirrus: handled outport sr_index %02x, sr_value %02x\n", + s->vga.sr_index, val); +#endif + break; + case 0x17: // Configuration Readback and Extended Control + s->vga.sr[s->vga.sr_index] = (s->vga.sr[s->vga.sr_index] & 0x38) + | (val & 0xc7); + cirrus_update_memory_access(s); + break; + default: +#ifdef DEBUG_CIRRUS + printf("cirrus: outport sr_index %02x, sr_value %02x\n", + s->vga.sr_index, val); +#endif + break; + } +} + +/*************************************** + * + * I/O access at 0x3c6 + * + ***************************************/ + +static int cirrus_read_hidden_dac(CirrusVGAState * s) +{ + if (++s->cirrus_hidden_dac_lockindex == 5) { + s->cirrus_hidden_dac_lockindex = 0; + return s->cirrus_hidden_dac_data; + } + return 0xff; +} + +static void cirrus_write_hidden_dac(CirrusVGAState * s, int reg_value) +{ + if (s->cirrus_hidden_dac_lockindex == 4) { + s->cirrus_hidden_dac_data = reg_value; +#if defined(DEBUG_CIRRUS) + printf("cirrus: outport hidden DAC, value %02x\n", reg_value); +#endif + } + s->cirrus_hidden_dac_lockindex = 0; +} + +/*************************************** + * + * I/O access at 0x3c9 + * + ***************************************/ + +static int cirrus_vga_read_palette(CirrusVGAState * s) +{ + int val; + + if ((s->vga.sr[0x12] & CIRRUS_CURSOR_HIDDENPEL)) { + val = s->cirrus_hidden_palette[(s->vga.dac_read_index & 0x0f) * 3 + + s->vga.dac_sub_index]; + } else { + val = s->vga.palette[s->vga.dac_read_index * 3 + s->vga.dac_sub_index]; + } + if (++s->vga.dac_sub_index == 3) { + s->vga.dac_sub_index = 0; + s->vga.dac_read_index++; + } + return val; +} + +static void cirrus_vga_write_palette(CirrusVGAState * s, int reg_value) +{ + s->vga.dac_cache[s->vga.dac_sub_index] = reg_value; + if (++s->vga.dac_sub_index == 3) { + if ((s->vga.sr[0x12] & CIRRUS_CURSOR_HIDDENPEL)) { + memcpy(&s->cirrus_hidden_palette[(s->vga.dac_write_index & 0x0f) * 3], + s->vga.dac_cache, 3); + } else { + memcpy(&s->vga.palette[s->vga.dac_write_index * 3], s->vga.dac_cache, 3); + } + /* XXX update cursor */ + s->vga.dac_sub_index = 0; + s->vga.dac_write_index++; + } +} + +/*************************************** + * + * I/O access between 0x3ce-0x3cf + * + ***************************************/ + +static int cirrus_vga_read_gr(CirrusVGAState * s, unsigned reg_index) +{ + switch (reg_index) { + case 0x00: // Standard VGA, BGCOLOR 0x000000ff + return s->cirrus_shadow_gr0; + case 0x01: // Standard VGA, FGCOLOR 0x000000ff + return s->cirrus_shadow_gr1; + case 0x02: // Standard VGA + case 0x03: // Standard VGA + case 0x04: // Standard VGA + case 0x06: // Standard VGA + case 0x07: // Standard VGA + case 0x08: // Standard VGA + return s->vga.gr[s->vga.gr_index]; + case 0x05: // Standard VGA, Cirrus extended mode + default: + break; + } + + if (reg_index < 0x3a) { + return s->vga.gr[reg_index]; + } else { +#ifdef DEBUG_CIRRUS + printf("cirrus: inport gr_index %02x\n", reg_index); +#endif + return 0xff; + } +} + +static void +cirrus_vga_write_gr(CirrusVGAState * s, unsigned reg_index, int reg_value) +{ +#if defined(DEBUG_BITBLT) && 0 + printf("gr%02x: %02x\n", reg_index, reg_value); +#endif + switch (reg_index) { + case 0x00: // Standard VGA, BGCOLOR 0x000000ff + s->vga.gr[reg_index] = reg_value & gr_mask[reg_index]; + s->cirrus_shadow_gr0 = reg_value; + break; + case 0x01: // Standard VGA, FGCOLOR 0x000000ff + s->vga.gr[reg_index] = reg_value & gr_mask[reg_index]; + s->cirrus_shadow_gr1 = reg_value; + break; + case 0x02: // Standard VGA + case 0x03: // Standard VGA + case 0x04: // Standard VGA + case 0x06: // Standard VGA + case 0x07: // Standard VGA + case 0x08: // Standard VGA + s->vga.gr[reg_index] = reg_value & gr_mask[reg_index]; + break; + case 0x05: // Standard VGA, Cirrus extended mode + s->vga.gr[reg_index] = reg_value & 0x7f; + cirrus_update_memory_access(s); + break; + case 0x09: // bank offset #0 + case 0x0A: // bank offset #1 + s->vga.gr[reg_index] = reg_value; + cirrus_update_bank_ptr(s, 0); + cirrus_update_bank_ptr(s, 1); + cirrus_update_memory_access(s); + break; + case 0x0B: + s->vga.gr[reg_index] = reg_value; + cirrus_update_bank_ptr(s, 0); + cirrus_update_bank_ptr(s, 1); + cirrus_update_memory_access(s); + break; + case 0x10: // BGCOLOR 0x0000ff00 + case 0x11: // FGCOLOR 0x0000ff00 + case 0x12: // BGCOLOR 0x00ff0000 + case 0x13: // FGCOLOR 0x00ff0000 + case 0x14: // BGCOLOR 0xff000000 + case 0x15: // FGCOLOR 0xff000000 + case 0x20: // BLT WIDTH 0x0000ff + case 0x22: // BLT HEIGHT 0x0000ff + case 0x24: // BLT DEST PITCH 0x0000ff + case 0x26: // BLT SRC PITCH 0x0000ff + case 0x28: // BLT DEST ADDR 0x0000ff + case 0x29: // BLT DEST ADDR 0x00ff00 + case 0x2c: // BLT SRC ADDR 0x0000ff + case 0x2d: // BLT SRC ADDR 0x00ff00 + case 0x2f: // BLT WRITEMASK + case 0x30: // BLT MODE + case 0x32: // RASTER OP + case 0x33: // BLT MODEEXT + case 0x34: // BLT TRANSPARENT COLOR 0x00ff + case 0x35: // BLT TRANSPARENT COLOR 0xff00 + case 0x38: // BLT TRANSPARENT COLOR MASK 0x00ff + case 0x39: // BLT TRANSPARENT COLOR MASK 0xff00 + s->vga.gr[reg_index] = reg_value; + break; + case 0x21: // BLT WIDTH 0x001f00 + case 0x23: // BLT HEIGHT 0x001f00 + case 0x25: // BLT DEST PITCH 0x001f00 + case 0x27: // BLT SRC PITCH 0x001f00 + s->vga.gr[reg_index] = reg_value & 0x1f; + break; + case 0x2a: // BLT DEST ADDR 0x3f0000 + s->vga.gr[reg_index] = reg_value & 0x3f; + /* if auto start mode, starts bit blt now */ + if (s->vga.gr[0x31] & CIRRUS_BLT_AUTOSTART) { + cirrus_bitblt_start(s); + } + break; + case 0x2e: // BLT SRC ADDR 0x3f0000 + s->vga.gr[reg_index] = reg_value & 0x3f; + break; + case 0x31: // BLT STATUS/START + cirrus_write_bitblt(s, reg_value); + break; + default: +#ifdef DEBUG_CIRRUS + printf("cirrus: outport gr_index %02x, gr_value %02x\n", reg_index, + reg_value); +#endif + break; + } +} + +/*************************************** + * + * I/O access between 0x3d4-0x3d5 + * + ***************************************/ + +static int cirrus_vga_read_cr(CirrusVGAState * s, unsigned reg_index) +{ + switch (reg_index) { + case 0x00: // Standard VGA + case 0x01: // Standard VGA + case 0x02: // Standard VGA + case 0x03: // Standard VGA + case 0x04: // Standard VGA + case 0x05: // Standard VGA + case 0x06: // Standard VGA + case 0x07: // Standard VGA + case 0x08: // Standard VGA + case 0x09: // Standard VGA + case 0x0a: // Standard VGA + case 0x0b: // Standard VGA + case 0x0c: // Standard VGA + case 0x0d: // Standard VGA + case 0x0e: // Standard VGA + case 0x0f: // Standard VGA + case 0x10: // Standard VGA + case 0x11: // Standard VGA + case 0x12: // Standard VGA + case 0x13: // Standard VGA + case 0x14: // Standard VGA + case 0x15: // Standard VGA + case 0x16: // Standard VGA + case 0x17: // Standard VGA + case 0x18: // Standard VGA + return s->vga.cr[s->vga.cr_index]; + case 0x24: // Attribute Controller Toggle Readback (R) + return (s->vga.ar_flip_flop << 7); + case 0x19: // Interlace End + case 0x1a: // Miscellaneous Control + case 0x1b: // Extended Display Control + case 0x1c: // Sync Adjust and Genlock + case 0x1d: // Overlay Extended Control + case 0x22: // Graphics Data Latches Readback (R) + case 0x25: // Part Status + case 0x27: // Part ID (R) + return s->vga.cr[s->vga.cr_index]; + case 0x26: // Attribute Controller Index Readback (R) + return s->vga.ar_index & 0x3f; + break; + default: +#ifdef DEBUG_CIRRUS + printf("cirrus: inport cr_index %02x\n", reg_index); +#endif + return 0xff; + } +} + +static void cirrus_vga_write_cr(CirrusVGAState * s, int reg_value) +{ + switch (s->vga.cr_index) { + case 0x00: // Standard VGA + case 0x01: // Standard VGA + case 0x02: // Standard VGA + case 0x03: // Standard VGA + case 0x04: // Standard VGA + case 0x05: // Standard VGA + case 0x06: // Standard VGA + case 0x07: // Standard VGA + case 0x08: // Standard VGA + case 0x09: // Standard VGA + case 0x0a: // Standard VGA + case 0x0b: // Standard VGA + case 0x0c: // Standard VGA + case 0x0d: // Standard VGA + case 0x0e: // Standard VGA + case 0x0f: // Standard VGA + case 0x10: // Standard VGA + case 0x11: // Standard VGA + case 0x12: // Standard VGA + case 0x13: // Standard VGA + case 0x14: // Standard VGA + case 0x15: // Standard VGA + case 0x16: // Standard VGA + case 0x17: // Standard VGA + case 0x18: // Standard VGA + /* handle CR0-7 protection */ + if ((s->vga.cr[0x11] & 0x80) && s->vga.cr_index <= 7) { + /* can always write bit 4 of CR7 */ + if (s->vga.cr_index == 7) + s->vga.cr[7] = (s->vga.cr[7] & ~0x10) | (reg_value & 0x10); + return; + } + s->vga.cr[s->vga.cr_index] = reg_value; + switch(s->vga.cr_index) { + case 0x00: + case 0x04: + case 0x05: + case 0x06: + case 0x07: + case 0x11: + case 0x17: + s->vga.update_retrace_info(&s->vga); + break; + } + break; + case 0x19: // Interlace End + case 0x1a: // Miscellaneous Control + case 0x1b: // Extended Display Control + case 0x1c: // Sync Adjust and Genlock + case 0x1d: // Overlay Extended Control + s->vga.cr[s->vga.cr_index] = reg_value; +#ifdef DEBUG_CIRRUS + printf("cirrus: handled outport cr_index %02x, cr_value %02x\n", + s->vga.cr_index, reg_value); +#endif + break; + case 0x22: // Graphics Data Latches Readback (R) + case 0x24: // Attribute Controller Toggle Readback (R) + case 0x26: // Attribute Controller Index Readback (R) + case 0x27: // Part ID (R) + break; + case 0x25: // Part Status + default: +#ifdef DEBUG_CIRRUS + printf("cirrus: outport cr_index %02x, cr_value %02x\n", + s->vga.cr_index, reg_value); +#endif + break; + } +} + +/*************************************** + * + * memory-mapped I/O (bitblt) + * + ***************************************/ + +static uint8_t cirrus_mmio_blt_read(CirrusVGAState * s, unsigned address) +{ + int value = 0xff; + + switch (address) { + case (CIRRUS_MMIO_BLTBGCOLOR + 0): + value = cirrus_vga_read_gr(s, 0x00); + break; + case (CIRRUS_MMIO_BLTBGCOLOR + 1): + value = cirrus_vga_read_gr(s, 0x10); + break; + case (CIRRUS_MMIO_BLTBGCOLOR + 2): + value = cirrus_vga_read_gr(s, 0x12); + break; + case (CIRRUS_MMIO_BLTBGCOLOR + 3): + value = cirrus_vga_read_gr(s, 0x14); + break; + case (CIRRUS_MMIO_BLTFGCOLOR + 0): + value = cirrus_vga_read_gr(s, 0x01); + break; + case (CIRRUS_MMIO_BLTFGCOLOR + 1): + value = cirrus_vga_read_gr(s, 0x11); + break; + case (CIRRUS_MMIO_BLTFGCOLOR + 2): + value = cirrus_vga_read_gr(s, 0x13); + break; + case (CIRRUS_MMIO_BLTFGCOLOR + 3): + value = cirrus_vga_read_gr(s, 0x15); + break; + case (CIRRUS_MMIO_BLTWIDTH + 0): + value = cirrus_vga_read_gr(s, 0x20); + break; + case (CIRRUS_MMIO_BLTWIDTH + 1): + value = cirrus_vga_read_gr(s, 0x21); + break; + case (CIRRUS_MMIO_BLTHEIGHT + 0): + value = cirrus_vga_read_gr(s, 0x22); + break; + case (CIRRUS_MMIO_BLTHEIGHT + 1): + value = cirrus_vga_read_gr(s, 0x23); + break; + case (CIRRUS_MMIO_BLTDESTPITCH + 0): + value = cirrus_vga_read_gr(s, 0x24); + break; + case (CIRRUS_MMIO_BLTDESTPITCH + 1): + value = cirrus_vga_read_gr(s, 0x25); + break; + case (CIRRUS_MMIO_BLTSRCPITCH + 0): + value = cirrus_vga_read_gr(s, 0x26); + break; + case (CIRRUS_MMIO_BLTSRCPITCH + 1): + value = cirrus_vga_read_gr(s, 0x27); + break; + case (CIRRUS_MMIO_BLTDESTADDR + 0): + value = cirrus_vga_read_gr(s, 0x28); + break; + case (CIRRUS_MMIO_BLTDESTADDR + 1): + value = cirrus_vga_read_gr(s, 0x29); + break; + case (CIRRUS_MMIO_BLTDESTADDR + 2): + value = cirrus_vga_read_gr(s, 0x2a); + break; + case (CIRRUS_MMIO_BLTSRCADDR + 0): + value = cirrus_vga_read_gr(s, 0x2c); + break; + case (CIRRUS_MMIO_BLTSRCADDR + 1): + value = cirrus_vga_read_gr(s, 0x2d); + break; + case (CIRRUS_MMIO_BLTSRCADDR + 2): + value = cirrus_vga_read_gr(s, 0x2e); + break; + case CIRRUS_MMIO_BLTWRITEMASK: + value = cirrus_vga_read_gr(s, 0x2f); + break; + case CIRRUS_MMIO_BLTMODE: + value = cirrus_vga_read_gr(s, 0x30); + break; + case CIRRUS_MMIO_BLTROP: + value = cirrus_vga_read_gr(s, 0x32); + break; + case CIRRUS_MMIO_BLTMODEEXT: + value = cirrus_vga_read_gr(s, 0x33); + break; + case (CIRRUS_MMIO_BLTTRANSPARENTCOLOR + 0): + value = cirrus_vga_read_gr(s, 0x34); + break; + case (CIRRUS_MMIO_BLTTRANSPARENTCOLOR + 1): + value = cirrus_vga_read_gr(s, 0x35); + break; + case (CIRRUS_MMIO_BLTTRANSPARENTCOLORMASK + 0): + value = cirrus_vga_read_gr(s, 0x38); + break; + case (CIRRUS_MMIO_BLTTRANSPARENTCOLORMASK + 1): + value = cirrus_vga_read_gr(s, 0x39); + break; + case CIRRUS_MMIO_BLTSTATUS: + value = cirrus_vga_read_gr(s, 0x31); + break; + default: +#ifdef DEBUG_CIRRUS + printf("cirrus: mmio read - address 0x%04x\n", address); +#endif + break; + } + + return (uint8_t) value; +} + +static void cirrus_mmio_blt_write(CirrusVGAState * s, unsigned address, + uint8_t value) +{ + switch (address) { + case (CIRRUS_MMIO_BLTBGCOLOR + 0): + cirrus_vga_write_gr(s, 0x00, value); + break; + case (CIRRUS_MMIO_BLTBGCOLOR + 1): + cirrus_vga_write_gr(s, 0x10, value); + break; + case (CIRRUS_MMIO_BLTBGCOLOR + 2): + cirrus_vga_write_gr(s, 0x12, value); + break; + case (CIRRUS_MMIO_BLTBGCOLOR + 3): + cirrus_vga_write_gr(s, 0x14, value); + break; + case (CIRRUS_MMIO_BLTFGCOLOR + 0): + cirrus_vga_write_gr(s, 0x01, value); + break; + case (CIRRUS_MMIO_BLTFGCOLOR + 1): + cirrus_vga_write_gr(s, 0x11, value); + break; + case (CIRRUS_MMIO_BLTFGCOLOR + 2): + cirrus_vga_write_gr(s, 0x13, value); + break; + case (CIRRUS_MMIO_BLTFGCOLOR + 3): + cirrus_vga_write_gr(s, 0x15, value); + break; + case (CIRRUS_MMIO_BLTWIDTH + 0): + cirrus_vga_write_gr(s, 0x20, value); + break; + case (CIRRUS_MMIO_BLTWIDTH + 1): + cirrus_vga_write_gr(s, 0x21, value); + break; + case (CIRRUS_MMIO_BLTHEIGHT + 0): + cirrus_vga_write_gr(s, 0x22, value); + break; + case (CIRRUS_MMIO_BLTHEIGHT + 1): + cirrus_vga_write_gr(s, 0x23, value); + break; + case (CIRRUS_MMIO_BLTDESTPITCH + 0): + cirrus_vga_write_gr(s, 0x24, value); + break; + case (CIRRUS_MMIO_BLTDESTPITCH + 1): + cirrus_vga_write_gr(s, 0x25, value); + break; + case (CIRRUS_MMIO_BLTSRCPITCH + 0): + cirrus_vga_write_gr(s, 0x26, value); + break; + case (CIRRUS_MMIO_BLTSRCPITCH + 1): + cirrus_vga_write_gr(s, 0x27, value); + break; + case (CIRRUS_MMIO_BLTDESTADDR + 0): + cirrus_vga_write_gr(s, 0x28, value); + break; + case (CIRRUS_MMIO_BLTDESTADDR + 1): + cirrus_vga_write_gr(s, 0x29, value); + break; + case (CIRRUS_MMIO_BLTDESTADDR + 2): + cirrus_vga_write_gr(s, 0x2a, value); + break; + case (CIRRUS_MMIO_BLTDESTADDR + 3): + /* ignored */ + break; + case (CIRRUS_MMIO_BLTSRCADDR + 0): + cirrus_vga_write_gr(s, 0x2c, value); + break; + case (CIRRUS_MMIO_BLTSRCADDR + 1): + cirrus_vga_write_gr(s, 0x2d, value); + break; + case (CIRRUS_MMIO_BLTSRCADDR + 2): + cirrus_vga_write_gr(s, 0x2e, value); + break; + case CIRRUS_MMIO_BLTWRITEMASK: + cirrus_vga_write_gr(s, 0x2f, value); + break; + case CIRRUS_MMIO_BLTMODE: + cirrus_vga_write_gr(s, 0x30, value); + break; + case CIRRUS_MMIO_BLTROP: + cirrus_vga_write_gr(s, 0x32, value); + break; + case CIRRUS_MMIO_BLTMODEEXT: + cirrus_vga_write_gr(s, 0x33, value); + break; + case (CIRRUS_MMIO_BLTTRANSPARENTCOLOR + 0): + cirrus_vga_write_gr(s, 0x34, value); + break; + case (CIRRUS_MMIO_BLTTRANSPARENTCOLOR + 1): + cirrus_vga_write_gr(s, 0x35, value); + break; + case (CIRRUS_MMIO_BLTTRANSPARENTCOLORMASK + 0): + cirrus_vga_write_gr(s, 0x38, value); + break; + case (CIRRUS_MMIO_BLTTRANSPARENTCOLORMASK + 1): + cirrus_vga_write_gr(s, 0x39, value); + break; + case CIRRUS_MMIO_BLTSTATUS: + cirrus_vga_write_gr(s, 0x31, value); + break; + default: +#ifdef DEBUG_CIRRUS + printf("cirrus: mmio write - addr 0x%04x val 0x%02x (ignored)\n", + address, value); +#endif + break; + } +} + +/*************************************** + * + * write mode 4/5 + * + ***************************************/ + +static void cirrus_mem_writeb_mode4and5_8bpp(CirrusVGAState * s, + unsigned mode, + unsigned offset, + uint32_t mem_value) +{ + int x; + unsigned val = mem_value; + uint8_t *dst; + + dst = s->vga.vram_ptr + (offset &= s->cirrus_addr_mask); + for (x = 0; x < 8; x++) { + if (val & 0x80) { + *dst = s->cirrus_shadow_gr1; + } else if (mode == 5) { + *dst = s->cirrus_shadow_gr0; + } + val <<= 1; + dst++; + } + memory_region_set_dirty(&s->vga.vram, offset, 8); +} + +static void cirrus_mem_writeb_mode4and5_16bpp(CirrusVGAState * s, + unsigned mode, + unsigned offset, + uint32_t mem_value) +{ + int x; + unsigned val = mem_value; + uint8_t *dst; + + dst = s->vga.vram_ptr + (offset &= s->cirrus_addr_mask); + for (x = 0; x < 8; x++) { + if (val & 0x80) { + *dst = s->cirrus_shadow_gr1; + *(dst + 1) = s->vga.gr[0x11]; + } else if (mode == 5) { + *dst = s->cirrus_shadow_gr0; + *(dst + 1) = s->vga.gr[0x10]; + } + val <<= 1; + dst += 2; + } + memory_region_set_dirty(&s->vga.vram, offset, 16); +} + +/*************************************** + * + * memory access between 0xa0000-0xbffff + * + ***************************************/ + +static uint64_t cirrus_vga_mem_read(void *opaque, + hwaddr addr, + uint32_t size) +{ + CirrusVGAState *s = opaque; + unsigned bank_index; + unsigned bank_offset; + uint32_t val; + + if ((s->vga.sr[0x07] & 0x01) == 0) { + return vga_mem_readb(&s->vga, addr); + } + + if (addr < 0x10000) { + /* XXX handle bitblt */ + /* video memory */ + bank_index = addr >> 15; + bank_offset = addr & 0x7fff; + if (bank_offset < s->cirrus_bank_limit[bank_index]) { + bank_offset += s->cirrus_bank_base[bank_index]; + if ((s->vga.gr[0x0B] & 0x14) == 0x14) { + bank_offset <<= 4; + } else if (s->vga.gr[0x0B] & 0x02) { + bank_offset <<= 3; + } + bank_offset &= s->cirrus_addr_mask; + val = *(s->vga.vram_ptr + bank_offset); + } else + val = 0xff; + } else if (addr >= 0x18000 && addr < 0x18100) { + /* memory-mapped I/O */ + val = 0xff; + if ((s->vga.sr[0x17] & 0x44) == 0x04) { + val = cirrus_mmio_blt_read(s, addr & 0xff); + } + } else { + val = 0xff; +#ifdef DEBUG_CIRRUS + printf("cirrus: mem_readb " TARGET_FMT_plx "\n", addr); +#endif + } + return val; +} + +static void cirrus_vga_mem_write(void *opaque, + hwaddr addr, + uint64_t mem_value, + uint32_t size) +{ + CirrusVGAState *s = opaque; + unsigned bank_index; + unsigned bank_offset; + unsigned mode; + + if ((s->vga.sr[0x07] & 0x01) == 0) { + vga_mem_writeb(&s->vga, addr, mem_value); + return; + } + + if (addr < 0x10000) { + if (s->cirrus_srcptr != s->cirrus_srcptr_end) { + /* bitblt */ + *s->cirrus_srcptr++ = (uint8_t) mem_value; + if (s->cirrus_srcptr >= s->cirrus_srcptr_end) { + cirrus_bitblt_cputovideo_next(s); + } + } else { + /* video memory */ + bank_index = addr >> 15; + bank_offset = addr & 0x7fff; + if (bank_offset < s->cirrus_bank_limit[bank_index]) { + bank_offset += s->cirrus_bank_base[bank_index]; + if ((s->vga.gr[0x0B] & 0x14) == 0x14) { + bank_offset <<= 4; + } else if (s->vga.gr[0x0B] & 0x02) { + bank_offset <<= 3; + } + bank_offset &= s->cirrus_addr_mask; + mode = s->vga.gr[0x05] & 0x7; + if (mode < 4 || mode > 5 || ((s->vga.gr[0x0B] & 0x4) == 0)) { + *(s->vga.vram_ptr + bank_offset) = mem_value; + memory_region_set_dirty(&s->vga.vram, bank_offset, + sizeof(mem_value)); + } else { + if ((s->vga.gr[0x0B] & 0x14) != 0x14) { + cirrus_mem_writeb_mode4and5_8bpp(s, mode, + bank_offset, + mem_value); + } else { + cirrus_mem_writeb_mode4and5_16bpp(s, mode, + bank_offset, + mem_value); + } + } + } + } + } else if (addr >= 0x18000 && addr < 0x18100) { + /* memory-mapped I/O */ + if ((s->vga.sr[0x17] & 0x44) == 0x04) { + cirrus_mmio_blt_write(s, addr & 0xff, mem_value); + } + } else { +#ifdef DEBUG_CIRRUS + printf("cirrus: mem_writeb " TARGET_FMT_plx " value %02x\n", addr, + mem_value); +#endif + } +} + +static const MemoryRegionOps cirrus_vga_mem_ops = { + .read = cirrus_vga_mem_read, + .write = cirrus_vga_mem_write, + .endianness = DEVICE_LITTLE_ENDIAN, + .impl = { + .min_access_size = 1, + .max_access_size = 1, + }, +}; + +/*************************************** + * + * hardware cursor + * + ***************************************/ + +static inline void invalidate_cursor1(CirrusVGAState *s) +{ + if (s->last_hw_cursor_size) { + vga_invalidate_scanlines(&s->vga, + s->last_hw_cursor_y + s->last_hw_cursor_y_start, + s->last_hw_cursor_y + s->last_hw_cursor_y_end); + } +} + +static inline void cirrus_cursor_compute_yrange(CirrusVGAState *s) +{ + const uint8_t *src; + uint32_t content; + int y, y_min, y_max; + + src = s->vga.vram_ptr + s->real_vram_size - 16 * 1024; + if (s->vga.sr[0x12] & CIRRUS_CURSOR_LARGE) { + src += (s->vga.sr[0x13] & 0x3c) * 256; + y_min = 64; + y_max = -1; + for(y = 0; y < 64; y++) { + content = ((uint32_t *)src)[0] | + ((uint32_t *)src)[1] | + ((uint32_t *)src)[2] | + ((uint32_t *)src)[3]; + if (content) { + if (y < y_min) + y_min = y; + if (y > y_max) + y_max = y; + } + src += 16; + } + } else { + src += (s->vga.sr[0x13] & 0x3f) * 256; + y_min = 32; + y_max = -1; + for(y = 0; y < 32; y++) { + content = ((uint32_t *)src)[0] | + ((uint32_t *)(src + 128))[0]; + if (content) { + if (y < y_min) + y_min = y; + if (y > y_max) + y_max = y; + } + src += 4; + } + } + if (y_min > y_max) { + s->last_hw_cursor_y_start = 0; + s->last_hw_cursor_y_end = 0; + } else { + s->last_hw_cursor_y_start = y_min; + s->last_hw_cursor_y_end = y_max + 1; + } +} + +/* NOTE: we do not currently handle the cursor bitmap change, so we + update the cursor only if it moves. */ +static void cirrus_cursor_invalidate(VGACommonState *s1) +{ + CirrusVGAState *s = container_of(s1, CirrusVGAState, vga); + int size; + + if (!(s->vga.sr[0x12] & CIRRUS_CURSOR_SHOW)) { + size = 0; + } else { + if (s->vga.sr[0x12] & CIRRUS_CURSOR_LARGE) + size = 64; + else + size = 32; + } + /* invalidate last cursor and new cursor if any change */ + if (s->last_hw_cursor_size != size || + s->last_hw_cursor_x != s->hw_cursor_x || + s->last_hw_cursor_y != s->hw_cursor_y) { + + invalidate_cursor1(s); + + s->last_hw_cursor_size = size; + s->last_hw_cursor_x = s->hw_cursor_x; + s->last_hw_cursor_y = s->hw_cursor_y; + /* compute the real cursor min and max y */ + cirrus_cursor_compute_yrange(s); + invalidate_cursor1(s); + } +} + +#define DEPTH 8 +#include "cirrus_vga_template.h" + +#define DEPTH 16 +#include "cirrus_vga_template.h" + +#define DEPTH 32 +#include "cirrus_vga_template.h" + +static void cirrus_cursor_draw_line(VGACommonState *s1, uint8_t *d1, int scr_y) +{ + CirrusVGAState *s = container_of(s1, CirrusVGAState, vga); + DisplaySurface *surface = qemu_console_surface(s->vga.con); + int w, h, bpp, x1, x2, poffset; + unsigned int color0, color1; + const uint8_t *palette, *src; + uint32_t content; + + if (!(s->vga.sr[0x12] & CIRRUS_CURSOR_SHOW)) + return; + /* fast test to see if the cursor intersects with the scan line */ + if (s->vga.sr[0x12] & CIRRUS_CURSOR_LARGE) { + h = 64; + } else { + h = 32; + } + if (scr_y < s->hw_cursor_y || + scr_y >= (s->hw_cursor_y + h)) + return; + + src = s->vga.vram_ptr + s->real_vram_size - 16 * 1024; + if (s->vga.sr[0x12] & CIRRUS_CURSOR_LARGE) { + src += (s->vga.sr[0x13] & 0x3c) * 256; + src += (scr_y - s->hw_cursor_y) * 16; + poffset = 8; + content = ((uint32_t *)src)[0] | + ((uint32_t *)src)[1] | + ((uint32_t *)src)[2] | + ((uint32_t *)src)[3]; + } else { + src += (s->vga.sr[0x13] & 0x3f) * 256; + src += (scr_y - s->hw_cursor_y) * 4; + poffset = 128; + content = ((uint32_t *)src)[0] | + ((uint32_t *)(src + 128))[0]; + } + /* if nothing to draw, no need to continue */ + if (!content) + return; + w = h; + + x1 = s->hw_cursor_x; + if (x1 >= s->vga.last_scr_width) + return; + x2 = s->hw_cursor_x + w; + if (x2 > s->vga.last_scr_width) + x2 = s->vga.last_scr_width; + w = x2 - x1; + palette = s->cirrus_hidden_palette; + color0 = s->vga.rgb_to_pixel(c6_to_8(palette[0x0 * 3]), + c6_to_8(palette[0x0 * 3 + 1]), + c6_to_8(palette[0x0 * 3 + 2])); + color1 = s->vga.rgb_to_pixel(c6_to_8(palette[0xf * 3]), + c6_to_8(palette[0xf * 3 + 1]), + c6_to_8(palette[0xf * 3 + 2])); + bpp = surface_bytes_per_pixel(surface); + d1 += x1 * bpp; + switch (surface_bits_per_pixel(surface)) { + default: + break; + case 8: + vga_draw_cursor_line_8(d1, src, poffset, w, color0, color1, 0xff); + break; + case 15: + vga_draw_cursor_line_16(d1, src, poffset, w, color0, color1, 0x7fff); + break; + case 16: + vga_draw_cursor_line_16(d1, src, poffset, w, color0, color1, 0xffff); + break; + case 32: + vga_draw_cursor_line_32(d1, src, poffset, w, color0, color1, 0xffffff); + break; + } +} + +/*************************************** + * + * LFB memory access + * + ***************************************/ + +static uint64_t cirrus_linear_read(void *opaque, hwaddr addr, + unsigned size) +{ + CirrusVGAState *s = opaque; + uint32_t ret; + + addr &= s->cirrus_addr_mask; + + if (((s->vga.sr[0x17] & 0x44) == 0x44) && + ((addr & s->linear_mmio_mask) == s->linear_mmio_mask)) { + /* memory-mapped I/O */ + ret = cirrus_mmio_blt_read(s, addr & 0xff); + } else if (0) { + /* XXX handle bitblt */ + ret = 0xff; + } else { + /* video memory */ + if ((s->vga.gr[0x0B] & 0x14) == 0x14) { + addr <<= 4; + } else if (s->vga.gr[0x0B] & 0x02) { + addr <<= 3; + } + addr &= s->cirrus_addr_mask; + ret = *(s->vga.vram_ptr + addr); + } + + return ret; +} + +static void cirrus_linear_write(void *opaque, hwaddr addr, + uint64_t val, unsigned size) +{ + CirrusVGAState *s = opaque; + unsigned mode; + + addr &= s->cirrus_addr_mask; + + if (((s->vga.sr[0x17] & 0x44) == 0x44) && + ((addr & s->linear_mmio_mask) == s->linear_mmio_mask)) { + /* memory-mapped I/O */ + cirrus_mmio_blt_write(s, addr & 0xff, val); + } else if (s->cirrus_srcptr != s->cirrus_srcptr_end) { + /* bitblt */ + *s->cirrus_srcptr++ = (uint8_t) val; + if (s->cirrus_srcptr >= s->cirrus_srcptr_end) { + cirrus_bitblt_cputovideo_next(s); + } + } else { + /* video memory */ + if ((s->vga.gr[0x0B] & 0x14) == 0x14) { + addr <<= 4; + } else if (s->vga.gr[0x0B] & 0x02) { + addr <<= 3; + } + addr &= s->cirrus_addr_mask; + + mode = s->vga.gr[0x05] & 0x7; + if (mode < 4 || mode > 5 || ((s->vga.gr[0x0B] & 0x4) == 0)) { + *(s->vga.vram_ptr + addr) = (uint8_t) val; + memory_region_set_dirty(&s->vga.vram, addr, 1); + } else { + if ((s->vga.gr[0x0B] & 0x14) != 0x14) { + cirrus_mem_writeb_mode4and5_8bpp(s, mode, addr, val); + } else { + cirrus_mem_writeb_mode4and5_16bpp(s, mode, addr, val); + } + } + } +} + +/*************************************** + * + * system to screen memory access + * + ***************************************/ + + +static uint64_t cirrus_linear_bitblt_read(void *opaque, + hwaddr addr, + unsigned size) +{ + CirrusVGAState *s = opaque; + uint32_t ret; + + /* XXX handle bitblt */ + (void)s; + ret = 0xff; + return ret; +} + +static void cirrus_linear_bitblt_write(void *opaque, + hwaddr addr, + uint64_t val, + unsigned size) +{ + CirrusVGAState *s = opaque; + + if (s->cirrus_srcptr != s->cirrus_srcptr_end) { + /* bitblt */ + *s->cirrus_srcptr++ = (uint8_t) val; + if (s->cirrus_srcptr >= s->cirrus_srcptr_end) { + cirrus_bitblt_cputovideo_next(s); + } + } +} + +static const MemoryRegionOps cirrus_linear_bitblt_io_ops = { + .read = cirrus_linear_bitblt_read, + .write = cirrus_linear_bitblt_write, + .endianness = DEVICE_LITTLE_ENDIAN, + .impl = { + .min_access_size = 1, + .max_access_size = 1, + }, +}; + +static void map_linear_vram_bank(CirrusVGAState *s, unsigned bank) +{ + MemoryRegion *mr = &s->cirrus_bank[bank]; + bool enabled = !(s->cirrus_srcptr != s->cirrus_srcptr_end) + && !((s->vga.sr[0x07] & 0x01) == 0) + && !((s->vga.gr[0x0B] & 0x14) == 0x14) + && !(s->vga.gr[0x0B] & 0x02); + + memory_region_set_enabled(mr, enabled); + memory_region_set_alias_offset(mr, s->cirrus_bank_base[bank]); +} + +static void map_linear_vram(CirrusVGAState *s) +{ + if (s->bustype == CIRRUS_BUSTYPE_PCI && !s->linear_vram) { + s->linear_vram = true; + memory_region_add_subregion_overlap(&s->pci_bar, 0, &s->vga.vram, 1); + } + map_linear_vram_bank(s, 0); + map_linear_vram_bank(s, 1); +} + +static void unmap_linear_vram(CirrusVGAState *s) +{ + if (s->bustype == CIRRUS_BUSTYPE_PCI && s->linear_vram) { + s->linear_vram = false; + memory_region_del_subregion(&s->pci_bar, &s->vga.vram); + } + memory_region_set_enabled(&s->cirrus_bank[0], false); + memory_region_set_enabled(&s->cirrus_bank[1], false); +} + +/* Compute the memory access functions */ +static void cirrus_update_memory_access(CirrusVGAState *s) +{ + unsigned mode; + + memory_region_transaction_begin(); + if ((s->vga.sr[0x17] & 0x44) == 0x44) { + goto generic_io; + } else if (s->cirrus_srcptr != s->cirrus_srcptr_end) { + goto generic_io; + } else { + if ((s->vga.gr[0x0B] & 0x14) == 0x14) { + goto generic_io; + } else if (s->vga.gr[0x0B] & 0x02) { + goto generic_io; + } + + mode = s->vga.gr[0x05] & 0x7; + if (mode < 4 || mode > 5 || ((s->vga.gr[0x0B] & 0x4) == 0)) { + map_linear_vram(s); + } else { + generic_io: + unmap_linear_vram(s); + } + } + memory_region_transaction_commit(); +} + + +/* I/O ports */ + +static uint64_t cirrus_vga_ioport_read(void *opaque, hwaddr addr, + unsigned size) +{ + CirrusVGAState *c = opaque; + VGACommonState *s = &c->vga; + int val, index; + + qemu_flush_coalesced_mmio_buffer(); + addr += 0x3b0; + + if (vga_ioport_invalid(s, addr)) { + val = 0xff; + } else { + switch (addr) { + case 0x3c0: + if (s->ar_flip_flop == 0) { + val = s->ar_index; + } else { + val = 0; + } + break; + case 0x3c1: + index = s->ar_index & 0x1f; + if (index < 21) + val = s->ar[index]; + else + val = 0; + break; + case 0x3c2: + val = s->st00; + break; + case 0x3c4: + val = s->sr_index; + break; + case 0x3c5: + val = cirrus_vga_read_sr(c); + break; +#ifdef DEBUG_VGA_REG + printf("vga: read SR%x = 0x%02x\n", s->sr_index, val); +#endif + break; + case 0x3c6: + val = cirrus_read_hidden_dac(c); + break; + case 0x3c7: + val = s->dac_state; + break; + case 0x3c8: + val = s->dac_write_index; + c->cirrus_hidden_dac_lockindex = 0; + break; + case 0x3c9: + val = cirrus_vga_read_palette(c); + break; + case 0x3ca: + val = s->fcr; + break; + case 0x3cc: + val = s->msr; + break; + case 0x3ce: + val = s->gr_index; + break; + case 0x3cf: + val = cirrus_vga_read_gr(c, s->gr_index); +#ifdef DEBUG_VGA_REG + printf("vga: read GR%x = 0x%02x\n", s->gr_index, val); +#endif + break; + case 0x3b4: + case 0x3d4: + val = s->cr_index; + break; + case 0x3b5: + case 0x3d5: + val = cirrus_vga_read_cr(c, s->cr_index); +#ifdef DEBUG_VGA_REG + printf("vga: read CR%x = 0x%02x\n", s->cr_index, val); +#endif + break; + case 0x3ba: + case 0x3da: + /* just toggle to fool polling */ + val = s->st01 = s->retrace(s); + s->ar_flip_flop = 0; + break; + default: + val = 0x00; + break; + } + } +#if defined(DEBUG_VGA) + printf("VGA: read addr=0x%04x data=0x%02x\n", addr, val); +#endif + return val; +} + +static void cirrus_vga_ioport_write(void *opaque, hwaddr addr, uint64_t val, + unsigned size) +{ + CirrusVGAState *c = opaque; + VGACommonState *s = &c->vga; + int index; + + qemu_flush_coalesced_mmio_buffer(); + addr += 0x3b0; + + /* check port range access depending on color/monochrome mode */ + if (vga_ioport_invalid(s, addr)) { + return; + } +#ifdef DEBUG_VGA + printf("VGA: write addr=0x%04x data=0x%02x\n", addr, val); +#endif + + switch (addr) { + case 0x3c0: + if (s->ar_flip_flop == 0) { + val &= 0x3f; + s->ar_index = val; + } else { + index = s->ar_index & 0x1f; + switch (index) { + case 0x00 ... 0x0f: + s->ar[index] = val & 0x3f; + break; + case 0x10: + s->ar[index] = val & ~0x10; + break; + case 0x11: + s->ar[index] = val; + break; + case 0x12: + s->ar[index] = val & ~0xc0; + break; + case 0x13: + s->ar[index] = val & ~0xf0; + break; + case 0x14: + s->ar[index] = val & ~0xf0; + break; + default: + break; + } + } + s->ar_flip_flop ^= 1; + break; + case 0x3c2: + s->msr = val & ~0x10; + s->update_retrace_info(s); + break; + case 0x3c4: + s->sr_index = val; + break; + case 0x3c5: +#ifdef DEBUG_VGA_REG + printf("vga: write SR%x = 0x%02x\n", s->sr_index, val); +#endif + cirrus_vga_write_sr(c, val); + break; + break; + case 0x3c6: + cirrus_write_hidden_dac(c, val); + break; + case 0x3c7: + s->dac_read_index = val; + s->dac_sub_index = 0; + s->dac_state = 3; + break; + case 0x3c8: + s->dac_write_index = val; + s->dac_sub_index = 0; + s->dac_state = 0; + break; + case 0x3c9: + cirrus_vga_write_palette(c, val); + break; + case 0x3ce: + s->gr_index = val; + break; + case 0x3cf: +#ifdef DEBUG_VGA_REG + printf("vga: write GR%x = 0x%02x\n", s->gr_index, val); +#endif + cirrus_vga_write_gr(c, s->gr_index, val); + break; + case 0x3b4: + case 0x3d4: + s->cr_index = val; + break; + case 0x3b5: + case 0x3d5: +#ifdef DEBUG_VGA_REG + printf("vga: write CR%x = 0x%02x\n", s->cr_index, val); +#endif + cirrus_vga_write_cr(c, val); + break; + case 0x3ba: + case 0x3da: + s->fcr = val & 0x10; + break; + } +} + +/*************************************** + * + * memory-mapped I/O access + * + ***************************************/ + +static uint64_t cirrus_mmio_read(void *opaque, hwaddr addr, + unsigned size) +{ + CirrusVGAState *s = opaque; + + if (addr >= 0x100) { + return cirrus_mmio_blt_read(s, addr - 0x100); + } else { + return cirrus_vga_ioport_read(s, addr + 0x10, size); + } +} + +static void cirrus_mmio_write(void *opaque, hwaddr addr, + uint64_t val, unsigned size) +{ + CirrusVGAState *s = opaque; + + if (addr >= 0x100) { + cirrus_mmio_blt_write(s, addr - 0x100, val); + } else { + cirrus_vga_ioport_write(s, addr + 0x10, val, size); + } +} + +static const MemoryRegionOps cirrus_mmio_io_ops = { + .read = cirrus_mmio_read, + .write = cirrus_mmio_write, + .endianness = DEVICE_LITTLE_ENDIAN, + .impl = { + .min_access_size = 1, + .max_access_size = 1, + }, +}; + +/* load/save state */ + +static int cirrus_post_load(void *opaque, int version_id) +{ + CirrusVGAState *s = opaque; + + s->vga.gr[0x00] = s->cirrus_shadow_gr0 & 0x0f; + s->vga.gr[0x01] = s->cirrus_shadow_gr1 & 0x0f; + + cirrus_update_memory_access(s); + /* force refresh */ + s->vga.graphic_mode = -1; + cirrus_update_bank_ptr(s, 0); + cirrus_update_bank_ptr(s, 1); + return 0; +} + +static const VMStateDescription vmstate_cirrus_vga = { + .name = "cirrus_vga", + .version_id = 2, + .minimum_version_id = 1, + .minimum_version_id_old = 1, + .post_load = cirrus_post_load, + .fields = (VMStateField []) { + VMSTATE_UINT32(vga.latch, CirrusVGAState), + VMSTATE_UINT8(vga.sr_index, CirrusVGAState), + VMSTATE_BUFFER(vga.sr, CirrusVGAState), + VMSTATE_UINT8(vga.gr_index, CirrusVGAState), + VMSTATE_UINT8(cirrus_shadow_gr0, CirrusVGAState), + VMSTATE_UINT8(cirrus_shadow_gr1, CirrusVGAState), + VMSTATE_BUFFER_START_MIDDLE(vga.gr, CirrusVGAState, 2), + VMSTATE_UINT8(vga.ar_index, CirrusVGAState), + VMSTATE_BUFFER(vga.ar, CirrusVGAState), + VMSTATE_INT32(vga.ar_flip_flop, CirrusVGAState), + VMSTATE_UINT8(vga.cr_index, CirrusVGAState), + VMSTATE_BUFFER(vga.cr, CirrusVGAState), + VMSTATE_UINT8(vga.msr, CirrusVGAState), + VMSTATE_UINT8(vga.fcr, CirrusVGAState), + VMSTATE_UINT8(vga.st00, CirrusVGAState), + VMSTATE_UINT8(vga.st01, CirrusVGAState), + VMSTATE_UINT8(vga.dac_state, CirrusVGAState), + VMSTATE_UINT8(vga.dac_sub_index, CirrusVGAState), + VMSTATE_UINT8(vga.dac_read_index, CirrusVGAState), + VMSTATE_UINT8(vga.dac_write_index, CirrusVGAState), + VMSTATE_BUFFER(vga.dac_cache, CirrusVGAState), + VMSTATE_BUFFER(vga.palette, CirrusVGAState), + VMSTATE_INT32(vga.bank_offset, CirrusVGAState), + VMSTATE_UINT8(cirrus_hidden_dac_lockindex, CirrusVGAState), + VMSTATE_UINT8(cirrus_hidden_dac_data, CirrusVGAState), + VMSTATE_UINT32(hw_cursor_x, CirrusVGAState), + VMSTATE_UINT32(hw_cursor_y, CirrusVGAState), + /* XXX: we do not save the bitblt state - we assume we do not save + the state when the blitter is active */ + VMSTATE_END_OF_LIST() + } +}; + +static const VMStateDescription vmstate_pci_cirrus_vga = { + .name = "cirrus_vga", + .version_id = 2, + .minimum_version_id = 2, + .minimum_version_id_old = 2, + .fields = (VMStateField []) { + VMSTATE_PCI_DEVICE(dev, PCICirrusVGAState), + VMSTATE_STRUCT(cirrus_vga, PCICirrusVGAState, 0, + vmstate_cirrus_vga, CirrusVGAState), + VMSTATE_END_OF_LIST() + } +}; + +/*************************************** + * + * initialize + * + ***************************************/ + +static void cirrus_reset(void *opaque) +{ + CirrusVGAState *s = opaque; + + vga_common_reset(&s->vga); + unmap_linear_vram(s); + s->vga.sr[0x06] = 0x0f; + if (s->device_id == CIRRUS_ID_CLGD5446) { + /* 4MB 64 bit memory config, always PCI */ + s->vga.sr[0x1F] = 0x2d; // MemClock + s->vga.gr[0x18] = 0x0f; // fastest memory configuration + s->vga.sr[0x0f] = 0x98; + s->vga.sr[0x17] = 0x20; + s->vga.sr[0x15] = 0x04; /* memory size, 3=2MB, 4=4MB */ + } else { + s->vga.sr[0x1F] = 0x22; // MemClock + s->vga.sr[0x0F] = CIRRUS_MEMSIZE_2M; + s->vga.sr[0x17] = s->bustype; + s->vga.sr[0x15] = 0x03; /* memory size, 3=2MB, 4=4MB */ + } + s->vga.cr[0x27] = s->device_id; + + s->cirrus_hidden_dac_lockindex = 5; + s->cirrus_hidden_dac_data = 0; +} + +static const MemoryRegionOps cirrus_linear_io_ops = { + .read = cirrus_linear_read, + .write = cirrus_linear_write, + .endianness = DEVICE_LITTLE_ENDIAN, + .impl = { + .min_access_size = 1, + .max_access_size = 1, + }, +}; + +static const MemoryRegionOps cirrus_vga_io_ops = { + .read = cirrus_vga_ioport_read, + .write = cirrus_vga_ioport_write, + .endianness = DEVICE_LITTLE_ENDIAN, + .impl = { + .min_access_size = 1, + .max_access_size = 1, + }, +}; + +static void cirrus_init_common(CirrusVGAState * s, int device_id, int is_pci, + MemoryRegion *system_memory, + MemoryRegion *system_io) +{ + int i; + static int inited; + + if (!inited) { + inited = 1; + for(i = 0;i < 256; i++) + rop_to_index[i] = CIRRUS_ROP_NOP_INDEX; /* nop rop */ + rop_to_index[CIRRUS_ROP_0] = 0; + rop_to_index[CIRRUS_ROP_SRC_AND_DST] = 1; + rop_to_index[CIRRUS_ROP_NOP] = 2; + rop_to_index[CIRRUS_ROP_SRC_AND_NOTDST] = 3; + rop_to_index[CIRRUS_ROP_NOTDST] = 4; + rop_to_index[CIRRUS_ROP_SRC] = 5; + rop_to_index[CIRRUS_ROP_1] = 6; + rop_to_index[CIRRUS_ROP_NOTSRC_AND_DST] = 7; + rop_to_index[CIRRUS_ROP_SRC_XOR_DST] = 8; + rop_to_index[CIRRUS_ROP_SRC_OR_DST] = 9; + rop_to_index[CIRRUS_ROP_NOTSRC_OR_NOTDST] = 10; + rop_to_index[CIRRUS_ROP_SRC_NOTXOR_DST] = 11; + rop_to_index[CIRRUS_ROP_SRC_OR_NOTDST] = 12; + rop_to_index[CIRRUS_ROP_NOTSRC] = 13; + rop_to_index[CIRRUS_ROP_NOTSRC_OR_DST] = 14; + rop_to_index[CIRRUS_ROP_NOTSRC_AND_NOTDST] = 15; + s->device_id = device_id; + if (is_pci) + s->bustype = CIRRUS_BUSTYPE_PCI; + else + s->bustype = CIRRUS_BUSTYPE_ISA; + } + + /* Register ioport 0x3b0 - 0x3df */ + memory_region_init_io(&s->cirrus_vga_io, &cirrus_vga_io_ops, s, + "cirrus-io", 0x30); + memory_region_add_subregion(system_io, 0x3b0, &s->cirrus_vga_io); + + memory_region_init(&s->low_mem_container, + "cirrus-lowmem-container", + 0x20000); + + memory_region_init_io(&s->low_mem, &cirrus_vga_mem_ops, s, + "cirrus-low-memory", 0x20000); + memory_region_add_subregion(&s->low_mem_container, 0, &s->low_mem); + for (i = 0; i < 2; ++i) { + static const char *names[] = { "vga.bank0", "vga.bank1" }; + MemoryRegion *bank = &s->cirrus_bank[i]; + memory_region_init_alias(bank, names[i], &s->vga.vram, 0, 0x8000); + memory_region_set_enabled(bank, false); + memory_region_add_subregion_overlap(&s->low_mem_container, i * 0x8000, + bank, 1); + } + memory_region_add_subregion_overlap(system_memory, + isa_mem_base + 0x000a0000, + &s->low_mem_container, + 1); + memory_region_set_coalescing(&s->low_mem); + + /* I/O handler for LFB */ + memory_region_init_io(&s->cirrus_linear_io, &cirrus_linear_io_ops, s, + "cirrus-linear-io", s->vga.vram_size_mb + * 1024 * 1024); + memory_region_set_flush_coalesced(&s->cirrus_linear_io); + + /* I/O handler for LFB */ + memory_region_init_io(&s->cirrus_linear_bitblt_io, + &cirrus_linear_bitblt_io_ops, + s, + "cirrus-bitblt-mmio", + 0x400000); + memory_region_set_flush_coalesced(&s->cirrus_linear_bitblt_io); + + /* I/O handler for memory-mapped I/O */ + memory_region_init_io(&s->cirrus_mmio_io, &cirrus_mmio_io_ops, s, + "cirrus-mmio", CIRRUS_PNPMMIO_SIZE); + memory_region_set_flush_coalesced(&s->cirrus_mmio_io); + + s->real_vram_size = + (s->device_id == CIRRUS_ID_CLGD5446) ? 4096 * 1024 : 2048 * 1024; + + /* XXX: s->vga.vram_size must be a power of two */ + s->cirrus_addr_mask = s->real_vram_size - 1; + s->linear_mmio_mask = s->real_vram_size - 256; + + s->vga.get_bpp = cirrus_get_bpp; + s->vga.get_offsets = cirrus_get_offsets; + s->vga.get_resolution = cirrus_get_resolution; + s->vga.cursor_invalidate = cirrus_cursor_invalidate; + s->vga.cursor_draw_line = cirrus_cursor_draw_line; + + qemu_register_reset(cirrus_reset, s); +} + +/*************************************** + * + * ISA bus support + * + ***************************************/ + +static int vga_initfn(ISADevice *dev) +{ + ISACirrusVGAState *d = DO_UPCAST(ISACirrusVGAState, dev, dev); + VGACommonState *s = &d->cirrus_vga.vga; + + vga_common_init(s); + cirrus_init_common(&d->cirrus_vga, CIRRUS_ID_CLGD5430, 0, + isa_address_space(dev), isa_address_space_io(dev)); + s->con = graphic_console_init(s->update, s->invalidate, + s->screen_dump, s->text_update, + s); + rom_add_vga(VGABIOS_CIRRUS_FILENAME); + /* XXX ISA-LFB support */ + /* FIXME not qdev yet */ + return 0; +} + +static Property isa_vga_cirrus_properties[] = { + DEFINE_PROP_UINT32("vgamem_mb", struct ISACirrusVGAState, + cirrus_vga.vga.vram_size_mb, 8), + DEFINE_PROP_END_OF_LIST(), +}; + +static void isa_cirrus_vga_class_init(ObjectClass *klass, void *data) +{ + ISADeviceClass *k = ISA_DEVICE_CLASS(klass); + DeviceClass *dc = DEVICE_CLASS(klass); + + dc->vmsd = &vmstate_cirrus_vga; + k->init = vga_initfn; + dc->props = isa_vga_cirrus_properties; +} + +static const TypeInfo isa_cirrus_vga_info = { + .name = "isa-cirrus-vga", + .parent = TYPE_ISA_DEVICE, + .instance_size = sizeof(ISACirrusVGAState), + .class_init = isa_cirrus_vga_class_init, +}; + +/*************************************** + * + * PCI bus support + * + ***************************************/ + +static int pci_cirrus_vga_initfn(PCIDevice *dev) +{ + PCICirrusVGAState *d = DO_UPCAST(PCICirrusVGAState, dev, dev); + CirrusVGAState *s = &d->cirrus_vga; + PCIDeviceClass *pc = PCI_DEVICE_GET_CLASS(dev); + int16_t device_id = pc->device_id; + + /* setup VGA */ + vga_common_init(&s->vga); + cirrus_init_common(s, device_id, 1, pci_address_space(dev), + pci_address_space_io(dev)); + s->vga.con = graphic_console_init(s->vga.update, s->vga.invalidate, + s->vga.screen_dump, s->vga.text_update, + &s->vga); + + /* setup PCI */ + + memory_region_init(&s->pci_bar, "cirrus-pci-bar0", 0x2000000); + + /* XXX: add byte swapping apertures */ + memory_region_add_subregion(&s->pci_bar, 0, &s->cirrus_linear_io); + memory_region_add_subregion(&s->pci_bar, 0x1000000, + &s->cirrus_linear_bitblt_io); + + /* setup memory space */ + /* memory #0 LFB */ + /* memory #1 memory-mapped I/O */ + /* XXX: s->vga.vram_size must be a power of two */ + pci_register_bar(&d->dev, 0, PCI_BASE_ADDRESS_MEM_PREFETCH, &s->pci_bar); + if (device_id == CIRRUS_ID_CLGD5446) { + pci_register_bar(&d->dev, 1, 0, &s->cirrus_mmio_io); + } + return 0; +} + +static Property pci_vga_cirrus_properties[] = { + DEFINE_PROP_UINT32("vgamem_mb", struct PCICirrusVGAState, + cirrus_vga.vga.vram_size_mb, 8), + DEFINE_PROP_END_OF_LIST(), +}; + +static void cirrus_vga_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + PCIDeviceClass *k = PCI_DEVICE_CLASS(klass); + + k->no_hotplug = 1; + k->init = pci_cirrus_vga_initfn; + k->romfile = VGABIOS_CIRRUS_FILENAME; + k->vendor_id = PCI_VENDOR_ID_CIRRUS; + k->device_id = CIRRUS_ID_CLGD5446; + k->class_id = PCI_CLASS_DISPLAY_VGA; + dc->desc = "Cirrus CLGD 54xx VGA"; + dc->vmsd = &vmstate_pci_cirrus_vga; + dc->props = pci_vga_cirrus_properties; +} + +static const TypeInfo cirrus_vga_info = { + .name = "cirrus-vga", + .parent = TYPE_PCI_DEVICE, + .instance_size = sizeof(PCICirrusVGAState), + .class_init = cirrus_vga_class_init, +}; + +static void cirrus_vga_register_types(void) +{ + type_register_static(&isa_cirrus_vga_info); + type_register_static(&cirrus_vga_info); +} + +type_init(cirrus_vga_register_types) diff --git a/hw/display/cirrus_vga_rop.h b/hw/display/cirrus_vga_rop.h new file mode 100644 index 0000000000..9c7bb09286 --- /dev/null +++ b/hw/display/cirrus_vga_rop.h @@ -0,0 +1,208 @@ +/* + * QEMU Cirrus CLGD 54xx VGA Emulator. + * + * Copyright (c) 2004 Fabrice Bellard + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +static inline void glue(rop_8_,ROP_NAME)(uint8_t *dst, uint8_t src) +{ + *dst = ROP_FN(*dst, src); +} + +static inline void glue(rop_16_,ROP_NAME)(uint16_t *dst, uint16_t src) +{ + *dst = ROP_FN(*dst, src); +} + +static inline void glue(rop_32_,ROP_NAME)(uint32_t *dst, uint32_t src) +{ + *dst = ROP_FN(*dst, src); +} + +#define ROP_OP(d, s) glue(rop_8_,ROP_NAME)(d, s) +#define ROP_OP_16(d, s) glue(rop_16_,ROP_NAME)(d, s) +#define ROP_OP_32(d, s) glue(rop_32_,ROP_NAME)(d, s) +#undef ROP_FN + +static void +glue(cirrus_bitblt_rop_fwd_, ROP_NAME)(CirrusVGAState *s, + uint8_t *dst,const uint8_t *src, + int dstpitch,int srcpitch, + int bltwidth,int bltheight) +{ + int x,y; + dstpitch -= bltwidth; + srcpitch -= bltwidth; + + if (dstpitch < 0 || srcpitch < 0) { + /* is 0 valid? srcpitch == 0 could be useful */ + return; + } + + for (y = 0; y < bltheight; y++) { + for (x = 0; x < bltwidth; x++) { + ROP_OP(dst, *src); + dst++; + src++; + } + dst += dstpitch; + src += srcpitch; + } +} + +static void +glue(cirrus_bitblt_rop_bkwd_, ROP_NAME)(CirrusVGAState *s, + uint8_t *dst,const uint8_t *src, + int dstpitch,int srcpitch, + int bltwidth,int bltheight) +{ + int x,y; + dstpitch += bltwidth; + srcpitch += bltwidth; + for (y = 0; y < bltheight; y++) { + for (x = 0; x < bltwidth; x++) { + ROP_OP(dst, *src); + dst--; + src--; + } + dst += dstpitch; + src += srcpitch; + } +} + +static void +glue(glue(cirrus_bitblt_rop_fwd_transp_, ROP_NAME),_8)(CirrusVGAState *s, + uint8_t *dst,const uint8_t *src, + int dstpitch,int srcpitch, + int bltwidth,int bltheight) +{ + int x,y; + uint8_t p; + dstpitch -= bltwidth; + srcpitch -= bltwidth; + for (y = 0; y < bltheight; y++) { + for (x = 0; x < bltwidth; x++) { + p = *dst; + ROP_OP(&p, *src); + if (p != s->vga.gr[0x34]) *dst = p; + dst++; + src++; + } + dst += dstpitch; + src += srcpitch; + } +} + +static void +glue(glue(cirrus_bitblt_rop_bkwd_transp_, ROP_NAME),_8)(CirrusVGAState *s, + uint8_t *dst,const uint8_t *src, + int dstpitch,int srcpitch, + int bltwidth,int bltheight) +{ + int x,y; + uint8_t p; + dstpitch += bltwidth; + srcpitch += bltwidth; + for (y = 0; y < bltheight; y++) { + for (x = 0; x < bltwidth; x++) { + p = *dst; + ROP_OP(&p, *src); + if (p != s->vga.gr[0x34]) *dst = p; + dst--; + src--; + } + dst += dstpitch; + src += srcpitch; + } +} + +static void +glue(glue(cirrus_bitblt_rop_fwd_transp_, ROP_NAME),_16)(CirrusVGAState *s, + uint8_t *dst,const uint8_t *src, + int dstpitch,int srcpitch, + int bltwidth,int bltheight) +{ + int x,y; + uint8_t p1, p2; + dstpitch -= bltwidth; + srcpitch -= bltwidth; + for (y = 0; y < bltheight; y++) { + for (x = 0; x < bltwidth; x+=2) { + p1 = *dst; + p2 = *(dst+1); + ROP_OP(&p1, *src); + ROP_OP(&p2, *(src + 1)); + if ((p1 != s->vga.gr[0x34]) || (p2 != s->vga.gr[0x35])) { + *dst = p1; + *(dst+1) = p2; + } + dst+=2; + src+=2; + } + dst += dstpitch; + src += srcpitch; + } +} + +static void +glue(glue(cirrus_bitblt_rop_bkwd_transp_, ROP_NAME),_16)(CirrusVGAState *s, + uint8_t *dst,const uint8_t *src, + int dstpitch,int srcpitch, + int bltwidth,int bltheight) +{ + int x,y; + uint8_t p1, p2; + dstpitch += bltwidth; + srcpitch += bltwidth; + for (y = 0; y < bltheight; y++) { + for (x = 0; x < bltwidth; x+=2) { + p1 = *(dst-1); + p2 = *dst; + ROP_OP(&p1, *(src - 1)); + ROP_OP(&p2, *src); + if ((p1 != s->vga.gr[0x34]) || (p2 != s->vga.gr[0x35])) { + *(dst-1) = p1; + *dst = p2; + } + dst-=2; + src-=2; + } + dst += dstpitch; + src += srcpitch; + } +} + +#define DEPTH 8 +#include "cirrus_vga_rop2.h" + +#define DEPTH 16 +#include "cirrus_vga_rop2.h" + +#define DEPTH 24 +#include "cirrus_vga_rop2.h" + +#define DEPTH 32 +#include "cirrus_vga_rop2.h" + +#undef ROP_NAME +#undef ROP_OP +#undef ROP_OP_16 +#undef ROP_OP_32 diff --git a/hw/display/cirrus_vga_rop2.h b/hw/display/cirrus_vga_rop2.h new file mode 100644 index 0000000000..d28bcc6f25 --- /dev/null +++ b/hw/display/cirrus_vga_rop2.h @@ -0,0 +1,281 @@ +/* + * QEMU Cirrus CLGD 54xx VGA Emulator. + * + * Copyright (c) 2004 Fabrice Bellard + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#if DEPTH == 8 +#define PUTPIXEL() ROP_OP(&d[0], col) +#elif DEPTH == 16 +#define PUTPIXEL() ROP_OP_16((uint16_t *)&d[0], col) +#elif DEPTH == 24 +#define PUTPIXEL() ROP_OP(&d[0], col); \ + ROP_OP(&d[1], (col >> 8)); \ + ROP_OP(&d[2], (col >> 16)) +#elif DEPTH == 32 +#define PUTPIXEL() ROP_OP_32(((uint32_t *)&d[0]), col) +#else +#error unsupported DEPTH +#endif + +static void +glue(glue(glue(cirrus_patternfill_, ROP_NAME), _),DEPTH) + (CirrusVGAState * s, uint8_t * dst, + const uint8_t * src, + int dstpitch, int srcpitch, + int bltwidth, int bltheight) +{ + uint8_t *d; + int x, y, pattern_y, pattern_pitch, pattern_x; + unsigned int col; + const uint8_t *src1; +#if DEPTH == 24 + int skipleft = s->vga.gr[0x2f] & 0x1f; +#else + int skipleft = (s->vga.gr[0x2f] & 0x07) * (DEPTH / 8); +#endif + +#if DEPTH == 8 + pattern_pitch = 8; +#elif DEPTH == 16 + pattern_pitch = 16; +#else + pattern_pitch = 32; +#endif + pattern_y = s->cirrus_blt_srcaddr & 7; + for(y = 0; y < bltheight; y++) { + pattern_x = skipleft; + d = dst + skipleft; + src1 = src + pattern_y * pattern_pitch; + for (x = skipleft; x < bltwidth; x += (DEPTH / 8)) { +#if DEPTH == 8 + col = src1[pattern_x]; + pattern_x = (pattern_x + 1) & 7; +#elif DEPTH == 16 + col = ((uint16_t *)(src1 + pattern_x))[0]; + pattern_x = (pattern_x + 2) & 15; +#elif DEPTH == 24 + { + const uint8_t *src2 = src1 + pattern_x * 3; + col = src2[0] | (src2[1] << 8) | (src2[2] << 16); + pattern_x = (pattern_x + 1) & 7; + } +#else + col = ((uint32_t *)(src1 + pattern_x))[0]; + pattern_x = (pattern_x + 4) & 31; +#endif + PUTPIXEL(); + d += (DEPTH / 8); + } + pattern_y = (pattern_y + 1) & 7; + dst += dstpitch; + } +} + +/* NOTE: srcpitch is ignored */ +static void +glue(glue(glue(cirrus_colorexpand_transp_, ROP_NAME), _),DEPTH) + (CirrusVGAState * s, uint8_t * dst, + const uint8_t * src, + int dstpitch, int srcpitch, + int bltwidth, int bltheight) +{ + uint8_t *d; + int x, y; + unsigned bits, bits_xor; + unsigned int col; + unsigned bitmask; + unsigned index; +#if DEPTH == 24 + int dstskipleft = s->vga.gr[0x2f] & 0x1f; + int srcskipleft = dstskipleft / 3; +#else + int srcskipleft = s->vga.gr[0x2f] & 0x07; + int dstskipleft = srcskipleft * (DEPTH / 8); +#endif + + if (s->cirrus_blt_modeext & CIRRUS_BLTMODEEXT_COLOREXPINV) { + bits_xor = 0xff; + col = s->cirrus_blt_bgcol; + } else { + bits_xor = 0x00; + col = s->cirrus_blt_fgcol; + } + + for(y = 0; y < bltheight; y++) { + bitmask = 0x80 >> srcskipleft; + bits = *src++ ^ bits_xor; + d = dst + dstskipleft; + for (x = dstskipleft; x < bltwidth; x += (DEPTH / 8)) { + if ((bitmask & 0xff) == 0) { + bitmask = 0x80; + bits = *src++ ^ bits_xor; + } + index = (bits & bitmask); + if (index) { + PUTPIXEL(); + } + d += (DEPTH / 8); + bitmask >>= 1; + } + dst += dstpitch; + } +} + +static void +glue(glue(glue(cirrus_colorexpand_, ROP_NAME), _),DEPTH) + (CirrusVGAState * s, uint8_t * dst, + const uint8_t * src, + int dstpitch, int srcpitch, + int bltwidth, int bltheight) +{ + uint32_t colors[2]; + uint8_t *d; + int x, y; + unsigned bits; + unsigned int col; + unsigned bitmask; + int srcskipleft = s->vga.gr[0x2f] & 0x07; + int dstskipleft = srcskipleft * (DEPTH / 8); + + colors[0] = s->cirrus_blt_bgcol; + colors[1] = s->cirrus_blt_fgcol; + for(y = 0; y < bltheight; y++) { + bitmask = 0x80 >> srcskipleft; + bits = *src++; + d = dst + dstskipleft; + for (x = dstskipleft; x < bltwidth; x += (DEPTH / 8)) { + if ((bitmask & 0xff) == 0) { + bitmask = 0x80; + bits = *src++; + } + col = colors[!!(bits & bitmask)]; + PUTPIXEL(); + d += (DEPTH / 8); + bitmask >>= 1; + } + dst += dstpitch; + } +} + +static void +glue(glue(glue(cirrus_colorexpand_pattern_transp_, ROP_NAME), _),DEPTH) + (CirrusVGAState * s, uint8_t * dst, + const uint8_t * src, + int dstpitch, int srcpitch, + int bltwidth, int bltheight) +{ + uint8_t *d; + int x, y, bitpos, pattern_y; + unsigned int bits, bits_xor; + unsigned int col; +#if DEPTH == 24 + int dstskipleft = s->vga.gr[0x2f] & 0x1f; + int srcskipleft = dstskipleft / 3; +#else + int srcskipleft = s->vga.gr[0x2f] & 0x07; + int dstskipleft = srcskipleft * (DEPTH / 8); +#endif + + if (s->cirrus_blt_modeext & CIRRUS_BLTMODEEXT_COLOREXPINV) { + bits_xor = 0xff; + col = s->cirrus_blt_bgcol; + } else { + bits_xor = 0x00; + col = s->cirrus_blt_fgcol; + } + pattern_y = s->cirrus_blt_srcaddr & 7; + + for(y = 0; y < bltheight; y++) { + bits = src[pattern_y] ^ bits_xor; + bitpos = 7 - srcskipleft; + d = dst + dstskipleft; + for (x = dstskipleft; x < bltwidth; x += (DEPTH / 8)) { + if ((bits >> bitpos) & 1) { + PUTPIXEL(); + } + d += (DEPTH / 8); + bitpos = (bitpos - 1) & 7; + } + pattern_y = (pattern_y + 1) & 7; + dst += dstpitch; + } +} + +static void +glue(glue(glue(cirrus_colorexpand_pattern_, ROP_NAME), _),DEPTH) + (CirrusVGAState * s, uint8_t * dst, + const uint8_t * src, + int dstpitch, int srcpitch, + int bltwidth, int bltheight) +{ + uint32_t colors[2]; + uint8_t *d; + int x, y, bitpos, pattern_y; + unsigned int bits; + unsigned int col; + int srcskipleft = s->vga.gr[0x2f] & 0x07; + int dstskipleft = srcskipleft * (DEPTH / 8); + + colors[0] = s->cirrus_blt_bgcol; + colors[1] = s->cirrus_blt_fgcol; + pattern_y = s->cirrus_blt_srcaddr & 7; + + for(y = 0; y < bltheight; y++) { + bits = src[pattern_y]; + bitpos = 7 - srcskipleft; + d = dst + dstskipleft; + for (x = dstskipleft; x < bltwidth; x += (DEPTH / 8)) { + col = colors[(bits >> bitpos) & 1]; + PUTPIXEL(); + d += (DEPTH / 8); + bitpos = (bitpos - 1) & 7; + } + pattern_y = (pattern_y + 1) & 7; + dst += dstpitch; + } +} + +static void +glue(glue(glue(cirrus_fill_, ROP_NAME), _),DEPTH) + (CirrusVGAState *s, + uint8_t *dst, int dst_pitch, + int width, int height) +{ + uint8_t *d, *d1; + uint32_t col; + int x, y; + + col = s->cirrus_blt_fgcol; + + d1 = dst; + for(y = 0; y < height; y++) { + d = d1; + for(x = 0; x < width; x += (DEPTH / 8)) { + PUTPIXEL(); + d += (DEPTH / 8); + } + d1 += dst_pitch; + } +} + +#undef DEPTH +#undef PUTPIXEL diff --git a/hw/display/cirrus_vga_template.h b/hw/display/cirrus_vga_template.h new file mode 100644 index 0000000000..3b28280588 --- /dev/null +++ b/hw/display/cirrus_vga_template.h @@ -0,0 +1,102 @@ +/* + * QEMU Cirrus VGA Emulator templates + * + * Copyright (c) 2003 Fabrice Bellard + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#if DEPTH == 8 +#define BPP 1 +#elif DEPTH == 15 || DEPTH == 16 +#define BPP 2 +#elif DEPTH == 32 +#define BPP 4 +#else +#error unsupported depth +#endif + +static void glue(vga_draw_cursor_line_, DEPTH)(uint8_t *d1, + const uint8_t *src1, + int poffset, int w, + unsigned int color0, + unsigned int color1, + unsigned int color_xor) +{ + const uint8_t *plane0, *plane1; + int x, b0, b1; + uint8_t *d; + + d = d1; + plane0 = src1; + plane1 = src1 + poffset; + for (x = 0; x < w; x++) { + b0 = (plane0[x >> 3] >> (7 - (x & 7))) & 1; + b1 = (plane1[x >> 3] >> (7 - (x & 7))) & 1; +#if DEPTH == 8 + switch (b0 | (b1 << 1)) { + case 0: + break; + case 1: + d[0] ^= color_xor; + break; + case 2: + d[0] = color0; + break; + case 3: + d[0] = color1; + break; + } +#elif DEPTH == 16 + switch (b0 | (b1 << 1)) { + case 0: + break; + case 1: + ((uint16_t *)d)[0] ^= color_xor; + break; + case 2: + ((uint16_t *)d)[0] = color0; + break; + case 3: + ((uint16_t *)d)[0] = color1; + break; + } +#elif DEPTH == 32 + switch (b0 | (b1 << 1)) { + case 0: + break; + case 1: + ((uint32_t *)d)[0] ^= color_xor; + break; + case 2: + ((uint32_t *)d)[0] = color0; + break; + case 3: + ((uint32_t *)d)[0] = color1; + break; + } +#else +#error unsupported depth +#endif + d += BPP; + } +} + +#undef DEPTH +#undef BPP diff --git a/hw/display/exynos4210_fimd.c b/hw/display/exynos4210_fimd.c new file mode 100644 index 0000000000..49cca4bf94 --- /dev/null +++ b/hw/display/exynos4210_fimd.c @@ -0,0 +1,1930 @@ +/* + * Samsung exynos4210 Display Controller (FIMD) + * + * Copyright (c) 2000 - 2011 Samsung Electronics Co., Ltd. + * All rights reserved. + * Based on LCD controller for Samsung S5PC1xx-based board emulation + * by Kirill Batuzov <batuzovk@ispras.ru> + * + * Contributed by Mitsyanko Igor <i.mitsyanko@samsung.com> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, see <http://www.gnu.org/licenses/>. + */ + +#include "qemu-common.h" +#include "hw/sysbus.h" +#include "ui/console.h" +#include "ui/pixel_ops.h" +#include "qemu/bswap.h" + +/* Debug messages configuration */ +#define EXYNOS4210_FIMD_DEBUG 0 +#define EXYNOS4210_FIMD_MODE_TRACE 0 + +#if EXYNOS4210_FIMD_DEBUG == 0 + #define DPRINT_L1(fmt, args...) do { } while (0) + #define DPRINT_L2(fmt, args...) do { } while (0) + #define DPRINT_ERROR(fmt, args...) do { } while (0) +#elif EXYNOS4210_FIMD_DEBUG == 1 + #define DPRINT_L1(fmt, args...) \ + do {fprintf(stderr, "QEMU FIMD: "fmt, ## args); } while (0) + #define DPRINT_L2(fmt, args...) do { } while (0) + #define DPRINT_ERROR(fmt, args...) \ + do {fprintf(stderr, "QEMU FIMD ERROR: "fmt, ## args); } while (0) +#else + #define DPRINT_L1(fmt, args...) \ + do {fprintf(stderr, "QEMU FIMD: "fmt, ## args); } while (0) + #define DPRINT_L2(fmt, args...) \ + do {fprintf(stderr, "QEMU FIMD: "fmt, ## args); } while (0) + #define DPRINT_ERROR(fmt, args...) \ + do {fprintf(stderr, "QEMU FIMD ERROR: "fmt, ## args); } while (0) +#endif + +#if EXYNOS4210_FIMD_MODE_TRACE == 0 + #define DPRINT_TRACE(fmt, args...) do { } while (0) +#else + #define DPRINT_TRACE(fmt, args...) \ + do {fprintf(stderr, "QEMU FIMD: "fmt, ## args); } while (0) +#endif + +#define NUM_OF_WINDOWS 5 +#define FIMD_REGS_SIZE 0x4114 + +/* Video main control registers */ +#define FIMD_VIDCON0 0x0000 +#define FIMD_VIDCON1 0x0004 +#define FIMD_VIDCON2 0x0008 +#define FIMD_VIDCON3 0x000C +#define FIMD_VIDCON0_ENVID_F (1 << 0) +#define FIMD_VIDCON0_ENVID (1 << 1) +#define FIMD_VIDCON0_ENVID_MASK ((1 << 0) | (1 << 1)) +#define FIMD_VIDCON1_ROMASK 0x07FFE000 + +/* Video time control registers */ +#define FIMD_VIDTCON_START 0x10 +#define FIMD_VIDTCON_END 0x1C +#define FIMD_VIDTCON2_SIZE_MASK 0x07FF +#define FIMD_VIDTCON2_HOR_SHIFT 0 +#define FIMD_VIDTCON2_VER_SHIFT 11 + +/* Window control registers */ +#define FIMD_WINCON_START 0x0020 +#define FIMD_WINCON_END 0x0030 +#define FIMD_WINCON_ROMASK 0x82200000 +#define FIMD_WINCON_ENWIN (1 << 0) +#define FIMD_WINCON_BLD_PIX (1 << 6) +#define FIMD_WINCON_ALPHA_MUL (1 << 7) +#define FIMD_WINCON_ALPHA_SEL (1 << 1) +#define FIMD_WINCON_SWAP 0x078000 +#define FIMD_WINCON_SWAP_SHIFT 15 +#define FIMD_WINCON_SWAP_WORD 0x1 +#define FIMD_WINCON_SWAP_HWORD 0x2 +#define FIMD_WINCON_SWAP_BYTE 0x4 +#define FIMD_WINCON_SWAP_BITS 0x8 +#define FIMD_WINCON_BUFSTAT_L (1 << 21) +#define FIMD_WINCON_BUFSTAT_H (1 << 31) +#define FIMD_WINCON_BUFSTATUS ((1 << 21) | (1 << 31)) +#define FIMD_WINCON_BUF0_STAT ((0 << 21) | (0 << 31)) +#define FIMD_WINCON_BUF1_STAT ((1 << 21) | (0 << 31)) +#define FIMD_WINCON_BUF2_STAT ((0 << 21) | (1 << 31)) +#define FIMD_WINCON_BUFSELECT ((1 << 20) | (1 << 30)) +#define FIMD_WINCON_BUF0_SEL ((0 << 20) | (0 << 30)) +#define FIMD_WINCON_BUF1_SEL ((1 << 20) | (0 << 30)) +#define FIMD_WINCON_BUF2_SEL ((0 << 20) | (1 << 30)) +#define FIMD_WINCON_BUFMODE (1 << 14) +#define IS_PALETTIZED_MODE(w) (w->wincon & 0xC) +#define PAL_MODE_WITH_ALPHA(x) ((x) == 7) +#define WIN_BPP_MODE(w) ((w->wincon >> 2) & 0xF) +#define WIN_BPP_MODE_WITH_ALPHA(w) \ + (WIN_BPP_MODE(w) == 0xD || WIN_BPP_MODE(w) == 0xE) + +/* Shadow control register */ +#define FIMD_SHADOWCON 0x0034 +#define FIMD_WINDOW_PROTECTED(s, w) ((s) & (1 << (10 + (w)))) +/* Channel mapping control register */ +#define FIMD_WINCHMAP 0x003C + +/* Window position control registers */ +#define FIMD_VIDOSD_START 0x0040 +#define FIMD_VIDOSD_END 0x0088 +#define FIMD_VIDOSD_COORD_MASK 0x07FF +#define FIMD_VIDOSD_HOR_SHIFT 11 +#define FIMD_VIDOSD_VER_SHIFT 0 +#define FIMD_VIDOSD_ALPHA_AEN0 0xFFF000 +#define FIMD_VIDOSD_AEN0_SHIFT 12 +#define FIMD_VIDOSD_ALPHA_AEN1 0x000FFF + +/* Frame buffer address registers */ +#define FIMD_VIDWADD0_START 0x00A0 +#define FIMD_VIDWADD0_END 0x00C4 +#define FIMD_VIDWADD0_END 0x00C4 +#define FIMD_VIDWADD1_START 0x00D0 +#define FIMD_VIDWADD1_END 0x00F4 +#define FIMD_VIDWADD2_START 0x0100 +#define FIMD_VIDWADD2_END 0x0110 +#define FIMD_VIDWADD2_PAGEWIDTH 0x1FFF +#define FIMD_VIDWADD2_OFFSIZE 0x1FFF +#define FIMD_VIDWADD2_OFFSIZE_SHIFT 13 +#define FIMD_VIDW0ADD0_B2 0x20A0 +#define FIMD_VIDW4ADD0_B2 0x20C0 + +/* Video interrupt control registers */ +#define FIMD_VIDINTCON0 0x130 +#define FIMD_VIDINTCON1 0x134 + +/* Window color key registers */ +#define FIMD_WKEYCON_START 0x140 +#define FIMD_WKEYCON_END 0x15C +#define FIMD_WKEYCON0_COMPKEY 0x00FFFFFF +#define FIMD_WKEYCON0_CTL_SHIFT 24 +#define FIMD_WKEYCON0_DIRCON (1 << 24) +#define FIMD_WKEYCON0_KEYEN (1 << 25) +#define FIMD_WKEYCON0_KEYBLEN (1 << 26) +/* Window color key alpha control register */ +#define FIMD_WKEYALPHA_START 0x160 +#define FIMD_WKEYALPHA_END 0x16C + +/* Dithering control register */ +#define FIMD_DITHMODE 0x170 + +/* Window alpha control registers */ +#define FIMD_VIDALPHA_ALPHA_LOWER 0x000F0F0F +#define FIMD_VIDALPHA_ALPHA_UPPER 0x00F0F0F0 +#define FIMD_VIDWALPHA_START 0x21C +#define FIMD_VIDWALPHA_END 0x240 + +/* Window color map registers */ +#define FIMD_WINMAP_START 0x180 +#define FIMD_WINMAP_END 0x190 +#define FIMD_WINMAP_EN (1 << 24) +#define FIMD_WINMAP_COLOR_MASK 0x00FFFFFF + +/* Window palette control registers */ +#define FIMD_WPALCON_HIGH 0x019C +#define FIMD_WPALCON_LOW 0x01A0 +#define FIMD_WPALCON_UPDATEEN (1 << 9) +#define FIMD_WPAL_W0PAL_L 0x07 +#define FIMD_WPAL_W0PAL_L_SHT 0 +#define FIMD_WPAL_W1PAL_L 0x07 +#define FIMD_WPAL_W1PAL_L_SHT 3 +#define FIMD_WPAL_W2PAL_L 0x01 +#define FIMD_WPAL_W2PAL_L_SHT 6 +#define FIMD_WPAL_W2PAL_H 0x06 +#define FIMD_WPAL_W2PAL_H_SHT 8 +#define FIMD_WPAL_W3PAL_L 0x01 +#define FIMD_WPAL_W3PAL_L_SHT 7 +#define FIMD_WPAL_W3PAL_H 0x06 +#define FIMD_WPAL_W3PAL_H_SHT 12 +#define FIMD_WPAL_W4PAL_L 0x01 +#define FIMD_WPAL_W4PAL_L_SHT 8 +#define FIMD_WPAL_W4PAL_H 0x06 +#define FIMD_WPAL_W4PAL_H_SHT 16 + +/* Trigger control registers */ +#define FIMD_TRIGCON 0x01A4 +#define FIMD_TRIGCON_ROMASK 0x00000004 + +/* LCD I80 Interface Control */ +#define FIMD_I80IFCON_START 0x01B0 +#define FIMD_I80IFCON_END 0x01BC +/* Color gain control register */ +#define FIMD_COLORGAINCON 0x01C0 +/* LCD i80 Interface Command Control */ +#define FIMD_LDI_CMDCON0 0x01D0 +#define FIMD_LDI_CMDCON1 0x01D4 +/* I80 System Interface Manual Command Control */ +#define FIMD_SIFCCON0 0x01E0 +#define FIMD_SIFCCON2 0x01E8 + +/* Hue Control Registers */ +#define FIMD_HUECOEFCR_START 0x01EC +#define FIMD_HUECOEFCR_END 0x01F4 +#define FIMD_HUECOEFCB_START 0x01FC +#define FIMD_HUECOEFCB_END 0x0208 +#define FIMD_HUEOFFSET 0x020C + +/* Video interrupt control registers */ +#define FIMD_VIDINT_INTFIFOPEND (1 << 0) +#define FIMD_VIDINT_INTFRMPEND (1 << 1) +#define FIMD_VIDINT_INTI80PEND (1 << 2) +#define FIMD_VIDINT_INTEN (1 << 0) +#define FIMD_VIDINT_INTFIFOEN (1 << 1) +#define FIMD_VIDINT_INTFRMEN (1 << 12) +#define FIMD_VIDINT_I80IFDONE (1 << 17) + +/* Window blend equation control registers */ +#define FIMD_BLENDEQ_START 0x0244 +#define FIMD_BLENDEQ_END 0x0250 +#define FIMD_BLENDCON 0x0260 +#define FIMD_ALPHA_8BIT (1 << 0) +#define FIMD_BLENDEQ_COEF_MASK 0xF + +/* Window RTQOS Control Registers */ +#define FIMD_WRTQOSCON_START 0x0264 +#define FIMD_WRTQOSCON_END 0x0274 + +/* LCD I80 Interface Command */ +#define FIMD_I80IFCMD_START 0x0280 +#define FIMD_I80IFCMD_END 0x02AC + +/* Shadow windows control registers */ +#define FIMD_SHD_ADD0_START 0x40A0 +#define FIMD_SHD_ADD0_END 0x40C0 +#define FIMD_SHD_ADD1_START 0x40D0 +#define FIMD_SHD_ADD1_END 0x40F0 +#define FIMD_SHD_ADD2_START 0x4100 +#define FIMD_SHD_ADD2_END 0x4110 + +/* Palette memory */ +#define FIMD_PAL_MEM_START 0x2400 +#define FIMD_PAL_MEM_END 0x37FC +/* Palette memory aliases for windows 0 and 1 */ +#define FIMD_PALMEM_AL_START 0x0400 +#define FIMD_PALMEM_AL_END 0x0BFC + +typedef struct { + uint8_t r, g, b; + /* D[31..24]dummy, D[23..16]rAlpha, D[15..8]gAlpha, D[7..0]bAlpha */ + uint32_t a; +} rgba; +#define RGBA_SIZE 7 + +typedef void pixel_to_rgb_func(uint32_t pixel, rgba *p); +typedef struct Exynos4210fimdWindow Exynos4210fimdWindow; + +struct Exynos4210fimdWindow { + uint32_t wincon; /* Window control register */ + uint32_t buf_start[3]; /* Start address for video frame buffer */ + uint32_t buf_end[3]; /* End address for video frame buffer */ + uint32_t keycon[2]; /* Window color key registers */ + uint32_t keyalpha; /* Color key alpha control register */ + uint32_t winmap; /* Window color map register */ + uint32_t blendeq; /* Window blending equation control register */ + uint32_t rtqoscon; /* Window RTQOS Control Registers */ + uint32_t palette[256]; /* Palette RAM */ + uint32_t shadow_buf_start; /* Start address of shadow frame buffer */ + uint32_t shadow_buf_end; /* End address of shadow frame buffer */ + uint32_t shadow_buf_size; /* Virtual shadow screen width */ + + pixel_to_rgb_func *pixel_to_rgb; + void (*draw_line)(Exynos4210fimdWindow *w, uint8_t *src, uint8_t *dst, + bool blend); + uint32_t (*get_alpha)(Exynos4210fimdWindow *w, uint32_t pix_a); + uint16_t lefttop_x, lefttop_y; /* VIDOSD0 register */ + uint16_t rightbot_x, rightbot_y; /* VIDOSD1 register */ + uint32_t osdsize; /* VIDOSD2&3 register */ + uint32_t alpha_val[2]; /* VIDOSD2&3, VIDWALPHA registers */ + uint16_t virtpage_width; /* VIDWADD2 register */ + uint16_t virtpage_offsize; /* VIDWADD2 register */ + MemoryRegionSection mem_section; /* RAM fragment containing framebuffer */ + uint8_t *host_fb_addr; /* Host pointer to window's framebuffer */ + hwaddr fb_len; /* Framebuffer length */ +}; + +typedef struct { + SysBusDevice busdev; + MemoryRegion iomem; + QemuConsole *console; + qemu_irq irq[3]; + + uint32_t vidcon[4]; /* Video main control registers 0-3 */ + uint32_t vidtcon[4]; /* Video time control registers 0-3 */ + uint32_t shadowcon; /* Window shadow control register */ + uint32_t winchmap; /* Channel mapping control register */ + uint32_t vidintcon[2]; /* Video interrupt control registers */ + uint32_t dithmode; /* Dithering control register */ + uint32_t wpalcon[2]; /* Window palette control registers */ + uint32_t trigcon; /* Trigger control register */ + uint32_t i80ifcon[4]; /* I80 interface control registers */ + uint32_t colorgaincon; /* Color gain control register */ + uint32_t ldi_cmdcon[2]; /* LCD I80 interface command control */ + uint32_t sifccon[3]; /* I80 System Interface Manual Command Control */ + uint32_t huecoef_cr[4]; /* Hue control registers */ + uint32_t huecoef_cb[4]; /* Hue control registers */ + uint32_t hueoffset; /* Hue offset control register */ + uint32_t blendcon; /* Blending control register */ + uint32_t i80ifcmd[12]; /* LCD I80 Interface Command */ + + Exynos4210fimdWindow window[5]; /* Window-specific registers */ + uint8_t *ifb; /* Internal frame buffer */ + bool invalidate; /* Image needs to be redrawn */ + bool enabled; /* Display controller is enabled */ +} Exynos4210fimdState; + +/* Perform byte/halfword/word swap of data according to WINCON */ +static inline void fimd_swap_data(unsigned int swap_ctl, uint64_t *data) +{ + int i; + uint64_t res; + uint64_t x = *data; + + if (swap_ctl & FIMD_WINCON_SWAP_BITS) { + res = 0; + for (i = 0; i < 64; i++) { + if (x & (1ULL << (64 - i))) { + res |= (1ULL << i); + } + } + x = res; + } + + if (swap_ctl & FIMD_WINCON_SWAP_BYTE) { + x = bswap64(x); + } + + if (swap_ctl & FIMD_WINCON_SWAP_HWORD) { + x = ((x & 0x000000000000FFFFULL) << 48) | + ((x & 0x00000000FFFF0000ULL) << 16) | + ((x & 0x0000FFFF00000000ULL) >> 16) | + ((x & 0xFFFF000000000000ULL) >> 48); + } + + if (swap_ctl & FIMD_WINCON_SWAP_WORD) { + x = ((x & 0x00000000FFFFFFFFULL) << 32) | + ((x & 0xFFFFFFFF00000000ULL) >> 32); + } + + *data = x; +} + +/* Conversion routines of Pixel data from frame buffer area to internal RGBA + * pixel representation. + * Every color component internally represented as 8-bit value. If original + * data has less than 8 bit for component, data is extended to 8 bit. For + * example, if blue component has only two possible values 0 and 1 it will be + * extended to 0 and 0xFF */ + +/* One bit for alpha representation */ +#define DEF_PIXEL_TO_RGB_A1(N, R, G, B) \ +static void N(uint32_t pixel, rgba *p) \ +{ \ + p->b = ((pixel & ((1 << (B)) - 1)) << (8 - (B))) | \ + ((pixel >> (2 * (B) - 8)) & ((1 << (8 - (B))) - 1)); \ + pixel >>= (B); \ + p->g = (pixel & ((1 << (G)) - 1)) << (8 - (G)) | \ + ((pixel >> (2 * (G) - 8)) & ((1 << (8 - (G))) - 1)); \ + pixel >>= (G); \ + p->r = (pixel & ((1 << (R)) - 1)) << (8 - (R)) | \ + ((pixel >> (2 * (R) - 8)) & ((1 << (8 - (R))) - 1)); \ + pixel >>= (R); \ + p->a = (pixel & 0x1); \ +} + +DEF_PIXEL_TO_RGB_A1(pixel_a444_to_rgb, 4, 4, 4) +DEF_PIXEL_TO_RGB_A1(pixel_a555_to_rgb, 5, 5, 5) +DEF_PIXEL_TO_RGB_A1(pixel_a666_to_rgb, 6, 6, 6) +DEF_PIXEL_TO_RGB_A1(pixel_a665_to_rgb, 6, 6, 5) +DEF_PIXEL_TO_RGB_A1(pixel_a888_to_rgb, 8, 8, 8) +DEF_PIXEL_TO_RGB_A1(pixel_a887_to_rgb, 8, 8, 7) + +/* Alpha component is always zero */ +#define DEF_PIXEL_TO_RGB_A0(N, R, G, B) \ +static void N(uint32_t pixel, rgba *p) \ +{ \ + p->b = ((pixel & ((1 << (B)) - 1)) << (8 - (B))) | \ + ((pixel >> (2 * (B) - 8)) & ((1 << (8 - (B))) - 1)); \ + pixel >>= (B); \ + p->g = (pixel & ((1 << (G)) - 1)) << (8 - (G)) | \ + ((pixel >> (2 * (G) - 8)) & ((1 << (8 - (G))) - 1)); \ + pixel >>= (G); \ + p->r = (pixel & ((1 << (R)) - 1)) << (8 - (R)) | \ + ((pixel >> (2 * (R) - 8)) & ((1 << (8 - (R))) - 1)); \ + p->a = 0x0; \ +} + +DEF_PIXEL_TO_RGB_A0(pixel_565_to_rgb, 5, 6, 5) +DEF_PIXEL_TO_RGB_A0(pixel_555_to_rgb, 5, 5, 5) +DEF_PIXEL_TO_RGB_A0(pixel_666_to_rgb, 6, 6, 6) +DEF_PIXEL_TO_RGB_A0(pixel_888_to_rgb, 8, 8, 8) + +/* Alpha component has some meaningful value */ +#define DEF_PIXEL_TO_RGB_A(N, R, G, B, A) \ +static void N(uint32_t pixel, rgba *p) \ +{ \ + p->b = ((pixel & ((1 << (B)) - 1)) << (8 - (B))) | \ + ((pixel >> (2 * (B) - 8)) & ((1 << (8 - (B))) - 1)); \ + pixel >>= (B); \ + p->g = (pixel & ((1 << (G)) - 1)) << (8 - (G)) | \ + ((pixel >> (2 * (G) - 8)) & ((1 << (8 - (G))) - 1)); \ + pixel >>= (G); \ + p->r = (pixel & ((1 << (R)) - 1)) << (8 - (R)) | \ + ((pixel >> (2 * (R) - 8)) & ((1 << (8 - (R))) - 1)); \ + pixel >>= (R); \ + p->a = (pixel & ((1 << (A)) - 1)) << (8 - (A)) | \ + ((pixel >> (2 * (A) - 8)) & ((1 << (8 - (A))) - 1)); \ + p->a = p->a | (p->a << 8) | (p->a << 16); \ +} + +DEF_PIXEL_TO_RGB_A(pixel_4444_to_rgb, 4, 4, 4, 4) +DEF_PIXEL_TO_RGB_A(pixel_8888_to_rgb, 8, 8, 8, 8) + +/* Lookup table to extent 2-bit color component to 8 bit */ +static const uint8_t pixel_lutable_2b[4] = { + 0x0, 0x55, 0xAA, 0xFF +}; +/* Lookup table to extent 3-bit color component to 8 bit */ +static const uint8_t pixel_lutable_3b[8] = { + 0x0, 0x24, 0x49, 0x6D, 0x92, 0xB6, 0xDB, 0xFF +}; +/* Special case for a232 bpp mode */ +static void pixel_a232_to_rgb(uint32_t pixel, rgba *p) +{ + p->b = pixel_lutable_2b[(pixel & 0x3)]; + pixel >>= 2; + p->g = pixel_lutable_3b[(pixel & 0x7)]; + pixel >>= 3; + p->r = pixel_lutable_2b[(pixel & 0x3)]; + pixel >>= 2; + p->a = (pixel & 0x1); +} + +/* Special case for (5+1, 5+1, 5+1) mode. Data bit 15 is common LSB + * for all three color components */ +static void pixel_1555_to_rgb(uint32_t pixel, rgba *p) +{ + uint8_t comm = (pixel >> 15) & 1; + p->b = ((((pixel & 0x1F) << 1) | comm) << 2) | ((pixel >> 3) & 0x3); + pixel >>= 5; + p->g = ((((pixel & 0x1F) << 1) | comm) << 2) | ((pixel >> 3) & 0x3); + pixel >>= 5; + p->r = ((((pixel & 0x1F) << 1) | comm) << 2) | ((pixel >> 3) & 0x3); + p->a = 0x0; +} + +/* Put/get pixel to/from internal LCD Controller framebuffer */ + +static int put_pixel_ifb(const rgba p, uint8_t *d) +{ + *(uint8_t *)d++ = p.r; + *(uint8_t *)d++ = p.g; + *(uint8_t *)d++ = p.b; + *(uint32_t *)d = p.a; + return RGBA_SIZE; +} + +static int get_pixel_ifb(const uint8_t *s, rgba *p) +{ + p->r = *(uint8_t *)s++; + p->g = *(uint8_t *)s++; + p->b = *(uint8_t *)s++; + p->a = (*(uint32_t *)s) & 0x00FFFFFF; + return RGBA_SIZE; +} + +static pixel_to_rgb_func *palette_data_format[8] = { + [0] = pixel_565_to_rgb, + [1] = pixel_a555_to_rgb, + [2] = pixel_666_to_rgb, + [3] = pixel_a665_to_rgb, + [4] = pixel_a666_to_rgb, + [5] = pixel_888_to_rgb, + [6] = pixel_a888_to_rgb, + [7] = pixel_8888_to_rgb +}; + +/* Returns Index in palette data formats table for given window number WINDOW */ +static uint32_t +exynos4210_fimd_palette_format(Exynos4210fimdState *s, int window) +{ + uint32_t ret; + + switch (window) { + case 0: + ret = (s->wpalcon[1] >> FIMD_WPAL_W0PAL_L_SHT) & FIMD_WPAL_W0PAL_L; + if (ret != 7) { + ret = 6 - ret; + } + break; + case 1: + ret = (s->wpalcon[1] >> FIMD_WPAL_W1PAL_L_SHT) & FIMD_WPAL_W1PAL_L; + if (ret != 7) { + ret = 6 - ret; + } + break; + case 2: + ret = ((s->wpalcon[0] >> FIMD_WPAL_W2PAL_H_SHT) & FIMD_WPAL_W2PAL_H) | + ((s->wpalcon[1] >> FIMD_WPAL_W2PAL_L_SHT) & FIMD_WPAL_W2PAL_L); + break; + case 3: + ret = ((s->wpalcon[0] >> FIMD_WPAL_W3PAL_H_SHT) & FIMD_WPAL_W3PAL_H) | + ((s->wpalcon[1] >> FIMD_WPAL_W3PAL_L_SHT) & FIMD_WPAL_W3PAL_L); + break; + case 4: + ret = ((s->wpalcon[0] >> FIMD_WPAL_W4PAL_H_SHT) & FIMD_WPAL_W4PAL_H) | + ((s->wpalcon[1] >> FIMD_WPAL_W4PAL_L_SHT) & FIMD_WPAL_W4PAL_L); + break; + default: + hw_error("exynos4210.fimd: incorrect window number %d\n", window); + ret = 0; + break; + } + return ret; +} + +#define FIMD_1_MINUS_COLOR(x) \ + ((0xFF - ((x) & 0xFF)) | (0xFF00 - ((x) & 0xFF00)) | \ + (0xFF0000 - ((x) & 0xFF0000))) +#define EXTEND_LOWER_HALFBYTE(x) (((x) & 0xF0F0F) | (((x) << 4) & 0xF0F0F0)) +#define EXTEND_UPPER_HALFBYTE(x) (((x) & 0xF0F0F0) | (((x) >> 4) & 0xF0F0F)) + +/* Multiply three lower bytes of two 32-bit words with each other. + * Each byte with values 0-255 is considered as a number with possible values + * in a range [0 - 1] */ +static inline uint32_t fimd_mult_each_byte(uint32_t a, uint32_t b) +{ + uint32_t tmp; + uint32_t ret; + + ret = ((tmp = (((a & 0xFF) * (b & 0xFF)) / 0xFF)) > 0xFF) ? 0xFF : tmp; + ret |= ((tmp = ((((a >> 8) & 0xFF) * ((b >> 8) & 0xFF)) / 0xFF)) > 0xFF) ? + 0xFF00 : tmp << 8; + ret |= ((tmp = ((((a >> 16) & 0xFF) * ((b >> 16) & 0xFF)) / 0xFF)) > 0xFF) ? + 0xFF0000 : tmp << 16; + return ret; +} + +/* For each corresponding bytes of two 32-bit words: (a*b + c*d) + * Byte values 0-255 are mapped to a range [0 .. 1] */ +static inline uint32_t +fimd_mult_and_sum_each_byte(uint32_t a, uint32_t b, uint32_t c, uint32_t d) +{ + uint32_t tmp; + uint32_t ret; + + ret = ((tmp = (((a & 0xFF) * (b & 0xFF) + (c & 0xFF) * (d & 0xFF)) / 0xFF)) + > 0xFF) ? 0xFF : tmp; + ret |= ((tmp = ((((a >> 8) & 0xFF) * ((b >> 8) & 0xFF) + ((c >> 8) & 0xFF) * + ((d >> 8) & 0xFF)) / 0xFF)) > 0xFF) ? 0xFF00 : tmp << 8; + ret |= ((tmp = ((((a >> 16) & 0xFF) * ((b >> 16) & 0xFF) + + ((c >> 16) & 0xFF) * ((d >> 16) & 0xFF)) / 0xFF)) > 0xFF) ? + 0xFF0000 : tmp << 16; + return ret; +} + +/* These routines cover all possible sources of window's transparent factor + * used in blending equation. Choice of routine is affected by WPALCON + * registers, BLENDCON register and window's WINCON register */ + +static uint32_t fimd_get_alpha_pix(Exynos4210fimdWindow *w, uint32_t pix_a) +{ + return pix_a; +} + +static uint32_t +fimd_get_alpha_pix_extlow(Exynos4210fimdWindow *w, uint32_t pix_a) +{ + return EXTEND_LOWER_HALFBYTE(pix_a); +} + +static uint32_t +fimd_get_alpha_pix_exthigh(Exynos4210fimdWindow *w, uint32_t pix_a) +{ + return EXTEND_UPPER_HALFBYTE(pix_a); +} + +static uint32_t fimd_get_alpha_mult(Exynos4210fimdWindow *w, uint32_t pix_a) +{ + return fimd_mult_each_byte(pix_a, w->alpha_val[0]); +} + +static uint32_t fimd_get_alpha_mult_ext(Exynos4210fimdWindow *w, uint32_t pix_a) +{ + return fimd_mult_each_byte(EXTEND_LOWER_HALFBYTE(pix_a), + EXTEND_UPPER_HALFBYTE(w->alpha_val[0])); +} + +static uint32_t fimd_get_alpha_aen(Exynos4210fimdWindow *w, uint32_t pix_a) +{ + return w->alpha_val[pix_a]; +} + +static uint32_t fimd_get_alpha_aen_ext(Exynos4210fimdWindow *w, uint32_t pix_a) +{ + return EXTEND_UPPER_HALFBYTE(w->alpha_val[pix_a]); +} + +static uint32_t fimd_get_alpha_sel(Exynos4210fimdWindow *w, uint32_t pix_a) +{ + return w->alpha_val[(w->wincon & FIMD_WINCON_ALPHA_SEL) ? 1 : 0]; +} + +static uint32_t fimd_get_alpha_sel_ext(Exynos4210fimdWindow *w, uint32_t pix_a) +{ + return EXTEND_UPPER_HALFBYTE(w->alpha_val[(w->wincon & + FIMD_WINCON_ALPHA_SEL) ? 1 : 0]); +} + +/* Updates currently active alpha value get function for specified window */ +static void fimd_update_get_alpha(Exynos4210fimdState *s, int win) +{ + Exynos4210fimdWindow *w = &s->window[win]; + const bool alpha_is_8bit = s->blendcon & FIMD_ALPHA_8BIT; + + if (w->wincon & FIMD_WINCON_BLD_PIX) { + if ((w->wincon & FIMD_WINCON_ALPHA_SEL) && WIN_BPP_MODE_WITH_ALPHA(w)) { + /* In this case, alpha component contains meaningful value */ + if (w->wincon & FIMD_WINCON_ALPHA_MUL) { + w->get_alpha = alpha_is_8bit ? + fimd_get_alpha_mult : fimd_get_alpha_mult_ext; + } else { + w->get_alpha = alpha_is_8bit ? + fimd_get_alpha_pix : fimd_get_alpha_pix_extlow; + } + } else { + if (IS_PALETTIZED_MODE(w) && + PAL_MODE_WITH_ALPHA(exynos4210_fimd_palette_format(s, win))) { + /* Alpha component has 8-bit numeric value */ + w->get_alpha = alpha_is_8bit ? + fimd_get_alpha_pix : fimd_get_alpha_pix_exthigh; + } else { + /* Alpha has only two possible values (AEN) */ + w->get_alpha = alpha_is_8bit ? + fimd_get_alpha_aen : fimd_get_alpha_aen_ext; + } + } + } else { + w->get_alpha = alpha_is_8bit ? fimd_get_alpha_sel : + fimd_get_alpha_sel_ext; + } +} + +/* Blends current window's (w) pixel (foreground pixel *ret) with background + * window (w_blend) pixel p_bg according to formula: + * NEW_COLOR = a_coef x FG_PIXEL_COLOR + b_coef x BG_PIXEL_COLOR + * NEW_ALPHA = p_coef x FG_ALPHA + q_coef x BG_ALPHA + */ +static void +exynos4210_fimd_blend_pixel(Exynos4210fimdWindow *w, rgba p_bg, rgba *ret) +{ + rgba p_fg = *ret; + uint32_t bg_color = ((p_bg.r & 0xFF) << 16) | ((p_bg.g & 0xFF) << 8) | + (p_bg.b & 0xFF); + uint32_t fg_color = ((p_fg.r & 0xFF) << 16) | ((p_fg.g & 0xFF) << 8) | + (p_fg.b & 0xFF); + uint32_t alpha_fg = p_fg.a; + int i; + /* It is possible that blending equation parameters a and b do not + * depend on window BLENEQ register. Account for this with first_coef */ + enum { A_COEF = 0, B_COEF = 1, P_COEF = 2, Q_COEF = 3, COEF_NUM = 4}; + uint32_t first_coef = A_COEF; + uint32_t blend_param[COEF_NUM]; + + if (w->keycon[0] & FIMD_WKEYCON0_KEYEN) { + uint32_t colorkey = (w->keycon[1] & + ~(w->keycon[0] & FIMD_WKEYCON0_COMPKEY)) & FIMD_WKEYCON0_COMPKEY; + + if ((w->keycon[0] & FIMD_WKEYCON0_DIRCON) && + (bg_color & ~(w->keycon[0] & FIMD_WKEYCON0_COMPKEY)) == colorkey) { + /* Foreground pixel is displayed */ + if (w->keycon[0] & FIMD_WKEYCON0_KEYBLEN) { + alpha_fg = w->keyalpha; + blend_param[A_COEF] = alpha_fg; + blend_param[B_COEF] = FIMD_1_MINUS_COLOR(alpha_fg); + } else { + alpha_fg = 0; + blend_param[A_COEF] = 0xFFFFFF; + blend_param[B_COEF] = 0x0; + } + first_coef = P_COEF; + } else if ((w->keycon[0] & FIMD_WKEYCON0_DIRCON) == 0 && + (fg_color & ~(w->keycon[0] & FIMD_WKEYCON0_COMPKEY)) == colorkey) { + /* Background pixel is displayed */ + if (w->keycon[0] & FIMD_WKEYCON0_KEYBLEN) { + alpha_fg = w->keyalpha; + blend_param[A_COEF] = alpha_fg; + blend_param[B_COEF] = FIMD_1_MINUS_COLOR(alpha_fg); + } else { + alpha_fg = 0; + blend_param[A_COEF] = 0x0; + blend_param[B_COEF] = 0xFFFFFF; + } + first_coef = P_COEF; + } + } + + for (i = first_coef; i < COEF_NUM; i++) { + switch ((w->blendeq >> i * 6) & FIMD_BLENDEQ_COEF_MASK) { + case 0: + blend_param[i] = 0; + break; + case 1: + blend_param[i] = 0xFFFFFF; + break; + case 2: + blend_param[i] = alpha_fg; + break; + case 3: + blend_param[i] = FIMD_1_MINUS_COLOR(alpha_fg); + break; + case 4: + blend_param[i] = p_bg.a; + break; + case 5: + blend_param[i] = FIMD_1_MINUS_COLOR(p_bg.a); + break; + case 6: + blend_param[i] = w->alpha_val[0]; + break; + case 10: + blend_param[i] = fg_color; + break; + case 11: + blend_param[i] = FIMD_1_MINUS_COLOR(fg_color); + break; + case 12: + blend_param[i] = bg_color; + break; + case 13: + blend_param[i] = FIMD_1_MINUS_COLOR(bg_color); + break; + default: + hw_error("exynos4210.fimd: blend equation coef illegal value\n"); + break; + } + } + + fg_color = fimd_mult_and_sum_each_byte(bg_color, blend_param[B_COEF], + fg_color, blend_param[A_COEF]); + ret->b = fg_color & 0xFF; + fg_color >>= 8; + ret->g = fg_color & 0xFF; + fg_color >>= 8; + ret->r = fg_color & 0xFF; + ret->a = fimd_mult_and_sum_each_byte(alpha_fg, blend_param[P_COEF], + p_bg.a, blend_param[Q_COEF]); +} + +/* These routines read data from video frame buffer in system RAM, convert + * this data to display controller internal representation, if necessary, + * perform pixel blending with data, currently presented in internal buffer. + * Result is stored in display controller internal frame buffer. */ + +/* Draw line with index in palette table in RAM frame buffer data */ +#define DEF_DRAW_LINE_PALETTE(N) \ +static void glue(draw_line_palette_, N)(Exynos4210fimdWindow *w, uint8_t *src, \ + uint8_t *dst, bool blend) \ +{ \ + int width = w->rightbot_x - w->lefttop_x + 1; \ + uint8_t *ifb = dst; \ + uint8_t swap = (w->wincon & FIMD_WINCON_SWAP) >> FIMD_WINCON_SWAP_SHIFT; \ + uint64_t data; \ + rgba p, p_old; \ + int i; \ + do { \ + memcpy(&data, src, sizeof(data)); \ + src += 8; \ + fimd_swap_data(swap, &data); \ + for (i = (64 / (N) - 1); i >= 0; i--) { \ + w->pixel_to_rgb(w->palette[(data >> ((N) * i)) & \ + ((1ULL << (N)) - 1)], &p); \ + p.a = w->get_alpha(w, p.a); \ + if (blend) { \ + ifb += get_pixel_ifb(ifb, &p_old); \ + exynos4210_fimd_blend_pixel(w, p_old, &p); \ + } \ + dst += put_pixel_ifb(p, dst); \ + } \ + width -= (64 / (N)); \ + } while (width > 0); \ +} + +/* Draw line with direct color value in RAM frame buffer data */ +#define DEF_DRAW_LINE_NOPALETTE(N) \ +static void glue(draw_line_, N)(Exynos4210fimdWindow *w, uint8_t *src, \ + uint8_t *dst, bool blend) \ +{ \ + int width = w->rightbot_x - w->lefttop_x + 1; \ + uint8_t *ifb = dst; \ + uint8_t swap = (w->wincon & FIMD_WINCON_SWAP) >> FIMD_WINCON_SWAP_SHIFT; \ + uint64_t data; \ + rgba p, p_old; \ + int i; \ + do { \ + memcpy(&data, src, sizeof(data)); \ + src += 8; \ + fimd_swap_data(swap, &data); \ + for (i = (64 / (N) - 1); i >= 0; i--) { \ + w->pixel_to_rgb((data >> ((N) * i)) & ((1ULL << (N)) - 1), &p); \ + p.a = w->get_alpha(w, p.a); \ + if (blend) { \ + ifb += get_pixel_ifb(ifb, &p_old); \ + exynos4210_fimd_blend_pixel(w, p_old, &p); \ + } \ + dst += put_pixel_ifb(p, dst); \ + } \ + width -= (64 / (N)); \ + } while (width > 0); \ +} + +DEF_DRAW_LINE_PALETTE(1) +DEF_DRAW_LINE_PALETTE(2) +DEF_DRAW_LINE_PALETTE(4) +DEF_DRAW_LINE_PALETTE(8) +DEF_DRAW_LINE_NOPALETTE(8) /* 8bpp mode has palette and non-palette versions */ +DEF_DRAW_LINE_NOPALETTE(16) +DEF_DRAW_LINE_NOPALETTE(32) + +/* Special draw line routine for window color map case */ +static void draw_line_mapcolor(Exynos4210fimdWindow *w, uint8_t *src, + uint8_t *dst, bool blend) +{ + rgba p, p_old; + uint8_t *ifb = dst; + int width = w->rightbot_x - w->lefttop_x + 1; + uint32_t map_color = w->winmap & FIMD_WINMAP_COLOR_MASK; + + do { + pixel_888_to_rgb(map_color, &p); + p.a = w->get_alpha(w, p.a); + if (blend) { + ifb += get_pixel_ifb(ifb, &p_old); + exynos4210_fimd_blend_pixel(w, p_old, &p); + } + dst += put_pixel_ifb(p, dst); + } while (--width); +} + +/* Write RGB to QEMU's GraphicConsole framebuffer */ + +static int put_to_qemufb_pixel8(const rgba p, uint8_t *d) +{ + uint32_t pixel = rgb_to_pixel8(p.r, p.g, p.b); + *(uint8_t *)d = pixel; + return 1; +} + +static int put_to_qemufb_pixel15(const rgba p, uint8_t *d) +{ + uint32_t pixel = rgb_to_pixel15(p.r, p.g, p.b); + *(uint16_t *)d = pixel; + return 2; +} + +static int put_to_qemufb_pixel16(const rgba p, uint8_t *d) +{ + uint32_t pixel = rgb_to_pixel16(p.r, p.g, p.b); + *(uint16_t *)d = pixel; + return 2; +} + +static int put_to_qemufb_pixel24(const rgba p, uint8_t *d) +{ + uint32_t pixel = rgb_to_pixel24(p.r, p.g, p.b); + *(uint8_t *)d++ = (pixel >> 0) & 0xFF; + *(uint8_t *)d++ = (pixel >> 8) & 0xFF; + *(uint8_t *)d++ = (pixel >> 16) & 0xFF; + return 3; +} + +static int put_to_qemufb_pixel32(const rgba p, uint8_t *d) +{ + uint32_t pixel = rgb_to_pixel24(p.r, p.g, p.b); + *(uint32_t *)d = pixel; + return 4; +} + +/* Routine to copy pixel from internal buffer to QEMU buffer */ +static int (*put_pixel_toqemu)(const rgba p, uint8_t *pixel); +static inline void fimd_update_putpix_qemu(int bpp) +{ + switch (bpp) { + case 8: + put_pixel_toqemu = put_to_qemufb_pixel8; + break; + case 15: + put_pixel_toqemu = put_to_qemufb_pixel15; + break; + case 16: + put_pixel_toqemu = put_to_qemufb_pixel16; + break; + case 24: + put_pixel_toqemu = put_to_qemufb_pixel24; + break; + case 32: + put_pixel_toqemu = put_to_qemufb_pixel32; + break; + default: + hw_error("exynos4210.fimd: unsupported BPP (%d)", bpp); + break; + } +} + +/* Routine to copy a line from internal frame buffer to QEMU display */ +static void fimd_copy_line_toqemu(int width, uint8_t *src, uint8_t *dst) +{ + rgba p; + + do { + src += get_pixel_ifb(src, &p); + dst += put_pixel_toqemu(p, dst); + } while (--width); +} + +/* Parse BPPMODE_F = WINCON1[5:2] bits */ +static void exynos4210_fimd_update_win_bppmode(Exynos4210fimdState *s, int win) +{ + Exynos4210fimdWindow *w = &s->window[win]; + + if (w->winmap & FIMD_WINMAP_EN) { + w->draw_line = draw_line_mapcolor; + return; + } + + switch (WIN_BPP_MODE(w)) { + case 0: + w->draw_line = draw_line_palette_1; + w->pixel_to_rgb = + palette_data_format[exynos4210_fimd_palette_format(s, win)]; + break; + case 1: + w->draw_line = draw_line_palette_2; + w->pixel_to_rgb = + palette_data_format[exynos4210_fimd_palette_format(s, win)]; + break; + case 2: + w->draw_line = draw_line_palette_4; + w->pixel_to_rgb = + palette_data_format[exynos4210_fimd_palette_format(s, win)]; + break; + case 3: + w->draw_line = draw_line_palette_8; + w->pixel_to_rgb = + palette_data_format[exynos4210_fimd_palette_format(s, win)]; + break; + case 4: + w->draw_line = draw_line_8; + w->pixel_to_rgb = pixel_a232_to_rgb; + break; + case 5: + w->draw_line = draw_line_16; + w->pixel_to_rgb = pixel_565_to_rgb; + break; + case 6: + w->draw_line = draw_line_16; + w->pixel_to_rgb = pixel_a555_to_rgb; + break; + case 7: + w->draw_line = draw_line_16; + w->pixel_to_rgb = pixel_1555_to_rgb; + break; + case 8: + w->draw_line = draw_line_32; + w->pixel_to_rgb = pixel_666_to_rgb; + break; + case 9: + w->draw_line = draw_line_32; + w->pixel_to_rgb = pixel_a665_to_rgb; + break; + case 10: + w->draw_line = draw_line_32; + w->pixel_to_rgb = pixel_a666_to_rgb; + break; + case 11: + w->draw_line = draw_line_32; + w->pixel_to_rgb = pixel_888_to_rgb; + break; + case 12: + w->draw_line = draw_line_32; + w->pixel_to_rgb = pixel_a887_to_rgb; + break; + case 13: + w->draw_line = draw_line_32; + if ((w->wincon & FIMD_WINCON_BLD_PIX) && (w->wincon & + FIMD_WINCON_ALPHA_SEL)) { + w->pixel_to_rgb = pixel_8888_to_rgb; + } else { + w->pixel_to_rgb = pixel_a888_to_rgb; + } + break; + case 14: + w->draw_line = draw_line_16; + if ((w->wincon & FIMD_WINCON_BLD_PIX) && (w->wincon & + FIMD_WINCON_ALPHA_SEL)) { + w->pixel_to_rgb = pixel_4444_to_rgb; + } else { + w->pixel_to_rgb = pixel_a444_to_rgb; + } + break; + case 15: + w->draw_line = draw_line_16; + w->pixel_to_rgb = pixel_555_to_rgb; + break; + } +} + +#if EXYNOS4210_FIMD_MODE_TRACE > 0 +static const char *exynos4210_fimd_get_bppmode(int mode_code) +{ + switch (mode_code) { + case 0: + return "1 bpp"; + case 1: + return "2 bpp"; + case 2: + return "4 bpp"; + case 3: + return "8 bpp (palettized)"; + case 4: + return "8 bpp (non-palettized, A: 1-R:2-G:3-B:2)"; + case 5: + return "16 bpp (non-palettized, R:5-G:6-B:5)"; + case 6: + return "16 bpp (non-palettized, A:1-R:5-G:5-B:5)"; + case 7: + return "16 bpp (non-palettized, I :1-R:5-G:5-B:5)"; + case 8: + return "Unpacked 18 bpp (non-palettized, R:6-G:6-B:6)"; + case 9: + return "Unpacked 18bpp (non-palettized,A:1-R:6-G:6-B:5)"; + case 10: + return "Unpacked 19bpp (non-palettized,A:1-R:6-G:6-B:6)"; + case 11: + return "Unpacked 24 bpp (non-palettized R:8-G:8-B:8)"; + case 12: + return "Unpacked 24 bpp (non-palettized A:1-R:8-G:8-B:7)"; + case 13: + return "Unpacked 25 bpp (non-palettized A:1-R:8-G:8-B:8)"; + case 14: + return "Unpacked 13 bpp (non-palettized A:1-R:4-G:4-B:4)"; + case 15: + return "Unpacked 15 bpp (non-palettized R:5-G:5-B:5)"; + default: + return "Non-existing bpp mode"; + } +} + +static inline void exynos4210_fimd_trace_bppmode(Exynos4210fimdState *s, + int win_num, uint32_t val) +{ + Exynos4210fimdWindow *w = &s->window[win_num]; + + if (w->winmap & FIMD_WINMAP_EN) { + printf("QEMU FIMD: Window %d is mapped with MAPCOLOR=0x%x\n", + win_num, w->winmap & 0xFFFFFF); + return; + } + + if ((val != 0xFFFFFFFF) && ((w->wincon >> 2) & 0xF) == ((val >> 2) & 0xF)) { + return; + } + printf("QEMU FIMD: Window %d BPP mode set to %s\n", win_num, + exynos4210_fimd_get_bppmode((val >> 2) & 0xF)); +} +#else +static inline void exynos4210_fimd_trace_bppmode(Exynos4210fimdState *s, + int win_num, uint32_t val) +{ + +} +#endif + +static inline int fimd_get_buffer_id(Exynos4210fimdWindow *w) +{ + switch (w->wincon & FIMD_WINCON_BUFSTATUS) { + case FIMD_WINCON_BUF0_STAT: + return 0; + case FIMD_WINCON_BUF1_STAT: + return 1; + case FIMD_WINCON_BUF2_STAT: + return 2; + default: + DPRINT_ERROR("Non-existent buffer index\n"); + return 0; + } +} + +/* Updates specified window's MemorySection based on values of WINCON, + * VIDOSDA, VIDOSDB, VIDWADDx and SHADOWCON registers */ +static void fimd_update_memory_section(Exynos4210fimdState *s, unsigned win) +{ + Exynos4210fimdWindow *w = &s->window[win]; + hwaddr fb_start_addr, fb_mapped_len; + + if (!s->enabled || !(w->wincon & FIMD_WINCON_ENWIN) || + FIMD_WINDOW_PROTECTED(s->shadowcon, win)) { + return; + } + + if (w->host_fb_addr) { + cpu_physical_memory_unmap(w->host_fb_addr, w->fb_len, 0, 0); + w->host_fb_addr = NULL; + w->fb_len = 0; + } + + fb_start_addr = w->buf_start[fimd_get_buffer_id(w)]; + /* Total number of bytes of virtual screen used by current window */ + w->fb_len = fb_mapped_len = (w->virtpage_width + w->virtpage_offsize) * + (w->rightbot_y - w->lefttop_y + 1); + w->mem_section = memory_region_find(sysbus_address_space(&s->busdev), + fb_start_addr, w->fb_len); + assert(w->mem_section.mr); + assert(w->mem_section.offset_within_address_space == fb_start_addr); + DPRINT_TRACE("Window %u framebuffer changed: address=0x%08x, len=0x%x\n", + win, fb_start_addr, w->fb_len); + + if (w->mem_section.size != w->fb_len || + !memory_region_is_ram(w->mem_section.mr)) { + DPRINT_ERROR("Failed to find window %u framebuffer region\n", win); + goto error_return; + } + + w->host_fb_addr = cpu_physical_memory_map(fb_start_addr, &fb_mapped_len, 0); + if (!w->host_fb_addr) { + DPRINT_ERROR("Failed to map window %u framebuffer\n", win); + goto error_return; + } + + if (fb_mapped_len != w->fb_len) { + DPRINT_ERROR("Window %u mapped framebuffer length is less then " + "expected\n", win); + cpu_physical_memory_unmap(w->host_fb_addr, fb_mapped_len, 0, 0); + goto error_return; + } + return; + +error_return: + w->mem_section.mr = NULL; + w->mem_section.size = 0; + w->host_fb_addr = NULL; + w->fb_len = 0; +} + +static void exynos4210_fimd_enable(Exynos4210fimdState *s, bool enabled) +{ + if (enabled && !s->enabled) { + unsigned w; + s->enabled = true; + for (w = 0; w < NUM_OF_WINDOWS; w++) { + fimd_update_memory_section(s, w); + } + } + s->enabled = enabled; + DPRINT_TRACE("display controller %s\n", enabled ? "enabled" : "disabled"); +} + +static inline uint32_t unpack_upper_4(uint32_t x) +{ + return ((x & 0xF00) << 12) | ((x & 0xF0) << 8) | ((x & 0xF) << 4); +} + +static inline uint32_t pack_upper_4(uint32_t x) +{ + return (((x & 0xF00000) >> 12) | ((x & 0xF000) >> 8) | + ((x & 0xF0) >> 4)) & 0xFFF; +} + +static void exynos4210_fimd_update_irq(Exynos4210fimdState *s) +{ + if (!(s->vidintcon[0] & FIMD_VIDINT_INTEN)) { + qemu_irq_lower(s->irq[0]); + qemu_irq_lower(s->irq[1]); + qemu_irq_lower(s->irq[2]); + return; + } + if ((s->vidintcon[0] & FIMD_VIDINT_INTFIFOEN) && + (s->vidintcon[1] & FIMD_VIDINT_INTFIFOPEND)) { + qemu_irq_raise(s->irq[0]); + } else { + qemu_irq_lower(s->irq[0]); + } + if ((s->vidintcon[0] & FIMD_VIDINT_INTFRMEN) && + (s->vidintcon[1] & FIMD_VIDINT_INTFRMPEND)) { + qemu_irq_raise(s->irq[1]); + } else { + qemu_irq_lower(s->irq[1]); + } + if ((s->vidintcon[0] & FIMD_VIDINT_I80IFDONE) && + (s->vidintcon[1] & FIMD_VIDINT_INTI80PEND)) { + qemu_irq_raise(s->irq[2]); + } else { + qemu_irq_lower(s->irq[2]); + } +} + +static void exynos4210_fimd_invalidate(void *opaque) +{ + Exynos4210fimdState *s = (Exynos4210fimdState *)opaque; + s->invalidate = true; +} + +static void exynos4210_update_resolution(Exynos4210fimdState *s) +{ + DisplaySurface *surface = qemu_console_surface(s->console); + + /* LCD resolution is stored in VIDEO TIME CONTROL REGISTER 2 */ + uint32_t width = ((s->vidtcon[2] >> FIMD_VIDTCON2_HOR_SHIFT) & + FIMD_VIDTCON2_SIZE_MASK) + 1; + uint32_t height = ((s->vidtcon[2] >> FIMD_VIDTCON2_VER_SHIFT) & + FIMD_VIDTCON2_SIZE_MASK) + 1; + + if (s->ifb == NULL || surface_width(surface) != width || + surface_height(surface) != height) { + DPRINT_L1("Resolution changed from %ux%u to %ux%u\n", + surface_width(surface), surface_height(surface), width, height); + qemu_console_resize(s->console, width, height); + s->ifb = g_realloc(s->ifb, width * height * RGBA_SIZE + 1); + memset(s->ifb, 0, width * height * RGBA_SIZE + 1); + exynos4210_fimd_invalidate(s); + } +} + +static void exynos4210_fimd_update(void *opaque) +{ + Exynos4210fimdState *s = (Exynos4210fimdState *)opaque; + DisplaySurface *surface = qemu_console_surface(s->console); + Exynos4210fimdWindow *w; + int i, line; + hwaddr fb_line_addr, inc_size; + int scrn_height; + int first_line = -1, last_line = -1, scrn_width; + bool blend = false; + uint8_t *host_fb_addr; + bool is_dirty = false; + const int global_width = (s->vidtcon[2] & FIMD_VIDTCON2_SIZE_MASK) + 1; + const int global_height = ((s->vidtcon[2] >> FIMD_VIDTCON2_VER_SHIFT) & + FIMD_VIDTCON2_SIZE_MASK) + 1; + + if (!s || !s->console || !surface_bits_per_pixel(surface) || + !s->enabled) { + return; + } + exynos4210_update_resolution(s); + + for (i = 0; i < NUM_OF_WINDOWS; i++) { + w = &s->window[i]; + if ((w->wincon & FIMD_WINCON_ENWIN) && w->host_fb_addr) { + scrn_height = w->rightbot_y - w->lefttop_y + 1; + scrn_width = w->virtpage_width; + /* Total width of virtual screen page in bytes */ + inc_size = scrn_width + w->virtpage_offsize; + memory_region_sync_dirty_bitmap(w->mem_section.mr); + host_fb_addr = w->host_fb_addr; + fb_line_addr = w->mem_section.offset_within_region; + + for (line = 0; line < scrn_height; line++) { + is_dirty = memory_region_get_dirty(w->mem_section.mr, + fb_line_addr, scrn_width, DIRTY_MEMORY_VGA); + + if (s->invalidate || is_dirty) { + if (first_line == -1) { + first_line = line; + } + last_line = line; + w->draw_line(w, host_fb_addr, s->ifb + + w->lefttop_x * RGBA_SIZE + (w->lefttop_y + line) * + global_width * RGBA_SIZE, blend); + } + host_fb_addr += inc_size; + fb_line_addr += inc_size; + is_dirty = false; + } + memory_region_reset_dirty(w->mem_section.mr, + w->mem_section.offset_within_region, + w->fb_len, DIRTY_MEMORY_VGA); + blend = true; + } + } + + /* Copy resulting image to QEMU_CONSOLE. */ + if (first_line >= 0) { + uint8_t *d; + int bpp; + + bpp = surface_bits_per_pixel(surface); + fimd_update_putpix_qemu(bpp); + bpp = (bpp + 1) >> 3; + d = surface_data(surface); + for (line = first_line; line <= last_line; line++) { + fimd_copy_line_toqemu(global_width, s->ifb + global_width * line * + RGBA_SIZE, d + global_width * line * bpp); + } + dpy_gfx_update(s->console, 0, 0, global_width, global_height); + } + s->invalidate = false; + s->vidintcon[1] |= FIMD_VIDINT_INTFRMPEND; + if ((s->vidcon[0] & FIMD_VIDCON0_ENVID_F) == 0) { + exynos4210_fimd_enable(s, false); + } + exynos4210_fimd_update_irq(s); +} + +static void exynos4210_fimd_reset(DeviceState *d) +{ + Exynos4210fimdState *s = DO_UPCAST(Exynos4210fimdState, busdev.qdev, d); + unsigned w; + + DPRINT_TRACE("Display controller reset\n"); + /* Set all display controller registers to 0 */ + memset(&s->vidcon, 0, (uint8_t *)&s->window - (uint8_t *)&s->vidcon); + for (w = 0; w < NUM_OF_WINDOWS; w++) { + memset(&s->window[w], 0, sizeof(Exynos4210fimdWindow)); + s->window[w].blendeq = 0xC2; + exynos4210_fimd_update_win_bppmode(s, w); + exynos4210_fimd_trace_bppmode(s, w, 0xFFFFFFFF); + fimd_update_get_alpha(s, w); + } + + if (s->ifb != NULL) { + g_free(s->ifb); + } + s->ifb = NULL; + + exynos4210_fimd_invalidate(s); + exynos4210_fimd_enable(s, false); + /* Some registers have non-zero initial values */ + s->winchmap = 0x7D517D51; + s->colorgaincon = 0x10040100; + s->huecoef_cr[0] = s->huecoef_cr[3] = 0x01000100; + s->huecoef_cb[0] = s->huecoef_cb[3] = 0x01000100; + s->hueoffset = 0x01800080; +} + +static void exynos4210_fimd_write(void *opaque, hwaddr offset, + uint64_t val, unsigned size) +{ + Exynos4210fimdState *s = (Exynos4210fimdState *)opaque; + unsigned w, i; + uint32_t old_value; + + DPRINT_L2("write offset 0x%08x, value=%llu(0x%08llx)\n", offset, + (long long unsigned int)val, (long long unsigned int)val); + + switch (offset) { + case FIMD_VIDCON0: + if ((val & FIMD_VIDCON0_ENVID_MASK) == FIMD_VIDCON0_ENVID_MASK) { + exynos4210_fimd_enable(s, true); + } else { + if ((val & FIMD_VIDCON0_ENVID) == 0) { + exynos4210_fimd_enable(s, false); + } + } + s->vidcon[0] = val; + break; + case FIMD_VIDCON1: + /* Leave read-only bits as is */ + val = (val & (~FIMD_VIDCON1_ROMASK)) | + (s->vidcon[1] & FIMD_VIDCON1_ROMASK); + s->vidcon[1] = val; + break; + case FIMD_VIDCON2 ... FIMD_VIDCON3: + s->vidcon[(offset) >> 2] = val; + break; + case FIMD_VIDTCON_START ... FIMD_VIDTCON_END: + s->vidtcon[(offset - FIMD_VIDTCON_START) >> 2] = val; + break; + case FIMD_WINCON_START ... FIMD_WINCON_END: + w = (offset - FIMD_WINCON_START) >> 2; + /* Window's current buffer ID */ + i = fimd_get_buffer_id(&s->window[w]); + old_value = s->window[w].wincon; + val = (val & ~FIMD_WINCON_ROMASK) | + (s->window[w].wincon & FIMD_WINCON_ROMASK); + if (w == 0) { + /* Window 0 wincon ALPHA_MUL bit must always be 0 */ + val &= ~FIMD_WINCON_ALPHA_MUL; + } + exynos4210_fimd_trace_bppmode(s, w, val); + switch (val & FIMD_WINCON_BUFSELECT) { + case FIMD_WINCON_BUF0_SEL: + val &= ~FIMD_WINCON_BUFSTATUS; + break; + case FIMD_WINCON_BUF1_SEL: + val = (val & ~FIMD_WINCON_BUFSTAT_H) | FIMD_WINCON_BUFSTAT_L; + break; + case FIMD_WINCON_BUF2_SEL: + if (val & FIMD_WINCON_BUFMODE) { + val = (val & ~FIMD_WINCON_BUFSTAT_L) | FIMD_WINCON_BUFSTAT_H; + } + break; + default: + break; + } + s->window[w].wincon = val; + exynos4210_fimd_update_win_bppmode(s, w); + fimd_update_get_alpha(s, w); + if ((i != fimd_get_buffer_id(&s->window[w])) || + (!(old_value & FIMD_WINCON_ENWIN) && (s->window[w].wincon & + FIMD_WINCON_ENWIN))) { + fimd_update_memory_section(s, w); + } + break; + case FIMD_SHADOWCON: + old_value = s->shadowcon; + s->shadowcon = val; + for (w = 0; w < NUM_OF_WINDOWS; w++) { + if (FIMD_WINDOW_PROTECTED(old_value, w) && + !FIMD_WINDOW_PROTECTED(s->shadowcon, w)) { + fimd_update_memory_section(s, w); + } + } + break; + case FIMD_WINCHMAP: + s->winchmap = val; + break; + case FIMD_VIDOSD_START ... FIMD_VIDOSD_END: + w = (offset - FIMD_VIDOSD_START) >> 4; + i = ((offset - FIMD_VIDOSD_START) & 0xF) >> 2; + switch (i) { + case 0: + old_value = s->window[w].lefttop_y; + s->window[w].lefttop_x = (val >> FIMD_VIDOSD_HOR_SHIFT) & + FIMD_VIDOSD_COORD_MASK; + s->window[w].lefttop_y = (val >> FIMD_VIDOSD_VER_SHIFT) & + FIMD_VIDOSD_COORD_MASK; + if (s->window[w].lefttop_y != old_value) { + fimd_update_memory_section(s, w); + } + break; + case 1: + old_value = s->window[w].rightbot_y; + s->window[w].rightbot_x = (val >> FIMD_VIDOSD_HOR_SHIFT) & + FIMD_VIDOSD_COORD_MASK; + s->window[w].rightbot_y = (val >> FIMD_VIDOSD_VER_SHIFT) & + FIMD_VIDOSD_COORD_MASK; + if (s->window[w].rightbot_y != old_value) { + fimd_update_memory_section(s, w); + } + break; + case 2: + if (w == 0) { + s->window[w].osdsize = val; + } else { + s->window[w].alpha_val[0] = + unpack_upper_4((val & FIMD_VIDOSD_ALPHA_AEN0) >> + FIMD_VIDOSD_AEN0_SHIFT) | + (s->window[w].alpha_val[0] & FIMD_VIDALPHA_ALPHA_LOWER); + s->window[w].alpha_val[1] = + unpack_upper_4(val & FIMD_VIDOSD_ALPHA_AEN1) | + (s->window[w].alpha_val[1] & FIMD_VIDALPHA_ALPHA_LOWER); + } + break; + case 3: + if (w != 1 && w != 2) { + DPRINT_ERROR("Bad write offset 0x%08x\n", offset); + return; + } + s->window[w].osdsize = val; + break; + } + break; + case FIMD_VIDWADD0_START ... FIMD_VIDWADD0_END: + w = (offset - FIMD_VIDWADD0_START) >> 3; + i = ((offset - FIMD_VIDWADD0_START) >> 2) & 1; + if (i == fimd_get_buffer_id(&s->window[w]) && + s->window[w].buf_start[i] != val) { + s->window[w].buf_start[i] = val; + fimd_update_memory_section(s, w); + break; + } + s->window[w].buf_start[i] = val; + break; + case FIMD_VIDWADD1_START ... FIMD_VIDWADD1_END: + w = (offset - FIMD_VIDWADD1_START) >> 3; + i = ((offset - FIMD_VIDWADD1_START) >> 2) & 1; + s->window[w].buf_end[i] = val; + break; + case FIMD_VIDWADD2_START ... FIMD_VIDWADD2_END: + w = (offset - FIMD_VIDWADD2_START) >> 2; + if (((val & FIMD_VIDWADD2_PAGEWIDTH) != s->window[w].virtpage_width) || + (((val >> FIMD_VIDWADD2_OFFSIZE_SHIFT) & FIMD_VIDWADD2_OFFSIZE) != + s->window[w].virtpage_offsize)) { + s->window[w].virtpage_width = val & FIMD_VIDWADD2_PAGEWIDTH; + s->window[w].virtpage_offsize = + (val >> FIMD_VIDWADD2_OFFSIZE_SHIFT) & FIMD_VIDWADD2_OFFSIZE; + fimd_update_memory_section(s, w); + } + break; + case FIMD_VIDINTCON0: + s->vidintcon[0] = val; + break; + case FIMD_VIDINTCON1: + s->vidintcon[1] &= ~(val & 7); + exynos4210_fimd_update_irq(s); + break; + case FIMD_WKEYCON_START ... FIMD_WKEYCON_END: + w = ((offset - FIMD_WKEYCON_START) >> 3) + 1; + i = ((offset - FIMD_WKEYCON_START) >> 2) & 1; + s->window[w].keycon[i] = val; + break; + case FIMD_WKEYALPHA_START ... FIMD_WKEYALPHA_END: + w = ((offset - FIMD_WKEYALPHA_START) >> 2) + 1; + s->window[w].keyalpha = val; + break; + case FIMD_DITHMODE: + s->dithmode = val; + break; + case FIMD_WINMAP_START ... FIMD_WINMAP_END: + w = (offset - FIMD_WINMAP_START) >> 2; + old_value = s->window[w].winmap; + s->window[w].winmap = val; + if ((val & FIMD_WINMAP_EN) ^ (old_value & FIMD_WINMAP_EN)) { + exynos4210_fimd_invalidate(s); + exynos4210_fimd_update_win_bppmode(s, w); + exynos4210_fimd_trace_bppmode(s, w, 0xFFFFFFFF); + exynos4210_fimd_update(s); + } + break; + case FIMD_WPALCON_HIGH ... FIMD_WPALCON_LOW: + i = (offset - FIMD_WPALCON_HIGH) >> 2; + s->wpalcon[i] = val; + if (s->wpalcon[1] & FIMD_WPALCON_UPDATEEN) { + for (w = 0; w < NUM_OF_WINDOWS; w++) { + exynos4210_fimd_update_win_bppmode(s, w); + fimd_update_get_alpha(s, w); + } + } + break; + case FIMD_TRIGCON: + val = (val & ~FIMD_TRIGCON_ROMASK) | (s->trigcon & FIMD_TRIGCON_ROMASK); + s->trigcon = val; + break; + case FIMD_I80IFCON_START ... FIMD_I80IFCON_END: + s->i80ifcon[(offset - FIMD_I80IFCON_START) >> 2] = val; + break; + case FIMD_COLORGAINCON: + s->colorgaincon = val; + break; + case FIMD_LDI_CMDCON0 ... FIMD_LDI_CMDCON1: + s->ldi_cmdcon[(offset - FIMD_LDI_CMDCON0) >> 2] = val; + break; + case FIMD_SIFCCON0 ... FIMD_SIFCCON2: + i = (offset - FIMD_SIFCCON0) >> 2; + if (i != 2) { + s->sifccon[i] = val; + } + break; + case FIMD_HUECOEFCR_START ... FIMD_HUECOEFCR_END: + i = (offset - FIMD_HUECOEFCR_START) >> 2; + s->huecoef_cr[i] = val; + break; + case FIMD_HUECOEFCB_START ... FIMD_HUECOEFCB_END: + i = (offset - FIMD_HUECOEFCB_START) >> 2; + s->huecoef_cb[i] = val; + break; + case FIMD_HUEOFFSET: + s->hueoffset = val; + break; + case FIMD_VIDWALPHA_START ... FIMD_VIDWALPHA_END: + w = ((offset - FIMD_VIDWALPHA_START) >> 3); + i = ((offset - FIMD_VIDWALPHA_START) >> 2) & 1; + if (w == 0) { + s->window[w].alpha_val[i] = val; + } else { + s->window[w].alpha_val[i] = (val & FIMD_VIDALPHA_ALPHA_LOWER) | + (s->window[w].alpha_val[i] & FIMD_VIDALPHA_ALPHA_UPPER); + } + break; + case FIMD_BLENDEQ_START ... FIMD_BLENDEQ_END: + s->window[(offset - FIMD_BLENDEQ_START) >> 2].blendeq = val; + break; + case FIMD_BLENDCON: + old_value = s->blendcon; + s->blendcon = val; + if ((s->blendcon & FIMD_ALPHA_8BIT) != (old_value & FIMD_ALPHA_8BIT)) { + for (w = 0; w < NUM_OF_WINDOWS; w++) { + fimd_update_get_alpha(s, w); + } + } + break; + case FIMD_WRTQOSCON_START ... FIMD_WRTQOSCON_END: + s->window[(offset - FIMD_WRTQOSCON_START) >> 2].rtqoscon = val; + break; + case FIMD_I80IFCMD_START ... FIMD_I80IFCMD_END: + s->i80ifcmd[(offset - FIMD_I80IFCMD_START) >> 2] = val; + break; + case FIMD_VIDW0ADD0_B2 ... FIMD_VIDW4ADD0_B2: + if (offset & 0x0004) { + DPRINT_ERROR("bad write offset 0x%08x\n", offset); + break; + } + w = (offset - FIMD_VIDW0ADD0_B2) >> 3; + if (fimd_get_buffer_id(&s->window[w]) == 2 && + s->window[w].buf_start[2] != val) { + s->window[w].buf_start[2] = val; + fimd_update_memory_section(s, w); + break; + } + s->window[w].buf_start[2] = val; + break; + case FIMD_SHD_ADD0_START ... FIMD_SHD_ADD0_END: + if (offset & 0x0004) { + DPRINT_ERROR("bad write offset 0x%08x\n", offset); + break; + } + s->window[(offset - FIMD_SHD_ADD0_START) >> 3].shadow_buf_start = val; + break; + case FIMD_SHD_ADD1_START ... FIMD_SHD_ADD1_END: + if (offset & 0x0004) { + DPRINT_ERROR("bad write offset 0x%08x\n", offset); + break; + } + s->window[(offset - FIMD_SHD_ADD1_START) >> 3].shadow_buf_end = val; + break; + case FIMD_SHD_ADD2_START ... FIMD_SHD_ADD2_END: + s->window[(offset - FIMD_SHD_ADD2_START) >> 2].shadow_buf_size = val; + break; + case FIMD_PAL_MEM_START ... FIMD_PAL_MEM_END: + w = (offset - FIMD_PAL_MEM_START) >> 10; + i = ((offset - FIMD_PAL_MEM_START) >> 2) & 0xFF; + s->window[w].palette[i] = val; + break; + case FIMD_PALMEM_AL_START ... FIMD_PALMEM_AL_END: + /* Palette memory aliases for windows 0 and 1 */ + w = (offset - FIMD_PALMEM_AL_START) >> 10; + i = ((offset - FIMD_PALMEM_AL_START) >> 2) & 0xFF; + s->window[w].palette[i] = val; + break; + default: + DPRINT_ERROR("bad write offset 0x%08x\n", offset); + break; + } +} + +static uint64_t exynos4210_fimd_read(void *opaque, hwaddr offset, + unsigned size) +{ + Exynos4210fimdState *s = (Exynos4210fimdState *)opaque; + int w, i; + uint32_t ret = 0; + + DPRINT_L2("read offset 0x%08x\n", offset); + + switch (offset) { + case FIMD_VIDCON0 ... FIMD_VIDCON3: + return s->vidcon[(offset - FIMD_VIDCON0) >> 2]; + case FIMD_VIDTCON_START ... FIMD_VIDTCON_END: + return s->vidtcon[(offset - FIMD_VIDTCON_START) >> 2]; + case FIMD_WINCON_START ... FIMD_WINCON_END: + return s->window[(offset - FIMD_WINCON_START) >> 2].wincon; + case FIMD_SHADOWCON: + return s->shadowcon; + case FIMD_WINCHMAP: + return s->winchmap; + case FIMD_VIDOSD_START ... FIMD_VIDOSD_END: + w = (offset - FIMD_VIDOSD_START) >> 4; + i = ((offset - FIMD_VIDOSD_START) & 0xF) >> 2; + switch (i) { + case 0: + ret = ((s->window[w].lefttop_x & FIMD_VIDOSD_COORD_MASK) << + FIMD_VIDOSD_HOR_SHIFT) | + (s->window[w].lefttop_y & FIMD_VIDOSD_COORD_MASK); + break; + case 1: + ret = ((s->window[w].rightbot_x & FIMD_VIDOSD_COORD_MASK) << + FIMD_VIDOSD_HOR_SHIFT) | + (s->window[w].rightbot_y & FIMD_VIDOSD_COORD_MASK); + break; + case 2: + if (w == 0) { + ret = s->window[w].osdsize; + } else { + ret = (pack_upper_4(s->window[w].alpha_val[0]) << + FIMD_VIDOSD_AEN0_SHIFT) | + pack_upper_4(s->window[w].alpha_val[1]); + } + break; + case 3: + if (w != 1 && w != 2) { + DPRINT_ERROR("bad read offset 0x%08x\n", offset); + return 0xBAADBAAD; + } + ret = s->window[w].osdsize; + break; + } + return ret; + case FIMD_VIDWADD0_START ... FIMD_VIDWADD0_END: + w = (offset - FIMD_VIDWADD0_START) >> 3; + i = ((offset - FIMD_VIDWADD0_START) >> 2) & 1; + return s->window[w].buf_start[i]; + case FIMD_VIDWADD1_START ... FIMD_VIDWADD1_END: + w = (offset - FIMD_VIDWADD1_START) >> 3; + i = ((offset - FIMD_VIDWADD1_START) >> 2) & 1; + return s->window[w].buf_end[i]; + case FIMD_VIDWADD2_START ... FIMD_VIDWADD2_END: + w = (offset - FIMD_VIDWADD2_START) >> 2; + return s->window[w].virtpage_width | (s->window[w].virtpage_offsize << + FIMD_VIDWADD2_OFFSIZE_SHIFT); + case FIMD_VIDINTCON0 ... FIMD_VIDINTCON1: + return s->vidintcon[(offset - FIMD_VIDINTCON0) >> 2]; + case FIMD_WKEYCON_START ... FIMD_WKEYCON_END: + w = ((offset - FIMD_WKEYCON_START) >> 3) + 1; + i = ((offset - FIMD_WKEYCON_START) >> 2) & 1; + return s->window[w].keycon[i]; + case FIMD_WKEYALPHA_START ... FIMD_WKEYALPHA_END: + w = ((offset - FIMD_WKEYALPHA_START) >> 2) + 1; + return s->window[w].keyalpha; + case FIMD_DITHMODE: + return s->dithmode; + case FIMD_WINMAP_START ... FIMD_WINMAP_END: + return s->window[(offset - FIMD_WINMAP_START) >> 2].winmap; + case FIMD_WPALCON_HIGH ... FIMD_WPALCON_LOW: + return s->wpalcon[(offset - FIMD_WPALCON_HIGH) >> 2]; + case FIMD_TRIGCON: + return s->trigcon; + case FIMD_I80IFCON_START ... FIMD_I80IFCON_END: + return s->i80ifcon[(offset - FIMD_I80IFCON_START) >> 2]; + case FIMD_COLORGAINCON: + return s->colorgaincon; + case FIMD_LDI_CMDCON0 ... FIMD_LDI_CMDCON1: + return s->ldi_cmdcon[(offset - FIMD_LDI_CMDCON0) >> 2]; + case FIMD_SIFCCON0 ... FIMD_SIFCCON2: + i = (offset - FIMD_SIFCCON0) >> 2; + return s->sifccon[i]; + case FIMD_HUECOEFCR_START ... FIMD_HUECOEFCR_END: + i = (offset - FIMD_HUECOEFCR_START) >> 2; + return s->huecoef_cr[i]; + case FIMD_HUECOEFCB_START ... FIMD_HUECOEFCB_END: + i = (offset - FIMD_HUECOEFCB_START) >> 2; + return s->huecoef_cb[i]; + case FIMD_HUEOFFSET: + return s->hueoffset; + case FIMD_VIDWALPHA_START ... FIMD_VIDWALPHA_END: + w = ((offset - FIMD_VIDWALPHA_START) >> 3); + i = ((offset - FIMD_VIDWALPHA_START) >> 2) & 1; + return s->window[w].alpha_val[i] & + (w == 0 ? 0xFFFFFF : FIMD_VIDALPHA_ALPHA_LOWER); + case FIMD_BLENDEQ_START ... FIMD_BLENDEQ_END: + return s->window[(offset - FIMD_BLENDEQ_START) >> 2].blendeq; + case FIMD_BLENDCON: + return s->blendcon; + case FIMD_WRTQOSCON_START ... FIMD_WRTQOSCON_END: + return s->window[(offset - FIMD_WRTQOSCON_START) >> 2].rtqoscon; + case FIMD_I80IFCMD_START ... FIMD_I80IFCMD_END: + return s->i80ifcmd[(offset - FIMD_I80IFCMD_START) >> 2]; + case FIMD_VIDW0ADD0_B2 ... FIMD_VIDW4ADD0_B2: + if (offset & 0x0004) { + break; + } + return s->window[(offset - FIMD_VIDW0ADD0_B2) >> 3].buf_start[2]; + case FIMD_SHD_ADD0_START ... FIMD_SHD_ADD0_END: + if (offset & 0x0004) { + break; + } + return s->window[(offset - FIMD_SHD_ADD0_START) >> 3].shadow_buf_start; + case FIMD_SHD_ADD1_START ... FIMD_SHD_ADD1_END: + if (offset & 0x0004) { + break; + } + return s->window[(offset - FIMD_SHD_ADD1_START) >> 3].shadow_buf_end; + case FIMD_SHD_ADD2_START ... FIMD_SHD_ADD2_END: + return s->window[(offset - FIMD_SHD_ADD2_START) >> 2].shadow_buf_size; + case FIMD_PAL_MEM_START ... FIMD_PAL_MEM_END: + w = (offset - FIMD_PAL_MEM_START) >> 10; + i = ((offset - FIMD_PAL_MEM_START) >> 2) & 0xFF; + return s->window[w].palette[i]; + case FIMD_PALMEM_AL_START ... FIMD_PALMEM_AL_END: + /* Palette aliases for win 0,1 */ + w = (offset - FIMD_PALMEM_AL_START) >> 10; + i = ((offset - FIMD_PALMEM_AL_START) >> 2) & 0xFF; + return s->window[w].palette[i]; + } + + DPRINT_ERROR("bad read offset 0x%08x\n", offset); + return 0xBAADBAAD; +} + +static const MemoryRegionOps exynos4210_fimd_mmio_ops = { + .read = exynos4210_fimd_read, + .write = exynos4210_fimd_write, + .valid = { + .min_access_size = 4, + .max_access_size = 4, + .unaligned = false + }, + .endianness = DEVICE_NATIVE_ENDIAN, +}; + +static int exynos4210_fimd_load(void *opaque, int version_id) +{ + Exynos4210fimdState *s = (Exynos4210fimdState *)opaque; + int w; + + if (version_id != 1) { + return -EINVAL; + } + + for (w = 0; w < NUM_OF_WINDOWS; w++) { + exynos4210_fimd_update_win_bppmode(s, w); + fimd_update_get_alpha(s, w); + fimd_update_memory_section(s, w); + } + + /* Redraw the whole screen */ + exynos4210_update_resolution(s); + exynos4210_fimd_invalidate(s); + exynos4210_fimd_enable(s, (s->vidcon[0] & FIMD_VIDCON0_ENVID_MASK) == + FIMD_VIDCON0_ENVID_MASK); + return 0; +} + +static const VMStateDescription exynos4210_fimd_window_vmstate = { + .name = "exynos4210.fimd_window", + .version_id = 1, + .minimum_version_id = 1, + .fields = (VMStateField[]) { + VMSTATE_UINT32(wincon, Exynos4210fimdWindow), + VMSTATE_UINT32_ARRAY(buf_start, Exynos4210fimdWindow, 3), + VMSTATE_UINT32_ARRAY(buf_end, Exynos4210fimdWindow, 3), + VMSTATE_UINT32_ARRAY(keycon, Exynos4210fimdWindow, 2), + VMSTATE_UINT32(keyalpha, Exynos4210fimdWindow), + VMSTATE_UINT32(winmap, Exynos4210fimdWindow), + VMSTATE_UINT32(blendeq, Exynos4210fimdWindow), + VMSTATE_UINT32(rtqoscon, Exynos4210fimdWindow), + VMSTATE_UINT32_ARRAY(palette, Exynos4210fimdWindow, 256), + VMSTATE_UINT32(shadow_buf_start, Exynos4210fimdWindow), + VMSTATE_UINT32(shadow_buf_end, Exynos4210fimdWindow), + VMSTATE_UINT32(shadow_buf_size, Exynos4210fimdWindow), + VMSTATE_UINT16(lefttop_x, Exynos4210fimdWindow), + VMSTATE_UINT16(lefttop_y, Exynos4210fimdWindow), + VMSTATE_UINT16(rightbot_x, Exynos4210fimdWindow), + VMSTATE_UINT16(rightbot_y, Exynos4210fimdWindow), + VMSTATE_UINT32(osdsize, Exynos4210fimdWindow), + VMSTATE_UINT32_ARRAY(alpha_val, Exynos4210fimdWindow, 2), + VMSTATE_UINT16(virtpage_width, Exynos4210fimdWindow), + VMSTATE_UINT16(virtpage_offsize, Exynos4210fimdWindow), + VMSTATE_END_OF_LIST() + } +}; + +static const VMStateDescription exynos4210_fimd_vmstate = { + .name = "exynos4210.fimd", + .version_id = 1, + .minimum_version_id = 1, + .post_load = exynos4210_fimd_load, + .fields = (VMStateField[]) { + VMSTATE_UINT32_ARRAY(vidcon, Exynos4210fimdState, 4), + VMSTATE_UINT32_ARRAY(vidtcon, Exynos4210fimdState, 4), + VMSTATE_UINT32(shadowcon, Exynos4210fimdState), + VMSTATE_UINT32(winchmap, Exynos4210fimdState), + VMSTATE_UINT32_ARRAY(vidintcon, Exynos4210fimdState, 2), + VMSTATE_UINT32(dithmode, Exynos4210fimdState), + VMSTATE_UINT32_ARRAY(wpalcon, Exynos4210fimdState, 2), + VMSTATE_UINT32(trigcon, Exynos4210fimdState), + VMSTATE_UINT32_ARRAY(i80ifcon, Exynos4210fimdState, 4), + VMSTATE_UINT32(colorgaincon, Exynos4210fimdState), + VMSTATE_UINT32_ARRAY(ldi_cmdcon, Exynos4210fimdState, 2), + VMSTATE_UINT32_ARRAY(sifccon, Exynos4210fimdState, 3), + VMSTATE_UINT32_ARRAY(huecoef_cr, Exynos4210fimdState, 4), + VMSTATE_UINT32_ARRAY(huecoef_cb, Exynos4210fimdState, 4), + VMSTATE_UINT32(hueoffset, Exynos4210fimdState), + VMSTATE_UINT32_ARRAY(i80ifcmd, Exynos4210fimdState, 12), + VMSTATE_UINT32(blendcon, Exynos4210fimdState), + VMSTATE_STRUCT_ARRAY(window, Exynos4210fimdState, 5, 1, + exynos4210_fimd_window_vmstate, Exynos4210fimdWindow), + VMSTATE_END_OF_LIST() + } +}; + +static int exynos4210_fimd_init(SysBusDevice *dev) +{ + Exynos4210fimdState *s = FROM_SYSBUS(Exynos4210fimdState, dev); + + s->ifb = NULL; + + sysbus_init_irq(dev, &s->irq[0]); + sysbus_init_irq(dev, &s->irq[1]); + sysbus_init_irq(dev, &s->irq[2]); + + memory_region_init_io(&s->iomem, &exynos4210_fimd_mmio_ops, s, + "exynos4210.fimd", FIMD_REGS_SIZE); + sysbus_init_mmio(dev, &s->iomem); + s->console = graphic_console_init(exynos4210_fimd_update, + exynos4210_fimd_invalidate, NULL, NULL, s); + + return 0; +} + +static void exynos4210_fimd_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass); + + dc->vmsd = &exynos4210_fimd_vmstate; + dc->reset = exynos4210_fimd_reset; + k->init = exynos4210_fimd_init; +} + +static const TypeInfo exynos4210_fimd_info = { + .name = "exynos4210.fimd", + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(Exynos4210fimdState), + .class_init = exynos4210_fimd_class_init, +}; + +static void exynos4210_fimd_register_types(void) +{ + type_register_static(&exynos4210_fimd_info); +} + +type_init(exynos4210_fimd_register_types) diff --git a/hw/display/framebuffer.c b/hw/display/framebuffer.c new file mode 100644 index 0000000000..6be31db2b5 --- /dev/null +++ b/hw/display/framebuffer.c @@ -0,0 +1,110 @@ +/* + * Framebuffer device helper routines + * + * Copyright (c) 2009 CodeSourcery + * Written by Paul Brook <paul@codesourcery.com> + * + * This code is licensed under the GNU GPLv2. + * + * Contributions after 2012-01-13 are licensed under the terms of the + * GNU GPL, version 2 or (at your option) any later version. + */ + +/* TODO: + - Do something similar for framebuffers with local ram + - Handle rotation here instead of hacking dest_pitch + - Use common pixel conversion routines instead of per-device drawfn + - Remove all DisplayState knowledge from devices. + */ + +#include "hw/hw.h" +#include "ui/console.h" +#include "framebuffer.h" + +/* Render an image from a shared memory framebuffer. */ + +void framebuffer_update_display( + DisplaySurface *ds, + MemoryRegion *address_space, + hwaddr base, + int cols, /* Width in pixels. */ + int rows, /* Height in pixels. */ + int src_width, /* Length of source line, in bytes. */ + int dest_row_pitch, /* Bytes between adjacent horizontal output pixels. */ + int dest_col_pitch, /* Bytes between adjacent vertical output pixels. */ + int invalidate, /* nonzero to redraw the whole image. */ + drawfn fn, + void *opaque, + int *first_row, /* Input and output. */ + int *last_row /* Output only */) +{ + hwaddr src_len; + uint8_t *dest; + uint8_t *src; + uint8_t *src_base; + int first, last = 0; + int dirty; + int i; + ram_addr_t addr; + MemoryRegionSection mem_section; + MemoryRegion *mem; + + i = *first_row; + *first_row = -1; + src_len = src_width * rows; + + mem_section = memory_region_find(address_space, base, src_len); + if (mem_section.size != src_len || !memory_region_is_ram(mem_section.mr)) { + return; + } + mem = mem_section.mr; + assert(mem); + assert(mem_section.offset_within_address_space == base); + + memory_region_sync_dirty_bitmap(mem); + src_base = cpu_physical_memory_map(base, &src_len, 0); + /* If we can't map the framebuffer then bail. We could try harder, + but it's not really worth it as dirty flag tracking will probably + already have failed above. */ + if (!src_base) + return; + if (src_len != src_width * rows) { + cpu_physical_memory_unmap(src_base, src_len, 0, 0); + return; + } + src = src_base; + dest = surface_data(ds); + if (dest_col_pitch < 0) + dest -= dest_col_pitch * (cols - 1); + if (dest_row_pitch < 0) { + dest -= dest_row_pitch * (rows - 1); + } + first = -1; + addr = mem_section.offset_within_region; + + addr += i * src_width; + src += i * src_width; + dest += i * dest_row_pitch; + + for (; i < rows; i++) { + dirty = memory_region_get_dirty(mem, addr, src_width, + DIRTY_MEMORY_VGA); + if (dirty || invalidate) { + fn(opaque, dest, src, cols, dest_col_pitch); + if (first == -1) + first = i; + last = i; + } + addr += src_width; + src += src_width; + dest += dest_row_pitch; + } + cpu_physical_memory_unmap(src_base, src_len, 0, 0); + if (first < 0) { + return; + } + memory_region_reset_dirty(mem, mem_section.offset_within_region, src_len, + DIRTY_MEMORY_VGA); + *first_row = first; + *last_row = last; +} diff --git a/hw/display/framebuffer.h b/hw/display/framebuffer.h new file mode 100644 index 0000000000..6eae035b7d --- /dev/null +++ b/hw/display/framebuffer.h @@ -0,0 +1,25 @@ +#ifndef QEMU_FRAMEBUFFER_H +#define QEMU_FRAMEBUFFER_H + +#include "exec/memory.h" + +/* Framebuffer device helper routines. */ + +typedef void (*drawfn)(void *, uint8_t *, const uint8_t *, int, int); + +void framebuffer_update_display( + DisplaySurface *ds, + MemoryRegion *address_space, + hwaddr base, + int cols, + int rows, + int src_width, + int dest_row_pitch, + int dest_col_pitch, + int invalidate, + drawfn fn, + void *opaque, + int *first_row, + int *last_row); + +#endif diff --git a/hw/display/g364fb.c b/hw/display/g364fb.c new file mode 100644 index 0000000000..f7014e9dd8 --- /dev/null +++ b/hw/display/g364fb.c @@ -0,0 +1,617 @@ +/* + * QEMU G364 framebuffer Emulator. + * + * Copyright (c) 2007-2011 Herve Poussineau + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, see <http://www.gnu.org/licenses/>. + */ + +#include "hw/hw.h" +#include "ui/console.h" +#include "ui/pixel_ops.h" +#include "trace.h" +#include "hw/sysbus.h" + +typedef struct G364State { + /* hardware */ + uint8_t *vram; + uint32_t vram_size; + qemu_irq irq; + MemoryRegion mem_vram; + MemoryRegion mem_ctrl; + /* registers */ + uint8_t color_palette[256][3]; + uint8_t cursor_palette[3][3]; + uint16_t cursor[512]; + uint32_t cursor_position; + uint32_t ctla; + uint32_t top_of_screen; + uint32_t width, height; /* in pixels */ + /* display refresh support */ + QemuConsole *con; + int depth; + int blanked; +} G364State; + +#define REG_BOOT 0x000000 +#define REG_DISPLAY 0x000118 +#define REG_VDISPLAY 0x000150 +#define REG_CTLA 0x000300 +#define REG_TOP 0x000400 +#define REG_CURS_PAL 0x000508 +#define REG_CURS_POS 0x000638 +#define REG_CLR_PAL 0x000800 +#define REG_CURS_PAT 0x001000 +#define REG_RESET 0x100000 + +#define CTLA_FORCE_BLANK 0x00000400 +#define CTLA_NO_CURSOR 0x00800000 + +#define G364_PAGE_SIZE 4096 + +static inline int check_dirty(G364State *s, ram_addr_t page) +{ + return memory_region_get_dirty(&s->mem_vram, page, G364_PAGE_SIZE, + DIRTY_MEMORY_VGA); +} + +static inline void reset_dirty(G364State *s, + ram_addr_t page_min, ram_addr_t page_max) +{ + memory_region_reset_dirty(&s->mem_vram, + page_min, + page_max + G364_PAGE_SIZE - page_min - 1, + DIRTY_MEMORY_VGA); +} + +static void g364fb_draw_graphic8(G364State *s) +{ + DisplaySurface *surface = qemu_console_surface(s->con); + int i, w; + uint8_t *vram; + uint8_t *data_display, *dd; + ram_addr_t page, page_min, page_max; + int x, y; + int xmin, xmax; + int ymin, ymax; + int xcursor, ycursor; + unsigned int (*rgb_to_pixel)(unsigned int r, unsigned int g, unsigned int b); + + switch (surface_bits_per_pixel(surface)) { + case 8: + rgb_to_pixel = rgb_to_pixel8; + w = 1; + break; + case 15: + rgb_to_pixel = rgb_to_pixel15; + w = 2; + break; + case 16: + rgb_to_pixel = rgb_to_pixel16; + w = 2; + break; + case 32: + rgb_to_pixel = rgb_to_pixel32; + w = 4; + break; + default: + hw_error("g364: unknown host depth %d", + surface_bits_per_pixel(surface)); + return; + } + + page = 0; + page_min = (ram_addr_t)-1; + page_max = 0; + + x = y = 0; + xmin = s->width; + xmax = 0; + ymin = s->height; + ymax = 0; + + if (!(s->ctla & CTLA_NO_CURSOR)) { + xcursor = s->cursor_position >> 12; + ycursor = s->cursor_position & 0xfff; + } else { + xcursor = ycursor = -65; + } + + vram = s->vram + s->top_of_screen; + /* XXX: out of range in vram? */ + data_display = dd = surface_data(surface); + while (y < s->height) { + if (check_dirty(s, page)) { + if (y < ymin) + ymin = ymax = y; + if (page_min == (ram_addr_t)-1) + page_min = page; + page_max = page; + if (x < xmin) + xmin = x; + for (i = 0; i < G364_PAGE_SIZE; i++) { + uint8_t index; + unsigned int color; + if (unlikely((y >= ycursor && y < ycursor + 64) && + (x >= xcursor && x < xcursor + 64))) { + /* pointer area */ + int xdiff = x - xcursor; + uint16_t curs = s->cursor[(y - ycursor) * 8 + xdiff / 8]; + int op = (curs >> ((xdiff & 7) * 2)) & 3; + if (likely(op == 0)) { + /* transparent */ + index = *vram; + color = (*rgb_to_pixel)( + s->color_palette[index][0], + s->color_palette[index][1], + s->color_palette[index][2]); + } else { + /* get cursor color */ + index = op - 1; + color = (*rgb_to_pixel)( + s->cursor_palette[index][0], + s->cursor_palette[index][1], + s->cursor_palette[index][2]); + } + } else { + /* normal area */ + index = *vram; + color = (*rgb_to_pixel)( + s->color_palette[index][0], + s->color_palette[index][1], + s->color_palette[index][2]); + } + memcpy(dd, &color, w); + dd += w; + x++; + vram++; + if (x == s->width) { + xmax = s->width - 1; + y++; + if (y == s->height) { + ymax = s->height - 1; + goto done; + } + data_display = dd = data_display + surface_stride(surface); + xmin = 0; + x = 0; + } + } + if (x > xmax) + xmax = x; + if (y > ymax) + ymax = y; + } else { + int dy; + if (page_min != (ram_addr_t)-1) { + reset_dirty(s, page_min, page_max); + page_min = (ram_addr_t)-1; + page_max = 0; + dpy_gfx_update(s->con, xmin, ymin, + xmax - xmin + 1, ymax - ymin + 1); + xmin = s->width; + xmax = 0; + ymin = s->height; + ymax = 0; + } + x += G364_PAGE_SIZE; + dy = x / s->width; + x = x % s->width; + y += dy; + vram += G364_PAGE_SIZE; + data_display += dy * surface_stride(surface); + dd = data_display + x * w; + } + page += G364_PAGE_SIZE; + } + +done: + if (page_min != (ram_addr_t)-1) { + dpy_gfx_update(s->con, xmin, ymin, xmax - xmin + 1, ymax - ymin + 1); + reset_dirty(s, page_min, page_max); + } +} + +static void g364fb_draw_blank(G364State *s) +{ + DisplaySurface *surface = qemu_console_surface(s->con); + int i, w; + uint8_t *d; + + if (s->blanked) { + /* Screen is already blank. No need to redraw it */ + return; + } + + w = s->width * surface_bytes_per_pixel(surface); + d = surface_data(surface); + for (i = 0; i < s->height; i++) { + memset(d, 0, w); + d += surface_stride(surface); + } + + dpy_gfx_update(s->con, 0, 0, s->width, s->height); + s->blanked = 1; +} + +static void g364fb_update_display(void *opaque) +{ + G364State *s = opaque; + DisplaySurface *surface = qemu_console_surface(s->con); + + qemu_flush_coalesced_mmio_buffer(); + + if (s->width == 0 || s->height == 0) + return; + + if (s->width != surface_width(surface) || + s->height != surface_height(surface)) { + qemu_console_resize(s->con, s->width, s->height); + } + + if (s->ctla & CTLA_FORCE_BLANK) { + g364fb_draw_blank(s); + } else if (s->depth == 8) { + g364fb_draw_graphic8(s); + } else { + error_report("g364: unknown guest depth %d", s->depth); + } + + qemu_irq_raise(s->irq); +} + +static inline void g364fb_invalidate_display(void *opaque) +{ + G364State *s = opaque; + + s->blanked = 0; + memory_region_set_dirty(&s->mem_vram, 0, s->vram_size); +} + +static void g364fb_reset(G364State *s) +{ + qemu_irq_lower(s->irq); + + memset(s->color_palette, 0, sizeof(s->color_palette)); + memset(s->cursor_palette, 0, sizeof(s->cursor_palette)); + memset(s->cursor, 0, sizeof(s->cursor)); + s->cursor_position = 0; + s->ctla = 0; + s->top_of_screen = 0; + s->width = s->height = 0; + memset(s->vram, 0, s->vram_size); + g364fb_invalidate_display(s); +} + +static void g364fb_screen_dump(void *opaque, const char *filename, bool cswitch, + Error **errp) +{ + G364State *s = opaque; + int ret, y, x; + uint8_t index; + uint8_t *data_buffer; + FILE *f; + + qemu_flush_coalesced_mmio_buffer(); + + if (s->depth != 8) { + error_setg(errp, "g364: unknown guest depth %d", s->depth); + return; + } + + f = fopen(filename, "wb"); + if (!f) { + error_setg(errp, "failed to open file '%s': %s", filename, + strerror(errno)); + return; + } + + if (s->ctla & CTLA_FORCE_BLANK) { + /* blank screen */ + ret = fprintf(f, "P4\n%d %d\n", s->width, s->height); + if (ret < 0) { + goto write_err; + } + for (y = 0; y < s->height; y++) + for (x = 0; x < s->width; x++) { + ret = fputc(0, f); + if (ret == EOF) { + goto write_err; + } + } + } else { + data_buffer = s->vram + s->top_of_screen; + ret = fprintf(f, "P6\n%d %d\n%d\n", s->width, s->height, 255); + if (ret < 0) { + goto write_err; + } + for (y = 0; y < s->height; y++) + for (x = 0; x < s->width; x++, data_buffer++) { + index = *data_buffer; + ret = fputc(s->color_palette[index][0], f); + if (ret == EOF) { + goto write_err; + } + ret = fputc(s->color_palette[index][1], f); + if (ret == EOF) { + goto write_err; + } + ret = fputc(s->color_palette[index][2], f); + if (ret == EOF) { + goto write_err; + } + } + } + +out: + fclose(f); + return; + +write_err: + error_setg(errp, "failed to write to file '%s': %s", filename, + strerror(errno)); + unlink(filename); + goto out; +} + +/* called for accesses to io ports */ +static uint64_t g364fb_ctrl_read(void *opaque, + hwaddr addr, + unsigned int size) +{ + G364State *s = opaque; + uint32_t val; + + if (addr >= REG_CURS_PAT && addr < REG_CURS_PAT + 0x1000) { + /* cursor pattern */ + int idx = (addr - REG_CURS_PAT) >> 3; + val = s->cursor[idx]; + } else if (addr >= REG_CURS_PAL && addr < REG_CURS_PAL + 0x18) { + /* cursor palette */ + int idx = (addr - REG_CURS_PAL) >> 3; + val = ((uint32_t)s->cursor_palette[idx][0] << 16); + val |= ((uint32_t)s->cursor_palette[idx][1] << 8); + val |= ((uint32_t)s->cursor_palette[idx][2] << 0); + } else { + switch (addr) { + case REG_DISPLAY: + val = s->width / 4; + break; + case REG_VDISPLAY: + val = s->height * 2; + break; + case REG_CTLA: + val = s->ctla; + break; + default: + { + error_report("g364: invalid read at [" TARGET_FMT_plx "]", + addr); + val = 0; + break; + } + } + } + + trace_g364fb_read(addr, val); + + return val; +} + +static void g364fb_update_depth(G364State *s) +{ + static const int depths[8] = { 1, 2, 4, 8, 15, 16, 0 }; + s->depth = depths[(s->ctla & 0x00700000) >> 20]; +} + +static void g364_invalidate_cursor_position(G364State *s) +{ + DisplaySurface *surface = qemu_console_surface(s->con); + int ymin, ymax, start, end; + + /* invalidate only near the cursor */ + ymin = s->cursor_position & 0xfff; + ymax = MIN(s->height, ymin + 64); + start = ymin * surface_stride(surface); + end = (ymax + 1) * surface_stride(surface); + + memory_region_set_dirty(&s->mem_vram, start, end - start); +} + +static void g364fb_ctrl_write(void *opaque, + hwaddr addr, + uint64_t val, + unsigned int size) +{ + G364State *s = opaque; + + trace_g364fb_write(addr, val); + + if (addr >= REG_CLR_PAL && addr < REG_CLR_PAL + 0x800) { + /* color palette */ + int idx = (addr - REG_CLR_PAL) >> 3; + s->color_palette[idx][0] = (val >> 16) & 0xff; + s->color_palette[idx][1] = (val >> 8) & 0xff; + s->color_palette[idx][2] = val & 0xff; + g364fb_invalidate_display(s); + } else if (addr >= REG_CURS_PAT && addr < REG_CURS_PAT + 0x1000) { + /* cursor pattern */ + int idx = (addr - REG_CURS_PAT) >> 3; + s->cursor[idx] = val; + g364fb_invalidate_display(s); + } else if (addr >= REG_CURS_PAL && addr < REG_CURS_PAL + 0x18) { + /* cursor palette */ + int idx = (addr - REG_CURS_PAL) >> 3; + s->cursor_palette[idx][0] = (val >> 16) & 0xff; + s->cursor_palette[idx][1] = (val >> 8) & 0xff; + s->cursor_palette[idx][2] = val & 0xff; + g364fb_invalidate_display(s); + } else { + switch (addr) { + case REG_BOOT: /* Boot timing */ + case 0x00108: /* Line timing: half sync */ + case 0x00110: /* Line timing: back porch */ + case 0x00120: /* Line timing: short display */ + case 0x00128: /* Frame timing: broad pulse */ + case 0x00130: /* Frame timing: v sync */ + case 0x00138: /* Frame timing: v preequalise */ + case 0x00140: /* Frame timing: v postequalise */ + case 0x00148: /* Frame timing: v blank */ + case 0x00158: /* Line timing: line time */ + case 0x00160: /* Frame store: line start */ + case 0x00168: /* vram cycle: mem init */ + case 0x00170: /* vram cycle: transfer delay */ + case 0x00200: /* vram cycle: mask register */ + /* ignore */ + break; + case REG_TOP: + s->top_of_screen = val; + g364fb_invalidate_display(s); + break; + case REG_DISPLAY: + s->width = val * 4; + break; + case REG_VDISPLAY: + s->height = val / 2; + break; + case REG_CTLA: + s->ctla = val; + g364fb_update_depth(s); + g364fb_invalidate_display(s); + break; + case REG_CURS_POS: + g364_invalidate_cursor_position(s); + s->cursor_position = val; + g364_invalidate_cursor_position(s); + break; + case REG_RESET: + g364fb_reset(s); + break; + default: + error_report("g364: invalid write of 0x%" PRIx64 + " at [" TARGET_FMT_plx "]", val, addr); + break; + } + } + qemu_irq_lower(s->irq); +} + +static const MemoryRegionOps g364fb_ctrl_ops = { + .read = g364fb_ctrl_read, + .write = g364fb_ctrl_write, + .endianness = DEVICE_LITTLE_ENDIAN, + .impl.min_access_size = 4, + .impl.max_access_size = 4, +}; + +static int g364fb_post_load(void *opaque, int version_id) +{ + G364State *s = opaque; + + /* force refresh */ + g364fb_update_depth(s); + g364fb_invalidate_display(s); + + return 0; +} + +static const VMStateDescription vmstate_g364fb = { + .name = "g364fb", + .version_id = 1, + .minimum_version_id = 1, + .minimum_version_id_old = 1, + .post_load = g364fb_post_load, + .fields = (VMStateField[]) { + VMSTATE_VBUFFER_UINT32(vram, G364State, 1, NULL, 0, vram_size), + VMSTATE_BUFFER_UNSAFE(color_palette, G364State, 0, 256 * 3), + VMSTATE_BUFFER_UNSAFE(cursor_palette, G364State, 0, 9), + VMSTATE_UINT16_ARRAY(cursor, G364State, 512), + VMSTATE_UINT32(cursor_position, G364State), + VMSTATE_UINT32(ctla, G364State), + VMSTATE_UINT32(top_of_screen, G364State), + VMSTATE_UINT32(width, G364State), + VMSTATE_UINT32(height, G364State), + VMSTATE_END_OF_LIST() + } +}; + +static void g364fb_init(DeviceState *dev, G364State *s) +{ + s->vram = g_malloc0(s->vram_size); + + s->con = graphic_console_init(g364fb_update_display, + g364fb_invalidate_display, + g364fb_screen_dump, NULL, s); + + memory_region_init_io(&s->mem_ctrl, &g364fb_ctrl_ops, s, "ctrl", 0x180000); + memory_region_init_ram_ptr(&s->mem_vram, "vram", + s->vram_size, s->vram); + vmstate_register_ram(&s->mem_vram, dev); + memory_region_set_coalescing(&s->mem_vram); +} + +typedef struct { + SysBusDevice busdev; + G364State g364; +} G364SysBusState; + +static int g364fb_sysbus_init(SysBusDevice *dev) +{ + G364State *s = &FROM_SYSBUS(G364SysBusState, dev)->g364; + + g364fb_init(&dev->qdev, s); + sysbus_init_irq(dev, &s->irq); + sysbus_init_mmio(dev, &s->mem_ctrl); + sysbus_init_mmio(dev, &s->mem_vram); + + return 0; +} + +static void g364fb_sysbus_reset(DeviceState *d) +{ + G364SysBusState *s = DO_UPCAST(G364SysBusState, busdev.qdev, d); + g364fb_reset(&s->g364); +} + +static Property g364fb_sysbus_properties[] = { + DEFINE_PROP_HEX32("vram_size", G364SysBusState, g364.vram_size, + 8 * 1024 * 1024), + DEFINE_PROP_END_OF_LIST(), +}; + +static void g364fb_sysbus_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass); + + k->init = g364fb_sysbus_init; + dc->desc = "G364 framebuffer"; + dc->reset = g364fb_sysbus_reset; + dc->vmsd = &vmstate_g364fb; + dc->props = g364fb_sysbus_properties; +} + +static const TypeInfo g364fb_sysbus_info = { + .name = "sysbus-g364", + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(G364SysBusState), + .class_init = g364fb_sysbus_class_init, +}; + +static void g364fb_register_types(void) +{ + type_register_static(&g364fb_sysbus_info); +} + +type_init(g364fb_register_types) diff --git a/hw/display/jazz_led.c b/hw/display/jazz_led.c new file mode 100644 index 0000000000..05528c7c81 --- /dev/null +++ b/hw/display/jazz_led.c @@ -0,0 +1,304 @@ +/* + * QEMU JAZZ LED emulator. + * + * Copyright (c) 2007-2012 Herve Poussineau + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "qemu-common.h" +#include "ui/console.h" +#include "ui/pixel_ops.h" +#include "trace.h" +#include "hw/sysbus.h" + +typedef enum { + REDRAW_NONE = 0, REDRAW_SEGMENTS = 1, REDRAW_BACKGROUND = 2, +} screen_state_t; + +typedef struct LedState { + SysBusDevice busdev; + MemoryRegion iomem; + uint8_t segments; + QemuConsole *con; + screen_state_t state; +} LedState; + +static uint64_t jazz_led_read(void *opaque, hwaddr addr, + unsigned int size) +{ + LedState *s = opaque; + uint8_t val; + + val = s->segments; + trace_jazz_led_read(addr, val); + + return val; +} + +static void jazz_led_write(void *opaque, hwaddr addr, + uint64_t val, unsigned int size) +{ + LedState *s = opaque; + uint8_t new_val = val & 0xff; + + trace_jazz_led_write(addr, new_val); + + s->segments = new_val; + s->state |= REDRAW_SEGMENTS; +} + +static const MemoryRegionOps led_ops = { + .read = jazz_led_read, + .write = jazz_led_write, + .endianness = DEVICE_NATIVE_ENDIAN, + .impl.min_access_size = 1, + .impl.max_access_size = 1, +}; + +/***********************************************************/ +/* jazz_led display */ + +static void draw_horizontal_line(DisplaySurface *ds, + int posy, int posx1, int posx2, + uint32_t color) +{ + uint8_t *d; + int x, bpp; + + bpp = (surface_bits_per_pixel(ds) + 7) >> 3; + d = surface_data(ds) + surface_stride(ds) * posy + bpp * posx1; + switch(bpp) { + case 1: + for (x = posx1; x <= posx2; x++) { + *((uint8_t *)d) = color; + d++; + } + break; + case 2: + for (x = posx1; x <= posx2; x++) { + *((uint16_t *)d) = color; + d += 2; + } + break; + case 4: + for (x = posx1; x <= posx2; x++) { + *((uint32_t *)d) = color; + d += 4; + } + break; + } +} + +static void draw_vertical_line(DisplaySurface *ds, + int posx, int posy1, int posy2, + uint32_t color) +{ + uint8_t *d; + int y, bpp; + + bpp = (surface_bits_per_pixel(ds) + 7) >> 3; + d = surface_data(ds) + surface_stride(ds) * posy1 + bpp * posx; + switch(bpp) { + case 1: + for (y = posy1; y <= posy2; y++) { + *((uint8_t *)d) = color; + d += surface_stride(ds); + } + break; + case 2: + for (y = posy1; y <= posy2; y++) { + *((uint16_t *)d) = color; + d += surface_stride(ds); + } + break; + case 4: + for (y = posy1; y <= posy2; y++) { + *((uint32_t *)d) = color; + d += surface_stride(ds); + } + break; + } +} + +static void jazz_led_update_display(void *opaque) +{ + LedState *s = opaque; + DisplaySurface *surface = qemu_console_surface(s->con); + uint8_t *d1; + uint32_t color_segment, color_led; + int y, bpp; + + if (s->state & REDRAW_BACKGROUND) { + /* clear screen */ + bpp = (surface_bits_per_pixel(surface) + 7) >> 3; + d1 = surface_data(surface); + for (y = 0; y < surface_height(surface); y++) { + memset(d1, 0x00, surface_width(surface) * bpp); + d1 += surface_stride(surface); + } + } + + if (s->state & REDRAW_SEGMENTS) { + /* set colors according to bpp */ + switch (surface_bits_per_pixel(surface)) { + case 8: + color_segment = rgb_to_pixel8(0xaa, 0xaa, 0xaa); + color_led = rgb_to_pixel8(0x00, 0xff, 0x00); + break; + case 15: + color_segment = rgb_to_pixel15(0xaa, 0xaa, 0xaa); + color_led = rgb_to_pixel15(0x00, 0xff, 0x00); + break; + case 16: + color_segment = rgb_to_pixel16(0xaa, 0xaa, 0xaa); + color_led = rgb_to_pixel16(0x00, 0xff, 0x00); + case 24: + color_segment = rgb_to_pixel24(0xaa, 0xaa, 0xaa); + color_led = rgb_to_pixel24(0x00, 0xff, 0x00); + break; + case 32: + color_segment = rgb_to_pixel32(0xaa, 0xaa, 0xaa); + color_led = rgb_to_pixel32(0x00, 0xff, 0x00); + break; + default: + return; + } + + /* display segments */ + draw_horizontal_line(surface, 40, 10, 40, + (s->segments & 0x02) ? color_segment : 0); + draw_vertical_line(surface, 10, 10, 40, + (s->segments & 0x04) ? color_segment : 0); + draw_vertical_line(surface, 10, 40, 70, + (s->segments & 0x08) ? color_segment : 0); + draw_horizontal_line(surface, 70, 10, 40, + (s->segments & 0x10) ? color_segment : 0); + draw_vertical_line(surface, 40, 40, 70, + (s->segments & 0x20) ? color_segment : 0); + draw_vertical_line(surface, 40, 10, 40, + (s->segments & 0x40) ? color_segment : 0); + draw_horizontal_line(surface, 10, 10, 40, + (s->segments & 0x80) ? color_segment : 0); + + /* display led */ + if (!(s->segments & 0x01)) + color_led = 0; /* black */ + draw_horizontal_line(surface, 68, 50, 50, color_led); + draw_horizontal_line(surface, 69, 49, 51, color_led); + draw_horizontal_line(surface, 70, 48, 52, color_led); + draw_horizontal_line(surface, 71, 49, 51, color_led); + draw_horizontal_line(surface, 72, 50, 50, color_led); + } + + s->state = REDRAW_NONE; + dpy_gfx_update(s->con, 0, 0, + surface_width(surface), surface_height(surface)); +} + +static void jazz_led_invalidate_display(void *opaque) +{ + LedState *s = opaque; + s->state |= REDRAW_SEGMENTS | REDRAW_BACKGROUND; +} + +static void jazz_led_text_update(void *opaque, console_ch_t *chardata) +{ + LedState *s = opaque; + char buf[2]; + + dpy_text_cursor(s->con, -1, -1); + qemu_console_resize(s->con, 2, 1); + + /* TODO: draw the segments */ + snprintf(buf, 2, "%02hhx\n", s->segments); + console_write_ch(chardata++, 0x00200100 | buf[0]); + console_write_ch(chardata++, 0x00200100 | buf[1]); + + dpy_text_update(s->con, 0, 0, 2, 1); +} + +static int jazz_led_post_load(void *opaque, int version_id) +{ + /* force refresh */ + jazz_led_invalidate_display(opaque); + + return 0; +} + +static const VMStateDescription vmstate_jazz_led = { + .name = "jazz-led", + .version_id = 0, + .minimum_version_id = 0, + .minimum_version_id_old = 0, + .post_load = jazz_led_post_load, + .fields = (VMStateField[]) { + VMSTATE_UINT8(segments, LedState), + VMSTATE_END_OF_LIST() + } +}; + +static int jazz_led_init(SysBusDevice *dev) +{ + LedState *s = FROM_SYSBUS(LedState, dev); + + memory_region_init_io(&s->iomem, &led_ops, s, "led", 1); + sysbus_init_mmio(dev, &s->iomem); + + s->con = graphic_console_init(jazz_led_update_display, + jazz_led_invalidate_display, + NULL, + jazz_led_text_update, s); + + return 0; +} + +static void jazz_led_reset(DeviceState *d) +{ + LedState *s = DO_UPCAST(LedState, busdev.qdev, d); + + s->segments = 0; + s->state = REDRAW_SEGMENTS | REDRAW_BACKGROUND; + qemu_console_resize(s->con, 60, 80); +} + +static void jazz_led_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass); + + k->init = jazz_led_init; + dc->desc = "Jazz LED display", + dc->vmsd = &vmstate_jazz_led; + dc->reset = jazz_led_reset; +} + +static const TypeInfo jazz_led_info = { + .name = "jazz-led", + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(LedState), + .class_init = jazz_led_class_init, +}; + +static void jazz_led_register(void) +{ + type_register_static(&jazz_led_info); +} + +type_init(jazz_led_register); diff --git a/hw/display/milkymist-tmu2.c b/hw/display/milkymist-tmu2.c new file mode 100644 index 0000000000..b723a04cc9 --- /dev/null +++ b/hw/display/milkymist-tmu2.c @@ -0,0 +1,490 @@ +/* + * QEMU model of the Milkymist texture mapping unit. + * + * Copyright (c) 2010 Michael Walle <michael@walle.cc> + * Copyright (c) 2010 Sebastien Bourdeauducq + * <sebastien.bourdeauducq@lekernel.net> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see <http://www.gnu.org/licenses/>. + * + * + * Specification available at: + * http://www.milkymist.org/socdoc/tmu2.pdf + * + */ + +#include "hw/hw.h" +#include "hw/sysbus.h" +#include "trace.h" +#include "qemu/error-report.h" + +#include <X11/Xlib.h> +#include <GL/gl.h> +#include <GL/glx.h> + +enum { + R_CTL = 0, + R_HMESHLAST, + R_VMESHLAST, + R_BRIGHTNESS, + R_CHROMAKEY, + R_VERTICESADDR, + R_TEXFBUF, + R_TEXHRES, + R_TEXVRES, + R_TEXHMASK, + R_TEXVMASK, + R_DSTFBUF, + R_DSTHRES, + R_DSTVRES, + R_DSTHOFFSET, + R_DSTVOFFSET, + R_DSTSQUAREW, + R_DSTSQUAREH, + R_ALPHA, + R_MAX +}; + +enum { + CTL_START_BUSY = (1<<0), + CTL_CHROMAKEY = (1<<1), +}; + +enum { + MAX_BRIGHTNESS = 63, + MAX_ALPHA = 63, +}; + +enum { + MESH_MAXSIZE = 128, +}; + +struct vertex { + int x; + int y; +} QEMU_PACKED; + +struct MilkymistTMU2State { + SysBusDevice busdev; + MemoryRegion regs_region; + CharDriverState *chr; + qemu_irq irq; + + uint32_t regs[R_MAX]; + + Display *dpy; + GLXFBConfig glx_fb_config; + GLXContext glx_context; +}; +typedef struct MilkymistTMU2State MilkymistTMU2State; + +static const int glx_fbconfig_attr[] = { + GLX_GREEN_SIZE, 5, + GLX_GREEN_SIZE, 6, + GLX_BLUE_SIZE, 5, + None +}; + +static int tmu2_glx_init(MilkymistTMU2State *s) +{ + GLXFBConfig *configs; + int nelements; + + s->dpy = XOpenDisplay(NULL); /* FIXME: call XCloseDisplay() */ + if (s->dpy == NULL) { + return 1; + } + + configs = glXChooseFBConfig(s->dpy, 0, glx_fbconfig_attr, &nelements); + if (configs == NULL) { + return 1; + } + + s->glx_fb_config = *configs; + XFree(configs); + + /* FIXME: call glXDestroyContext() */ + s->glx_context = glXCreateNewContext(s->dpy, s->glx_fb_config, + GLX_RGBA_TYPE, NULL, 1); + if (s->glx_context == NULL) { + return 1; + } + + return 0; +} + +static void tmu2_gl_map(struct vertex *mesh, int texhres, int texvres, + int hmeshlast, int vmeshlast, int ho, int vo, int sw, int sh) +{ + int x, y; + int x0, y0, x1, y1; + int u0, v0, u1, v1, u2, v2, u3, v3; + double xscale = 1.0 / ((double)(64 * texhres)); + double yscale = 1.0 / ((double)(64 * texvres)); + + glLoadIdentity(); + glTranslatef(ho, vo, 0); + glEnable(GL_TEXTURE_2D); + glBegin(GL_QUADS); + + for (y = 0; y < vmeshlast; y++) { + y0 = y * sh; + y1 = y0 + sh; + for (x = 0; x < hmeshlast; x++) { + x0 = x * sw; + x1 = x0 + sw; + + u0 = be32_to_cpu(mesh[MESH_MAXSIZE * y + x].x); + v0 = be32_to_cpu(mesh[MESH_MAXSIZE * y + x].y); + u1 = be32_to_cpu(mesh[MESH_MAXSIZE * y + x + 1].x); + v1 = be32_to_cpu(mesh[MESH_MAXSIZE * y + x + 1].y); + u2 = be32_to_cpu(mesh[MESH_MAXSIZE * (y + 1) + x + 1].x); + v2 = be32_to_cpu(mesh[MESH_MAXSIZE * (y + 1) + x + 1].y); + u3 = be32_to_cpu(mesh[MESH_MAXSIZE * (y + 1) + x].x); + v3 = be32_to_cpu(mesh[MESH_MAXSIZE * (y + 1) + x].y); + + glTexCoord2d(((double)u0) * xscale, ((double)v0) * yscale); + glVertex3i(x0, y0, 0); + glTexCoord2d(((double)u1) * xscale, ((double)v1) * yscale); + glVertex3i(x1, y0, 0); + glTexCoord2d(((double)u2) * xscale, ((double)v2) * yscale); + glVertex3i(x1, y1, 0); + glTexCoord2d(((double)u3) * xscale, ((double)v3) * yscale); + glVertex3i(x0, y1, 0); + } + } + + glEnd(); +} + +static void tmu2_start(MilkymistTMU2State *s) +{ + int pbuffer_attrib[6] = { + GLX_PBUFFER_WIDTH, + 0, + GLX_PBUFFER_HEIGHT, + 0, + GLX_PRESERVED_CONTENTS, + True + }; + + GLXPbuffer pbuffer; + GLuint texture; + void *fb; + hwaddr fb_len; + void *mesh; + hwaddr mesh_len; + float m; + + trace_milkymist_tmu2_start(); + + /* Create and set up a suitable OpenGL context */ + pbuffer_attrib[1] = s->regs[R_DSTHRES]; + pbuffer_attrib[3] = s->regs[R_DSTVRES]; + pbuffer = glXCreatePbuffer(s->dpy, s->glx_fb_config, pbuffer_attrib); + glXMakeContextCurrent(s->dpy, pbuffer, pbuffer, s->glx_context); + + /* Fixup endianness. TODO: would it work on BE hosts? */ + glPixelStorei(GL_UNPACK_SWAP_BYTES, 1); + glPixelStorei(GL_PACK_SWAP_BYTES, 1); + + /* Row alignment */ + glPixelStorei(GL_UNPACK_ALIGNMENT, 2); + glPixelStorei(GL_PACK_ALIGNMENT, 2); + + /* Read the QEMU source framebuffer into an OpenGL texture */ + glGenTextures(1, &texture); + glBindTexture(GL_TEXTURE_2D, texture); + fb_len = 2*s->regs[R_TEXHRES]*s->regs[R_TEXVRES]; + fb = cpu_physical_memory_map(s->regs[R_TEXFBUF], &fb_len, 0); + if (fb == NULL) { + glDeleteTextures(1, &texture); + glXMakeContextCurrent(s->dpy, None, None, NULL); + glXDestroyPbuffer(s->dpy, pbuffer); + return; + } + glTexImage2D(GL_TEXTURE_2D, 0, 3, s->regs[R_TEXHRES], s->regs[R_TEXVRES], + 0, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, fb); + cpu_physical_memory_unmap(fb, fb_len, 0, fb_len); + + /* Set up texturing options */ + /* WARNING: + * Many cases of TMU2 masking are not supported by OpenGL. + * We only implement the most common ones: + * - full bilinear filtering vs. nearest texel + * - texture clamping vs. texture wrapping + */ + if ((s->regs[R_TEXHMASK] & 0x3f) > 0x20) { + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + } else { + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + } + if ((s->regs[R_TEXHMASK] >> 6) & s->regs[R_TEXHRES]) { + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP); + } else { + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); + } + if ((s->regs[R_TEXVMASK] >> 6) & s->regs[R_TEXVRES]) { + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP); + } else { + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); + } + + /* Translucency and decay */ + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + m = (float)(s->regs[R_BRIGHTNESS] + 1) / 64.0f; + glColor4f(m, m, m, (float)(s->regs[R_ALPHA] + 1) / 64.0f); + + /* Read the QEMU dest. framebuffer into the OpenGL framebuffer */ + fb_len = 2 * s->regs[R_DSTHRES] * s->regs[R_DSTVRES]; + fb = cpu_physical_memory_map(s->regs[R_DSTFBUF], &fb_len, 0); + if (fb == NULL) { + glDeleteTextures(1, &texture); + glXMakeContextCurrent(s->dpy, None, None, NULL); + glXDestroyPbuffer(s->dpy, pbuffer); + return; + } + + glDrawPixels(s->regs[R_DSTHRES], s->regs[R_DSTVRES], GL_RGB, + GL_UNSIGNED_SHORT_5_6_5, fb); + cpu_physical_memory_unmap(fb, fb_len, 0, fb_len); + glViewport(0, 0, s->regs[R_DSTHRES], s->regs[R_DSTVRES]); + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); + glOrtho(0.0, s->regs[R_DSTHRES], 0.0, s->regs[R_DSTVRES], -1.0, 1.0); + glMatrixMode(GL_MODELVIEW); + + /* Map the texture */ + mesh_len = MESH_MAXSIZE*MESH_MAXSIZE*sizeof(struct vertex); + mesh = cpu_physical_memory_map(s->regs[R_VERTICESADDR], &mesh_len, 0); + if (mesh == NULL) { + glDeleteTextures(1, &texture); + glXMakeContextCurrent(s->dpy, None, None, NULL); + glXDestroyPbuffer(s->dpy, pbuffer); + return; + } + + tmu2_gl_map((struct vertex *)mesh, + s->regs[R_TEXHRES], s->regs[R_TEXVRES], + s->regs[R_HMESHLAST], s->regs[R_VMESHLAST], + s->regs[R_DSTHOFFSET], s->regs[R_DSTVOFFSET], + s->regs[R_DSTSQUAREW], s->regs[R_DSTSQUAREH]); + cpu_physical_memory_unmap(mesh, mesh_len, 0, mesh_len); + + /* Write back the OpenGL framebuffer to the QEMU framebuffer */ + fb_len = 2 * s->regs[R_DSTHRES] * s->regs[R_DSTVRES]; + fb = cpu_physical_memory_map(s->regs[R_DSTFBUF], &fb_len, 1); + if (fb == NULL) { + glDeleteTextures(1, &texture); + glXMakeContextCurrent(s->dpy, None, None, NULL); + glXDestroyPbuffer(s->dpy, pbuffer); + return; + } + + glReadPixels(0, 0, s->regs[R_DSTHRES], s->regs[R_DSTVRES], GL_RGB, + GL_UNSIGNED_SHORT_5_6_5, fb); + cpu_physical_memory_unmap(fb, fb_len, 1, fb_len); + + /* Free OpenGL allocs */ + glDeleteTextures(1, &texture); + glXMakeContextCurrent(s->dpy, None, None, NULL); + glXDestroyPbuffer(s->dpy, pbuffer); + + s->regs[R_CTL] &= ~CTL_START_BUSY; + + trace_milkymist_tmu2_pulse_irq(); + qemu_irq_pulse(s->irq); +} + +static uint64_t tmu2_read(void *opaque, hwaddr addr, + unsigned size) +{ + MilkymistTMU2State *s = opaque; + uint32_t r = 0; + + addr >>= 2; + switch (addr) { + case R_CTL: + case R_HMESHLAST: + case R_VMESHLAST: + case R_BRIGHTNESS: + case R_CHROMAKEY: + case R_VERTICESADDR: + case R_TEXFBUF: + case R_TEXHRES: + case R_TEXVRES: + case R_TEXHMASK: + case R_TEXVMASK: + case R_DSTFBUF: + case R_DSTHRES: + case R_DSTVRES: + case R_DSTHOFFSET: + case R_DSTVOFFSET: + case R_DSTSQUAREW: + case R_DSTSQUAREH: + case R_ALPHA: + r = s->regs[addr]; + break; + + default: + error_report("milkymist_tmu2: read access to unknown register 0x" + TARGET_FMT_plx, addr << 2); + break; + } + + trace_milkymist_tmu2_memory_read(addr << 2, r); + + return r; +} + +static void tmu2_check_registers(MilkymistTMU2State *s) +{ + if (s->regs[R_BRIGHTNESS] > MAX_BRIGHTNESS) { + error_report("milkymist_tmu2: max brightness is %d", MAX_BRIGHTNESS); + } + + if (s->regs[R_ALPHA] > MAX_ALPHA) { + error_report("milkymist_tmu2: max alpha is %d", MAX_ALPHA); + } + + if (s->regs[R_VERTICESADDR] & 0x07) { + error_report("milkymist_tmu2: vertex mesh address has to be 64-bit " + "aligned"); + } + + if (s->regs[R_TEXFBUF] & 0x01) { + error_report("milkymist_tmu2: texture buffer address has to be " + "16-bit aligned"); + } +} + +static void tmu2_write(void *opaque, hwaddr addr, uint64_t value, + unsigned size) +{ + MilkymistTMU2State *s = opaque; + + trace_milkymist_tmu2_memory_write(addr, value); + + addr >>= 2; + switch (addr) { + case R_CTL: + s->regs[addr] = value; + if (value & CTL_START_BUSY) { + tmu2_start(s); + } + break; + case R_BRIGHTNESS: + case R_HMESHLAST: + case R_VMESHLAST: + case R_CHROMAKEY: + case R_VERTICESADDR: + case R_TEXFBUF: + case R_TEXHRES: + case R_TEXVRES: + case R_TEXHMASK: + case R_TEXVMASK: + case R_DSTFBUF: + case R_DSTHRES: + case R_DSTVRES: + case R_DSTHOFFSET: + case R_DSTVOFFSET: + case R_DSTSQUAREW: + case R_DSTSQUAREH: + case R_ALPHA: + s->regs[addr] = value; + break; + + default: + error_report("milkymist_tmu2: write access to unknown register 0x" + TARGET_FMT_plx, addr << 2); + break; + } + + tmu2_check_registers(s); +} + +static const MemoryRegionOps tmu2_mmio_ops = { + .read = tmu2_read, + .write = tmu2_write, + .valid = { + .min_access_size = 4, + .max_access_size = 4, + }, + .endianness = DEVICE_NATIVE_ENDIAN, +}; + +static void milkymist_tmu2_reset(DeviceState *d) +{ + MilkymistTMU2State *s = container_of(d, MilkymistTMU2State, busdev.qdev); + int i; + + for (i = 0; i < R_MAX; i++) { + s->regs[i] = 0; + } +} + +static int milkymist_tmu2_init(SysBusDevice *dev) +{ + MilkymistTMU2State *s = FROM_SYSBUS(typeof(*s), dev); + + if (tmu2_glx_init(s)) { + return 1; + } + + sysbus_init_irq(dev, &s->irq); + + memory_region_init_io(&s->regs_region, &tmu2_mmio_ops, s, + "milkymist-tmu2", R_MAX * 4); + sysbus_init_mmio(dev, &s->regs_region); + + return 0; +} + +static const VMStateDescription vmstate_milkymist_tmu2 = { + .name = "milkymist-tmu2", + .version_id = 1, + .minimum_version_id = 1, + .minimum_version_id_old = 1, + .fields = (VMStateField[]) { + VMSTATE_UINT32_ARRAY(regs, MilkymistTMU2State, R_MAX), + VMSTATE_END_OF_LIST() + } +}; + +static void milkymist_tmu2_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass); + + k->init = milkymist_tmu2_init; + dc->reset = milkymist_tmu2_reset; + dc->vmsd = &vmstate_milkymist_tmu2; +} + +static const TypeInfo milkymist_tmu2_info = { + .name = "milkymist-tmu2", + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(MilkymistTMU2State), + .class_init = milkymist_tmu2_class_init, +}; + +static void milkymist_tmu2_register_types(void) +{ + type_register_static(&milkymist_tmu2_info); +} + +type_init(milkymist_tmu2_register_types) diff --git a/hw/display/milkymist-vgafb.c b/hw/display/milkymist-vgafb.c new file mode 100644 index 0000000000..3219041c81 --- /dev/null +++ b/hw/display/milkymist-vgafb.c @@ -0,0 +1,335 @@ + +/* + * QEMU model of the Milkymist VGA framebuffer. + * + * Copyright (c) 2010-2012 Michael Walle <michael@walle.cc> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see <http://www.gnu.org/licenses/>. + * + * + * Specification available at: + * http://www.milkymist.org/socdoc/vgafb.pdf + */ + +#include "hw/hw.h" +#include "hw/sysbus.h" +#include "trace.h" +#include "ui/console.h" +#include "framebuffer.h" +#include "ui/pixel_ops.h" +#include "qemu/error-report.h" + +#define BITS 8 +#include "milkymist-vgafb_template.h" +#define BITS 15 +#include "milkymist-vgafb_template.h" +#define BITS 16 +#include "milkymist-vgafb_template.h" +#define BITS 24 +#include "milkymist-vgafb_template.h" +#define BITS 32 +#include "milkymist-vgafb_template.h" + +enum { + R_CTRL = 0, + R_HRES, + R_HSYNC_START, + R_HSYNC_END, + R_HSCAN, + R_VRES, + R_VSYNC_START, + R_VSYNC_END, + R_VSCAN, + R_BASEADDRESS, + R_BASEADDRESS_ACT, + R_BURST_COUNT, + R_DDC, + R_SOURCE_CLOCK, + R_MAX +}; + +enum { + CTRL_RESET = (1<<0), +}; + +struct MilkymistVgafbState { + SysBusDevice busdev; + MemoryRegion regs_region; + QemuConsole *con; + + int invalidate; + uint32_t fb_offset; + uint32_t fb_mask; + + uint32_t regs[R_MAX]; +}; +typedef struct MilkymistVgafbState MilkymistVgafbState; + +static int vgafb_enabled(MilkymistVgafbState *s) +{ + return !(s->regs[R_CTRL] & CTRL_RESET); +} + +static void vgafb_update_display(void *opaque) +{ + MilkymistVgafbState *s = opaque; + DisplaySurface *surface = qemu_console_surface(s->con); + int first = 0; + int last = 0; + drawfn fn; + + if (!vgafb_enabled(s)) { + return; + } + + int dest_width = s->regs[R_HRES]; + + switch (surface_bits_per_pixel(surface)) { + case 0: + return; + case 8: + fn = draw_line_8; + break; + case 15: + fn = draw_line_15; + dest_width *= 2; + break; + case 16: + fn = draw_line_16; + dest_width *= 2; + break; + case 24: + fn = draw_line_24; + dest_width *= 3; + break; + case 32: + fn = draw_line_32; + dest_width *= 4; + break; + default: + hw_error("milkymist_vgafb: bad color depth\n"); + break; + } + + framebuffer_update_display(surface, sysbus_address_space(&s->busdev), + s->regs[R_BASEADDRESS] + s->fb_offset, + s->regs[R_HRES], + s->regs[R_VRES], + s->regs[R_HRES] * 2, + dest_width, + 0, + s->invalidate, + fn, + NULL, + &first, &last); + + if (first >= 0) { + dpy_gfx_update(s->con, 0, first, s->regs[R_HRES], last - first + 1); + } + s->invalidate = 0; +} + +static void vgafb_invalidate_display(void *opaque) +{ + MilkymistVgafbState *s = opaque; + s->invalidate = 1; +} + +static void vgafb_resize(MilkymistVgafbState *s) +{ + if (!vgafb_enabled(s)) { + return; + } + + qemu_console_resize(s->con, s->regs[R_HRES], s->regs[R_VRES]); + s->invalidate = 1; +} + +static uint64_t vgafb_read(void *opaque, hwaddr addr, + unsigned size) +{ + MilkymistVgafbState *s = opaque; + uint32_t r = 0; + + addr >>= 2; + switch (addr) { + case R_CTRL: + case R_HRES: + case R_HSYNC_START: + case R_HSYNC_END: + case R_HSCAN: + case R_VRES: + case R_VSYNC_START: + case R_VSYNC_END: + case R_VSCAN: + case R_BASEADDRESS: + case R_BURST_COUNT: + case R_DDC: + case R_SOURCE_CLOCK: + r = s->regs[addr]; + break; + case R_BASEADDRESS_ACT: + r = s->regs[R_BASEADDRESS]; + break; + + default: + error_report("milkymist_vgafb: read access to unknown register 0x" + TARGET_FMT_plx, addr << 2); + break; + } + + trace_milkymist_vgafb_memory_read(addr << 2, r); + + return r; +} + +static void vgafb_write(void *opaque, hwaddr addr, uint64_t value, + unsigned size) +{ + MilkymistVgafbState *s = opaque; + + trace_milkymist_vgafb_memory_write(addr, value); + + addr >>= 2; + switch (addr) { + case R_CTRL: + s->regs[addr] = value; + vgafb_resize(s); + break; + case R_HSYNC_START: + case R_HSYNC_END: + case R_HSCAN: + case R_VSYNC_START: + case R_VSYNC_END: + case R_VSCAN: + case R_BURST_COUNT: + case R_DDC: + case R_SOURCE_CLOCK: + s->regs[addr] = value; + break; + case R_BASEADDRESS: + if (value & 0x1f) { + error_report("milkymist_vgafb: framebuffer base address have to " + "be 32 byte aligned"); + break; + } + s->regs[addr] = value & s->fb_mask; + s->invalidate = 1; + break; + case R_HRES: + case R_VRES: + s->regs[addr] = value; + vgafb_resize(s); + break; + case R_BASEADDRESS_ACT: + error_report("milkymist_vgafb: write to read-only register 0x" + TARGET_FMT_plx, addr << 2); + break; + + default: + error_report("milkymist_vgafb: write access to unknown register 0x" + TARGET_FMT_plx, addr << 2); + break; + } +} + +static const MemoryRegionOps vgafb_mmio_ops = { + .read = vgafb_read, + .write = vgafb_write, + .valid = { + .min_access_size = 4, + .max_access_size = 4, + }, + .endianness = DEVICE_NATIVE_ENDIAN, +}; + +static void milkymist_vgafb_reset(DeviceState *d) +{ + MilkymistVgafbState *s = container_of(d, MilkymistVgafbState, busdev.qdev); + int i; + + for (i = 0; i < R_MAX; i++) { + s->regs[i] = 0; + } + + /* defaults */ + s->regs[R_CTRL] = CTRL_RESET; + s->regs[R_HRES] = 640; + s->regs[R_VRES] = 480; + s->regs[R_BASEADDRESS] = 0; +} + +static int milkymist_vgafb_init(SysBusDevice *dev) +{ + MilkymistVgafbState *s = FROM_SYSBUS(typeof(*s), dev); + + memory_region_init_io(&s->regs_region, &vgafb_mmio_ops, s, + "milkymist-vgafb", R_MAX * 4); + sysbus_init_mmio(dev, &s->regs_region); + + s->con = graphic_console_init(vgafb_update_display, + vgafb_invalidate_display, + NULL, NULL, s); + + return 0; +} + +static int vgafb_post_load(void *opaque, int version_id) +{ + vgafb_invalidate_display(opaque); + return 0; +} + +static const VMStateDescription vmstate_milkymist_vgafb = { + .name = "milkymist-vgafb", + .version_id = 1, + .minimum_version_id = 1, + .minimum_version_id_old = 1, + .post_load = vgafb_post_load, + .fields = (VMStateField[]) { + VMSTATE_UINT32_ARRAY(regs, MilkymistVgafbState, R_MAX), + VMSTATE_END_OF_LIST() + } +}; + +static Property milkymist_vgafb_properties[] = { + DEFINE_PROP_UINT32("fb_offset", MilkymistVgafbState, fb_offset, 0x0), + DEFINE_PROP_UINT32("fb_mask", MilkymistVgafbState, fb_mask, 0xffffffff), + DEFINE_PROP_END_OF_LIST(), +}; + +static void milkymist_vgafb_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass); + + k->init = milkymist_vgafb_init; + dc->reset = milkymist_vgafb_reset; + dc->vmsd = &vmstate_milkymist_vgafb; + dc->props = milkymist_vgafb_properties; +} + +static const TypeInfo milkymist_vgafb_info = { + .name = "milkymist-vgafb", + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(MilkymistVgafbState), + .class_init = milkymist_vgafb_class_init, +}; + +static void milkymist_vgafb_register_types(void) +{ + type_register_static(&milkymist_vgafb_info); +} + +type_init(milkymist_vgafb_register_types) diff --git a/hw/display/milkymist-vgafb_template.h b/hw/display/milkymist-vgafb_template.h new file mode 100644 index 0000000000..e0036e16cf --- /dev/null +++ b/hw/display/milkymist-vgafb_template.h @@ -0,0 +1,74 @@ +/* + * QEMU model of the Milkymist VGA framebuffer. + * + * Copyright (c) 2010 Michael Walle <michael@walle.cc> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see <http://www.gnu.org/licenses/>. + * + */ + +#if BITS == 8 +#define COPY_PIXEL(to, r, g, b) \ + do { \ + *to = rgb_to_pixel8(r, g, b); \ + to += 1; \ + } while (0) +#elif BITS == 15 +#define COPY_PIXEL(to, r, g, b) \ + do { \ + *(uint16_t *)to = rgb_to_pixel15(r, g, b); \ + to += 2; \ + } while (0) +#elif BITS == 16 +#define COPY_PIXEL(to, r, g, b) \ + do { \ + *(uint16_t *)to = rgb_to_pixel16(r, g, b); \ + to += 2; \ + } while (0) +#elif BITS == 24 +#define COPY_PIXEL(to, r, g, b) \ + do { \ + uint32_t tmp = rgb_to_pixel24(r, g, b); \ + *(to++) = tmp & 0xff; \ + *(to++) = (tmp >> 8) & 0xff; \ + *(to++) = (tmp >> 16) & 0xff; \ + } while (0) +#elif BITS == 32 +#define COPY_PIXEL(to, r, g, b) \ + do { \ + *(uint32_t *)to = rgb_to_pixel32(r, g, b); \ + to += 4; \ + } while (0) +#else +#error unknown bit depth +#endif + +static void glue(draw_line_, BITS)(void *opaque, uint8_t *d, const uint8_t *s, + int width, int deststep) +{ + uint16_t rgb565; + uint8_t r, g, b; + + while (width--) { + memcpy(&rgb565, s, sizeof(rgb565)); + r = ((rgb565 >> 11) & 0x1f) << 3; + g = ((rgb565 >> 5) & 0x3f) << 2; + b = ((rgb565 >> 0) & 0x1f) << 3; + COPY_PIXEL(d, r, g, b); + s += 2; + } +} + +#undef BITS +#undef COPY_PIXEL diff --git a/hw/display/omap_dss.c b/hw/display/omap_dss.c new file mode 100644 index 0000000000..ea3afcef5e --- /dev/null +++ b/hw/display/omap_dss.c @@ -0,0 +1,1086 @@ +/* + * OMAP2 Display Subsystem. + * + * Copyright (C) 2008 Nokia Corporation + * Written by Andrzej Zaborowski <andrew@openedhand.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 or + * (at your option) version 3 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, see <http://www.gnu.org/licenses/>. + */ +#include "hw/hw.h" +#include "ui/console.h" +#include "hw/arm/omap.h" + +struct omap_dss_s { + qemu_irq irq; + qemu_irq drq; + DisplayState *state; + MemoryRegion iomem_diss1, iomem_disc1, iomem_rfbi1, iomem_venc1, iomem_im3; + + int autoidle; + int control; + int enable; + + struct omap_dss_panel_s { + int enable; + int nx; + int ny; + + int x; + int y; + } dig, lcd; + + struct { + uint32_t idlemode; + uint32_t irqst; + uint32_t irqen; + uint32_t control; + uint32_t config; + uint32_t capable; + uint32_t timing[4]; + int line; + uint32_t bg[2]; + uint32_t trans[2]; + + struct omap_dss_plane_s { + int enable; + int bpp; + int posx; + int posy; + int nx; + int ny; + + hwaddr addr[3]; + + uint32_t attr; + uint32_t tresh; + int rowinc; + int colinc; + int wininc; + } l[3]; + + int invalidate; + uint16_t palette[256]; + } dispc; + + struct { + int idlemode; + uint32_t control; + int enable; + int pixels; + int busy; + int skiplines; + uint16_t rxbuf; + uint32_t config[2]; + uint32_t time[4]; + uint32_t data[6]; + uint16_t vsync; + uint16_t hsync; + struct rfbi_chip_s *chip[2]; + } rfbi; +}; + +static void omap_dispc_interrupt_update(struct omap_dss_s *s) +{ + qemu_set_irq(s->irq, s->dispc.irqst & s->dispc.irqen); +} + +static void omap_rfbi_reset(struct omap_dss_s *s) +{ + s->rfbi.idlemode = 0; + s->rfbi.control = 2; + s->rfbi.enable = 0; + s->rfbi.pixels = 0; + s->rfbi.skiplines = 0; + s->rfbi.busy = 0; + s->rfbi.config[0] = 0x00310000; + s->rfbi.config[1] = 0x00310000; + s->rfbi.time[0] = 0; + s->rfbi.time[1] = 0; + s->rfbi.time[2] = 0; + s->rfbi.time[3] = 0; + s->rfbi.data[0] = 0; + s->rfbi.data[1] = 0; + s->rfbi.data[2] = 0; + s->rfbi.data[3] = 0; + s->rfbi.data[4] = 0; + s->rfbi.data[5] = 0; + s->rfbi.vsync = 0; + s->rfbi.hsync = 0; +} + +void omap_dss_reset(struct omap_dss_s *s) +{ + s->autoidle = 0; + s->control = 0; + s->enable = 0; + + s->dig.enable = 0; + s->dig.nx = 1; + s->dig.ny = 1; + + s->lcd.enable = 0; + s->lcd.nx = 1; + s->lcd.ny = 1; + + s->dispc.idlemode = 0; + s->dispc.irqst = 0; + s->dispc.irqen = 0; + s->dispc.control = 0; + s->dispc.config = 0; + s->dispc.capable = 0x161; + s->dispc.timing[0] = 0; + s->dispc.timing[1] = 0; + s->dispc.timing[2] = 0; + s->dispc.timing[3] = 0; + s->dispc.line = 0; + s->dispc.bg[0] = 0; + s->dispc.bg[1] = 0; + s->dispc.trans[0] = 0; + s->dispc.trans[1] = 0; + + s->dispc.l[0].enable = 0; + s->dispc.l[0].bpp = 0; + s->dispc.l[0].addr[0] = 0; + s->dispc.l[0].addr[1] = 0; + s->dispc.l[0].addr[2] = 0; + s->dispc.l[0].posx = 0; + s->dispc.l[0].posy = 0; + s->dispc.l[0].nx = 1; + s->dispc.l[0].ny = 1; + s->dispc.l[0].attr = 0; + s->dispc.l[0].tresh = 0; + s->dispc.l[0].rowinc = 1; + s->dispc.l[0].colinc = 1; + s->dispc.l[0].wininc = 0; + + omap_rfbi_reset(s); + omap_dispc_interrupt_update(s); +} + +static uint64_t omap_diss_read(void *opaque, hwaddr addr, + unsigned size) +{ + struct omap_dss_s *s = (struct omap_dss_s *) opaque; + + if (size != 4) { + return omap_badwidth_read32(opaque, addr); + } + + switch (addr) { + case 0x00: /* DSS_REVISIONNUMBER */ + return 0x20; + + case 0x10: /* DSS_SYSCONFIG */ + return s->autoidle; + + case 0x14: /* DSS_SYSSTATUS */ + return 1; /* RESETDONE */ + + case 0x40: /* DSS_CONTROL */ + return s->control; + + case 0x50: /* DSS_PSA_LCD_REG_1 */ + case 0x54: /* DSS_PSA_LCD_REG_2 */ + case 0x58: /* DSS_PSA_VIDEO_REG */ + /* TODO: fake some values when appropriate s->control bits are set */ + return 0; + + case 0x5c: /* DSS_STATUS */ + return 1 + (s->control & 1); + + default: + break; + } + OMAP_BAD_REG(addr); + return 0; +} + +static void omap_diss_write(void *opaque, hwaddr addr, + uint64_t value, unsigned size) +{ + struct omap_dss_s *s = (struct omap_dss_s *) opaque; + + if (size != 4) { + return omap_badwidth_write32(opaque, addr, value); + } + + switch (addr) { + case 0x00: /* DSS_REVISIONNUMBER */ + case 0x14: /* DSS_SYSSTATUS */ + case 0x50: /* DSS_PSA_LCD_REG_1 */ + case 0x54: /* DSS_PSA_LCD_REG_2 */ + case 0x58: /* DSS_PSA_VIDEO_REG */ + case 0x5c: /* DSS_STATUS */ + OMAP_RO_REG(addr); + break; + + case 0x10: /* DSS_SYSCONFIG */ + if (value & 2) /* SOFTRESET */ + omap_dss_reset(s); + s->autoidle = value & 1; + break; + + case 0x40: /* DSS_CONTROL */ + s->control = value & 0x3dd; + break; + + default: + OMAP_BAD_REG(addr); + } +} + +static const MemoryRegionOps omap_diss_ops = { + .read = omap_diss_read, + .write = omap_diss_write, + .endianness = DEVICE_NATIVE_ENDIAN, +}; + +static uint64_t omap_disc_read(void *opaque, hwaddr addr, + unsigned size) +{ + struct omap_dss_s *s = (struct omap_dss_s *) opaque; + + if (size != 4) { + return omap_badwidth_read32(opaque, addr); + } + + switch (addr) { + case 0x000: /* DISPC_REVISION */ + return 0x20; + + case 0x010: /* DISPC_SYSCONFIG */ + return s->dispc.idlemode; + + case 0x014: /* DISPC_SYSSTATUS */ + return 1; /* RESETDONE */ + + case 0x018: /* DISPC_IRQSTATUS */ + return s->dispc.irqst; + + case 0x01c: /* DISPC_IRQENABLE */ + return s->dispc.irqen; + + case 0x040: /* DISPC_CONTROL */ + return s->dispc.control; + + case 0x044: /* DISPC_CONFIG */ + return s->dispc.config; + + case 0x048: /* DISPC_CAPABLE */ + return s->dispc.capable; + + case 0x04c: /* DISPC_DEFAULT_COLOR0 */ + return s->dispc.bg[0]; + case 0x050: /* DISPC_DEFAULT_COLOR1 */ + return s->dispc.bg[1]; + case 0x054: /* DISPC_TRANS_COLOR0 */ + return s->dispc.trans[0]; + case 0x058: /* DISPC_TRANS_COLOR1 */ + return s->dispc.trans[1]; + + case 0x05c: /* DISPC_LINE_STATUS */ + return 0x7ff; + case 0x060: /* DISPC_LINE_NUMBER */ + return s->dispc.line; + + case 0x064: /* DISPC_TIMING_H */ + return s->dispc.timing[0]; + case 0x068: /* DISPC_TIMING_V */ + return s->dispc.timing[1]; + case 0x06c: /* DISPC_POL_FREQ */ + return s->dispc.timing[2]; + case 0x070: /* DISPC_DIVISOR */ + return s->dispc.timing[3]; + + case 0x078: /* DISPC_SIZE_DIG */ + return ((s->dig.ny - 1) << 16) | (s->dig.nx - 1); + case 0x07c: /* DISPC_SIZE_LCD */ + return ((s->lcd.ny - 1) << 16) | (s->lcd.nx - 1); + + case 0x080: /* DISPC_GFX_BA0 */ + return s->dispc.l[0].addr[0]; + case 0x084: /* DISPC_GFX_BA1 */ + return s->dispc.l[0].addr[1]; + case 0x088: /* DISPC_GFX_POSITION */ + return (s->dispc.l[0].posy << 16) | s->dispc.l[0].posx; + case 0x08c: /* DISPC_GFX_SIZE */ + return ((s->dispc.l[0].ny - 1) << 16) | (s->dispc.l[0].nx - 1); + case 0x0a0: /* DISPC_GFX_ATTRIBUTES */ + return s->dispc.l[0].attr; + case 0x0a4: /* DISPC_GFX_FIFO_TRESHOLD */ + return s->dispc.l[0].tresh; + case 0x0a8: /* DISPC_GFX_FIFO_SIZE_STATUS */ + return 256; + case 0x0ac: /* DISPC_GFX_ROW_INC */ + return s->dispc.l[0].rowinc; + case 0x0b0: /* DISPC_GFX_PIXEL_INC */ + return s->dispc.l[0].colinc; + case 0x0b4: /* DISPC_GFX_WINDOW_SKIP */ + return s->dispc.l[0].wininc; + case 0x0b8: /* DISPC_GFX_TABLE_BA */ + return s->dispc.l[0].addr[2]; + + case 0x0bc: /* DISPC_VID1_BA0 */ + case 0x0c0: /* DISPC_VID1_BA1 */ + case 0x0c4: /* DISPC_VID1_POSITION */ + case 0x0c8: /* DISPC_VID1_SIZE */ + case 0x0cc: /* DISPC_VID1_ATTRIBUTES */ + case 0x0d0: /* DISPC_VID1_FIFO_TRESHOLD */ + case 0x0d4: /* DISPC_VID1_FIFO_SIZE_STATUS */ + case 0x0d8: /* DISPC_VID1_ROW_INC */ + case 0x0dc: /* DISPC_VID1_PIXEL_INC */ + case 0x0e0: /* DISPC_VID1_FIR */ + case 0x0e4: /* DISPC_VID1_PICTURE_SIZE */ + case 0x0e8: /* DISPC_VID1_ACCU0 */ + case 0x0ec: /* DISPC_VID1_ACCU1 */ + case 0x0f0 ... 0x140: /* DISPC_VID1_FIR_COEF, DISPC_VID1_CONV_COEF */ + case 0x14c: /* DISPC_VID2_BA0 */ + case 0x150: /* DISPC_VID2_BA1 */ + case 0x154: /* DISPC_VID2_POSITION */ + case 0x158: /* DISPC_VID2_SIZE */ + case 0x15c: /* DISPC_VID2_ATTRIBUTES */ + case 0x160: /* DISPC_VID2_FIFO_TRESHOLD */ + case 0x164: /* DISPC_VID2_FIFO_SIZE_STATUS */ + case 0x168: /* DISPC_VID2_ROW_INC */ + case 0x16c: /* DISPC_VID2_PIXEL_INC */ + case 0x170: /* DISPC_VID2_FIR */ + case 0x174: /* DISPC_VID2_PICTURE_SIZE */ + case 0x178: /* DISPC_VID2_ACCU0 */ + case 0x17c: /* DISPC_VID2_ACCU1 */ + case 0x180 ... 0x1d0: /* DISPC_VID2_FIR_COEF, DISPC_VID2_CONV_COEF */ + case 0x1d4: /* DISPC_DATA_CYCLE1 */ + case 0x1d8: /* DISPC_DATA_CYCLE2 */ + case 0x1dc: /* DISPC_DATA_CYCLE3 */ + return 0; + + default: + break; + } + OMAP_BAD_REG(addr); + return 0; +} + +static void omap_disc_write(void *opaque, hwaddr addr, + uint64_t value, unsigned size) +{ + struct omap_dss_s *s = (struct omap_dss_s *) opaque; + + if (size != 4) { + return omap_badwidth_write32(opaque, addr, value); + } + + switch (addr) { + case 0x010: /* DISPC_SYSCONFIG */ + if (value & 2) /* SOFTRESET */ + omap_dss_reset(s); + s->dispc.idlemode = value & 0x301b; + break; + + case 0x018: /* DISPC_IRQSTATUS */ + s->dispc.irqst &= ~value; + omap_dispc_interrupt_update(s); + break; + + case 0x01c: /* DISPC_IRQENABLE */ + s->dispc.irqen = value & 0xffff; + omap_dispc_interrupt_update(s); + break; + + case 0x040: /* DISPC_CONTROL */ + s->dispc.control = value & 0x07ff9fff; + s->dig.enable = (value >> 1) & 1; + s->lcd.enable = (value >> 0) & 1; + if (value & (1 << 12)) /* OVERLAY_OPTIMIZATION */ + if (!((s->dispc.l[1].attr | s->dispc.l[2].attr) & 1)) { + fprintf(stderr, "%s: Overlay Optimization when no overlay " + "region effectively exists leads to " + "unpredictable behaviour!\n", __func__); + } + if (value & (1 << 6)) { /* GODIGITAL */ + /* XXX: Shadowed fields are: + * s->dispc.config + * s->dispc.capable + * s->dispc.bg[0] + * s->dispc.bg[1] + * s->dispc.trans[0] + * s->dispc.trans[1] + * s->dispc.line + * s->dispc.timing[0] + * s->dispc.timing[1] + * s->dispc.timing[2] + * s->dispc.timing[3] + * s->lcd.nx + * s->lcd.ny + * s->dig.nx + * s->dig.ny + * s->dispc.l[0].addr[0] + * s->dispc.l[0].addr[1] + * s->dispc.l[0].addr[2] + * s->dispc.l[0].posx + * s->dispc.l[0].posy + * s->dispc.l[0].nx + * s->dispc.l[0].ny + * s->dispc.l[0].tresh + * s->dispc.l[0].rowinc + * s->dispc.l[0].colinc + * s->dispc.l[0].wininc + * All they need to be loaded here from their shadow registers. + */ + } + if (value & (1 << 5)) { /* GOLCD */ + /* XXX: Likewise for LCD here. */ + } + s->dispc.invalidate = 1; + break; + + case 0x044: /* DISPC_CONFIG */ + s->dispc.config = value & 0x3fff; + /* XXX: + * bits 2:1 (LOADMODE) reset to 0 after set to 1 and palette loaded + * bits 2:1 (LOADMODE) reset to 2 after set to 3 and palette loaded + */ + s->dispc.invalidate = 1; + break; + + case 0x048: /* DISPC_CAPABLE */ + s->dispc.capable = value & 0x3ff; + break; + + case 0x04c: /* DISPC_DEFAULT_COLOR0 */ + s->dispc.bg[0] = value & 0xffffff; + s->dispc.invalidate = 1; + break; + case 0x050: /* DISPC_DEFAULT_COLOR1 */ + s->dispc.bg[1] = value & 0xffffff; + s->dispc.invalidate = 1; + break; + case 0x054: /* DISPC_TRANS_COLOR0 */ + s->dispc.trans[0] = value & 0xffffff; + s->dispc.invalidate = 1; + break; + case 0x058: /* DISPC_TRANS_COLOR1 */ + s->dispc.trans[1] = value & 0xffffff; + s->dispc.invalidate = 1; + break; + + case 0x060: /* DISPC_LINE_NUMBER */ + s->dispc.line = value & 0x7ff; + break; + + case 0x064: /* DISPC_TIMING_H */ + s->dispc.timing[0] = value & 0x0ff0ff3f; + break; + case 0x068: /* DISPC_TIMING_V */ + s->dispc.timing[1] = value & 0x0ff0ff3f; + break; + case 0x06c: /* DISPC_POL_FREQ */ + s->dispc.timing[2] = value & 0x0003ffff; + break; + case 0x070: /* DISPC_DIVISOR */ + s->dispc.timing[3] = value & 0x00ff00ff; + break; + + case 0x078: /* DISPC_SIZE_DIG */ + s->dig.nx = ((value >> 0) & 0x7ff) + 1; /* PPL */ + s->dig.ny = ((value >> 16) & 0x7ff) + 1; /* LPP */ + s->dispc.invalidate = 1; + break; + case 0x07c: /* DISPC_SIZE_LCD */ + s->lcd.nx = ((value >> 0) & 0x7ff) + 1; /* PPL */ + s->lcd.ny = ((value >> 16) & 0x7ff) + 1; /* LPP */ + s->dispc.invalidate = 1; + break; + case 0x080: /* DISPC_GFX_BA0 */ + s->dispc.l[0].addr[0] = (hwaddr) value; + s->dispc.invalidate = 1; + break; + case 0x084: /* DISPC_GFX_BA1 */ + s->dispc.l[0].addr[1] = (hwaddr) value; + s->dispc.invalidate = 1; + break; + case 0x088: /* DISPC_GFX_POSITION */ + s->dispc.l[0].posx = ((value >> 0) & 0x7ff); /* GFXPOSX */ + s->dispc.l[0].posy = ((value >> 16) & 0x7ff); /* GFXPOSY */ + s->dispc.invalidate = 1; + break; + case 0x08c: /* DISPC_GFX_SIZE */ + s->dispc.l[0].nx = ((value >> 0) & 0x7ff) + 1; /* GFXSIZEX */ + s->dispc.l[0].ny = ((value >> 16) & 0x7ff) + 1; /* GFXSIZEY */ + s->dispc.invalidate = 1; + break; + case 0x0a0: /* DISPC_GFX_ATTRIBUTES */ + s->dispc.l[0].attr = value & 0x7ff; + if (value & (3 << 9)) + fprintf(stderr, "%s: Big-endian pixel format not supported\n", + __FUNCTION__); + s->dispc.l[0].enable = value & 1; + s->dispc.l[0].bpp = (value >> 1) & 0xf; + s->dispc.invalidate = 1; + break; + case 0x0a4: /* DISPC_GFX_FIFO_TRESHOLD */ + s->dispc.l[0].tresh = value & 0x01ff01ff; + break; + case 0x0ac: /* DISPC_GFX_ROW_INC */ + s->dispc.l[0].rowinc = value; + s->dispc.invalidate = 1; + break; + case 0x0b0: /* DISPC_GFX_PIXEL_INC */ + s->dispc.l[0].colinc = value; + s->dispc.invalidate = 1; + break; + case 0x0b4: /* DISPC_GFX_WINDOW_SKIP */ + s->dispc.l[0].wininc = value; + break; + case 0x0b8: /* DISPC_GFX_TABLE_BA */ + s->dispc.l[0].addr[2] = (hwaddr) value; + s->dispc.invalidate = 1; + break; + + case 0x0bc: /* DISPC_VID1_BA0 */ + case 0x0c0: /* DISPC_VID1_BA1 */ + case 0x0c4: /* DISPC_VID1_POSITION */ + case 0x0c8: /* DISPC_VID1_SIZE */ + case 0x0cc: /* DISPC_VID1_ATTRIBUTES */ + case 0x0d0: /* DISPC_VID1_FIFO_TRESHOLD */ + case 0x0d8: /* DISPC_VID1_ROW_INC */ + case 0x0dc: /* DISPC_VID1_PIXEL_INC */ + case 0x0e0: /* DISPC_VID1_FIR */ + case 0x0e4: /* DISPC_VID1_PICTURE_SIZE */ + case 0x0e8: /* DISPC_VID1_ACCU0 */ + case 0x0ec: /* DISPC_VID1_ACCU1 */ + case 0x0f0 ... 0x140: /* DISPC_VID1_FIR_COEF, DISPC_VID1_CONV_COEF */ + case 0x14c: /* DISPC_VID2_BA0 */ + case 0x150: /* DISPC_VID2_BA1 */ + case 0x154: /* DISPC_VID2_POSITION */ + case 0x158: /* DISPC_VID2_SIZE */ + case 0x15c: /* DISPC_VID2_ATTRIBUTES */ + case 0x160: /* DISPC_VID2_FIFO_TRESHOLD */ + case 0x168: /* DISPC_VID2_ROW_INC */ + case 0x16c: /* DISPC_VID2_PIXEL_INC */ + case 0x170: /* DISPC_VID2_FIR */ + case 0x174: /* DISPC_VID2_PICTURE_SIZE */ + case 0x178: /* DISPC_VID2_ACCU0 */ + case 0x17c: /* DISPC_VID2_ACCU1 */ + case 0x180 ... 0x1d0: /* DISPC_VID2_FIR_COEF, DISPC_VID2_CONV_COEF */ + case 0x1d4: /* DISPC_DATA_CYCLE1 */ + case 0x1d8: /* DISPC_DATA_CYCLE2 */ + case 0x1dc: /* DISPC_DATA_CYCLE3 */ + break; + + default: + OMAP_BAD_REG(addr); + } +} + +static const MemoryRegionOps omap_disc_ops = { + .read = omap_disc_read, + .write = omap_disc_write, + .endianness = DEVICE_NATIVE_ENDIAN, +}; + +static void omap_rfbi_transfer_stop(struct omap_dss_s *s) +{ + if (!s->rfbi.busy) + return; + + /* TODO: in non-Bypass mode we probably need to just deassert the DRQ. */ + + s->rfbi.busy = 0; +} + +static void omap_rfbi_transfer_start(struct omap_dss_s *s) +{ + void *data; + hwaddr len; + hwaddr data_addr; + int pitch; + static void *bounce_buffer; + static hwaddr bounce_len; + + if (!s->rfbi.enable || s->rfbi.busy) + return; + + if (s->rfbi.control & (1 << 1)) { /* BYPASS */ + /* TODO: in non-Bypass mode we probably need to just assert the + * DRQ and wait for DMA to write the pixels. */ + fprintf(stderr, "%s: Bypass mode unimplemented\n", __FUNCTION__); + return; + } + + if (!(s->dispc.control & (1 << 11))) /* RFBIMODE */ + return; + /* TODO: check that LCD output is enabled in DISPC. */ + + s->rfbi.busy = 1; + + len = s->rfbi.pixels * 2; + + data_addr = s->dispc.l[0].addr[0]; + data = cpu_physical_memory_map(data_addr, &len, 0); + if (data && len != s->rfbi.pixels * 2) { + cpu_physical_memory_unmap(data, len, 0, 0); + data = NULL; + len = s->rfbi.pixels * 2; + } + if (!data) { + if (len > bounce_len) { + bounce_buffer = g_realloc(bounce_buffer, len); + } + data = bounce_buffer; + cpu_physical_memory_read(data_addr, data, len); + } + + /* TODO bpp */ + s->rfbi.pixels = 0; + + /* TODO: negative values */ + pitch = s->dispc.l[0].nx + (s->dispc.l[0].rowinc - 1) / 2; + + if ((s->rfbi.control & (1 << 2)) && s->rfbi.chip[0]) + s->rfbi.chip[0]->block(s->rfbi.chip[0]->opaque, 1, data, len, pitch); + if ((s->rfbi.control & (1 << 3)) && s->rfbi.chip[1]) + s->rfbi.chip[1]->block(s->rfbi.chip[1]->opaque, 1, data, len, pitch); + + if (data != bounce_buffer) { + cpu_physical_memory_unmap(data, len, 0, len); + } + + omap_rfbi_transfer_stop(s); + + /* TODO */ + s->dispc.irqst |= 1; /* FRAMEDONE */ + omap_dispc_interrupt_update(s); +} + +static uint64_t omap_rfbi_read(void *opaque, hwaddr addr, + unsigned size) +{ + struct omap_dss_s *s = (struct omap_dss_s *) opaque; + + if (size != 4) { + return omap_badwidth_read32(opaque, addr); + } + + switch (addr) { + case 0x00: /* RFBI_REVISION */ + return 0x10; + + case 0x10: /* RFBI_SYSCONFIG */ + return s->rfbi.idlemode; + + case 0x14: /* RFBI_SYSSTATUS */ + return 1 | (s->rfbi.busy << 8); /* RESETDONE */ + + case 0x40: /* RFBI_CONTROL */ + return s->rfbi.control; + + case 0x44: /* RFBI_PIXELCNT */ + return s->rfbi.pixels; + + case 0x48: /* RFBI_LINE_NUMBER */ + return s->rfbi.skiplines; + + case 0x58: /* RFBI_READ */ + case 0x5c: /* RFBI_STATUS */ + return s->rfbi.rxbuf; + + case 0x60: /* RFBI_CONFIG0 */ + return s->rfbi.config[0]; + case 0x64: /* RFBI_ONOFF_TIME0 */ + return s->rfbi.time[0]; + case 0x68: /* RFBI_CYCLE_TIME0 */ + return s->rfbi.time[1]; + case 0x6c: /* RFBI_DATA_CYCLE1_0 */ + return s->rfbi.data[0]; + case 0x70: /* RFBI_DATA_CYCLE2_0 */ + return s->rfbi.data[1]; + case 0x74: /* RFBI_DATA_CYCLE3_0 */ + return s->rfbi.data[2]; + + case 0x78: /* RFBI_CONFIG1 */ + return s->rfbi.config[1]; + case 0x7c: /* RFBI_ONOFF_TIME1 */ + return s->rfbi.time[2]; + case 0x80: /* RFBI_CYCLE_TIME1 */ + return s->rfbi.time[3]; + case 0x84: /* RFBI_DATA_CYCLE1_1 */ + return s->rfbi.data[3]; + case 0x88: /* RFBI_DATA_CYCLE2_1 */ + return s->rfbi.data[4]; + case 0x8c: /* RFBI_DATA_CYCLE3_1 */ + return s->rfbi.data[5]; + + case 0x90: /* RFBI_VSYNC_WIDTH */ + return s->rfbi.vsync; + case 0x94: /* RFBI_HSYNC_WIDTH */ + return s->rfbi.hsync; + } + OMAP_BAD_REG(addr); + return 0; +} + +static void omap_rfbi_write(void *opaque, hwaddr addr, + uint64_t value, unsigned size) +{ + struct omap_dss_s *s = (struct omap_dss_s *) opaque; + + if (size != 4) { + return omap_badwidth_write32(opaque, addr, value); + } + + switch (addr) { + case 0x10: /* RFBI_SYSCONFIG */ + if (value & 2) /* SOFTRESET */ + omap_rfbi_reset(s); + s->rfbi.idlemode = value & 0x19; + break; + + case 0x40: /* RFBI_CONTROL */ + s->rfbi.control = value & 0xf; + s->rfbi.enable = value & 1; + if (value & (1 << 4) && /* ITE */ + !(s->rfbi.config[0] & s->rfbi.config[1] & 0xc)) + omap_rfbi_transfer_start(s); + break; + + case 0x44: /* RFBI_PIXELCNT */ + s->rfbi.pixels = value; + break; + + case 0x48: /* RFBI_LINE_NUMBER */ + s->rfbi.skiplines = value & 0x7ff; + break; + + case 0x4c: /* RFBI_CMD */ + if ((s->rfbi.control & (1 << 2)) && s->rfbi.chip[0]) + s->rfbi.chip[0]->write(s->rfbi.chip[0]->opaque, 0, value & 0xffff); + if ((s->rfbi.control & (1 << 3)) && s->rfbi.chip[1]) + s->rfbi.chip[1]->write(s->rfbi.chip[1]->opaque, 0, value & 0xffff); + break; + case 0x50: /* RFBI_PARAM */ + if ((s->rfbi.control & (1 << 2)) && s->rfbi.chip[0]) + s->rfbi.chip[0]->write(s->rfbi.chip[0]->opaque, 1, value & 0xffff); + if ((s->rfbi.control & (1 << 3)) && s->rfbi.chip[1]) + s->rfbi.chip[1]->write(s->rfbi.chip[1]->opaque, 1, value & 0xffff); + break; + case 0x54: /* RFBI_DATA */ + /* TODO: take into account the format set up in s->rfbi.config[?] and + * s->rfbi.data[?], but special-case the most usual scenario so that + * speed doesn't suffer. */ + if ((s->rfbi.control & (1 << 2)) && s->rfbi.chip[0]) { + s->rfbi.chip[0]->write(s->rfbi.chip[0]->opaque, 1, value & 0xffff); + s->rfbi.chip[0]->write(s->rfbi.chip[0]->opaque, 1, value >> 16); + } + if ((s->rfbi.control & (1 << 3)) && s->rfbi.chip[1]) { + s->rfbi.chip[1]->write(s->rfbi.chip[1]->opaque, 1, value & 0xffff); + s->rfbi.chip[1]->write(s->rfbi.chip[1]->opaque, 1, value >> 16); + } + if (!-- s->rfbi.pixels) + omap_rfbi_transfer_stop(s); + break; + case 0x58: /* RFBI_READ */ + if ((s->rfbi.control & (1 << 2)) && s->rfbi.chip[0]) + s->rfbi.rxbuf = s->rfbi.chip[0]->read(s->rfbi.chip[0]->opaque, 1); + else if ((s->rfbi.control & (1 << 3)) && s->rfbi.chip[1]) + s->rfbi.rxbuf = s->rfbi.chip[1]->read(s->rfbi.chip[1]->opaque, 1); + if (!-- s->rfbi.pixels) + omap_rfbi_transfer_stop(s); + break; + + case 0x5c: /* RFBI_STATUS */ + if ((s->rfbi.control & (1 << 2)) && s->rfbi.chip[0]) + s->rfbi.rxbuf = s->rfbi.chip[0]->read(s->rfbi.chip[0]->opaque, 0); + else if ((s->rfbi.control & (1 << 3)) && s->rfbi.chip[1]) + s->rfbi.rxbuf = s->rfbi.chip[1]->read(s->rfbi.chip[1]->opaque, 0); + if (!-- s->rfbi.pixels) + omap_rfbi_transfer_stop(s); + break; + + case 0x60: /* RFBI_CONFIG0 */ + s->rfbi.config[0] = value & 0x003f1fff; + break; + + case 0x64: /* RFBI_ONOFF_TIME0 */ + s->rfbi.time[0] = value & 0x3fffffff; + break; + case 0x68: /* RFBI_CYCLE_TIME0 */ + s->rfbi.time[1] = value & 0x0fffffff; + break; + case 0x6c: /* RFBI_DATA_CYCLE1_0 */ + s->rfbi.data[0] = value & 0x0f1f0f1f; + break; + case 0x70: /* RFBI_DATA_CYCLE2_0 */ + s->rfbi.data[1] = value & 0x0f1f0f1f; + break; + case 0x74: /* RFBI_DATA_CYCLE3_0 */ + s->rfbi.data[2] = value & 0x0f1f0f1f; + break; + case 0x78: /* RFBI_CONFIG1 */ + s->rfbi.config[1] = value & 0x003f1fff; + break; + + case 0x7c: /* RFBI_ONOFF_TIME1 */ + s->rfbi.time[2] = value & 0x3fffffff; + break; + case 0x80: /* RFBI_CYCLE_TIME1 */ + s->rfbi.time[3] = value & 0x0fffffff; + break; + case 0x84: /* RFBI_DATA_CYCLE1_1 */ + s->rfbi.data[3] = value & 0x0f1f0f1f; + break; + case 0x88: /* RFBI_DATA_CYCLE2_1 */ + s->rfbi.data[4] = value & 0x0f1f0f1f; + break; + case 0x8c: /* RFBI_DATA_CYCLE3_1 */ + s->rfbi.data[5] = value & 0x0f1f0f1f; + break; + + case 0x90: /* RFBI_VSYNC_WIDTH */ + s->rfbi.vsync = value & 0xffff; + break; + case 0x94: /* RFBI_HSYNC_WIDTH */ + s->rfbi.hsync = value & 0xffff; + break; + + default: + OMAP_BAD_REG(addr); + } +} + +static const MemoryRegionOps omap_rfbi_ops = { + .read = omap_rfbi_read, + .write = omap_rfbi_write, + .endianness = DEVICE_NATIVE_ENDIAN, +}; + +static uint64_t omap_venc_read(void *opaque, hwaddr addr, + unsigned size) +{ + if (size != 4) { + return omap_badwidth_read32(opaque, addr); + } + + switch (addr) { + case 0x00: /* REV_ID */ + case 0x04: /* STATUS */ + case 0x08: /* F_CONTROL */ + case 0x10: /* VIDOUT_CTRL */ + case 0x14: /* SYNC_CTRL */ + case 0x1c: /* LLEN */ + case 0x20: /* FLENS */ + case 0x24: /* HFLTR_CTRL */ + case 0x28: /* CC_CARR_WSS_CARR */ + case 0x2c: /* C_PHASE */ + case 0x30: /* GAIN_U */ + case 0x34: /* GAIN_V */ + case 0x38: /* GAIN_Y */ + case 0x3c: /* BLACK_LEVEL */ + case 0x40: /* BLANK_LEVEL */ + case 0x44: /* X_COLOR */ + case 0x48: /* M_CONTROL */ + case 0x4c: /* BSTAMP_WSS_DATA */ + case 0x50: /* S_CARR */ + case 0x54: /* LINE21 */ + case 0x58: /* LN_SEL */ + case 0x5c: /* L21__WC_CTL */ + case 0x60: /* HTRIGGER_VTRIGGER */ + case 0x64: /* SAVID__EAVID */ + case 0x68: /* FLEN__FAL */ + case 0x6c: /* LAL__PHASE_RESET */ + case 0x70: /* HS_INT_START_STOP_X */ + case 0x74: /* HS_EXT_START_STOP_X */ + case 0x78: /* VS_INT_START_X */ + case 0x7c: /* VS_INT_STOP_X__VS_INT_START_Y */ + case 0x80: /* VS_INT_STOP_Y__VS_INT_START_X */ + case 0x84: /* VS_EXT_STOP_X__VS_EXT_START_Y */ + case 0x88: /* VS_EXT_STOP_Y */ + case 0x90: /* AVID_START_STOP_X */ + case 0x94: /* AVID_START_STOP_Y */ + case 0xa0: /* FID_INT_START_X__FID_INT_START_Y */ + case 0xa4: /* FID_INT_OFFSET_Y__FID_EXT_START_X */ + case 0xa8: /* FID_EXT_START_Y__FID_EXT_OFFSET_Y */ + case 0xb0: /* TVDETGP_INT_START_STOP_X */ + case 0xb4: /* TVDETGP_INT_START_STOP_Y */ + case 0xb8: /* GEN_CTRL */ + case 0xc4: /* DAC_TST__DAC_A */ + case 0xc8: /* DAC_B__DAC_C */ + return 0; + + default: + break; + } + OMAP_BAD_REG(addr); + return 0; +} + +static void omap_venc_write(void *opaque, hwaddr addr, + uint64_t value, unsigned size) +{ + if (size != 4) { + return omap_badwidth_write32(opaque, addr, size); + } + + switch (addr) { + case 0x08: /* F_CONTROL */ + case 0x10: /* VIDOUT_CTRL */ + case 0x14: /* SYNC_CTRL */ + case 0x1c: /* LLEN */ + case 0x20: /* FLENS */ + case 0x24: /* HFLTR_CTRL */ + case 0x28: /* CC_CARR_WSS_CARR */ + case 0x2c: /* C_PHASE */ + case 0x30: /* GAIN_U */ + case 0x34: /* GAIN_V */ + case 0x38: /* GAIN_Y */ + case 0x3c: /* BLACK_LEVEL */ + case 0x40: /* BLANK_LEVEL */ + case 0x44: /* X_COLOR */ + case 0x48: /* M_CONTROL */ + case 0x4c: /* BSTAMP_WSS_DATA */ + case 0x50: /* S_CARR */ + case 0x54: /* LINE21 */ + case 0x58: /* LN_SEL */ + case 0x5c: /* L21__WC_CTL */ + case 0x60: /* HTRIGGER_VTRIGGER */ + case 0x64: /* SAVID__EAVID */ + case 0x68: /* FLEN__FAL */ + case 0x6c: /* LAL__PHASE_RESET */ + case 0x70: /* HS_INT_START_STOP_X */ + case 0x74: /* HS_EXT_START_STOP_X */ + case 0x78: /* VS_INT_START_X */ + case 0x7c: /* VS_INT_STOP_X__VS_INT_START_Y */ + case 0x80: /* VS_INT_STOP_Y__VS_INT_START_X */ + case 0x84: /* VS_EXT_STOP_X__VS_EXT_START_Y */ + case 0x88: /* VS_EXT_STOP_Y */ + case 0x90: /* AVID_START_STOP_X */ + case 0x94: /* AVID_START_STOP_Y */ + case 0xa0: /* FID_INT_START_X__FID_INT_START_Y */ + case 0xa4: /* FID_INT_OFFSET_Y__FID_EXT_START_X */ + case 0xa8: /* FID_EXT_START_Y__FID_EXT_OFFSET_Y */ + case 0xb0: /* TVDETGP_INT_START_STOP_X */ + case 0xb4: /* TVDETGP_INT_START_STOP_Y */ + case 0xb8: /* GEN_CTRL */ + case 0xc4: /* DAC_TST__DAC_A */ + case 0xc8: /* DAC_B__DAC_C */ + break; + + default: + OMAP_BAD_REG(addr); + } +} + +static const MemoryRegionOps omap_venc_ops = { + .read = omap_venc_read, + .write = omap_venc_write, + .endianness = DEVICE_NATIVE_ENDIAN, +}; + +static uint64_t omap_im3_read(void *opaque, hwaddr addr, + unsigned size) +{ + if (size != 4) { + return omap_badwidth_read32(opaque, addr); + } + + switch (addr) { + case 0x0a8: /* SBIMERRLOGA */ + case 0x0b0: /* SBIMERRLOG */ + case 0x190: /* SBIMSTATE */ + case 0x198: /* SBTMSTATE_L */ + case 0x19c: /* SBTMSTATE_H */ + case 0x1a8: /* SBIMCONFIG_L */ + case 0x1ac: /* SBIMCONFIG_H */ + case 0x1f8: /* SBID_L */ + case 0x1fc: /* SBID_H */ + return 0; + + default: + break; + } + OMAP_BAD_REG(addr); + return 0; +} + +static void omap_im3_write(void *opaque, hwaddr addr, + uint64_t value, unsigned size) +{ + if (size != 4) { + return omap_badwidth_write32(opaque, addr, value); + } + + switch (addr) { + case 0x0b0: /* SBIMERRLOG */ + case 0x190: /* SBIMSTATE */ + case 0x198: /* SBTMSTATE_L */ + case 0x19c: /* SBTMSTATE_H */ + case 0x1a8: /* SBIMCONFIG_L */ + case 0x1ac: /* SBIMCONFIG_H */ + break; + + default: + OMAP_BAD_REG(addr); + } +} + +static const MemoryRegionOps omap_im3_ops = { + .read = omap_im3_read, + .write = omap_im3_write, + .endianness = DEVICE_NATIVE_ENDIAN, +}; + +struct omap_dss_s *omap_dss_init(struct omap_target_agent_s *ta, + MemoryRegion *sysmem, + hwaddr l3_base, + qemu_irq irq, qemu_irq drq, + omap_clk fck1, omap_clk fck2, omap_clk ck54m, + omap_clk ick1, omap_clk ick2) +{ + struct omap_dss_s *s = (struct omap_dss_s *) + g_malloc0(sizeof(struct omap_dss_s)); + + s->irq = irq; + s->drq = drq; + omap_dss_reset(s); + + memory_region_init_io(&s->iomem_diss1, &omap_diss_ops, s, "omap.diss1", + omap_l4_region_size(ta, 0)); + memory_region_init_io(&s->iomem_disc1, &omap_disc_ops, s, "omap.disc1", + omap_l4_region_size(ta, 1)); + memory_region_init_io(&s->iomem_rfbi1, &omap_rfbi_ops, s, "omap.rfbi1", + omap_l4_region_size(ta, 2)); + memory_region_init_io(&s->iomem_venc1, &omap_venc_ops, s, "omap.venc1", + omap_l4_region_size(ta, 3)); + memory_region_init_io(&s->iomem_im3, &omap_im3_ops, s, + "omap.im3", 0x1000); + + omap_l4_attach(ta, 0, &s->iomem_diss1); + omap_l4_attach(ta, 1, &s->iomem_disc1); + omap_l4_attach(ta, 2, &s->iomem_rfbi1); + omap_l4_attach(ta, 3, &s->iomem_venc1); + memory_region_add_subregion(sysmem, l3_base, &s->iomem_im3); + +#if 0 + s->state = graphic_console_init(omap_update_display, + omap_invalidate_display, omap_screen_dump, s); +#endif + + return s; +} + +void omap_rfbi_attach(struct omap_dss_s *s, int cs, struct rfbi_chip_s *chip) +{ + if (cs < 0 || cs > 1) + hw_error("%s: wrong CS %i\n", __FUNCTION__, cs); + s->rfbi.chip[cs] = chip; +} diff --git a/hw/display/omap_lcd_template.h b/hw/display/omap_lcd_template.h new file mode 100644 index 0000000000..2fb96f83ae --- /dev/null +++ b/hw/display/omap_lcd_template.h @@ -0,0 +1,175 @@ +/* + * QEMU OMAP LCD Emulator templates + * + * Copyright (c) 2006 Andrzej Zaborowski <balrog@zabor.org> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#if DEPTH == 8 +# define BPP 1 +# define PIXEL_TYPE uint8_t +#elif DEPTH == 15 || DEPTH == 16 +# define BPP 2 +# define PIXEL_TYPE uint16_t +#elif DEPTH == 32 +# define BPP 4 +# define PIXEL_TYPE uint32_t +#else +# error unsupport depth +#endif + +/* + * 2-bit colour + */ +static void glue(draw_line2_, DEPTH)(void *opaque, + uint8_t *d, const uint8_t *s, int width, int deststep) +{ + uint16_t *pal = opaque; + uint8_t v, r, g, b; + + do { + v = ldub_raw((void *) s); + r = (pal[v & 3] >> 4) & 0xf0; + g = pal[v & 3] & 0xf0; + b = (pal[v & 3] << 4) & 0xf0; + ((PIXEL_TYPE *) d)[0] = glue(rgb_to_pixel, DEPTH)(r, g, b); + d += BPP; + v >>= 2; + r = (pal[v & 3] >> 4) & 0xf0; + g = pal[v & 3] & 0xf0; + b = (pal[v & 3] << 4) & 0xf0; + ((PIXEL_TYPE *) d)[0] = glue(rgb_to_pixel, DEPTH)(r, g, b); + d += BPP; + v >>= 2; + r = (pal[v & 3] >> 4) & 0xf0; + g = pal[v & 3] & 0xf0; + b = (pal[v & 3] << 4) & 0xf0; + ((PIXEL_TYPE *) d)[0] = glue(rgb_to_pixel, DEPTH)(r, g, b); + d += BPP; + v >>= 2; + r = (pal[v & 3] >> 4) & 0xf0; + g = pal[v & 3] & 0xf0; + b = (pal[v & 3] << 4) & 0xf0; + ((PIXEL_TYPE *) d)[0] = glue(rgb_to_pixel, DEPTH)(r, g, b); + d += BPP; + s ++; + width -= 4; + } while (width > 0); +} + +/* + * 4-bit colour + */ +static void glue(draw_line4_, DEPTH)(void *opaque, + uint8_t *d, const uint8_t *s, int width, int deststep) +{ + uint16_t *pal = opaque; + uint8_t v, r, g, b; + + do { + v = ldub_raw((void *) s); + r = (pal[v & 0xf] >> 4) & 0xf0; + g = pal[v & 0xf] & 0xf0; + b = (pal[v & 0xf] << 4) & 0xf0; + ((PIXEL_TYPE *) d)[0] = glue(rgb_to_pixel, DEPTH)(r, g, b); + d += BPP; + v >>= 4; + r = (pal[v & 0xf] >> 4) & 0xf0; + g = pal[v & 0xf] & 0xf0; + b = (pal[v & 0xf] << 4) & 0xf0; + ((PIXEL_TYPE *) d)[0] = glue(rgb_to_pixel, DEPTH)(r, g, b); + d += BPP; + s ++; + width -= 2; + } while (width > 0); +} + +/* + * 8-bit colour + */ +static void glue(draw_line8_, DEPTH)(void *opaque, + uint8_t *d, const uint8_t *s, int width, int deststep) +{ + uint16_t *pal = opaque; + uint8_t v, r, g, b; + + do { + v = ldub_raw((void *) s); + r = (pal[v] >> 4) & 0xf0; + g = pal[v] & 0xf0; + b = (pal[v] << 4) & 0xf0; + ((PIXEL_TYPE *) d)[0] = glue(rgb_to_pixel, DEPTH)(r, g, b); + s ++; + d += BPP; + } while (-- width != 0); +} + +/* + * 12-bit colour + */ +static void glue(draw_line12_, DEPTH)(void *opaque, + uint8_t *d, const uint8_t *s, int width, int deststep) +{ + uint16_t v; + uint8_t r, g, b; + + do { + v = lduw_raw((void *) s); + r = (v >> 4) & 0xf0; + g = v & 0xf0; + b = (v << 4) & 0xf0; + ((PIXEL_TYPE *) d)[0] = glue(rgb_to_pixel, DEPTH)(r, g, b); + s += 2; + d += BPP; + } while (-- width != 0); +} + +/* + * 16-bit colour + */ +static void glue(draw_line16_, DEPTH)(void *opaque, + uint8_t *d, const uint8_t *s, int width, int deststep) +{ +#if DEPTH == 16 && defined(HOST_WORDS_BIGENDIAN) == defined(TARGET_WORDS_BIGENDIAN) + memcpy(d, s, width * 2); +#else + uint16_t v; + uint8_t r, g, b; + + do { + v = lduw_raw((void *) s); + r = (v >> 8) & 0xf8; + g = (v >> 3) & 0xfc; + b = (v << 3) & 0xf8; + ((PIXEL_TYPE *) d)[0] = glue(rgb_to_pixel, DEPTH)(r, g, b); + s += 2; + d += BPP; + } while (-- width != 0); +#endif +} + +#undef DEPTH +#undef BPP +#undef PIXEL_TYPE diff --git a/hw/display/omap_lcdc.c b/hw/display/omap_lcdc.c new file mode 100644 index 0000000000..be7e9c0d74 --- /dev/null +++ b/hw/display/omap_lcdc.c @@ -0,0 +1,493 @@ +/* + * OMAP LCD controller. + * + * Copyright (C) 2006-2007 Andrzej Zaborowski <balrog@zabor.org> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, see <http://www.gnu.org/licenses/>. + */ +#include "hw/hw.h" +#include "ui/console.h" +#include "hw/arm/omap.h" +#include "framebuffer.h" +#include "ui/pixel_ops.h" + +struct omap_lcd_panel_s { + MemoryRegion *sysmem; + MemoryRegion iomem; + qemu_irq irq; + QemuConsole *con; + + int plm; + int tft; + int mono; + int enable; + int width; + int height; + int interrupts; + uint32_t timing[3]; + uint32_t subpanel; + uint32_t ctrl; + + struct omap_dma_lcd_channel_s *dma; + uint16_t palette[256]; + int palette_done; + int frame_done; + int invalidate; + int sync_error; +}; + +static void omap_lcd_interrupts(struct omap_lcd_panel_s *s) +{ + if (s->frame_done && (s->interrupts & 1)) { + qemu_irq_raise(s->irq); + return; + } + + if (s->palette_done && (s->interrupts & 2)) { + qemu_irq_raise(s->irq); + return; + } + + if (s->sync_error) { + qemu_irq_raise(s->irq); + return; + } + + qemu_irq_lower(s->irq); +} + +#define draw_line_func drawfn + +#define DEPTH 8 +#include "omap_lcd_template.h" +#define DEPTH 15 +#include "omap_lcd_template.h" +#define DEPTH 16 +#include "omap_lcd_template.h" +#define DEPTH 32 +#include "omap_lcd_template.h" + +static draw_line_func draw_line_table2[33] = { + [0 ... 32] = NULL, + [8] = draw_line2_8, + [15] = draw_line2_15, + [16] = draw_line2_16, + [32] = draw_line2_32, +}, draw_line_table4[33] = { + [0 ... 32] = NULL, + [8] = draw_line4_8, + [15] = draw_line4_15, + [16] = draw_line4_16, + [32] = draw_line4_32, +}, draw_line_table8[33] = { + [0 ... 32] = NULL, + [8] = draw_line8_8, + [15] = draw_line8_15, + [16] = draw_line8_16, + [32] = draw_line8_32, +}, draw_line_table12[33] = { + [0 ... 32] = NULL, + [8] = draw_line12_8, + [15] = draw_line12_15, + [16] = draw_line12_16, + [32] = draw_line12_32, +}, draw_line_table16[33] = { + [0 ... 32] = NULL, + [8] = draw_line16_8, + [15] = draw_line16_15, + [16] = draw_line16_16, + [32] = draw_line16_32, +}; + +static void omap_update_display(void *opaque) +{ + struct omap_lcd_panel_s *omap_lcd = (struct omap_lcd_panel_s *) opaque; + DisplaySurface *surface = qemu_console_surface(omap_lcd->con); + draw_line_func draw_line; + int size, height, first, last; + int width, linesize, step, bpp, frame_offset; + hwaddr frame_base; + + if (!omap_lcd || omap_lcd->plm == 1 || !omap_lcd->enable || + !surface_bits_per_pixel(surface)) { + return; + } + + frame_offset = 0; + if (omap_lcd->plm != 2) { + cpu_physical_memory_read(omap_lcd->dma->phys_framebuffer[ + omap_lcd->dma->current_frame], + (void *)omap_lcd->palette, 0x200); + switch (omap_lcd->palette[0] >> 12 & 7) { + case 3 ... 7: + frame_offset += 0x200; + break; + default: + frame_offset += 0x20; + } + } + + /* Colour depth */ + switch ((omap_lcd->palette[0] >> 12) & 7) { + case 1: + draw_line = draw_line_table2[surface_bits_per_pixel(surface)]; + bpp = 2; + break; + + case 2: + draw_line = draw_line_table4[surface_bits_per_pixel(surface)]; + bpp = 4; + break; + + case 3: + draw_line = draw_line_table8[surface_bits_per_pixel(surface)]; + bpp = 8; + break; + + case 4 ... 7: + if (!omap_lcd->tft) + draw_line = draw_line_table12[surface_bits_per_pixel(surface)]; + else + draw_line = draw_line_table16[surface_bits_per_pixel(surface)]; + bpp = 16; + break; + + default: + /* Unsupported at the moment. */ + return; + } + + /* Resolution */ + width = omap_lcd->width; + if (width != surface_width(surface) || + omap_lcd->height != surface_height(surface)) { + qemu_console_resize(omap_lcd->con, + omap_lcd->width, omap_lcd->height); + surface = qemu_console_surface(omap_lcd->con); + omap_lcd->invalidate = 1; + } + + if (omap_lcd->dma->current_frame == 0) + size = omap_lcd->dma->src_f1_bottom - omap_lcd->dma->src_f1_top; + else + size = omap_lcd->dma->src_f2_bottom - omap_lcd->dma->src_f2_top; + + if (frame_offset + ((width * omap_lcd->height * bpp) >> 3) > size + 2) { + omap_lcd->sync_error = 1; + omap_lcd_interrupts(omap_lcd); + omap_lcd->enable = 0; + return; + } + + /* Content */ + frame_base = omap_lcd->dma->phys_framebuffer[ + omap_lcd->dma->current_frame] + frame_offset; + omap_lcd->dma->condition |= 1 << omap_lcd->dma->current_frame; + if (omap_lcd->dma->interrupts & 1) + qemu_irq_raise(omap_lcd->dma->irq); + if (omap_lcd->dma->dual) + omap_lcd->dma->current_frame ^= 1; + + if (!surface_bits_per_pixel(surface)) { + return; + } + + first = 0; + height = omap_lcd->height; + if (omap_lcd->subpanel & (1 << 31)) { + if (omap_lcd->subpanel & (1 << 29)) + first = (omap_lcd->subpanel >> 16) & 0x3ff; + else + height = (omap_lcd->subpanel >> 16) & 0x3ff; + /* TODO: fill the rest of the panel with DPD */ + } + + step = width * bpp >> 3; + linesize = surface_stride(surface); + framebuffer_update_display(surface, omap_lcd->sysmem, + frame_base, width, height, + step, linesize, 0, + omap_lcd->invalidate, + draw_line, omap_lcd->palette, + &first, &last); + if (first >= 0) { + dpy_gfx_update(omap_lcd->con, 0, first, width, last - first + 1); + } + omap_lcd->invalidate = 0; +} + +static void omap_ppm_save(const char *filename, uint8_t *data, + int w, int h, int linesize, Error **errp) +{ + FILE *f; + uint8_t *d, *d1; + unsigned int v; + int ret, y, x, bpp; + + f = fopen(filename, "wb"); + if (!f) { + error_setg(errp, "failed to open file '%s': %s", filename, + strerror(errno)); + return; + } + ret = fprintf(f, "P6\n%d %d\n%d\n", w, h, 255); + if (ret < 0) { + goto write_err; + } + d1 = data; + bpp = linesize / w; + for (y = 0; y < h; y ++) { + d = d1; + for (x = 0; x < w; x ++) { + v = *(uint32_t *) d; + switch (bpp) { + case 2: + ret = fputc((v >> 8) & 0xf8, f); + if (ret == EOF) { + goto write_err; + } + ret = fputc((v >> 3) & 0xfc, f); + if (ret == EOF) { + goto write_err; + } + ret = fputc((v << 3) & 0xf8, f); + if (ret == EOF) { + goto write_err; + } + break; + case 3: + case 4: + default: + ret = fputc((v >> 16) & 0xff, f); + if (ret == EOF) { + goto write_err; + } + ret = fputc((v >> 8) & 0xff, f); + if (ret == EOF) { + goto write_err; + } + ret = fputc((v) & 0xff, f); + if (ret == EOF) { + goto write_err; + } + break; + } + d += bpp; + } + d1 += linesize; + } +out: + fclose(f); + return; + +write_err: + error_setg(errp, "failed to write to file '%s': %s", filename, + strerror(errno)); + unlink(filename); + goto out; +} + +static void omap_screen_dump(void *opaque, const char *filename, bool cswitch, + Error **errp) +{ + struct omap_lcd_panel_s *omap_lcd = opaque; + DisplaySurface *surface = qemu_console_surface(omap_lcd->con); + + omap_update_display(opaque); + if (omap_lcd && surface_data(surface)) + omap_ppm_save(filename, surface_data(surface), + omap_lcd->width, omap_lcd->height, + surface_stride(surface), errp); +} + +static void omap_invalidate_display(void *opaque) { + struct omap_lcd_panel_s *omap_lcd = opaque; + omap_lcd->invalidate = 1; +} + +static void omap_lcd_update(struct omap_lcd_panel_s *s) { + if (!s->enable) { + s->dma->current_frame = -1; + s->sync_error = 0; + if (s->plm != 1) + s->frame_done = 1; + omap_lcd_interrupts(s); + return; + } + + if (s->dma->current_frame == -1) { + s->frame_done = 0; + s->palette_done = 0; + s->dma->current_frame = 0; + } + + if (!s->dma->mpu->port[s->dma->src].addr_valid(s->dma->mpu, + s->dma->src_f1_top) || + !s->dma->mpu->port[ + s->dma->src].addr_valid(s->dma->mpu, + s->dma->src_f1_bottom) || + (s->dma->dual && + (!s->dma->mpu->port[ + s->dma->src].addr_valid(s->dma->mpu, + s->dma->src_f2_top) || + !s->dma->mpu->port[ + s->dma->src].addr_valid(s->dma->mpu, + s->dma->src_f2_bottom)))) { + s->dma->condition |= 1 << 2; + if (s->dma->interrupts & (1 << 1)) + qemu_irq_raise(s->dma->irq); + s->enable = 0; + return; + } + + s->dma->phys_framebuffer[0] = s->dma->src_f1_top; + s->dma->phys_framebuffer[1] = s->dma->src_f2_top; + + if (s->plm != 2 && !s->palette_done) { + cpu_physical_memory_read( + s->dma->phys_framebuffer[s->dma->current_frame], + (void *)s->palette, 0x200); + s->palette_done = 1; + omap_lcd_interrupts(s); + } +} + +static uint64_t omap_lcdc_read(void *opaque, hwaddr addr, + unsigned size) +{ + struct omap_lcd_panel_s *s = (struct omap_lcd_panel_s *) opaque; + + switch (addr) { + case 0x00: /* LCD_CONTROL */ + return (s->tft << 23) | (s->plm << 20) | + (s->tft << 7) | (s->interrupts << 3) | + (s->mono << 1) | s->enable | s->ctrl | 0xfe000c34; + + case 0x04: /* LCD_TIMING0 */ + return (s->timing[0] << 10) | (s->width - 1) | 0x0000000f; + + case 0x08: /* LCD_TIMING1 */ + return (s->timing[1] << 10) | (s->height - 1); + + case 0x0c: /* LCD_TIMING2 */ + return s->timing[2] | 0xfc000000; + + case 0x10: /* LCD_STATUS */ + return (s->palette_done << 6) | (s->sync_error << 2) | s->frame_done; + + case 0x14: /* LCD_SUBPANEL */ + return s->subpanel; + + default: + break; + } + OMAP_BAD_REG(addr); + return 0; +} + +static void omap_lcdc_write(void *opaque, hwaddr addr, + uint64_t value, unsigned size) +{ + struct omap_lcd_panel_s *s = (struct omap_lcd_panel_s *) opaque; + + switch (addr) { + case 0x00: /* LCD_CONTROL */ + s->plm = (value >> 20) & 3; + s->tft = (value >> 7) & 1; + s->interrupts = (value >> 3) & 3; + s->mono = (value >> 1) & 1; + s->ctrl = value & 0x01cff300; + if (s->enable != (value & 1)) { + s->enable = value & 1; + omap_lcd_update(s); + } + break; + + case 0x04: /* LCD_TIMING0 */ + s->timing[0] = value >> 10; + s->width = (value & 0x3ff) + 1; + break; + + case 0x08: /* LCD_TIMING1 */ + s->timing[1] = value >> 10; + s->height = (value & 0x3ff) + 1; + break; + + case 0x0c: /* LCD_TIMING2 */ + s->timing[2] = value; + break; + + case 0x10: /* LCD_STATUS */ + break; + + case 0x14: /* LCD_SUBPANEL */ + s->subpanel = value & 0xa1ffffff; + break; + + default: + OMAP_BAD_REG(addr); + } +} + +static const MemoryRegionOps omap_lcdc_ops = { + .read = omap_lcdc_read, + .write = omap_lcdc_write, + .endianness = DEVICE_NATIVE_ENDIAN, +}; + +void omap_lcdc_reset(struct omap_lcd_panel_s *s) +{ + s->dma->current_frame = -1; + s->plm = 0; + s->tft = 0; + s->mono = 0; + s->enable = 0; + s->width = 0; + s->height = 0; + s->interrupts = 0; + s->timing[0] = 0; + s->timing[1] = 0; + s->timing[2] = 0; + s->subpanel = 0; + s->palette_done = 0; + s->frame_done = 0; + s->sync_error = 0; + s->invalidate = 1; + s->subpanel = 0; + s->ctrl = 0; +} + +struct omap_lcd_panel_s *omap_lcdc_init(MemoryRegion *sysmem, + hwaddr base, + qemu_irq irq, + struct omap_dma_lcd_channel_s *dma, + omap_clk clk) +{ + struct omap_lcd_panel_s *s = (struct omap_lcd_panel_s *) + g_malloc0(sizeof(struct omap_lcd_panel_s)); + + s->irq = irq; + s->dma = dma; + s->sysmem = sysmem; + omap_lcdc_reset(s); + + memory_region_init_io(&s->iomem, &omap_lcdc_ops, s, "omap.lcdc", 0x100); + memory_region_add_subregion(sysmem, base, &s->iomem); + + s->con = graphic_console_init(omap_update_display, + omap_invalidate_display, + omap_screen_dump, NULL, s); + + return s; +} diff --git a/hw/display/pl110.c b/hw/display/pl110.c new file mode 100644 index 0000000000..295434eded --- /dev/null +++ b/hw/display/pl110.c @@ -0,0 +1,533 @@ +/* + * Arm PrimeCell PL110 Color LCD Controller + * + * Copyright (c) 2005-2009 CodeSourcery. + * Written by Paul Brook + * + * This code is licensed under the GNU LGPL + */ + +#include "hw/sysbus.h" +#include "ui/console.h" +#include "framebuffer.h" +#include "ui/pixel_ops.h" + +#define PL110_CR_EN 0x001 +#define PL110_CR_BGR 0x100 +#define PL110_CR_BEBO 0x200 +#define PL110_CR_BEPO 0x400 +#define PL110_CR_PWR 0x800 + +enum pl110_bppmode +{ + BPP_1, + BPP_2, + BPP_4, + BPP_8, + BPP_16, + BPP_32, + BPP_16_565, /* PL111 only */ + BPP_12 /* PL111 only */ +}; + + +/* The Versatile/PB uses a slightly modified PL110 controller. */ +enum pl110_version +{ + PL110, + PL110_VERSATILE, + PL111 +}; + +typedef struct { + SysBusDevice busdev; + MemoryRegion iomem; + QemuConsole *con; + + int version; + uint32_t timing[4]; + uint32_t cr; + uint32_t upbase; + uint32_t lpbase; + uint32_t int_status; + uint32_t int_mask; + int cols; + int rows; + enum pl110_bppmode bpp; + int invalidate; + uint32_t mux_ctrl; + uint32_t palette[256]; + uint32_t raw_palette[128]; + qemu_irq irq; +} pl110_state; + +static int vmstate_pl110_post_load(void *opaque, int version_id); + +static const VMStateDescription vmstate_pl110 = { + .name = "pl110", + .version_id = 2, + .minimum_version_id = 1, + .post_load = vmstate_pl110_post_load, + .fields = (VMStateField[]) { + VMSTATE_INT32(version, pl110_state), + VMSTATE_UINT32_ARRAY(timing, pl110_state, 4), + VMSTATE_UINT32(cr, pl110_state), + VMSTATE_UINT32(upbase, pl110_state), + VMSTATE_UINT32(lpbase, pl110_state), + VMSTATE_UINT32(int_status, pl110_state), + VMSTATE_UINT32(int_mask, pl110_state), + VMSTATE_INT32(cols, pl110_state), + VMSTATE_INT32(rows, pl110_state), + VMSTATE_UINT32(bpp, pl110_state), + VMSTATE_INT32(invalidate, pl110_state), + VMSTATE_UINT32_ARRAY(palette, pl110_state, 256), + VMSTATE_UINT32_ARRAY(raw_palette, pl110_state, 128), + VMSTATE_UINT32_V(mux_ctrl, pl110_state, 2), + VMSTATE_END_OF_LIST() + } +}; + +static const unsigned char pl110_id[] = +{ 0x10, 0x11, 0x04, 0x00, 0x0d, 0xf0, 0x05, 0xb1 }; + +/* The Arm documentation (DDI0224C) says the CLDC on the Versatile board + has a different ID. However Linux only looks for the normal ID. */ +#if 0 +static const unsigned char pl110_versatile_id[] = +{ 0x93, 0x10, 0x04, 0x00, 0x0d, 0xf0, 0x05, 0xb1 }; +#else +#define pl110_versatile_id pl110_id +#endif + +static const unsigned char pl111_id[] = { + 0x11, 0x11, 0x24, 0x00, 0x0d, 0xf0, 0x05, 0xb1 +}; + +/* Indexed by pl110_version */ +static const unsigned char *idregs[] = { + pl110_id, + pl110_versatile_id, + pl111_id +}; + +#define BITS 8 +#include "pl110_template.h" +#define BITS 15 +#include "pl110_template.h" +#define BITS 16 +#include "pl110_template.h" +#define BITS 24 +#include "pl110_template.h" +#define BITS 32 +#include "pl110_template.h" + +static int pl110_enabled(pl110_state *s) +{ + return (s->cr & PL110_CR_EN) && (s->cr & PL110_CR_PWR); +} + +static void pl110_update_display(void *opaque) +{ + pl110_state *s = (pl110_state *)opaque; + DisplaySurface *surface = qemu_console_surface(s->con); + drawfn* fntable; + drawfn fn; + int dest_width; + int src_width; + int bpp_offset; + int first; + int last; + + if (!pl110_enabled(s)) + return; + + switch (surface_bits_per_pixel(surface)) { + case 0: + return; + case 8: + fntable = pl110_draw_fn_8; + dest_width = 1; + break; + case 15: + fntable = pl110_draw_fn_15; + dest_width = 2; + break; + case 16: + fntable = pl110_draw_fn_16; + dest_width = 2; + break; + case 24: + fntable = pl110_draw_fn_24; + dest_width = 3; + break; + case 32: + fntable = pl110_draw_fn_32; + dest_width = 4; + break; + default: + fprintf(stderr, "pl110: Bad color depth\n"); + exit(1); + } + if (s->cr & PL110_CR_BGR) + bpp_offset = 0; + else + bpp_offset = 24; + + if ((s->version != PL111) && (s->bpp == BPP_16)) { + /* The PL110's native 16 bit mode is 5551; however + * most boards with a PL110 implement an external + * mux which allows bits to be reshuffled to give + * 565 format. The mux is typically controlled by + * an external system register. + * This is controlled by a GPIO input pin + * so boards can wire it up to their register. + * + * The PL111 straightforwardly implements both + * 5551 and 565 under control of the bpp field + * in the LCDControl register. + */ + switch (s->mux_ctrl) { + case 3: /* 565 BGR */ + bpp_offset = (BPP_16_565 - BPP_16); + break; + case 1: /* 5551 */ + break; + case 0: /* 888; also if we have loaded vmstate from an old version */ + case 2: /* 565 RGB */ + default: + /* treat as 565 but honour BGR bit */ + bpp_offset += (BPP_16_565 - BPP_16); + break; + } + } + + if (s->cr & PL110_CR_BEBO) + fn = fntable[s->bpp + 8 + bpp_offset]; + else if (s->cr & PL110_CR_BEPO) + fn = fntable[s->bpp + 16 + bpp_offset]; + else + fn = fntable[s->bpp + bpp_offset]; + + src_width = s->cols; + switch (s->bpp) { + case BPP_1: + src_width >>= 3; + break; + case BPP_2: + src_width >>= 2; + break; + case BPP_4: + src_width >>= 1; + break; + case BPP_8: + break; + case BPP_16: + case BPP_16_565: + case BPP_12: + src_width <<= 1; + break; + case BPP_32: + src_width <<= 2; + break; + } + dest_width *= s->cols; + first = 0; + framebuffer_update_display(surface, sysbus_address_space(&s->busdev), + s->upbase, s->cols, s->rows, + src_width, dest_width, 0, + s->invalidate, + fn, s->palette, + &first, &last); + if (first >= 0) { + dpy_gfx_update(s->con, 0, first, s->cols, last - first + 1); + } + s->invalidate = 0; +} + +static void pl110_invalidate_display(void * opaque) +{ + pl110_state *s = (pl110_state *)opaque; + s->invalidate = 1; + if (pl110_enabled(s)) { + qemu_console_resize(s->con, s->cols, s->rows); + } +} + +static void pl110_update_palette(pl110_state *s, int n) +{ + DisplaySurface *surface = qemu_console_surface(s->con); + int i; + uint32_t raw; + unsigned int r, g, b; + + raw = s->raw_palette[n]; + n <<= 1; + for (i = 0; i < 2; i++) { + r = (raw & 0x1f) << 3; + raw >>= 5; + g = (raw & 0x1f) << 3; + raw >>= 5; + b = (raw & 0x1f) << 3; + /* The I bit is ignored. */ + raw >>= 6; + switch (surface_bits_per_pixel(surface)) { + case 8: + s->palette[n] = rgb_to_pixel8(r, g, b); + break; + case 15: + s->palette[n] = rgb_to_pixel15(r, g, b); + break; + case 16: + s->palette[n] = rgb_to_pixel16(r, g, b); + break; + case 24: + case 32: + s->palette[n] = rgb_to_pixel32(r, g, b); + break; + } + n++; + } +} + +static void pl110_resize(pl110_state *s, int width, int height) +{ + if (width != s->cols || height != s->rows) { + if (pl110_enabled(s)) { + qemu_console_resize(s->con, width, height); + } + } + s->cols = width; + s->rows = height; +} + +/* Update interrupts. */ +static void pl110_update(pl110_state *s) +{ + /* TODO: Implement interrupts. */ +} + +static uint64_t pl110_read(void *opaque, hwaddr offset, + unsigned size) +{ + pl110_state *s = (pl110_state *)opaque; + + if (offset >= 0xfe0 && offset < 0x1000) { + return idregs[s->version][(offset - 0xfe0) >> 2]; + } + if (offset >= 0x200 && offset < 0x400) { + return s->raw_palette[(offset - 0x200) >> 2]; + } + switch (offset >> 2) { + case 0: /* LCDTiming0 */ + return s->timing[0]; + case 1: /* LCDTiming1 */ + return s->timing[1]; + case 2: /* LCDTiming2 */ + return s->timing[2]; + case 3: /* LCDTiming3 */ + return s->timing[3]; + case 4: /* LCDUPBASE */ + return s->upbase; + case 5: /* LCDLPBASE */ + return s->lpbase; + case 6: /* LCDIMSC */ + if (s->version != PL110) { + return s->cr; + } + return s->int_mask; + case 7: /* LCDControl */ + if (s->version != PL110) { + return s->int_mask; + } + return s->cr; + case 8: /* LCDRIS */ + return s->int_status; + case 9: /* LCDMIS */ + return s->int_status & s->int_mask; + case 11: /* LCDUPCURR */ + /* TODO: Implement vertical refresh. */ + return s->upbase; + case 12: /* LCDLPCURR */ + return s->lpbase; + default: + qemu_log_mask(LOG_GUEST_ERROR, + "pl110_read: Bad offset %x\n", (int)offset); + return 0; + } +} + +static void pl110_write(void *opaque, hwaddr offset, + uint64_t val, unsigned size) +{ + pl110_state *s = (pl110_state *)opaque; + int n; + + /* For simplicity invalidate the display whenever a control register + is written to. */ + s->invalidate = 1; + if (offset >= 0x200 && offset < 0x400) { + /* Palette. */ + n = (offset - 0x200) >> 2; + s->raw_palette[(offset - 0x200) >> 2] = val; + pl110_update_palette(s, n); + return; + } + switch (offset >> 2) { + case 0: /* LCDTiming0 */ + s->timing[0] = val; + n = ((val & 0xfc) + 4) * 4; + pl110_resize(s, n, s->rows); + break; + case 1: /* LCDTiming1 */ + s->timing[1] = val; + n = (val & 0x3ff) + 1; + pl110_resize(s, s->cols, n); + break; + case 2: /* LCDTiming2 */ + s->timing[2] = val; + break; + case 3: /* LCDTiming3 */ + s->timing[3] = val; + break; + case 4: /* LCDUPBASE */ + s->upbase = val; + break; + case 5: /* LCDLPBASE */ + s->lpbase = val; + break; + case 6: /* LCDIMSC */ + if (s->version != PL110) { + goto control; + } + imsc: + s->int_mask = val; + pl110_update(s); + break; + case 7: /* LCDControl */ + if (s->version != PL110) { + goto imsc; + } + control: + s->cr = val; + s->bpp = (val >> 1) & 7; + if (pl110_enabled(s)) { + qemu_console_resize(s->con, s->cols, s->rows); + } + break; + case 10: /* LCDICR */ + s->int_status &= ~val; + pl110_update(s); + break; + default: + qemu_log_mask(LOG_GUEST_ERROR, + "pl110_write: Bad offset %x\n", (int)offset); + } +} + +static const MemoryRegionOps pl110_ops = { + .read = pl110_read, + .write = pl110_write, + .endianness = DEVICE_NATIVE_ENDIAN, +}; + +static void pl110_mux_ctrl_set(void *opaque, int line, int level) +{ + pl110_state *s = (pl110_state *)opaque; + s->mux_ctrl = level; +} + +static int vmstate_pl110_post_load(void *opaque, int version_id) +{ + pl110_state *s = opaque; + /* Make sure we redraw, and at the right size */ + pl110_invalidate_display(s); + return 0; +} + +static int pl110_init(SysBusDevice *dev) +{ + pl110_state *s = FROM_SYSBUS(pl110_state, dev); + + memory_region_init_io(&s->iomem, &pl110_ops, s, "pl110", 0x1000); + sysbus_init_mmio(dev, &s->iomem); + sysbus_init_irq(dev, &s->irq); + qdev_init_gpio_in(&s->busdev.qdev, pl110_mux_ctrl_set, 1); + s->con = graphic_console_init(pl110_update_display, + pl110_invalidate_display, + NULL, NULL, s); + return 0; +} + +static int pl110_versatile_init(SysBusDevice *dev) +{ + pl110_state *s = FROM_SYSBUS(pl110_state, dev); + s->version = PL110_VERSATILE; + return pl110_init(dev); +} + +static int pl111_init(SysBusDevice *dev) +{ + pl110_state *s = FROM_SYSBUS(pl110_state, dev); + s->version = PL111; + return pl110_init(dev); +} + +static void pl110_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass); + + k->init = pl110_init; + dc->no_user = 1; + dc->vmsd = &vmstate_pl110; +} + +static const TypeInfo pl110_info = { + .name = "pl110", + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(pl110_state), + .class_init = pl110_class_init, +}; + +static void pl110_versatile_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass); + + k->init = pl110_versatile_init; + dc->no_user = 1; + dc->vmsd = &vmstate_pl110; +} + +static const TypeInfo pl110_versatile_info = { + .name = "pl110_versatile", + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(pl110_state), + .class_init = pl110_versatile_class_init, +}; + +static void pl111_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass); + + k->init = pl111_init; + dc->no_user = 1; + dc->vmsd = &vmstate_pl110; +} + +static const TypeInfo pl111_info = { + .name = "pl111", + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(pl110_state), + .class_init = pl111_class_init, +}; + +static void pl110_register_types(void) +{ + type_register_static(&pl110_info); + type_register_static(&pl110_versatile_info); + type_register_static(&pl111_info); +} + +type_init(pl110_register_types) diff --git a/hw/display/pl110_template.h b/hw/display/pl110_template.h new file mode 100644 index 0000000000..e738e4a241 --- /dev/null +++ b/hw/display/pl110_template.h @@ -0,0 +1,395 @@ +/* + * Arm PrimeCell PL110 Color LCD Controller + * + * Copyright (c) 2005 CodeSourcery, LLC. + * Written by Paul Brook + * + * This code is licensed under the GNU LGPL + * + * Framebuffer format conversion routines. + */ + +#ifndef ORDER + +#if BITS == 8 +#define COPY_PIXEL(to, from) *(to++) = from +#elif BITS == 15 || BITS == 16 +#define COPY_PIXEL(to, from) *(uint16_t *)to = from; to += 2; +#elif BITS == 24 +#define COPY_PIXEL(to, from) \ + *(to++) = from; *(to++) = (from) >> 8; *(to++) = (from) >> 16 +#elif BITS == 32 +#define COPY_PIXEL(to, from) *(uint32_t *)to = from; to += 4; +#else +#error unknown bit depth +#endif + +#undef RGB +#define BORDER bgr +#define ORDER 0 +#include "pl110_template.h" +#define ORDER 1 +#include "pl110_template.h" +#define ORDER 2 +#include "pl110_template.h" +#undef BORDER +#define RGB +#define BORDER rgb +#define ORDER 0 +#include "pl110_template.h" +#define ORDER 1 +#include "pl110_template.h" +#define ORDER 2 +#include "pl110_template.h" +#undef BORDER + +static drawfn glue(pl110_draw_fn_,BITS)[48] = +{ + glue(pl110_draw_line1_lblp_bgr,BITS), + glue(pl110_draw_line2_lblp_bgr,BITS), + glue(pl110_draw_line4_lblp_bgr,BITS), + glue(pl110_draw_line8_lblp_bgr,BITS), + glue(pl110_draw_line16_555_lblp_bgr,BITS), + glue(pl110_draw_line32_lblp_bgr,BITS), + glue(pl110_draw_line16_lblp_bgr,BITS), + glue(pl110_draw_line12_lblp_bgr,BITS), + + glue(pl110_draw_line1_bbbp_bgr,BITS), + glue(pl110_draw_line2_bbbp_bgr,BITS), + glue(pl110_draw_line4_bbbp_bgr,BITS), + glue(pl110_draw_line8_bbbp_bgr,BITS), + glue(pl110_draw_line16_555_bbbp_bgr,BITS), + glue(pl110_draw_line32_bbbp_bgr,BITS), + glue(pl110_draw_line16_bbbp_bgr,BITS), + glue(pl110_draw_line12_bbbp_bgr,BITS), + + glue(pl110_draw_line1_lbbp_bgr,BITS), + glue(pl110_draw_line2_lbbp_bgr,BITS), + glue(pl110_draw_line4_lbbp_bgr,BITS), + glue(pl110_draw_line8_lbbp_bgr,BITS), + glue(pl110_draw_line16_555_lbbp_bgr,BITS), + glue(pl110_draw_line32_lbbp_bgr,BITS), + glue(pl110_draw_line16_lbbp_bgr,BITS), + glue(pl110_draw_line12_lbbp_bgr,BITS), + + glue(pl110_draw_line1_lblp_rgb,BITS), + glue(pl110_draw_line2_lblp_rgb,BITS), + glue(pl110_draw_line4_lblp_rgb,BITS), + glue(pl110_draw_line8_lblp_rgb,BITS), + glue(pl110_draw_line16_555_lblp_rgb,BITS), + glue(pl110_draw_line32_lblp_rgb,BITS), + glue(pl110_draw_line16_lblp_rgb,BITS), + glue(pl110_draw_line12_lblp_rgb,BITS), + + glue(pl110_draw_line1_bbbp_rgb,BITS), + glue(pl110_draw_line2_bbbp_rgb,BITS), + glue(pl110_draw_line4_bbbp_rgb,BITS), + glue(pl110_draw_line8_bbbp_rgb,BITS), + glue(pl110_draw_line16_555_bbbp_rgb,BITS), + glue(pl110_draw_line32_bbbp_rgb,BITS), + glue(pl110_draw_line16_bbbp_rgb,BITS), + glue(pl110_draw_line12_bbbp_rgb,BITS), + + glue(pl110_draw_line1_lbbp_rgb,BITS), + glue(pl110_draw_line2_lbbp_rgb,BITS), + glue(pl110_draw_line4_lbbp_rgb,BITS), + glue(pl110_draw_line8_lbbp_rgb,BITS), + glue(pl110_draw_line16_555_lbbp_rgb,BITS), + glue(pl110_draw_line32_lbbp_rgb,BITS), + glue(pl110_draw_line16_lbbp_rgb,BITS), + glue(pl110_draw_line12_lbbp_rgb,BITS), +}; + +#undef BITS +#undef COPY_PIXEL + +#else + +#if ORDER == 0 +#define NAME glue(glue(lblp_, BORDER), BITS) +#ifdef HOST_WORDS_BIGENDIAN +#define SWAP_WORDS 1 +#endif +#elif ORDER == 1 +#define NAME glue(glue(bbbp_, BORDER), BITS) +#ifndef HOST_WORDS_BIGENDIAN +#define SWAP_WORDS 1 +#endif +#else +#define SWAP_PIXELS 1 +#define NAME glue(glue(lbbp_, BORDER), BITS) +#ifdef HOST_WORDS_BIGENDIAN +#define SWAP_WORDS 1 +#endif +#endif + +#define FN_2(x, y) FN(x, y) FN(x+1, y) +#define FN_4(x, y) FN_2(x, y) FN_2(x+2, y) +#define FN_8(y) FN_4(0, y) FN_4(4, y) + +static void glue(pl110_draw_line1_,NAME)(void *opaque, uint8_t *d, const uint8_t *src, int width, int deststep) +{ + uint32_t *palette = opaque; + uint32_t data; + while (width > 0) { + data = *(uint32_t *)src; +#ifdef SWAP_PIXELS +#define FN(x, y) COPY_PIXEL(d, palette[(data >> (y + 7 - (x))) & 1]); +#else +#define FN(x, y) COPY_PIXEL(d, palette[(data >> ((x) + y)) & 1]); +#endif +#ifdef SWAP_WORDS + FN_8(24) + FN_8(16) + FN_8(8) + FN_8(0) +#else + FN_8(0) + FN_8(8) + FN_8(16) + FN_8(24) +#endif +#undef FN + width -= 32; + src += 4; + } +} + +static void glue(pl110_draw_line2_,NAME)(void *opaque, uint8_t *d, const uint8_t *src, int width, int deststep) +{ + uint32_t *palette = opaque; + uint32_t data; + while (width > 0) { + data = *(uint32_t *)src; +#ifdef SWAP_PIXELS +#define FN(x, y) COPY_PIXEL(d, palette[(data >> (y + 6 - (x)*2)) & 3]); +#else +#define FN(x, y) COPY_PIXEL(d, palette[(data >> ((x)*2 + y)) & 3]); +#endif +#ifdef SWAP_WORDS + FN_4(0, 24) + FN_4(0, 16) + FN_4(0, 8) + FN_4(0, 0) +#else + FN_4(0, 0) + FN_4(0, 8) + FN_4(0, 16) + FN_4(0, 24) +#endif +#undef FN + width -= 16; + src += 4; + } +} + +static void glue(pl110_draw_line4_,NAME)(void *opaque, uint8_t *d, const uint8_t *src, int width, int deststep) +{ + uint32_t *palette = opaque; + uint32_t data; + while (width > 0) { + data = *(uint32_t *)src; +#ifdef SWAP_PIXELS +#define FN(x, y) COPY_PIXEL(d, palette[(data >> (y + 4 - (x)*4)) & 0xf]); +#else +#define FN(x, y) COPY_PIXEL(d, palette[(data >> ((x)*4 + y)) & 0xf]); +#endif +#ifdef SWAP_WORDS + FN_2(0, 24) + FN_2(0, 16) + FN_2(0, 8) + FN_2(0, 0) +#else + FN_2(0, 0) + FN_2(0, 8) + FN_2(0, 16) + FN_2(0, 24) +#endif +#undef FN + width -= 8; + src += 4; + } +} + +static void glue(pl110_draw_line8_,NAME)(void *opaque, uint8_t *d, const uint8_t *src, int width, int deststep) +{ + uint32_t *palette = opaque; + uint32_t data; + while (width > 0) { + data = *(uint32_t *)src; +#define FN(x) COPY_PIXEL(d, palette[(data >> (x)) & 0xff]); +#ifdef SWAP_WORDS + FN(24) + FN(16) + FN(8) + FN(0) +#else + FN(0) + FN(8) + FN(16) + FN(24) +#endif +#undef FN + width -= 4; + src += 4; + } +} + +static void glue(pl110_draw_line16_,NAME)(void *opaque, uint8_t *d, const uint8_t *src, int width, int deststep) +{ + uint32_t data; + unsigned int r, g, b; + while (width > 0) { + data = *(uint32_t *)src; +#ifdef SWAP_WORDS + data = bswap32(data); +#endif +#ifdef RGB +#define LSB r +#define MSB b +#else +#define LSB b +#define MSB r +#endif +#if 0 + LSB = data & 0x1f; + data >>= 5; + g = data & 0x3f; + data >>= 6; + MSB = data & 0x1f; + data >>= 5; +#else + LSB = (data & 0x1f) << 3; + data >>= 5; + g = (data & 0x3f) << 2; + data >>= 6; + MSB = (data & 0x1f) << 3; + data >>= 5; +#endif + COPY_PIXEL(d, glue(rgb_to_pixel,BITS)(r, g, b)); + LSB = (data & 0x1f) << 3; + data >>= 5; + g = (data & 0x3f) << 2; + data >>= 6; + MSB = (data & 0x1f) << 3; + data >>= 5; + COPY_PIXEL(d, glue(rgb_to_pixel,BITS)(r, g, b)); +#undef MSB +#undef LSB + width -= 2; + src += 4; + } +} + +static void glue(pl110_draw_line32_,NAME)(void *opaque, uint8_t *d, const uint8_t *src, int width, int deststep) +{ + uint32_t data; + unsigned int r, g, b; + while (width > 0) { + data = *(uint32_t *)src; +#ifdef RGB +#define LSB r +#define MSB b +#else +#define LSB b +#define MSB r +#endif +#ifndef SWAP_WORDS + LSB = data & 0xff; + g = (data >> 8) & 0xff; + MSB = (data >> 16) & 0xff; +#else + LSB = (data >> 24) & 0xff; + g = (data >> 16) & 0xff; + MSB = (data >> 8) & 0xff; +#endif + COPY_PIXEL(d, glue(rgb_to_pixel,BITS)(r, g, b)); +#undef MSB +#undef LSB + width--; + src += 4; + } +} + +static void glue(pl110_draw_line16_555_,NAME)(void *opaque, uint8_t *d, const uint8_t *src, int width, int deststep) +{ + /* RGB 555 plus an intensity bit (which we ignore) */ + uint32_t data; + unsigned int r, g, b; + while (width > 0) { + data = *(uint32_t *)src; +#ifdef SWAP_WORDS + data = bswap32(data); +#endif +#ifdef RGB +#define LSB r +#define MSB b +#else +#define LSB b +#define MSB r +#endif + LSB = (data & 0x1f) << 3; + data >>= 5; + g = (data & 0x1f) << 3; + data >>= 5; + MSB = (data & 0x1f) << 3; + data >>= 5; + COPY_PIXEL(d, glue(rgb_to_pixel,BITS)(r, g, b)); + LSB = (data & 0x1f) << 3; + data >>= 5; + g = (data & 0x1f) << 3; + data >>= 5; + MSB = (data & 0x1f) << 3; + data >>= 6; + COPY_PIXEL(d, glue(rgb_to_pixel,BITS)(r, g, b)); +#undef MSB +#undef LSB + width -= 2; + src += 4; + } +} + +static void glue(pl110_draw_line12_,NAME)(void *opaque, uint8_t *d, const uint8_t *src, int width, int deststep) +{ + /* RGB 444 with 4 bits of zeroes at the top of each halfword */ + uint32_t data; + unsigned int r, g, b; + while (width > 0) { + data = *(uint32_t *)src; +#ifdef SWAP_WORDS + data = bswap32(data); +#endif +#ifdef RGB +#define LSB r +#define MSB b +#else +#define LSB b +#define MSB r +#endif + LSB = (data & 0xf) << 4; + data >>= 4; + g = (data & 0xf) << 4; + data >>= 4; + MSB = (data & 0xf) << 4; + data >>= 8; + COPY_PIXEL(d, glue(rgb_to_pixel,BITS)(r, g, b)); + LSB = (data & 0xf) << 4; + data >>= 4; + g = (data & 0xf) << 4; + data >>= 4; + MSB = (data & 0xf) << 4; + data >>= 8; + COPY_PIXEL(d, glue(rgb_to_pixel,BITS)(r, g, b)); +#undef MSB +#undef LSB + width -= 2; + src += 4; + } +} + +#undef SWAP_PIXELS +#undef NAME +#undef SWAP_WORDS +#undef ORDER + +#endif diff --git a/hw/display/pxa2xx_lcd.c b/hw/display/pxa2xx_lcd.c new file mode 100644 index 0000000000..c9bd42e023 --- /dev/null +++ b/hw/display/pxa2xx_lcd.c @@ -0,0 +1,1058 @@ +/* + * Intel XScale PXA255/270 LCDC emulation. + * + * Copyright (c) 2006 Openedhand Ltd. + * Written by Andrzej Zaborowski <balrog@zabor.org> + * + * This code is licensed under the GPLv2. + * + * Contributions after 2012-01-13 are licensed under the terms of the + * GNU GPL, version 2 or (at your option) any later version. + */ + +#include "hw/hw.h" +#include "ui/console.h" +#include "hw/arm/pxa.h" +#include "ui/pixel_ops.h" +/* FIXME: For graphic_rotate. Should probably be done in common code. */ +#include "sysemu/sysemu.h" +#include "framebuffer.h" + +struct DMAChannel { + uint32_t branch; + uint8_t up; + uint8_t palette[1024]; + uint8_t pbuffer[1024]; + void (*redraw)(PXA2xxLCDState *s, hwaddr addr, + int *miny, int *maxy); + + uint32_t descriptor; + uint32_t source; + uint32_t id; + uint32_t command; +}; + +struct PXA2xxLCDState { + MemoryRegion *sysmem; + MemoryRegion iomem; + qemu_irq irq; + int irqlevel; + + int invalidated; + QemuConsole *con; + drawfn *line_fn[2]; + int dest_width; + int xres, yres; + int pal_for; + int transp; + enum { + pxa_lcdc_2bpp = 1, + pxa_lcdc_4bpp = 2, + pxa_lcdc_8bpp = 3, + pxa_lcdc_16bpp = 4, + pxa_lcdc_18bpp = 5, + pxa_lcdc_18pbpp = 6, + pxa_lcdc_19bpp = 7, + pxa_lcdc_19pbpp = 8, + pxa_lcdc_24bpp = 9, + pxa_lcdc_25bpp = 10, + } bpp; + + uint32_t control[6]; + uint32_t status[2]; + uint32_t ovl1c[2]; + uint32_t ovl2c[2]; + uint32_t ccr; + uint32_t cmdcr; + uint32_t trgbr; + uint32_t tcr; + uint32_t liidr; + uint8_t bscntr; + + struct DMAChannel dma_ch[7]; + + qemu_irq vsync_cb; + int orientation; +}; + +typedef struct QEMU_PACKED { + uint32_t fdaddr; + uint32_t fsaddr; + uint32_t fidr; + uint32_t ldcmd; +} PXAFrameDescriptor; + +#define LCCR0 0x000 /* LCD Controller Control register 0 */ +#define LCCR1 0x004 /* LCD Controller Control register 1 */ +#define LCCR2 0x008 /* LCD Controller Control register 2 */ +#define LCCR3 0x00c /* LCD Controller Control register 3 */ +#define LCCR4 0x010 /* LCD Controller Control register 4 */ +#define LCCR5 0x014 /* LCD Controller Control register 5 */ + +#define FBR0 0x020 /* DMA Channel 0 Frame Branch register */ +#define FBR1 0x024 /* DMA Channel 1 Frame Branch register */ +#define FBR2 0x028 /* DMA Channel 2 Frame Branch register */ +#define FBR3 0x02c /* DMA Channel 3 Frame Branch register */ +#define FBR4 0x030 /* DMA Channel 4 Frame Branch register */ +#define FBR5 0x110 /* DMA Channel 5 Frame Branch register */ +#define FBR6 0x114 /* DMA Channel 6 Frame Branch register */ + +#define LCSR1 0x034 /* LCD Controller Status register 1 */ +#define LCSR0 0x038 /* LCD Controller Status register 0 */ +#define LIIDR 0x03c /* LCD Controller Interrupt ID register */ + +#define TRGBR 0x040 /* TMED RGB Seed register */ +#define TCR 0x044 /* TMED Control register */ + +#define OVL1C1 0x050 /* Overlay 1 Control register 1 */ +#define OVL1C2 0x060 /* Overlay 1 Control register 2 */ +#define OVL2C1 0x070 /* Overlay 2 Control register 1 */ +#define OVL2C2 0x080 /* Overlay 2 Control register 2 */ +#define CCR 0x090 /* Cursor Control register */ + +#define CMDCR 0x100 /* Command Control register */ +#define PRSR 0x104 /* Panel Read Status register */ + +#define PXA_LCDDMA_CHANS 7 +#define DMA_FDADR 0x00 /* Frame Descriptor Address register */ +#define DMA_FSADR 0x04 /* Frame Source Address register */ +#define DMA_FIDR 0x08 /* Frame ID register */ +#define DMA_LDCMD 0x0c /* Command register */ + +/* LCD Buffer Strength Control register */ +#define BSCNTR 0x04000054 + +/* Bitfield masks */ +#define LCCR0_ENB (1 << 0) +#define LCCR0_CMS (1 << 1) +#define LCCR0_SDS (1 << 2) +#define LCCR0_LDM (1 << 3) +#define LCCR0_SOFM0 (1 << 4) +#define LCCR0_IUM (1 << 5) +#define LCCR0_EOFM0 (1 << 6) +#define LCCR0_PAS (1 << 7) +#define LCCR0_DPD (1 << 9) +#define LCCR0_DIS (1 << 10) +#define LCCR0_QDM (1 << 11) +#define LCCR0_PDD (0xff << 12) +#define LCCR0_BSM0 (1 << 20) +#define LCCR0_OUM (1 << 21) +#define LCCR0_LCDT (1 << 22) +#define LCCR0_RDSTM (1 << 23) +#define LCCR0_CMDIM (1 << 24) +#define LCCR0_OUC (1 << 25) +#define LCCR0_LDDALT (1 << 26) +#define LCCR1_PPL(x) ((x) & 0x3ff) +#define LCCR2_LPP(x) ((x) & 0x3ff) +#define LCCR3_API (15 << 16) +#define LCCR3_BPP(x) ((((x) >> 24) & 7) | (((x) >> 26) & 8)) +#define LCCR3_PDFOR(x) (((x) >> 30) & 3) +#define LCCR4_K1(x) (((x) >> 0) & 7) +#define LCCR4_K2(x) (((x) >> 3) & 7) +#define LCCR4_K3(x) (((x) >> 6) & 7) +#define LCCR4_PALFOR(x) (((x) >> 15) & 3) +#define LCCR5_SOFM(ch) (1 << (ch - 1)) +#define LCCR5_EOFM(ch) (1 << (ch + 7)) +#define LCCR5_BSM(ch) (1 << (ch + 15)) +#define LCCR5_IUM(ch) (1 << (ch + 23)) +#define OVLC1_EN (1 << 31) +#define CCR_CEN (1 << 31) +#define FBR_BRA (1 << 0) +#define FBR_BINT (1 << 1) +#define FBR_SRCADDR (0xfffffff << 4) +#define LCSR0_LDD (1 << 0) +#define LCSR0_SOF0 (1 << 1) +#define LCSR0_BER (1 << 2) +#define LCSR0_ABC (1 << 3) +#define LCSR0_IU0 (1 << 4) +#define LCSR0_IU1 (1 << 5) +#define LCSR0_OU (1 << 6) +#define LCSR0_QD (1 << 7) +#define LCSR0_EOF0 (1 << 8) +#define LCSR0_BS0 (1 << 9) +#define LCSR0_SINT (1 << 10) +#define LCSR0_RDST (1 << 11) +#define LCSR0_CMDINT (1 << 12) +#define LCSR0_BERCH(x) (((x) & 7) << 28) +#define LCSR1_SOF(ch) (1 << (ch - 1)) +#define LCSR1_EOF(ch) (1 << (ch + 7)) +#define LCSR1_BS(ch) (1 << (ch + 15)) +#define LCSR1_IU(ch) (1 << (ch + 23)) +#define LDCMD_LENGTH(x) ((x) & 0x001ffffc) +#define LDCMD_EOFINT (1 << 21) +#define LDCMD_SOFINT (1 << 22) +#define LDCMD_PAL (1 << 26) + +/* Route internal interrupt lines to the global IC */ +static void pxa2xx_lcdc_int_update(PXA2xxLCDState *s) +{ + int level = 0; + level |= (s->status[0] & LCSR0_LDD) && !(s->control[0] & LCCR0_LDM); + level |= (s->status[0] & LCSR0_SOF0) && !(s->control[0] & LCCR0_SOFM0); + level |= (s->status[0] & LCSR0_IU0) && !(s->control[0] & LCCR0_IUM); + level |= (s->status[0] & LCSR0_IU1) && !(s->control[5] & LCCR5_IUM(1)); + level |= (s->status[0] & LCSR0_OU) && !(s->control[0] & LCCR0_OUM); + level |= (s->status[0] & LCSR0_QD) && !(s->control[0] & LCCR0_QDM); + level |= (s->status[0] & LCSR0_EOF0) && !(s->control[0] & LCCR0_EOFM0); + level |= (s->status[0] & LCSR0_BS0) && !(s->control[0] & LCCR0_BSM0); + level |= (s->status[0] & LCSR0_RDST) && !(s->control[0] & LCCR0_RDSTM); + level |= (s->status[0] & LCSR0_CMDINT) && !(s->control[0] & LCCR0_CMDIM); + level |= (s->status[1] & ~s->control[5]); + + qemu_set_irq(s->irq, !!level); + s->irqlevel = level; +} + +/* Set Branch Status interrupt high and poke associated registers */ +static inline void pxa2xx_dma_bs_set(PXA2xxLCDState *s, int ch) +{ + int unmasked; + if (ch == 0) { + s->status[0] |= LCSR0_BS0; + unmasked = !(s->control[0] & LCCR0_BSM0); + } else { + s->status[1] |= LCSR1_BS(ch); + unmasked = !(s->control[5] & LCCR5_BSM(ch)); + } + + if (unmasked) { + if (s->irqlevel) + s->status[0] |= LCSR0_SINT; + else + s->liidr = s->dma_ch[ch].id; + } +} + +/* Set Start Of Frame Status interrupt high and poke associated registers */ +static inline void pxa2xx_dma_sof_set(PXA2xxLCDState *s, int ch) +{ + int unmasked; + if (!(s->dma_ch[ch].command & LDCMD_SOFINT)) + return; + + if (ch == 0) { + s->status[0] |= LCSR0_SOF0; + unmasked = !(s->control[0] & LCCR0_SOFM0); + } else { + s->status[1] |= LCSR1_SOF(ch); + unmasked = !(s->control[5] & LCCR5_SOFM(ch)); + } + + if (unmasked) { + if (s->irqlevel) + s->status[0] |= LCSR0_SINT; + else + s->liidr = s->dma_ch[ch].id; + } +} + +/* Set End Of Frame Status interrupt high and poke associated registers */ +static inline void pxa2xx_dma_eof_set(PXA2xxLCDState *s, int ch) +{ + int unmasked; + if (!(s->dma_ch[ch].command & LDCMD_EOFINT)) + return; + + if (ch == 0) { + s->status[0] |= LCSR0_EOF0; + unmasked = !(s->control[0] & LCCR0_EOFM0); + } else { + s->status[1] |= LCSR1_EOF(ch); + unmasked = !(s->control[5] & LCCR5_EOFM(ch)); + } + + if (unmasked) { + if (s->irqlevel) + s->status[0] |= LCSR0_SINT; + else + s->liidr = s->dma_ch[ch].id; + } +} + +/* Set Bus Error Status interrupt high and poke associated registers */ +static inline void pxa2xx_dma_ber_set(PXA2xxLCDState *s, int ch) +{ + s->status[0] |= LCSR0_BERCH(ch) | LCSR0_BER; + if (s->irqlevel) + s->status[0] |= LCSR0_SINT; + else + s->liidr = s->dma_ch[ch].id; +} + +/* Set Read Status interrupt high and poke associated registers */ +static inline void pxa2xx_dma_rdst_set(PXA2xxLCDState *s) +{ + s->status[0] |= LCSR0_RDST; + if (s->irqlevel && !(s->control[0] & LCCR0_RDSTM)) + s->status[0] |= LCSR0_SINT; +} + +/* Load new Frame Descriptors from DMA */ +static void pxa2xx_descriptor_load(PXA2xxLCDState *s) +{ + PXAFrameDescriptor desc; + hwaddr descptr; + int i; + + for (i = 0; i < PXA_LCDDMA_CHANS; i ++) { + s->dma_ch[i].source = 0; + + if (!s->dma_ch[i].up) + continue; + + if (s->dma_ch[i].branch & FBR_BRA) { + descptr = s->dma_ch[i].branch & FBR_SRCADDR; + if (s->dma_ch[i].branch & FBR_BINT) + pxa2xx_dma_bs_set(s, i); + s->dma_ch[i].branch &= ~FBR_BRA; + } else + descptr = s->dma_ch[i].descriptor; + + if (!((descptr >= PXA2XX_SDRAM_BASE && descptr + + sizeof(desc) <= PXA2XX_SDRAM_BASE + ram_size) || + (descptr >= PXA2XX_INTERNAL_BASE && descptr + sizeof(desc) <= + PXA2XX_INTERNAL_BASE + PXA2XX_INTERNAL_SIZE))) { + continue; + } + + cpu_physical_memory_read(descptr, (void *)&desc, sizeof(desc)); + s->dma_ch[i].descriptor = tswap32(desc.fdaddr); + s->dma_ch[i].source = tswap32(desc.fsaddr); + s->dma_ch[i].id = tswap32(desc.fidr); + s->dma_ch[i].command = tswap32(desc.ldcmd); + } +} + +static uint64_t pxa2xx_lcdc_read(void *opaque, hwaddr offset, + unsigned size) +{ + PXA2xxLCDState *s = (PXA2xxLCDState *) opaque; + int ch; + + switch (offset) { + case LCCR0: + return s->control[0]; + case LCCR1: + return s->control[1]; + case LCCR2: + return s->control[2]; + case LCCR3: + return s->control[3]; + case LCCR4: + return s->control[4]; + case LCCR5: + return s->control[5]; + + case OVL1C1: + return s->ovl1c[0]; + case OVL1C2: + return s->ovl1c[1]; + case OVL2C1: + return s->ovl2c[0]; + case OVL2C2: + return s->ovl2c[1]; + + case CCR: + return s->ccr; + + case CMDCR: + return s->cmdcr; + + case TRGBR: + return s->trgbr; + case TCR: + return s->tcr; + + case 0x200 ... 0x1000: /* DMA per-channel registers */ + ch = (offset - 0x200) >> 4; + if (!(ch >= 0 && ch < PXA_LCDDMA_CHANS)) + goto fail; + + switch (offset & 0xf) { + case DMA_FDADR: + return s->dma_ch[ch].descriptor; + case DMA_FSADR: + return s->dma_ch[ch].source; + case DMA_FIDR: + return s->dma_ch[ch].id; + case DMA_LDCMD: + return s->dma_ch[ch].command; + default: + goto fail; + } + + case FBR0: + return s->dma_ch[0].branch; + case FBR1: + return s->dma_ch[1].branch; + case FBR2: + return s->dma_ch[2].branch; + case FBR3: + return s->dma_ch[3].branch; + case FBR4: + return s->dma_ch[4].branch; + case FBR5: + return s->dma_ch[5].branch; + case FBR6: + return s->dma_ch[6].branch; + + case BSCNTR: + return s->bscntr; + + case PRSR: + return 0; + + case LCSR0: + return s->status[0]; + case LCSR1: + return s->status[1]; + case LIIDR: + return s->liidr; + + default: + fail: + hw_error("%s: Bad offset " REG_FMT "\n", __FUNCTION__, offset); + } + + return 0; +} + +static void pxa2xx_lcdc_write(void *opaque, hwaddr offset, + uint64_t value, unsigned size) +{ + PXA2xxLCDState *s = (PXA2xxLCDState *) opaque; + int ch; + + switch (offset) { + case LCCR0: + /* ACK Quick Disable done */ + if ((s->control[0] & LCCR0_ENB) && !(value & LCCR0_ENB)) + s->status[0] |= LCSR0_QD; + + if (!(s->control[0] & LCCR0_LCDT) && (value & LCCR0_LCDT)) + printf("%s: internal frame buffer unsupported\n", __FUNCTION__); + + if ((s->control[3] & LCCR3_API) && + (value & LCCR0_ENB) && !(value & LCCR0_LCDT)) + s->status[0] |= LCSR0_ABC; + + s->control[0] = value & 0x07ffffff; + pxa2xx_lcdc_int_update(s); + + s->dma_ch[0].up = !!(value & LCCR0_ENB); + s->dma_ch[1].up = (s->ovl1c[0] & OVLC1_EN) || (value & LCCR0_SDS); + break; + + case LCCR1: + s->control[1] = value; + break; + + case LCCR2: + s->control[2] = value; + break; + + case LCCR3: + s->control[3] = value & 0xefffffff; + s->bpp = LCCR3_BPP(value); + break; + + case LCCR4: + s->control[4] = value & 0x83ff81ff; + break; + + case LCCR5: + s->control[5] = value & 0x3f3f3f3f; + break; + + case OVL1C1: + if (!(s->ovl1c[0] & OVLC1_EN) && (value & OVLC1_EN)) + printf("%s: Overlay 1 not supported\n", __FUNCTION__); + + s->ovl1c[0] = value & 0x80ffffff; + s->dma_ch[1].up = (value & OVLC1_EN) || (s->control[0] & LCCR0_SDS); + break; + + case OVL1C2: + s->ovl1c[1] = value & 0x000fffff; + break; + + case OVL2C1: + if (!(s->ovl2c[0] & OVLC1_EN) && (value & OVLC1_EN)) + printf("%s: Overlay 2 not supported\n", __FUNCTION__); + + s->ovl2c[0] = value & 0x80ffffff; + s->dma_ch[2].up = !!(value & OVLC1_EN); + s->dma_ch[3].up = !!(value & OVLC1_EN); + s->dma_ch[4].up = !!(value & OVLC1_EN); + break; + + case OVL2C2: + s->ovl2c[1] = value & 0x007fffff; + break; + + case CCR: + if (!(s->ccr & CCR_CEN) && (value & CCR_CEN)) + printf("%s: Hardware cursor unimplemented\n", __FUNCTION__); + + s->ccr = value & 0x81ffffe7; + s->dma_ch[5].up = !!(value & CCR_CEN); + break; + + case CMDCR: + s->cmdcr = value & 0xff; + break; + + case TRGBR: + s->trgbr = value & 0x00ffffff; + break; + + case TCR: + s->tcr = value & 0x7fff; + break; + + case 0x200 ... 0x1000: /* DMA per-channel registers */ + ch = (offset - 0x200) >> 4; + if (!(ch >= 0 && ch < PXA_LCDDMA_CHANS)) + goto fail; + + switch (offset & 0xf) { + case DMA_FDADR: + s->dma_ch[ch].descriptor = value & 0xfffffff0; + break; + + default: + goto fail; + } + break; + + case FBR0: + s->dma_ch[0].branch = value & 0xfffffff3; + break; + case FBR1: + s->dma_ch[1].branch = value & 0xfffffff3; + break; + case FBR2: + s->dma_ch[2].branch = value & 0xfffffff3; + break; + case FBR3: + s->dma_ch[3].branch = value & 0xfffffff3; + break; + case FBR4: + s->dma_ch[4].branch = value & 0xfffffff3; + break; + case FBR5: + s->dma_ch[5].branch = value & 0xfffffff3; + break; + case FBR6: + s->dma_ch[6].branch = value & 0xfffffff3; + break; + + case BSCNTR: + s->bscntr = value & 0xf; + break; + + case PRSR: + break; + + case LCSR0: + s->status[0] &= ~(value & 0xfff); + if (value & LCSR0_BER) + s->status[0] &= ~LCSR0_BERCH(7); + break; + + case LCSR1: + s->status[1] &= ~(value & 0x3e3f3f); + break; + + default: + fail: + hw_error("%s: Bad offset " REG_FMT "\n", __FUNCTION__, offset); + } +} + +static const MemoryRegionOps pxa2xx_lcdc_ops = { + .read = pxa2xx_lcdc_read, + .write = pxa2xx_lcdc_write, + .endianness = DEVICE_NATIVE_ENDIAN, +}; + +/* Load new palette for a given DMA channel, convert to internal format */ +static void pxa2xx_palette_parse(PXA2xxLCDState *s, int ch, int bpp) +{ + DisplaySurface *surface = qemu_console_surface(s->con); + int i, n, format, r, g, b, alpha; + uint32_t *dest; + uint8_t *src; + s->pal_for = LCCR4_PALFOR(s->control[4]); + format = s->pal_for; + + switch (bpp) { + case pxa_lcdc_2bpp: + n = 4; + break; + case pxa_lcdc_4bpp: + n = 16; + break; + case pxa_lcdc_8bpp: + n = 256; + break; + default: + format = 0; + return; + } + + src = (uint8_t *) s->dma_ch[ch].pbuffer; + dest = (uint32_t *) s->dma_ch[ch].palette; + alpha = r = g = b = 0; + + for (i = 0; i < n; i ++) { + switch (format) { + case 0: /* 16 bpp, no transparency */ + alpha = 0; + if (s->control[0] & LCCR0_CMS) { + r = g = b = *(uint16_t *) src & 0xff; + } + else { + r = (*(uint16_t *) src & 0xf800) >> 8; + g = (*(uint16_t *) src & 0x07e0) >> 3; + b = (*(uint16_t *) src & 0x001f) << 3; + } + src += 2; + break; + case 1: /* 16 bpp plus transparency */ + alpha = *(uint16_t *) src & (1 << 24); + if (s->control[0] & LCCR0_CMS) + r = g = b = *(uint16_t *) src & 0xff; + else { + r = (*(uint16_t *) src & 0xf800) >> 8; + g = (*(uint16_t *) src & 0x07e0) >> 3; + b = (*(uint16_t *) src & 0x001f) << 3; + } + src += 2; + break; + case 2: /* 18 bpp plus transparency */ + alpha = *(uint32_t *) src & (1 << 24); + if (s->control[0] & LCCR0_CMS) + r = g = b = *(uint32_t *) src & 0xff; + else { + r = (*(uint32_t *) src & 0xf80000) >> 16; + g = (*(uint32_t *) src & 0x00fc00) >> 8; + b = (*(uint32_t *) src & 0x0000f8); + } + src += 4; + break; + case 3: /* 24 bpp plus transparency */ + alpha = *(uint32_t *) src & (1 << 24); + if (s->control[0] & LCCR0_CMS) + r = g = b = *(uint32_t *) src & 0xff; + else { + r = (*(uint32_t *) src & 0xff0000) >> 16; + g = (*(uint32_t *) src & 0x00ff00) >> 8; + b = (*(uint32_t *) src & 0x0000ff); + } + src += 4; + break; + } + switch (surface_bits_per_pixel(surface)) { + case 8: + *dest = rgb_to_pixel8(r, g, b) | alpha; + break; + case 15: + *dest = rgb_to_pixel15(r, g, b) | alpha; + break; + case 16: + *dest = rgb_to_pixel16(r, g, b) | alpha; + break; + case 24: + *dest = rgb_to_pixel24(r, g, b) | alpha; + break; + case 32: + *dest = rgb_to_pixel32(r, g, b) | alpha; + break; + } + dest ++; + } +} + +static void pxa2xx_lcdc_dma0_redraw_rot0(PXA2xxLCDState *s, + hwaddr addr, int *miny, int *maxy) +{ + DisplaySurface *surface = qemu_console_surface(s->con); + int src_width, dest_width; + drawfn fn = NULL; + if (s->dest_width) + fn = s->line_fn[s->transp][s->bpp]; + if (!fn) + return; + + src_width = (s->xres + 3) & ~3; /* Pad to a 4 pixels multiple */ + if (s->bpp == pxa_lcdc_19pbpp || s->bpp == pxa_lcdc_18pbpp) + src_width *= 3; + else if (s->bpp > pxa_lcdc_16bpp) + src_width *= 4; + else if (s->bpp > pxa_lcdc_8bpp) + src_width *= 2; + + dest_width = s->xres * s->dest_width; + *miny = 0; + framebuffer_update_display(surface, s->sysmem, + addr, s->xres, s->yres, + src_width, dest_width, s->dest_width, + s->invalidated, + fn, s->dma_ch[0].palette, miny, maxy); +} + +static void pxa2xx_lcdc_dma0_redraw_rot90(PXA2xxLCDState *s, + hwaddr addr, int *miny, int *maxy) +{ + DisplaySurface *surface = qemu_console_surface(s->con); + int src_width, dest_width; + drawfn fn = NULL; + if (s->dest_width) + fn = s->line_fn[s->transp][s->bpp]; + if (!fn) + return; + + src_width = (s->xres + 3) & ~3; /* Pad to a 4 pixels multiple */ + if (s->bpp == pxa_lcdc_19pbpp || s->bpp == pxa_lcdc_18pbpp) + src_width *= 3; + else if (s->bpp > pxa_lcdc_16bpp) + src_width *= 4; + else if (s->bpp > pxa_lcdc_8bpp) + src_width *= 2; + + dest_width = s->yres * s->dest_width; + *miny = 0; + framebuffer_update_display(surface, s->sysmem, + addr, s->xres, s->yres, + src_width, s->dest_width, -dest_width, + s->invalidated, + fn, s->dma_ch[0].palette, + miny, maxy); +} + +static void pxa2xx_lcdc_dma0_redraw_rot180(PXA2xxLCDState *s, + hwaddr addr, int *miny, int *maxy) +{ + DisplaySurface *surface = qemu_console_surface(s->con); + int src_width, dest_width; + drawfn fn = NULL; + if (s->dest_width) { + fn = s->line_fn[s->transp][s->bpp]; + } + if (!fn) { + return; + } + + src_width = (s->xres + 3) & ~3; /* Pad to a 4 pixels multiple */ + if (s->bpp == pxa_lcdc_19pbpp || s->bpp == pxa_lcdc_18pbpp) { + src_width *= 3; + } else if (s->bpp > pxa_lcdc_16bpp) { + src_width *= 4; + } else if (s->bpp > pxa_lcdc_8bpp) { + src_width *= 2; + } + + dest_width = s->xres * s->dest_width; + *miny = 0; + framebuffer_update_display(surface, s->sysmem, + addr, s->xres, s->yres, + src_width, -dest_width, -s->dest_width, + s->invalidated, + fn, s->dma_ch[0].palette, miny, maxy); +} + +static void pxa2xx_lcdc_dma0_redraw_rot270(PXA2xxLCDState *s, + hwaddr addr, int *miny, int *maxy) +{ + DisplaySurface *surface = qemu_console_surface(s->con); + int src_width, dest_width; + drawfn fn = NULL; + if (s->dest_width) { + fn = s->line_fn[s->transp][s->bpp]; + } + if (!fn) { + return; + } + + src_width = (s->xres + 3) & ~3; /* Pad to a 4 pixels multiple */ + if (s->bpp == pxa_lcdc_19pbpp || s->bpp == pxa_lcdc_18pbpp) { + src_width *= 3; + } else if (s->bpp > pxa_lcdc_16bpp) { + src_width *= 4; + } else if (s->bpp > pxa_lcdc_8bpp) { + src_width *= 2; + } + + dest_width = s->yres * s->dest_width; + *miny = 0; + framebuffer_update_display(surface, s->sysmem, + addr, s->xres, s->yres, + src_width, -s->dest_width, dest_width, + s->invalidated, + fn, s->dma_ch[0].palette, + miny, maxy); +} + +static void pxa2xx_lcdc_resize(PXA2xxLCDState *s) +{ + int width, height; + if (!(s->control[0] & LCCR0_ENB)) + return; + + width = LCCR1_PPL(s->control[1]) + 1; + height = LCCR2_LPP(s->control[2]) + 1; + + if (width != s->xres || height != s->yres) { + if (s->orientation == 90 || s->orientation == 270) { + qemu_console_resize(s->con, height, width); + } else { + qemu_console_resize(s->con, width, height); + } + s->invalidated = 1; + s->xres = width; + s->yres = height; + } +} + +static void pxa2xx_update_display(void *opaque) +{ + PXA2xxLCDState *s = (PXA2xxLCDState *) opaque; + hwaddr fbptr; + int miny, maxy; + int ch; + if (!(s->control[0] & LCCR0_ENB)) + return; + + pxa2xx_descriptor_load(s); + + pxa2xx_lcdc_resize(s); + miny = s->yres; + maxy = 0; + s->transp = s->dma_ch[2].up || s->dma_ch[3].up; + /* Note: With overlay planes the order depends on LCCR0 bit 25. */ + for (ch = 0; ch < PXA_LCDDMA_CHANS; ch ++) + if (s->dma_ch[ch].up) { + if (!s->dma_ch[ch].source) { + pxa2xx_dma_ber_set(s, ch); + continue; + } + fbptr = s->dma_ch[ch].source; + if (!((fbptr >= PXA2XX_SDRAM_BASE && + fbptr <= PXA2XX_SDRAM_BASE + ram_size) || + (fbptr >= PXA2XX_INTERNAL_BASE && + fbptr <= PXA2XX_INTERNAL_BASE + PXA2XX_INTERNAL_SIZE))) { + pxa2xx_dma_ber_set(s, ch); + continue; + } + + if (s->dma_ch[ch].command & LDCMD_PAL) { + cpu_physical_memory_read(fbptr, s->dma_ch[ch].pbuffer, + MAX(LDCMD_LENGTH(s->dma_ch[ch].command), + sizeof(s->dma_ch[ch].pbuffer))); + pxa2xx_palette_parse(s, ch, s->bpp); + } else { + /* Do we need to reparse palette */ + if (LCCR4_PALFOR(s->control[4]) != s->pal_for) + pxa2xx_palette_parse(s, ch, s->bpp); + + /* ACK frame start */ + pxa2xx_dma_sof_set(s, ch); + + s->dma_ch[ch].redraw(s, fbptr, &miny, &maxy); + s->invalidated = 0; + + /* ACK frame completed */ + pxa2xx_dma_eof_set(s, ch); + } + } + + if (s->control[0] & LCCR0_DIS) { + /* ACK last frame completed */ + s->control[0] &= ~LCCR0_ENB; + s->status[0] |= LCSR0_LDD; + } + + if (miny >= 0) { + switch (s->orientation) { + case 0: + dpy_gfx_update(s->con, 0, miny, s->xres, maxy - miny + 1); + break; + case 90: + dpy_gfx_update(s->con, miny, 0, maxy - miny + 1, s->xres); + break; + case 180: + maxy = s->yres - maxy - 1; + miny = s->yres - miny - 1; + dpy_gfx_update(s->con, 0, maxy, s->xres, miny - maxy + 1); + break; + case 270: + maxy = s->yres - maxy - 1; + miny = s->yres - miny - 1; + dpy_gfx_update(s->con, maxy, 0, miny - maxy + 1, s->xres); + break; + } + } + pxa2xx_lcdc_int_update(s); + + qemu_irq_raise(s->vsync_cb); +} + +static void pxa2xx_invalidate_display(void *opaque) +{ + PXA2xxLCDState *s = (PXA2xxLCDState *) opaque; + s->invalidated = 1; +} + +static void pxa2xx_lcdc_orientation(void *opaque, int angle) +{ + PXA2xxLCDState *s = (PXA2xxLCDState *) opaque; + + switch (angle) { + case 0: + s->dma_ch[0].redraw = pxa2xx_lcdc_dma0_redraw_rot0; + break; + case 90: + s->dma_ch[0].redraw = pxa2xx_lcdc_dma0_redraw_rot90; + break; + case 180: + s->dma_ch[0].redraw = pxa2xx_lcdc_dma0_redraw_rot180; + break; + case 270: + s->dma_ch[0].redraw = pxa2xx_lcdc_dma0_redraw_rot270; + break; + } + + s->orientation = angle; + s->xres = s->yres = -1; + pxa2xx_lcdc_resize(s); +} + +static const VMStateDescription vmstate_dma_channel = { + .name = "dma_channel", + .version_id = 0, + .minimum_version_id = 0, + .minimum_version_id_old = 0, + .fields = (VMStateField[]) { + VMSTATE_UINT32(branch, struct DMAChannel), + VMSTATE_UINT8(up, struct DMAChannel), + VMSTATE_BUFFER(pbuffer, struct DMAChannel), + VMSTATE_UINT32(descriptor, struct DMAChannel), + VMSTATE_UINT32(source, struct DMAChannel), + VMSTATE_UINT32(id, struct DMAChannel), + VMSTATE_UINT32(command, struct DMAChannel), + VMSTATE_END_OF_LIST() + } +}; + +static int pxa2xx_lcdc_post_load(void *opaque, int version_id) +{ + PXA2xxLCDState *s = opaque; + + s->bpp = LCCR3_BPP(s->control[3]); + s->xres = s->yres = s->pal_for = -1; + + return 0; +} + +static const VMStateDescription vmstate_pxa2xx_lcdc = { + .name = "pxa2xx_lcdc", + .version_id = 0, + .minimum_version_id = 0, + .minimum_version_id_old = 0, + .post_load = pxa2xx_lcdc_post_load, + .fields = (VMStateField[]) { + VMSTATE_INT32(irqlevel, PXA2xxLCDState), + VMSTATE_INT32(transp, PXA2xxLCDState), + VMSTATE_UINT32_ARRAY(control, PXA2xxLCDState, 6), + VMSTATE_UINT32_ARRAY(status, PXA2xxLCDState, 2), + VMSTATE_UINT32_ARRAY(ovl1c, PXA2xxLCDState, 2), + VMSTATE_UINT32_ARRAY(ovl2c, PXA2xxLCDState, 2), + VMSTATE_UINT32(ccr, PXA2xxLCDState), + VMSTATE_UINT32(cmdcr, PXA2xxLCDState), + VMSTATE_UINT32(trgbr, PXA2xxLCDState), + VMSTATE_UINT32(tcr, PXA2xxLCDState), + VMSTATE_UINT32(liidr, PXA2xxLCDState), + VMSTATE_UINT8(bscntr, PXA2xxLCDState), + VMSTATE_STRUCT_ARRAY(dma_ch, PXA2xxLCDState, 7, 0, + vmstate_dma_channel, struct DMAChannel), + VMSTATE_END_OF_LIST() + } +}; + +#define BITS 8 +#include "pxa2xx_template.h" +#define BITS 15 +#include "pxa2xx_template.h" +#define BITS 16 +#include "pxa2xx_template.h" +#define BITS 24 +#include "pxa2xx_template.h" +#define BITS 32 +#include "pxa2xx_template.h" + +PXA2xxLCDState *pxa2xx_lcdc_init(MemoryRegion *sysmem, + hwaddr base, qemu_irq irq) +{ + PXA2xxLCDState *s; + DisplaySurface *surface; + + s = (PXA2xxLCDState *) g_malloc0(sizeof(PXA2xxLCDState)); + s->invalidated = 1; + s->irq = irq; + s->sysmem = sysmem; + + pxa2xx_lcdc_orientation(s, graphic_rotate); + + memory_region_init_io(&s->iomem, &pxa2xx_lcdc_ops, s, + "pxa2xx-lcd-controller", 0x00100000); + memory_region_add_subregion(sysmem, base, &s->iomem); + + s->con = graphic_console_init(pxa2xx_update_display, + pxa2xx_invalidate_display, + NULL, NULL, s); + surface = qemu_console_surface(s->con); + + switch (surface_bits_per_pixel(surface)) { + case 0: + s->dest_width = 0; + break; + case 8: + s->line_fn[0] = pxa2xx_draw_fn_8; + s->line_fn[1] = pxa2xx_draw_fn_8t; + s->dest_width = 1; + break; + case 15: + s->line_fn[0] = pxa2xx_draw_fn_15; + s->line_fn[1] = pxa2xx_draw_fn_15t; + s->dest_width = 2; + break; + case 16: + s->line_fn[0] = pxa2xx_draw_fn_16; + s->line_fn[1] = pxa2xx_draw_fn_16t; + s->dest_width = 2; + break; + case 24: + s->line_fn[0] = pxa2xx_draw_fn_24; + s->line_fn[1] = pxa2xx_draw_fn_24t; + s->dest_width = 3; + break; + case 32: + s->line_fn[0] = pxa2xx_draw_fn_32; + s->line_fn[1] = pxa2xx_draw_fn_32t; + s->dest_width = 4; + break; + default: + fprintf(stderr, "%s: Bad color depth\n", __FUNCTION__); + exit(1); + } + + vmstate_register(NULL, 0, &vmstate_pxa2xx_lcdc, s); + + return s; +} + +void pxa2xx_lcd_vsync_notifier(PXA2xxLCDState *s, qemu_irq handler) +{ + s->vsync_cb = handler; +} diff --git a/hw/display/pxa2xx_template.h b/hw/display/pxa2xx_template.h new file mode 100644 index 0000000000..1cbe36cb80 --- /dev/null +++ b/hw/display/pxa2xx_template.h @@ -0,0 +1,435 @@ +/* + * Intel XScale PXA255/270 LCDC emulation. + * + * Copyright (c) 2006 Openedhand Ltd. + * Written by Andrzej Zaborowski <balrog@zabor.org> + * + * This code is licensed under the GPLv2. + * + * Framebuffer format conversion routines. + */ + +# define SKIP_PIXEL(to) to += deststep +#if BITS == 8 +# define COPY_PIXEL(to, from) *to = from; SKIP_PIXEL(to) +#elif BITS == 15 || BITS == 16 +# define COPY_PIXEL(to, from) *(uint16_t *) to = from; SKIP_PIXEL(to) +#elif BITS == 24 +# define COPY_PIXEL(to, from) \ + *(uint16_t *) to = from; *(to + 2) = (from) >> 16; SKIP_PIXEL(to) +#elif BITS == 32 +# define COPY_PIXEL(to, from) *(uint32_t *) to = from; SKIP_PIXEL(to) +#else +# error unknown bit depth +#endif + +#ifdef HOST_WORDS_BIGENDIAN +# define SWAP_WORDS 1 +#endif + +#define FN_2(x) FN(x + 1) FN(x) +#define FN_4(x) FN_2(x + 2) FN_2(x) + +static void glue(pxa2xx_draw_line2_, BITS)(void *opaque, + uint8_t *dest, const uint8_t *src, int width, int deststep) +{ + uint32_t *palette = opaque; + uint32_t data; + while (width > 0) { + data = *(uint32_t *) src; +#define FN(x) COPY_PIXEL(dest, palette[(data >> ((x) * 2)) & 3]); +#ifdef SWAP_WORDS + FN_4(12) + FN_4(8) + FN_4(4) + FN_4(0) +#else + FN_4(0) + FN_4(4) + FN_4(8) + FN_4(12) +#endif +#undef FN + width -= 16; + src += 4; + } +} + +static void glue(pxa2xx_draw_line4_, BITS)(void *opaque, + uint8_t *dest, const uint8_t *src, int width, int deststep) +{ + uint32_t *palette = opaque; + uint32_t data; + while (width > 0) { + data = *(uint32_t *) src; +#define FN(x) COPY_PIXEL(dest, palette[(data >> ((x) * 4)) & 0xf]); +#ifdef SWAP_WORDS + FN_2(6) + FN_2(4) + FN_2(2) + FN_2(0) +#else + FN_2(0) + FN_2(2) + FN_2(4) + FN_2(6) +#endif +#undef FN + width -= 8; + src += 4; + } +} + +static void glue(pxa2xx_draw_line8_, BITS)(void *opaque, + uint8_t *dest, const uint8_t *src, int width, int deststep) +{ + uint32_t *palette = opaque; + uint32_t data; + while (width > 0) { + data = *(uint32_t *) src; +#define FN(x) COPY_PIXEL(dest, palette[(data >> (x)) & 0xff]); +#ifdef SWAP_WORDS + FN(24) + FN(16) + FN(8) + FN(0) +#else + FN(0) + FN(8) + FN(16) + FN(24) +#endif +#undef FN + width -= 4; + src += 4; + } +} + +static void glue(pxa2xx_draw_line16_, BITS)(void *opaque, + uint8_t *dest, const uint8_t *src, int width, int deststep) +{ + uint32_t data; + unsigned int r, g, b; + while (width > 0) { + data = *(uint32_t *) src; +#ifdef SWAP_WORDS + data = bswap32(data); +#endif + b = (data & 0x1f) << 3; + data >>= 5; + g = (data & 0x3f) << 2; + data >>= 6; + r = (data & 0x1f) << 3; + data >>= 5; + COPY_PIXEL(dest, glue(rgb_to_pixel, BITS)(r, g, b)); + b = (data & 0x1f) << 3; + data >>= 5; + g = (data & 0x3f) << 2; + data >>= 6; + r = (data & 0x1f) << 3; + COPY_PIXEL(dest, glue(rgb_to_pixel, BITS)(r, g, b)); + width -= 2; + src += 4; + } +} + +static void glue(pxa2xx_draw_line16t_, BITS)(void *opaque, + uint8_t *dest, const uint8_t *src, int width, int deststep) +{ + uint32_t data; + unsigned int r, g, b; + while (width > 0) { + data = *(uint32_t *) src; +#ifdef SWAP_WORDS + data = bswap32(data); +#endif + b = (data & 0x1f) << 3; + data >>= 5; + g = (data & 0x1f) << 3; + data >>= 5; + r = (data & 0x1f) << 3; + data >>= 5; + if (data & 1) + SKIP_PIXEL(dest); + else + COPY_PIXEL(dest, glue(rgb_to_pixel, BITS)(r, g, b)); + data >>= 1; + b = (data & 0x1f) << 3; + data >>= 5; + g = (data & 0x1f) << 3; + data >>= 5; + r = (data & 0x1f) << 3; + data >>= 5; + if (data & 1) + SKIP_PIXEL(dest); + else + COPY_PIXEL(dest, glue(rgb_to_pixel, BITS)(r, g, b)); + width -= 2; + src += 4; + } +} + +static void glue(pxa2xx_draw_line18_, BITS)(void *opaque, + uint8_t *dest, const uint8_t *src, int width, int deststep) +{ + uint32_t data; + unsigned int r, g, b; + while (width > 0) { + data = *(uint32_t *) src; +#ifdef SWAP_WORDS + data = bswap32(data); +#endif + b = (data & 0x3f) << 2; + data >>= 6; + g = (data & 0x3f) << 2; + data >>= 6; + r = (data & 0x3f) << 2; + COPY_PIXEL(dest, glue(rgb_to_pixel, BITS)(r, g, b)); + width -= 1; + src += 4; + } +} + +/* The wicked packed format */ +static void glue(pxa2xx_draw_line18p_, BITS)(void *opaque, + uint8_t *dest, const uint8_t *src, int width, int deststep) +{ + uint32_t data[3]; + unsigned int r, g, b; + while (width > 0) { + data[0] = *(uint32_t *) src; + src += 4; + data[1] = *(uint32_t *) src; + src += 4; + data[2] = *(uint32_t *) src; + src += 4; +#ifdef SWAP_WORDS + data[0] = bswap32(data[0]); + data[1] = bswap32(data[1]); + data[2] = bswap32(data[2]); +#endif + b = (data[0] & 0x3f) << 2; + data[0] >>= 6; + g = (data[0] & 0x3f) << 2; + data[0] >>= 6; + r = (data[0] & 0x3f) << 2; + data[0] >>= 12; + COPY_PIXEL(dest, glue(rgb_to_pixel, BITS)(r, g, b)); + b = (data[0] & 0x3f) << 2; + data[0] >>= 6; + g = ((data[1] & 0xf) << 4) | (data[0] << 2); + data[1] >>= 4; + r = (data[1] & 0x3f) << 2; + data[1] >>= 12; + COPY_PIXEL(dest, glue(rgb_to_pixel, BITS)(r, g, b)); + b = (data[1] & 0x3f) << 2; + data[1] >>= 6; + g = (data[1] & 0x3f) << 2; + data[1] >>= 6; + r = ((data[2] & 0x3) << 6) | (data[1] << 2); + data[2] >>= 8; + COPY_PIXEL(dest, glue(rgb_to_pixel, BITS)(r, g, b)); + b = (data[2] & 0x3f) << 2; + data[2] >>= 6; + g = (data[2] & 0x3f) << 2; + data[2] >>= 6; + r = data[2] << 2; + COPY_PIXEL(dest, glue(rgb_to_pixel, BITS)(r, g, b)); + width -= 4; + } +} + +static void glue(pxa2xx_draw_line19_, BITS)(void *opaque, + uint8_t *dest, const uint8_t *src, int width, int deststep) +{ + uint32_t data; + unsigned int r, g, b; + while (width > 0) { + data = *(uint32_t *) src; +#ifdef SWAP_WORDS + data = bswap32(data); +#endif + b = (data & 0x3f) << 2; + data >>= 6; + g = (data & 0x3f) << 2; + data >>= 6; + r = (data & 0x3f) << 2; + data >>= 6; + if (data & 1) + SKIP_PIXEL(dest); + else + COPY_PIXEL(dest, glue(rgb_to_pixel, BITS)(r, g, b)); + width -= 1; + src += 4; + } +} + +/* The wicked packed format */ +static void glue(pxa2xx_draw_line19p_, BITS)(void *opaque, + uint8_t *dest, const uint8_t *src, int width, int deststep) +{ + uint32_t data[3]; + unsigned int r, g, b; + while (width > 0) { + data[0] = *(uint32_t *) src; + src += 4; + data[1] = *(uint32_t *) src; + src += 4; + data[2] = *(uint32_t *) src; + src += 4; +# ifdef SWAP_WORDS + data[0] = bswap32(data[0]); + data[1] = bswap32(data[1]); + data[2] = bswap32(data[2]); +# endif + b = (data[0] & 0x3f) << 2; + data[0] >>= 6; + g = (data[0] & 0x3f) << 2; + data[0] >>= 6; + r = (data[0] & 0x3f) << 2; + data[0] >>= 6; + if (data[0] & 1) + SKIP_PIXEL(dest); + else + COPY_PIXEL(dest, glue(rgb_to_pixel, BITS)(r, g, b)); + data[0] >>= 6; + b = (data[0] & 0x3f) << 2; + data[0] >>= 6; + g = ((data[1] & 0xf) << 4) | (data[0] << 2); + data[1] >>= 4; + r = (data[1] & 0x3f) << 2; + data[1] >>= 6; + if (data[1] & 1) + SKIP_PIXEL(dest); + else + COPY_PIXEL(dest, glue(rgb_to_pixel, BITS)(r, g, b)); + data[1] >>= 6; + b = (data[1] & 0x3f) << 2; + data[1] >>= 6; + g = (data[1] & 0x3f) << 2; + data[1] >>= 6; + r = ((data[2] & 0x3) << 6) | (data[1] << 2); + data[2] >>= 2; + if (data[2] & 1) + SKIP_PIXEL(dest); + else + COPY_PIXEL(dest, glue(rgb_to_pixel, BITS)(r, g, b)); + data[2] >>= 6; + b = (data[2] & 0x3f) << 2; + data[2] >>= 6; + g = (data[2] & 0x3f) << 2; + data[2] >>= 6; + r = data[2] << 2; + data[2] >>= 6; + if (data[2] & 1) + SKIP_PIXEL(dest); + else + COPY_PIXEL(dest, glue(rgb_to_pixel, BITS)(r, g, b)); + width -= 4; + } +} + +static void glue(pxa2xx_draw_line24_, BITS)(void *opaque, + uint8_t *dest, const uint8_t *src, int width, int deststep) +{ + uint32_t data; + unsigned int r, g, b; + while (width > 0) { + data = *(uint32_t *) src; +#ifdef SWAP_WORDS + data = bswap32(data); +#endif + b = data & 0xff; + data >>= 8; + g = data & 0xff; + data >>= 8; + r = data & 0xff; + COPY_PIXEL(dest, glue(rgb_to_pixel, BITS)(r, g, b)); + width -= 1; + src += 4; + } +} + +static void glue(pxa2xx_draw_line24t_, BITS)(void *opaque, + uint8_t *dest, const uint8_t *src, int width, int deststep) +{ + uint32_t data; + unsigned int r, g, b; + while (width > 0) { + data = *(uint32_t *) src; +#ifdef SWAP_WORDS + data = bswap32(data); +#endif + b = (data & 0x7f) << 1; + data >>= 7; + g = data & 0xff; + data >>= 8; + r = data & 0xff; + data >>= 8; + if (data & 1) + SKIP_PIXEL(dest); + else + COPY_PIXEL(dest, glue(rgb_to_pixel, BITS)(r, g, b)); + width -= 1; + src += 4; + } +} + +static void glue(pxa2xx_draw_line25_, BITS)(void *opaque, + uint8_t *dest, const uint8_t *src, int width, int deststep) +{ + uint32_t data; + unsigned int r, g, b; + while (width > 0) { + data = *(uint32_t *) src; +#ifdef SWAP_WORDS + data = bswap32(data); +#endif + b = data & 0xff; + data >>= 8; + g = data & 0xff; + data >>= 8; + r = data & 0xff; + data >>= 8; + if (data & 1) + SKIP_PIXEL(dest); + else + COPY_PIXEL(dest, glue(rgb_to_pixel, BITS)(r, g, b)); + width -= 1; + src += 4; + } +} + +/* Overlay planes disabled, no transparency */ +static drawfn glue(pxa2xx_draw_fn_, BITS)[16] = +{ + [0 ... 0xf] = NULL, + [pxa_lcdc_2bpp] = glue(pxa2xx_draw_line2_, BITS), + [pxa_lcdc_4bpp] = glue(pxa2xx_draw_line4_, BITS), + [pxa_lcdc_8bpp] = glue(pxa2xx_draw_line8_, BITS), + [pxa_lcdc_16bpp] = glue(pxa2xx_draw_line16_, BITS), + [pxa_lcdc_18bpp] = glue(pxa2xx_draw_line18_, BITS), + [pxa_lcdc_18pbpp] = glue(pxa2xx_draw_line18p_, BITS), + [pxa_lcdc_24bpp] = glue(pxa2xx_draw_line24_, BITS), +}; + +/* Overlay planes enabled, transparency used */ +static drawfn glue(glue(pxa2xx_draw_fn_, BITS), t)[16] = +{ + [0 ... 0xf] = NULL, + [pxa_lcdc_4bpp] = glue(pxa2xx_draw_line4_, BITS), + [pxa_lcdc_8bpp] = glue(pxa2xx_draw_line8_, BITS), + [pxa_lcdc_16bpp] = glue(pxa2xx_draw_line16t_, BITS), + [pxa_lcdc_19bpp] = glue(pxa2xx_draw_line19_, BITS), + [pxa_lcdc_19pbpp] = glue(pxa2xx_draw_line19p_, BITS), + [pxa_lcdc_24bpp] = glue(pxa2xx_draw_line24t_, BITS), + [pxa_lcdc_25bpp] = glue(pxa2xx_draw_line25_, BITS), +}; + +#undef BITS +#undef COPY_PIXEL +#undef SKIP_PIXEL + +#ifdef SWAP_WORDS +# undef SWAP_WORDS +#endif diff --git a/hw/display/qxl-logger.c b/hw/display/qxl-logger.c new file mode 100644 index 0000000000..3cd85d9b97 --- /dev/null +++ b/hw/display/qxl-logger.c @@ -0,0 +1,275 @@ +/* + * qxl command logging -- for debug purposes + * + * Copyright (C) 2010 Red Hat, Inc. + * + * maintained by Gerd Hoffmann <kraxel@redhat.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 or + * (at your option) version 3 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <http://www.gnu.org/licenses/>. + */ + +#include "qemu/timer.h" +#include "qxl.h" + +static const char *qxl_type[] = { + [ QXL_CMD_NOP ] = "nop", + [ QXL_CMD_DRAW ] = "draw", + [ QXL_CMD_UPDATE ] = "update", + [ QXL_CMD_CURSOR ] = "cursor", + [ QXL_CMD_MESSAGE ] = "message", + [ QXL_CMD_SURFACE ] = "surface", +}; + +static const char *qxl_draw_type[] = { + [ QXL_DRAW_NOP ] = "nop", + [ QXL_DRAW_FILL ] = "fill", + [ QXL_DRAW_OPAQUE ] = "opaque", + [ QXL_DRAW_COPY ] = "copy", + [ QXL_COPY_BITS ] = "copy-bits", + [ QXL_DRAW_BLEND ] = "blend", + [ QXL_DRAW_BLACKNESS ] = "blackness", + [ QXL_DRAW_WHITENESS ] = "whitemess", + [ QXL_DRAW_INVERS ] = "invers", + [ QXL_DRAW_ROP3 ] = "rop3", + [ QXL_DRAW_STROKE ] = "stroke", + [ QXL_DRAW_TEXT ] = "text", + [ QXL_DRAW_TRANSPARENT ] = "transparent", + [ QXL_DRAW_ALPHA_BLEND ] = "alpha-blend", +}; + +static const char *qxl_draw_effect[] = { + [ QXL_EFFECT_BLEND ] = "blend", + [ QXL_EFFECT_OPAQUE ] = "opaque", + [ QXL_EFFECT_REVERT_ON_DUP ] = "revert-on-dup", + [ QXL_EFFECT_BLACKNESS_ON_DUP ] = "blackness-on-dup", + [ QXL_EFFECT_WHITENESS_ON_DUP ] = "whiteness-on-dup", + [ QXL_EFFECT_NOP_ON_DUP ] = "nop-on-dup", + [ QXL_EFFECT_NOP ] = "nop", + [ QXL_EFFECT_OPAQUE_BRUSH ] = "opaque-brush", +}; + +static const char *qxl_surface_cmd[] = { + [ QXL_SURFACE_CMD_CREATE ] = "create", + [ QXL_SURFACE_CMD_DESTROY ] = "destroy", +}; + +static const char *spice_surface_fmt[] = { + [ SPICE_SURFACE_FMT_INVALID ] = "invalid", + [ SPICE_SURFACE_FMT_1_A ] = "alpha/1", + [ SPICE_SURFACE_FMT_8_A ] = "alpha/8", + [ SPICE_SURFACE_FMT_16_555 ] = "555/16", + [ SPICE_SURFACE_FMT_16_565 ] = "565/16", + [ SPICE_SURFACE_FMT_32_xRGB ] = "xRGB/32", + [ SPICE_SURFACE_FMT_32_ARGB ] = "ARGB/32", +}; + +static const char *qxl_cursor_cmd[] = { + [ QXL_CURSOR_SET ] = "set", + [ QXL_CURSOR_MOVE ] = "move", + [ QXL_CURSOR_HIDE ] = "hide", + [ QXL_CURSOR_TRAIL ] = "trail", +}; + +static const char *spice_cursor_type[] = { + [ SPICE_CURSOR_TYPE_ALPHA ] = "alpha", + [ SPICE_CURSOR_TYPE_MONO ] = "mono", + [ SPICE_CURSOR_TYPE_COLOR4 ] = "color4", + [ SPICE_CURSOR_TYPE_COLOR8 ] = "color8", + [ SPICE_CURSOR_TYPE_COLOR16 ] = "color16", + [ SPICE_CURSOR_TYPE_COLOR24 ] = "color24", + [ SPICE_CURSOR_TYPE_COLOR32 ] = "color32", +}; + +static const char *qxl_v2n(const char *n[], size_t l, int v) +{ + if (v >= l || !n[v]) { + return "???"; + } + return n[v]; +} +#define qxl_name(_list, _value) qxl_v2n(_list, ARRAY_SIZE(_list), _value) + +static int qxl_log_image(PCIQXLDevice *qxl, QXLPHYSICAL addr, int group_id) +{ + QXLImage *image; + QXLImageDescriptor *desc; + + image = qxl_phys2virt(qxl, addr, group_id); + if (!image) { + return 1; + } + desc = &image->descriptor; + fprintf(stderr, " (id %" PRIx64 " type %d flags %d width %d height %d", + desc->id, desc->type, desc->flags, desc->width, desc->height); + switch (desc->type) { + case SPICE_IMAGE_TYPE_BITMAP: + fprintf(stderr, ", fmt %d flags %d x %d y %d stride %d" + " palette %" PRIx64 " data %" PRIx64, + image->bitmap.format, image->bitmap.flags, + image->bitmap.x, image->bitmap.y, + image->bitmap.stride, + image->bitmap.palette, image->bitmap.data); + break; + } + fprintf(stderr, ")"); + return 0; +} + +static void qxl_log_rect(QXLRect *rect) +{ + fprintf(stderr, " %dx%d+%d+%d", + rect->right - rect->left, + rect->bottom - rect->top, + rect->left, rect->top); +} + +static int qxl_log_cmd_draw_copy(PCIQXLDevice *qxl, QXLCopy *copy, + int group_id) +{ + int ret; + + fprintf(stderr, " src %" PRIx64, + copy->src_bitmap); + ret = qxl_log_image(qxl, copy->src_bitmap, group_id); + if (ret != 0) { + return ret; + } + fprintf(stderr, " area"); + qxl_log_rect(©->src_area); + fprintf(stderr, " rop %d", copy->rop_descriptor); + return 0; +} + +static int qxl_log_cmd_draw(PCIQXLDevice *qxl, QXLDrawable *draw, int group_id) +{ + fprintf(stderr, ": surface_id %d type %s effect %s", + draw->surface_id, + qxl_name(qxl_draw_type, draw->type), + qxl_name(qxl_draw_effect, draw->effect)); + switch (draw->type) { + case QXL_DRAW_COPY: + return qxl_log_cmd_draw_copy(qxl, &draw->u.copy, group_id); + break; + } + return 0; +} + +static int qxl_log_cmd_draw_compat(PCIQXLDevice *qxl, QXLCompatDrawable *draw, + int group_id) +{ + fprintf(stderr, ": type %s effect %s", + qxl_name(qxl_draw_type, draw->type), + qxl_name(qxl_draw_effect, draw->effect)); + if (draw->bitmap_offset) { + fprintf(stderr, ": bitmap %d", + draw->bitmap_offset); + qxl_log_rect(&draw->bitmap_area); + } + switch (draw->type) { + case QXL_DRAW_COPY: + return qxl_log_cmd_draw_copy(qxl, &draw->u.copy, group_id); + break; + } + return 0; +} + +static void qxl_log_cmd_surface(PCIQXLDevice *qxl, QXLSurfaceCmd *cmd) +{ + fprintf(stderr, ": %s id %d", + qxl_name(qxl_surface_cmd, cmd->type), + cmd->surface_id); + if (cmd->type == QXL_SURFACE_CMD_CREATE) { + fprintf(stderr, " size %dx%d stride %d format %s (count %d, max %d)", + cmd->u.surface_create.width, + cmd->u.surface_create.height, + cmd->u.surface_create.stride, + qxl_name(spice_surface_fmt, cmd->u.surface_create.format), + qxl->guest_surfaces.count, qxl->guest_surfaces.max); + } + if (cmd->type == QXL_SURFACE_CMD_DESTROY) { + fprintf(stderr, " (count %d)", qxl->guest_surfaces.count); + } +} + +int qxl_log_cmd_cursor(PCIQXLDevice *qxl, QXLCursorCmd *cmd, int group_id) +{ + QXLCursor *cursor; + + fprintf(stderr, ": %s", + qxl_name(qxl_cursor_cmd, cmd->type)); + switch (cmd->type) { + case QXL_CURSOR_SET: + fprintf(stderr, " +%d+%d visible %s, shape @ 0x%" PRIx64, + cmd->u.set.position.x, + cmd->u.set.position.y, + cmd->u.set.visible ? "yes" : "no", + cmd->u.set.shape); + cursor = qxl_phys2virt(qxl, cmd->u.set.shape, group_id); + if (!cursor) { + return 1; + } + fprintf(stderr, " type %s size %dx%d hot-spot +%d+%d" + " unique 0x%" PRIx64 " data-size %d", + qxl_name(spice_cursor_type, cursor->header.type), + cursor->header.width, cursor->header.height, + cursor->header.hot_spot_x, cursor->header.hot_spot_y, + cursor->header.unique, cursor->data_size); + break; + case QXL_CURSOR_MOVE: + fprintf(stderr, " +%d+%d", cmd->u.position.x, cmd->u.position.y); + break; + } + return 0; +} + +int qxl_log_command(PCIQXLDevice *qxl, const char *ring, QXLCommandExt *ext) +{ + bool compat = ext->flags & QXL_COMMAND_FLAG_COMPAT; + void *data; + int ret; + + if (!qxl->cmdlog) { + return 0; + } + fprintf(stderr, "%" PRId64 " qxl-%d/%s:", qemu_get_clock_ns(vm_clock), + qxl->id, ring); + fprintf(stderr, " cmd @ 0x%" PRIx64 " %s%s", ext->cmd.data, + qxl_name(qxl_type, ext->cmd.type), + compat ? "(compat)" : ""); + + data = qxl_phys2virt(qxl, ext->cmd.data, ext->group_id); + if (!data) { + return 1; + } + switch (ext->cmd.type) { + case QXL_CMD_DRAW: + if (!compat) { + ret = qxl_log_cmd_draw(qxl, data, ext->group_id); + } else { + ret = qxl_log_cmd_draw_compat(qxl, data, ext->group_id); + } + if (ret) { + return ret; + } + break; + case QXL_CMD_SURFACE: + qxl_log_cmd_surface(qxl, data); + break; + case QXL_CMD_CURSOR: + qxl_log_cmd_cursor(qxl, data, ext->group_id); + break; + } + fprintf(stderr, "\n"); + return 0; +} diff --git a/hw/display/qxl-render.c b/hw/display/qxl-render.c new file mode 100644 index 0000000000..f511a622c6 --- /dev/null +++ b/hw/display/qxl-render.c @@ -0,0 +1,280 @@ +/* + * qxl local rendering (aka display on sdl/vnc) + * + * Copyright (C) 2010 Red Hat, Inc. + * + * maintained by Gerd Hoffmann <kraxel@redhat.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 or + * (at your option) version 3 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <http://www.gnu.org/licenses/>. + */ + +#include "qxl.h" + +static void qxl_blit(PCIQXLDevice *qxl, QXLRect *rect) +{ + DisplaySurface *surface = qemu_console_surface(qxl->vga.con); + uint8_t *dst = surface_data(surface); + uint8_t *src; + int len, i; + + if (is_buffer_shared(surface)) { + return; + } + if (!qxl->guest_primary.data) { + trace_qxl_render_blit_guest_primary_initialized(); + qxl->guest_primary.data = memory_region_get_ram_ptr(&qxl->vga.vram); + } + trace_qxl_render_blit(qxl->guest_primary.qxl_stride, + rect->left, rect->right, rect->top, rect->bottom); + src = qxl->guest_primary.data; + if (qxl->guest_primary.qxl_stride < 0) { + /* qxl surface is upside down, walk src scanlines + * in reverse order to flip it */ + src += (qxl->guest_primary.surface.height - rect->top - 1) * + qxl->guest_primary.abs_stride; + } else { + src += rect->top * qxl->guest_primary.abs_stride; + } + dst += rect->top * qxl->guest_primary.abs_stride; + src += rect->left * qxl->guest_primary.bytes_pp; + dst += rect->left * qxl->guest_primary.bytes_pp; + len = (rect->right - rect->left) * qxl->guest_primary.bytes_pp; + + for (i = rect->top; i < rect->bottom; i++) { + memcpy(dst, src, len); + dst += qxl->guest_primary.abs_stride; + src += qxl->guest_primary.qxl_stride; + } +} + +void qxl_render_resize(PCIQXLDevice *qxl) +{ + QXLSurfaceCreate *sc = &qxl->guest_primary.surface; + + qxl->guest_primary.qxl_stride = sc->stride; + qxl->guest_primary.abs_stride = abs(sc->stride); + qxl->guest_primary.resized++; + switch (sc->format) { + case SPICE_SURFACE_FMT_16_555: + qxl->guest_primary.bytes_pp = 2; + qxl->guest_primary.bits_pp = 15; + break; + case SPICE_SURFACE_FMT_16_565: + qxl->guest_primary.bytes_pp = 2; + qxl->guest_primary.bits_pp = 16; + break; + case SPICE_SURFACE_FMT_32_xRGB: + case SPICE_SURFACE_FMT_32_ARGB: + qxl->guest_primary.bytes_pp = 4; + qxl->guest_primary.bits_pp = 32; + break; + default: + fprintf(stderr, "%s: unhandled format: %x\n", __FUNCTION__, + qxl->guest_primary.surface.format); + qxl->guest_primary.bytes_pp = 4; + qxl->guest_primary.bits_pp = 32; + break; + } +} + +static void qxl_set_rect_to_surface(PCIQXLDevice *qxl, QXLRect *area) +{ + area->left = 0; + area->right = qxl->guest_primary.surface.width; + area->top = 0; + area->bottom = qxl->guest_primary.surface.height; +} + +static void qxl_render_update_area_unlocked(PCIQXLDevice *qxl) +{ + VGACommonState *vga = &qxl->vga; + DisplaySurface *surface; + int i; + + if (qxl->guest_primary.resized) { + qxl->guest_primary.resized = 0; + qxl->guest_primary.data = memory_region_get_ram_ptr(&qxl->vga.vram); + qxl_set_rect_to_surface(qxl, &qxl->dirty[0]); + qxl->num_dirty_rects = 1; + trace_qxl_render_guest_primary_resized( + qxl->guest_primary.surface.width, + qxl->guest_primary.surface.height, + qxl->guest_primary.qxl_stride, + qxl->guest_primary.bytes_pp, + qxl->guest_primary.bits_pp); + if (qxl->guest_primary.qxl_stride > 0) { + surface = qemu_create_displaysurface_from + (qxl->guest_primary.surface.width, + qxl->guest_primary.surface.height, + qxl->guest_primary.bits_pp, + qxl->guest_primary.abs_stride, + qxl->guest_primary.data, + false); + } else { + surface = qemu_create_displaysurface + (qxl->guest_primary.surface.width, + qxl->guest_primary.surface.height); + } + dpy_gfx_replace_surface(vga->con, surface); + } + for (i = 0; i < qxl->num_dirty_rects; i++) { + if (qemu_spice_rect_is_empty(qxl->dirty+i)) { + break; + } + qxl_blit(qxl, qxl->dirty+i); + dpy_gfx_update(vga->con, + qxl->dirty[i].left, qxl->dirty[i].top, + qxl->dirty[i].right - qxl->dirty[i].left, + qxl->dirty[i].bottom - qxl->dirty[i].top); + } + qxl->num_dirty_rects = 0; +} + +/* + * use ssd.lock to protect render_update_cookie_num. + * qxl_render_update is called by io thread or vcpu thread, and the completion + * callbacks are called by spice_server thread, defering to bh called from the + * io thread. + */ +void qxl_render_update(PCIQXLDevice *qxl) +{ + QXLCookie *cookie; + + qemu_mutex_lock(&qxl->ssd.lock); + + if (!runstate_is_running() || !qxl->guest_primary.commands) { + qxl_render_update_area_unlocked(qxl); + qemu_mutex_unlock(&qxl->ssd.lock); + return; + } + + qxl->guest_primary.commands = 0; + qxl->render_update_cookie_num++; + qemu_mutex_unlock(&qxl->ssd.lock); + cookie = qxl_cookie_new(QXL_COOKIE_TYPE_RENDER_UPDATE_AREA, + 0); + qxl_set_rect_to_surface(qxl, &cookie->u.render.area); + qxl_spice_update_area(qxl, 0, &cookie->u.render.area, NULL, + 0, 1 /* clear_dirty_region */, QXL_ASYNC, cookie); +} + +void qxl_render_update_area_bh(void *opaque) +{ + PCIQXLDevice *qxl = opaque; + + qemu_mutex_lock(&qxl->ssd.lock); + qxl_render_update_area_unlocked(qxl); + qemu_mutex_unlock(&qxl->ssd.lock); +} + +void qxl_render_update_area_done(PCIQXLDevice *qxl, QXLCookie *cookie) +{ + qemu_mutex_lock(&qxl->ssd.lock); + trace_qxl_render_update_area_done(cookie); + qemu_bh_schedule(qxl->update_area_bh); + qxl->render_update_cookie_num--; + qemu_mutex_unlock(&qxl->ssd.lock); + g_free(cookie); +} + +static QEMUCursor *qxl_cursor(PCIQXLDevice *qxl, QXLCursor *cursor) +{ + QEMUCursor *c; + uint8_t *image, *mask; + size_t size; + + c = cursor_alloc(cursor->header.width, cursor->header.height); + c->hot_x = cursor->header.hot_spot_x; + c->hot_y = cursor->header.hot_spot_y; + switch (cursor->header.type) { + case SPICE_CURSOR_TYPE_ALPHA: + size = cursor->header.width * cursor->header.height * sizeof(uint32_t); + memcpy(c->data, cursor->chunk.data, size); + if (qxl->debug > 2) { + cursor_print_ascii_art(c, "qxl/alpha"); + } + break; + case SPICE_CURSOR_TYPE_MONO: + mask = cursor->chunk.data; + image = mask + cursor_get_mono_bpl(c) * c->width; + cursor_set_mono(c, 0xffffff, 0x000000, image, 1, mask); + if (qxl->debug > 2) { + cursor_print_ascii_art(c, "qxl/mono"); + } + break; + default: + fprintf(stderr, "%s: not implemented: type %d\n", + __FUNCTION__, cursor->header.type); + goto fail; + } + return c; + +fail: + cursor_put(c); + return NULL; +} + + +/* called from spice server thread context only */ +int qxl_render_cursor(PCIQXLDevice *qxl, QXLCommandExt *ext) +{ + QXLCursorCmd *cmd = qxl_phys2virt(qxl, ext->cmd.data, ext->group_id); + QXLCursor *cursor; + QEMUCursor *c; + + if (!cmd) { + return 1; + } + + if (!dpy_cursor_define_supported(qxl->vga.con)) { + return 0; + } + + if (qxl->debug > 1 && cmd->type != QXL_CURSOR_MOVE) { + fprintf(stderr, "%s", __FUNCTION__); + qxl_log_cmd_cursor(qxl, cmd, ext->group_id); + fprintf(stderr, "\n"); + } + switch (cmd->type) { + case QXL_CURSOR_SET: + cursor = qxl_phys2virt(qxl, cmd->u.set.shape, ext->group_id); + if (!cursor) { + return 1; + } + if (cursor->chunk.data_size != cursor->data_size) { + fprintf(stderr, "%s: multiple chunks\n", __FUNCTION__); + return 1; + } + c = qxl_cursor(qxl, cursor); + if (c == NULL) { + c = cursor_builtin_left_ptr(); + } + qemu_mutex_lock(&qxl->ssd.lock); + if (qxl->ssd.cursor) { + cursor_put(qxl->ssd.cursor); + } + qxl->ssd.cursor = c; + qxl->ssd.mouse_x = cmd->u.set.position.x; + qxl->ssd.mouse_y = cmd->u.set.position.y; + qemu_mutex_unlock(&qxl->ssd.lock); + break; + case QXL_CURSOR_MOVE: + qemu_mutex_lock(&qxl->ssd.lock); + qxl->ssd.mouse_x = cmd->u.position.x; + qxl->ssd.mouse_y = cmd->u.position.y; + qemu_mutex_unlock(&qxl->ssd.lock); + break; + } + return 0; +} diff --git a/hw/display/qxl.c b/hw/display/qxl.c new file mode 100644 index 0000000000..930b7cf2ad --- /dev/null +++ b/hw/display/qxl.c @@ -0,0 +1,2365 @@ +/* + * Copyright (C) 2010 Red Hat, Inc. + * + * written by Yaniv Kamay, Izik Eidus, Gerd Hoffmann + * maintained by Gerd Hoffmann <kraxel@redhat.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 or + * (at your option) version 3 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <http://www.gnu.org/licenses/>. + */ + +#include <zlib.h> + +#include "qemu-common.h" +#include "qemu/timer.h" +#include "qemu/queue.h" +#include "monitor/monitor.h" +#include "sysemu/sysemu.h" +#include "trace.h" + +#include "qxl.h" + +/* + * NOTE: SPICE_RING_PROD_ITEM accesses memory on the pci bar and as + * such can be changed by the guest, so to avoid a guest trigerrable + * abort we just qxl_set_guest_bug and set the return to NULL. Still + * it may happen as a result of emulator bug as well. + */ +#undef SPICE_RING_PROD_ITEM +#define SPICE_RING_PROD_ITEM(qxl, r, ret) { \ + uint32_t prod = (r)->prod & SPICE_RING_INDEX_MASK(r); \ + if (prod >= ARRAY_SIZE((r)->items)) { \ + qxl_set_guest_bug(qxl, "SPICE_RING_PROD_ITEM indices mismatch " \ + "%u >= %zu", prod, ARRAY_SIZE((r)->items)); \ + ret = NULL; \ + } else { \ + ret = &(r)->items[prod].el; \ + } \ + } + +#undef SPICE_RING_CONS_ITEM +#define SPICE_RING_CONS_ITEM(qxl, r, ret) { \ + uint32_t cons = (r)->cons & SPICE_RING_INDEX_MASK(r); \ + if (cons >= ARRAY_SIZE((r)->items)) { \ + qxl_set_guest_bug(qxl, "SPICE_RING_CONS_ITEM indices mismatch " \ + "%u >= %zu", cons, ARRAY_SIZE((r)->items)); \ + ret = NULL; \ + } else { \ + ret = &(r)->items[cons].el; \ + } \ + } + +#undef ALIGN +#define ALIGN(a, b) (((a) + ((b) - 1)) & ~((b) - 1)) + +#define PIXEL_SIZE 0.2936875 //1280x1024 is 14.8" x 11.9" + +#define QXL_MODE(_x, _y, _b, _o) \ + { .x_res = _x, \ + .y_res = _y, \ + .bits = _b, \ + .stride = (_x) * (_b) / 8, \ + .x_mili = PIXEL_SIZE * (_x), \ + .y_mili = PIXEL_SIZE * (_y), \ + .orientation = _o, \ + } + +#define QXL_MODE_16_32(x_res, y_res, orientation) \ + QXL_MODE(x_res, y_res, 16, orientation), \ + QXL_MODE(x_res, y_res, 32, orientation) + +#define QXL_MODE_EX(x_res, y_res) \ + QXL_MODE_16_32(x_res, y_res, 0), \ + QXL_MODE_16_32(x_res, y_res, 1) + +static QXLMode qxl_modes[] = { + QXL_MODE_EX(640, 480), + QXL_MODE_EX(800, 480), + QXL_MODE_EX(800, 600), + QXL_MODE_EX(832, 624), + QXL_MODE_EX(960, 640), + QXL_MODE_EX(1024, 600), + QXL_MODE_EX(1024, 768), + QXL_MODE_EX(1152, 864), + QXL_MODE_EX(1152, 870), + QXL_MODE_EX(1280, 720), + QXL_MODE_EX(1280, 760), + QXL_MODE_EX(1280, 768), + QXL_MODE_EX(1280, 800), + QXL_MODE_EX(1280, 960), + QXL_MODE_EX(1280, 1024), + QXL_MODE_EX(1360, 768), + QXL_MODE_EX(1366, 768), + QXL_MODE_EX(1400, 1050), + QXL_MODE_EX(1440, 900), + QXL_MODE_EX(1600, 900), + QXL_MODE_EX(1600, 1200), + QXL_MODE_EX(1680, 1050), + QXL_MODE_EX(1920, 1080), + /* these modes need more than 8 MB video memory */ + QXL_MODE_EX(1920, 1200), + QXL_MODE_EX(1920, 1440), + QXL_MODE_EX(2048, 1536), + QXL_MODE_EX(2560, 1440), + QXL_MODE_EX(2560, 1600), + /* these modes need more than 16 MB video memory */ + QXL_MODE_EX(2560, 2048), + QXL_MODE_EX(2800, 2100), + QXL_MODE_EX(3200, 2400), +}; + +static void qxl_send_events(PCIQXLDevice *d, uint32_t events); +static int qxl_destroy_primary(PCIQXLDevice *d, qxl_async_io async); +static void qxl_reset_memslots(PCIQXLDevice *d); +static void qxl_reset_surfaces(PCIQXLDevice *d); +static void qxl_ring_set_dirty(PCIQXLDevice *qxl); + +void qxl_set_guest_bug(PCIQXLDevice *qxl, const char *msg, ...) +{ + trace_qxl_set_guest_bug(qxl->id); + qxl_send_events(qxl, QXL_INTERRUPT_ERROR); + qxl->guest_bug = 1; + if (qxl->guestdebug) { + va_list ap; + va_start(ap, msg); + fprintf(stderr, "qxl-%d: guest bug: ", qxl->id); + vfprintf(stderr, msg, ap); + fprintf(stderr, "\n"); + va_end(ap); + } +} + +static void qxl_clear_guest_bug(PCIQXLDevice *qxl) +{ + qxl->guest_bug = 0; +} + +void qxl_spice_update_area(PCIQXLDevice *qxl, uint32_t surface_id, + struct QXLRect *area, struct QXLRect *dirty_rects, + uint32_t num_dirty_rects, + uint32_t clear_dirty_region, + qxl_async_io async, struct QXLCookie *cookie) +{ + trace_qxl_spice_update_area(qxl->id, surface_id, area->left, area->right, + area->top, area->bottom); + trace_qxl_spice_update_area_rest(qxl->id, num_dirty_rects, + clear_dirty_region); + if (async == QXL_SYNC) { + qxl->ssd.worker->update_area(qxl->ssd.worker, surface_id, area, + dirty_rects, num_dirty_rects, clear_dirty_region); + } else { + assert(cookie != NULL); + spice_qxl_update_area_async(&qxl->ssd.qxl, surface_id, area, + clear_dirty_region, (uintptr_t)cookie); + } +} + +static void qxl_spice_destroy_surface_wait_complete(PCIQXLDevice *qxl, + uint32_t id) +{ + trace_qxl_spice_destroy_surface_wait_complete(qxl->id, id); + qemu_mutex_lock(&qxl->track_lock); + qxl->guest_surfaces.cmds[id] = 0; + qxl->guest_surfaces.count--; + qemu_mutex_unlock(&qxl->track_lock); +} + +static void qxl_spice_destroy_surface_wait(PCIQXLDevice *qxl, uint32_t id, + qxl_async_io async) +{ + QXLCookie *cookie; + + trace_qxl_spice_destroy_surface_wait(qxl->id, id, async); + if (async) { + cookie = qxl_cookie_new(QXL_COOKIE_TYPE_IO, + QXL_IO_DESTROY_SURFACE_ASYNC); + cookie->u.surface_id = id; + spice_qxl_destroy_surface_async(&qxl->ssd.qxl, id, (uintptr_t)cookie); + } else { + qxl->ssd.worker->destroy_surface_wait(qxl->ssd.worker, id); + qxl_spice_destroy_surface_wait_complete(qxl, id); + } +} + +static void qxl_spice_flush_surfaces_async(PCIQXLDevice *qxl) +{ + trace_qxl_spice_flush_surfaces_async(qxl->id, qxl->guest_surfaces.count, + qxl->num_free_res); + spice_qxl_flush_surfaces_async(&qxl->ssd.qxl, + (uintptr_t)qxl_cookie_new(QXL_COOKIE_TYPE_IO, + QXL_IO_FLUSH_SURFACES_ASYNC)); +} + +void qxl_spice_loadvm_commands(PCIQXLDevice *qxl, struct QXLCommandExt *ext, + uint32_t count) +{ + trace_qxl_spice_loadvm_commands(qxl->id, ext, count); + qxl->ssd.worker->loadvm_commands(qxl->ssd.worker, ext, count); +} + +void qxl_spice_oom(PCIQXLDevice *qxl) +{ + trace_qxl_spice_oom(qxl->id); + qxl->ssd.worker->oom(qxl->ssd.worker); +} + +void qxl_spice_reset_memslots(PCIQXLDevice *qxl) +{ + trace_qxl_spice_reset_memslots(qxl->id); + qxl->ssd.worker->reset_memslots(qxl->ssd.worker); +} + +static void qxl_spice_destroy_surfaces_complete(PCIQXLDevice *qxl) +{ + trace_qxl_spice_destroy_surfaces_complete(qxl->id); + qemu_mutex_lock(&qxl->track_lock); + memset(qxl->guest_surfaces.cmds, 0, + sizeof(qxl->guest_surfaces.cmds) * qxl->ssd.num_surfaces); + qxl->guest_surfaces.count = 0; + qemu_mutex_unlock(&qxl->track_lock); +} + +static void qxl_spice_destroy_surfaces(PCIQXLDevice *qxl, qxl_async_io async) +{ + trace_qxl_spice_destroy_surfaces(qxl->id, async); + if (async) { + spice_qxl_destroy_surfaces_async(&qxl->ssd.qxl, + (uintptr_t)qxl_cookie_new(QXL_COOKIE_TYPE_IO, + QXL_IO_DESTROY_ALL_SURFACES_ASYNC)); + } else { + qxl->ssd.worker->destroy_surfaces(qxl->ssd.worker); + qxl_spice_destroy_surfaces_complete(qxl); + } +} + +static void qxl_spice_monitors_config_async(PCIQXLDevice *qxl, int replay) +{ + trace_qxl_spice_monitors_config(qxl->id); + if (replay) { + /* + * don't use QXL_COOKIE_TYPE_IO: + * - we are not running yet (post_load), we will assert + * in send_events + * - this is not a guest io, but a reply, so async_io isn't set. + */ + spice_qxl_monitors_config_async(&qxl->ssd.qxl, + qxl->guest_monitors_config, + MEMSLOT_GROUP_GUEST, + (uintptr_t)qxl_cookie_new( + QXL_COOKIE_TYPE_POST_LOAD_MONITORS_CONFIG, + 0)); + } else { + qxl->guest_monitors_config = qxl->ram->monitors_config; + spice_qxl_monitors_config_async(&qxl->ssd.qxl, + qxl->ram->monitors_config, + MEMSLOT_GROUP_GUEST, + (uintptr_t)qxl_cookie_new(QXL_COOKIE_TYPE_IO, + QXL_IO_MONITORS_CONFIG_ASYNC)); + } +} + +void qxl_spice_reset_image_cache(PCIQXLDevice *qxl) +{ + trace_qxl_spice_reset_image_cache(qxl->id); + qxl->ssd.worker->reset_image_cache(qxl->ssd.worker); +} + +void qxl_spice_reset_cursor(PCIQXLDevice *qxl) +{ + trace_qxl_spice_reset_cursor(qxl->id); + qxl->ssd.worker->reset_cursor(qxl->ssd.worker); + qemu_mutex_lock(&qxl->track_lock); + qxl->guest_cursor = 0; + qemu_mutex_unlock(&qxl->track_lock); + if (qxl->ssd.cursor) { + cursor_put(qxl->ssd.cursor); + } + qxl->ssd.cursor = cursor_builtin_hidden(); +} + + +static inline uint32_t msb_mask(uint32_t val) +{ + uint32_t mask; + + do { + mask = ~(val - 1) & val; + val &= ~mask; + } while (mask < val); + + return mask; +} + +static ram_addr_t qxl_rom_size(void) +{ + uint32_t required_rom_size = sizeof(QXLRom) + sizeof(QXLModes) + + sizeof(qxl_modes); + uint32_t rom_size = 8192; /* two pages */ + + required_rom_size = MAX(required_rom_size, TARGET_PAGE_SIZE); + required_rom_size = msb_mask(required_rom_size * 2 - 1); + assert(required_rom_size <= rom_size); + return rom_size; +} + +static void init_qxl_rom(PCIQXLDevice *d) +{ + QXLRom *rom = memory_region_get_ram_ptr(&d->rom_bar); + QXLModes *modes = (QXLModes *)(rom + 1); + uint32_t ram_header_size; + uint32_t surface0_area_size; + uint32_t num_pages; + uint32_t fb; + int i, n; + + memset(rom, 0, d->rom_size); + + rom->magic = cpu_to_le32(QXL_ROM_MAGIC); + rom->id = cpu_to_le32(d->id); + rom->log_level = cpu_to_le32(d->guestdebug); + rom->modes_offset = cpu_to_le32(sizeof(QXLRom)); + + rom->slot_gen_bits = MEMSLOT_GENERATION_BITS; + rom->slot_id_bits = MEMSLOT_SLOT_BITS; + rom->slots_start = 1; + rom->slots_end = NUM_MEMSLOTS - 1; + rom->n_surfaces = cpu_to_le32(d->ssd.num_surfaces); + + for (i = 0, n = 0; i < ARRAY_SIZE(qxl_modes); i++) { + fb = qxl_modes[i].y_res * qxl_modes[i].stride; + if (fb > d->vgamem_size) { + continue; + } + modes->modes[n].id = cpu_to_le32(i); + modes->modes[n].x_res = cpu_to_le32(qxl_modes[i].x_res); + modes->modes[n].y_res = cpu_to_le32(qxl_modes[i].y_res); + modes->modes[n].bits = cpu_to_le32(qxl_modes[i].bits); + modes->modes[n].stride = cpu_to_le32(qxl_modes[i].stride); + modes->modes[n].x_mili = cpu_to_le32(qxl_modes[i].x_mili); + modes->modes[n].y_mili = cpu_to_le32(qxl_modes[i].y_mili); + modes->modes[n].orientation = cpu_to_le32(qxl_modes[i].orientation); + n++; + } + modes->n_modes = cpu_to_le32(n); + + ram_header_size = ALIGN(sizeof(QXLRam), 4096); + surface0_area_size = ALIGN(d->vgamem_size, 4096); + num_pages = d->vga.vram_size; + num_pages -= ram_header_size; + num_pages -= surface0_area_size; + num_pages = num_pages / TARGET_PAGE_SIZE; + + rom->draw_area_offset = cpu_to_le32(0); + rom->surface0_area_size = cpu_to_le32(surface0_area_size); + rom->pages_offset = cpu_to_le32(surface0_area_size); + rom->num_pages = cpu_to_le32(num_pages); + rom->ram_header_offset = cpu_to_le32(d->vga.vram_size - ram_header_size); + + d->shadow_rom = *rom; + d->rom = rom; + d->modes = modes; +} + +static void init_qxl_ram(PCIQXLDevice *d) +{ + uint8_t *buf; + uint64_t *item; + + buf = d->vga.vram_ptr; + d->ram = (QXLRam *)(buf + le32_to_cpu(d->shadow_rom.ram_header_offset)); + d->ram->magic = cpu_to_le32(QXL_RAM_MAGIC); + d->ram->int_pending = cpu_to_le32(0); + d->ram->int_mask = cpu_to_le32(0); + d->ram->update_surface = 0; + SPICE_RING_INIT(&d->ram->cmd_ring); + SPICE_RING_INIT(&d->ram->cursor_ring); + SPICE_RING_INIT(&d->ram->release_ring); + SPICE_RING_PROD_ITEM(d, &d->ram->release_ring, item); + assert(item); + *item = 0; + qxl_ring_set_dirty(d); +} + +/* can be called from spice server thread context */ +static void qxl_set_dirty(MemoryRegion *mr, ram_addr_t addr, ram_addr_t end) +{ + memory_region_set_dirty(mr, addr, end - addr); +} + +static void qxl_rom_set_dirty(PCIQXLDevice *qxl) +{ + qxl_set_dirty(&qxl->rom_bar, 0, qxl->rom_size); +} + +/* called from spice server thread context only */ +static void qxl_ram_set_dirty(PCIQXLDevice *qxl, void *ptr) +{ + void *base = qxl->vga.vram_ptr; + intptr_t offset; + + offset = ptr - base; + offset &= ~(TARGET_PAGE_SIZE-1); + assert(offset < qxl->vga.vram_size); + qxl_set_dirty(&qxl->vga.vram, offset, offset + TARGET_PAGE_SIZE); +} + +/* can be called from spice server thread context */ +static void qxl_ring_set_dirty(PCIQXLDevice *qxl) +{ + ram_addr_t addr = qxl->shadow_rom.ram_header_offset; + ram_addr_t end = qxl->vga.vram_size; + qxl_set_dirty(&qxl->vga.vram, addr, end); +} + +/* + * keep track of some command state, for savevm/loadvm. + * called from spice server thread context only + */ +static int qxl_track_command(PCIQXLDevice *qxl, struct QXLCommandExt *ext) +{ + switch (le32_to_cpu(ext->cmd.type)) { + case QXL_CMD_SURFACE: + { + QXLSurfaceCmd *cmd = qxl_phys2virt(qxl, ext->cmd.data, ext->group_id); + + if (!cmd) { + return 1; + } + uint32_t id = le32_to_cpu(cmd->surface_id); + + if (id >= qxl->ssd.num_surfaces) { + qxl_set_guest_bug(qxl, "QXL_CMD_SURFACE id %d >= %d", id, + qxl->ssd.num_surfaces); + return 1; + } + if (cmd->type == QXL_SURFACE_CMD_CREATE && + (cmd->u.surface_create.stride & 0x03) != 0) { + qxl_set_guest_bug(qxl, "QXL_CMD_SURFACE stride = %d %% 4 != 0\n", + cmd->u.surface_create.stride); + return 1; + } + qemu_mutex_lock(&qxl->track_lock); + if (cmd->type == QXL_SURFACE_CMD_CREATE) { + qxl->guest_surfaces.cmds[id] = ext->cmd.data; + qxl->guest_surfaces.count++; + if (qxl->guest_surfaces.max < qxl->guest_surfaces.count) + qxl->guest_surfaces.max = qxl->guest_surfaces.count; + } + if (cmd->type == QXL_SURFACE_CMD_DESTROY) { + qxl->guest_surfaces.cmds[id] = 0; + qxl->guest_surfaces.count--; + } + qemu_mutex_unlock(&qxl->track_lock); + break; + } + case QXL_CMD_CURSOR: + { + QXLCursorCmd *cmd = qxl_phys2virt(qxl, ext->cmd.data, ext->group_id); + + if (!cmd) { + return 1; + } + if (cmd->type == QXL_CURSOR_SET) { + qemu_mutex_lock(&qxl->track_lock); + qxl->guest_cursor = ext->cmd.data; + qemu_mutex_unlock(&qxl->track_lock); + } + break; + } + } + return 0; +} + +/* spice display interface callbacks */ + +static void interface_attach_worker(QXLInstance *sin, QXLWorker *qxl_worker) +{ + PCIQXLDevice *qxl = container_of(sin, PCIQXLDevice, ssd.qxl); + + trace_qxl_interface_attach_worker(qxl->id); + qxl->ssd.worker = qxl_worker; +} + +static void interface_set_compression_level(QXLInstance *sin, int level) +{ + PCIQXLDevice *qxl = container_of(sin, PCIQXLDevice, ssd.qxl); + + trace_qxl_interface_set_compression_level(qxl->id, level); + qxl->shadow_rom.compression_level = cpu_to_le32(level); + qxl->rom->compression_level = cpu_to_le32(level); + qxl_rom_set_dirty(qxl); +} + +static void interface_set_mm_time(QXLInstance *sin, uint32_t mm_time) +{ + PCIQXLDevice *qxl = container_of(sin, PCIQXLDevice, ssd.qxl); + + trace_qxl_interface_set_mm_time(qxl->id, mm_time); + qxl->shadow_rom.mm_clock = cpu_to_le32(mm_time); + qxl->rom->mm_clock = cpu_to_le32(mm_time); + qxl_rom_set_dirty(qxl); +} + +static void interface_get_init_info(QXLInstance *sin, QXLDevInitInfo *info) +{ + PCIQXLDevice *qxl = container_of(sin, PCIQXLDevice, ssd.qxl); + + trace_qxl_interface_get_init_info(qxl->id); + info->memslot_gen_bits = MEMSLOT_GENERATION_BITS; + info->memslot_id_bits = MEMSLOT_SLOT_BITS; + info->num_memslots = NUM_MEMSLOTS; + info->num_memslots_groups = NUM_MEMSLOTS_GROUPS; + info->internal_groupslot_id = 0; + info->qxl_ram_size = le32_to_cpu(qxl->shadow_rom.num_pages) << TARGET_PAGE_BITS; + info->n_surfaces = qxl->ssd.num_surfaces; +} + +static const char *qxl_mode_to_string(int mode) +{ + switch (mode) { + case QXL_MODE_COMPAT: + return "compat"; + case QXL_MODE_NATIVE: + return "native"; + case QXL_MODE_UNDEFINED: + return "undefined"; + case QXL_MODE_VGA: + return "vga"; + } + return "INVALID"; +} + +static const char *io_port_to_string(uint32_t io_port) +{ + if (io_port >= QXL_IO_RANGE_SIZE) { + return "out of range"; + } + static const char *io_port_to_string[QXL_IO_RANGE_SIZE + 1] = { + [QXL_IO_NOTIFY_CMD] = "QXL_IO_NOTIFY_CMD", + [QXL_IO_NOTIFY_CURSOR] = "QXL_IO_NOTIFY_CURSOR", + [QXL_IO_UPDATE_AREA] = "QXL_IO_UPDATE_AREA", + [QXL_IO_UPDATE_IRQ] = "QXL_IO_UPDATE_IRQ", + [QXL_IO_NOTIFY_OOM] = "QXL_IO_NOTIFY_OOM", + [QXL_IO_RESET] = "QXL_IO_RESET", + [QXL_IO_SET_MODE] = "QXL_IO_SET_MODE", + [QXL_IO_LOG] = "QXL_IO_LOG", + [QXL_IO_MEMSLOT_ADD] = "QXL_IO_MEMSLOT_ADD", + [QXL_IO_MEMSLOT_DEL] = "QXL_IO_MEMSLOT_DEL", + [QXL_IO_DETACH_PRIMARY] = "QXL_IO_DETACH_PRIMARY", + [QXL_IO_ATTACH_PRIMARY] = "QXL_IO_ATTACH_PRIMARY", + [QXL_IO_CREATE_PRIMARY] = "QXL_IO_CREATE_PRIMARY", + [QXL_IO_DESTROY_PRIMARY] = "QXL_IO_DESTROY_PRIMARY", + [QXL_IO_DESTROY_SURFACE_WAIT] = "QXL_IO_DESTROY_SURFACE_WAIT", + [QXL_IO_DESTROY_ALL_SURFACES] = "QXL_IO_DESTROY_ALL_SURFACES", + [QXL_IO_UPDATE_AREA_ASYNC] = "QXL_IO_UPDATE_AREA_ASYNC", + [QXL_IO_MEMSLOT_ADD_ASYNC] = "QXL_IO_MEMSLOT_ADD_ASYNC", + [QXL_IO_CREATE_PRIMARY_ASYNC] = "QXL_IO_CREATE_PRIMARY_ASYNC", + [QXL_IO_DESTROY_PRIMARY_ASYNC] = "QXL_IO_DESTROY_PRIMARY_ASYNC", + [QXL_IO_DESTROY_SURFACE_ASYNC] = "QXL_IO_DESTROY_SURFACE_ASYNC", + [QXL_IO_DESTROY_ALL_SURFACES_ASYNC] + = "QXL_IO_DESTROY_ALL_SURFACES_ASYNC", + [QXL_IO_FLUSH_SURFACES_ASYNC] = "QXL_IO_FLUSH_SURFACES_ASYNC", + [QXL_IO_FLUSH_RELEASE] = "QXL_IO_FLUSH_RELEASE", + [QXL_IO_MONITORS_CONFIG_ASYNC] = "QXL_IO_MONITORS_CONFIG_ASYNC", + }; + return io_port_to_string[io_port]; +} + +/* called from spice server thread context only */ +static int interface_get_command(QXLInstance *sin, struct QXLCommandExt *ext) +{ + PCIQXLDevice *qxl = container_of(sin, PCIQXLDevice, ssd.qxl); + SimpleSpiceUpdate *update; + QXLCommandRing *ring; + QXLCommand *cmd; + int notify, ret; + + trace_qxl_ring_command_check(qxl->id, qxl_mode_to_string(qxl->mode)); + + switch (qxl->mode) { + case QXL_MODE_VGA: + ret = false; + qemu_mutex_lock(&qxl->ssd.lock); + update = QTAILQ_FIRST(&qxl->ssd.updates); + if (update != NULL) { + QTAILQ_REMOVE(&qxl->ssd.updates, update, next); + *ext = update->ext; + ret = true; + } + qemu_mutex_unlock(&qxl->ssd.lock); + if (ret) { + trace_qxl_ring_command_get(qxl->id, qxl_mode_to_string(qxl->mode)); + qxl_log_command(qxl, "vga", ext); + } + return ret; + case QXL_MODE_COMPAT: + case QXL_MODE_NATIVE: + case QXL_MODE_UNDEFINED: + ring = &qxl->ram->cmd_ring; + if (qxl->guest_bug || SPICE_RING_IS_EMPTY(ring)) { + return false; + } + SPICE_RING_CONS_ITEM(qxl, ring, cmd); + if (!cmd) { + return false; + } + ext->cmd = *cmd; + ext->group_id = MEMSLOT_GROUP_GUEST; + ext->flags = qxl->cmdflags; + SPICE_RING_POP(ring, notify); + qxl_ring_set_dirty(qxl); + if (notify) { + qxl_send_events(qxl, QXL_INTERRUPT_DISPLAY); + } + qxl->guest_primary.commands++; + qxl_track_command(qxl, ext); + qxl_log_command(qxl, "cmd", ext); + trace_qxl_ring_command_get(qxl->id, qxl_mode_to_string(qxl->mode)); + return true; + default: + return false; + } +} + +/* called from spice server thread context only */ +static int interface_req_cmd_notification(QXLInstance *sin) +{ + PCIQXLDevice *qxl = container_of(sin, PCIQXLDevice, ssd.qxl); + int wait = 1; + + trace_qxl_ring_command_req_notification(qxl->id); + switch (qxl->mode) { + case QXL_MODE_COMPAT: + case QXL_MODE_NATIVE: + case QXL_MODE_UNDEFINED: + SPICE_RING_CONS_WAIT(&qxl->ram->cmd_ring, wait); + qxl_ring_set_dirty(qxl); + break; + default: + /* nothing */ + break; + } + return wait; +} + +/* called from spice server thread context only */ +static inline void qxl_push_free_res(PCIQXLDevice *d, int flush) +{ + QXLReleaseRing *ring = &d->ram->release_ring; + uint64_t *item; + int notify; + +#define QXL_FREE_BUNCH_SIZE 32 + + if (ring->prod - ring->cons + 1 == ring->num_items) { + /* ring full -- can't push */ + return; + } + if (!flush && d->oom_running) { + /* collect everything from oom handler before pushing */ + return; + } + if (!flush && d->num_free_res < QXL_FREE_BUNCH_SIZE) { + /* collect a bit more before pushing */ + return; + } + + SPICE_RING_PUSH(ring, notify); + trace_qxl_ring_res_push(d->id, qxl_mode_to_string(d->mode), + d->guest_surfaces.count, d->num_free_res, + d->last_release, notify ? "yes" : "no"); + trace_qxl_ring_res_push_rest(d->id, ring->prod - ring->cons, + ring->num_items, ring->prod, ring->cons); + if (notify) { + qxl_send_events(d, QXL_INTERRUPT_DISPLAY); + } + SPICE_RING_PROD_ITEM(d, ring, item); + if (!item) { + return; + } + *item = 0; + d->num_free_res = 0; + d->last_release = NULL; + qxl_ring_set_dirty(d); +} + +/* called from spice server thread context only */ +static void interface_release_resource(QXLInstance *sin, + struct QXLReleaseInfoExt ext) +{ + PCIQXLDevice *qxl = container_of(sin, PCIQXLDevice, ssd.qxl); + QXLReleaseRing *ring; + uint64_t *item, id; + + if (ext.group_id == MEMSLOT_GROUP_HOST) { + /* host group -> vga mode update request */ + qemu_spice_destroy_update(&qxl->ssd, (void *)(intptr_t)ext.info->id); + return; + } + + /* + * ext->info points into guest-visible memory + * pci bar 0, $command.release_info + */ + ring = &qxl->ram->release_ring; + SPICE_RING_PROD_ITEM(qxl, ring, item); + if (!item) { + return; + } + if (*item == 0) { + /* stick head into the ring */ + id = ext.info->id; + ext.info->next = 0; + qxl_ram_set_dirty(qxl, &ext.info->next); + *item = id; + qxl_ring_set_dirty(qxl); + } else { + /* append item to the list */ + qxl->last_release->next = ext.info->id; + qxl_ram_set_dirty(qxl, &qxl->last_release->next); + ext.info->next = 0; + qxl_ram_set_dirty(qxl, &ext.info->next); + } + qxl->last_release = ext.info; + qxl->num_free_res++; + trace_qxl_ring_res_put(qxl->id, qxl->num_free_res); + qxl_push_free_res(qxl, 0); +} + +/* called from spice server thread context only */ +static int interface_get_cursor_command(QXLInstance *sin, struct QXLCommandExt *ext) +{ + PCIQXLDevice *qxl = container_of(sin, PCIQXLDevice, ssd.qxl); + QXLCursorRing *ring; + QXLCommand *cmd; + int notify; + + trace_qxl_ring_cursor_check(qxl->id, qxl_mode_to_string(qxl->mode)); + + switch (qxl->mode) { + case QXL_MODE_COMPAT: + case QXL_MODE_NATIVE: + case QXL_MODE_UNDEFINED: + ring = &qxl->ram->cursor_ring; + if (SPICE_RING_IS_EMPTY(ring)) { + return false; + } + SPICE_RING_CONS_ITEM(qxl, ring, cmd); + if (!cmd) { + return false; + } + ext->cmd = *cmd; + ext->group_id = MEMSLOT_GROUP_GUEST; + ext->flags = qxl->cmdflags; + SPICE_RING_POP(ring, notify); + qxl_ring_set_dirty(qxl); + if (notify) { + qxl_send_events(qxl, QXL_INTERRUPT_CURSOR); + } + qxl->guest_primary.commands++; + qxl_track_command(qxl, ext); + qxl_log_command(qxl, "csr", ext); + if (qxl->id == 0) { + qxl_render_cursor(qxl, ext); + } + trace_qxl_ring_cursor_get(qxl->id, qxl_mode_to_string(qxl->mode)); + return true; + default: + return false; + } +} + +/* called from spice server thread context only */ +static int interface_req_cursor_notification(QXLInstance *sin) +{ + PCIQXLDevice *qxl = container_of(sin, PCIQXLDevice, ssd.qxl); + int wait = 1; + + trace_qxl_ring_cursor_req_notification(qxl->id); + switch (qxl->mode) { + case QXL_MODE_COMPAT: + case QXL_MODE_NATIVE: + case QXL_MODE_UNDEFINED: + SPICE_RING_CONS_WAIT(&qxl->ram->cursor_ring, wait); + qxl_ring_set_dirty(qxl); + break; + default: + /* nothing */ + break; + } + return wait; +} + +/* called from spice server thread context */ +static void interface_notify_update(QXLInstance *sin, uint32_t update_id) +{ + /* + * Called by spice-server as a result of a QXL_CMD_UPDATE which is not in + * use by xf86-video-qxl and is defined out in the qxl windows driver. + * Probably was at some earlier version that is prior to git start (2009), + * and is still guest trigerrable. + */ + fprintf(stderr, "%s: deprecated\n", __func__); +} + +/* called from spice server thread context only */ +static int interface_flush_resources(QXLInstance *sin) +{ + PCIQXLDevice *qxl = container_of(sin, PCIQXLDevice, ssd.qxl); + int ret; + + ret = qxl->num_free_res; + if (ret) { + qxl_push_free_res(qxl, 1); + } + return ret; +} + +static void qxl_create_guest_primary_complete(PCIQXLDevice *d); + +/* called from spice server thread context only */ +static void interface_async_complete_io(PCIQXLDevice *qxl, QXLCookie *cookie) +{ + uint32_t current_async; + + qemu_mutex_lock(&qxl->async_lock); + current_async = qxl->current_async; + qxl->current_async = QXL_UNDEFINED_IO; + qemu_mutex_unlock(&qxl->async_lock); + + trace_qxl_interface_async_complete_io(qxl->id, current_async, cookie); + if (!cookie) { + fprintf(stderr, "qxl: %s: error, cookie is NULL\n", __func__); + return; + } + if (cookie && current_async != cookie->io) { + fprintf(stderr, + "qxl: %s: error: current_async = %d != %" + PRId64 " = cookie->io\n", __func__, current_async, cookie->io); + } + switch (current_async) { + case QXL_IO_MEMSLOT_ADD_ASYNC: + case QXL_IO_DESTROY_PRIMARY_ASYNC: + case QXL_IO_UPDATE_AREA_ASYNC: + case QXL_IO_FLUSH_SURFACES_ASYNC: + case QXL_IO_MONITORS_CONFIG_ASYNC: + break; + case QXL_IO_CREATE_PRIMARY_ASYNC: + qxl_create_guest_primary_complete(qxl); + break; + case QXL_IO_DESTROY_ALL_SURFACES_ASYNC: + qxl_spice_destroy_surfaces_complete(qxl); + break; + case QXL_IO_DESTROY_SURFACE_ASYNC: + qxl_spice_destroy_surface_wait_complete(qxl, cookie->u.surface_id); + break; + default: + fprintf(stderr, "qxl: %s: unexpected current_async %d\n", __func__, + current_async); + } + qxl_send_events(qxl, QXL_INTERRUPT_IO_CMD); +} + +/* called from spice server thread context only */ +static void interface_update_area_complete(QXLInstance *sin, + uint32_t surface_id, + QXLRect *dirty, uint32_t num_updated_rects) +{ + PCIQXLDevice *qxl = container_of(sin, PCIQXLDevice, ssd.qxl); + int i; + int qxl_i; + + qemu_mutex_lock(&qxl->ssd.lock); + if (surface_id != 0 || !qxl->render_update_cookie_num) { + qemu_mutex_unlock(&qxl->ssd.lock); + return; + } + trace_qxl_interface_update_area_complete(qxl->id, surface_id, dirty->left, + dirty->right, dirty->top, dirty->bottom); + trace_qxl_interface_update_area_complete_rest(qxl->id, num_updated_rects); + if (qxl->num_dirty_rects + num_updated_rects > QXL_NUM_DIRTY_RECTS) { + /* + * overflow - treat this as a full update. Not expected to be common. + */ + trace_qxl_interface_update_area_complete_overflow(qxl->id, + QXL_NUM_DIRTY_RECTS); + qxl->guest_primary.resized = 1; + } + if (qxl->guest_primary.resized) { + /* + * Don't bother copying or scheduling the bh since we will flip + * the whole area anyway on completion of the update_area async call + */ + qemu_mutex_unlock(&qxl->ssd.lock); + return; + } + qxl_i = qxl->num_dirty_rects; + for (i = 0; i < num_updated_rects; i++) { + qxl->dirty[qxl_i++] = dirty[i]; + } + qxl->num_dirty_rects += num_updated_rects; + trace_qxl_interface_update_area_complete_schedule_bh(qxl->id, + qxl->num_dirty_rects); + qemu_bh_schedule(qxl->update_area_bh); + qemu_mutex_unlock(&qxl->ssd.lock); +} + +/* called from spice server thread context only */ +static void interface_async_complete(QXLInstance *sin, uint64_t cookie_token) +{ + PCIQXLDevice *qxl = container_of(sin, PCIQXLDevice, ssd.qxl); + QXLCookie *cookie = (QXLCookie *)(uintptr_t)cookie_token; + + switch (cookie->type) { + case QXL_COOKIE_TYPE_IO: + interface_async_complete_io(qxl, cookie); + g_free(cookie); + break; + case QXL_COOKIE_TYPE_RENDER_UPDATE_AREA: + qxl_render_update_area_done(qxl, cookie); + break; + case QXL_COOKIE_TYPE_POST_LOAD_MONITORS_CONFIG: + break; + default: + fprintf(stderr, "qxl: %s: unexpected cookie type %d\n", + __func__, cookie->type); + g_free(cookie); + } +} + +/* called from spice server thread context only */ +static void interface_set_client_capabilities(QXLInstance *sin, + uint8_t client_present, + uint8_t caps[58]) +{ + PCIQXLDevice *qxl = container_of(sin, PCIQXLDevice, ssd.qxl); + + if (qxl->revision < 4) { + trace_qxl_set_client_capabilities_unsupported_by_revision(qxl->id, + qxl->revision); + return; + } + + if (runstate_check(RUN_STATE_INMIGRATE) || + runstate_check(RUN_STATE_POSTMIGRATE)) { + return; + } + + qxl->shadow_rom.client_present = client_present; + memcpy(qxl->shadow_rom.client_capabilities, caps, + sizeof(qxl->shadow_rom.client_capabilities)); + qxl->rom->client_present = client_present; + memcpy(qxl->rom->client_capabilities, caps, + sizeof(qxl->rom->client_capabilities)); + qxl_rom_set_dirty(qxl); + + qxl_send_events(qxl, QXL_INTERRUPT_CLIENT); +} + +static uint32_t qxl_crc32(const uint8_t *p, unsigned len) +{ + /* + * zlib xors the seed with 0xffffffff, and xors the result + * again with 0xffffffff; Both are not done with linux's crc32, + * which we want to be compatible with, so undo that. + */ + return crc32(0xffffffff, p, len) ^ 0xffffffff; +} + +/* called from main context only */ +static int interface_client_monitors_config(QXLInstance *sin, + VDAgentMonitorsConfig *monitors_config) +{ + PCIQXLDevice *qxl = container_of(sin, PCIQXLDevice, ssd.qxl); + QXLRom *rom = memory_region_get_ram_ptr(&qxl->rom_bar); + int i; + + if (qxl->revision < 4) { + trace_qxl_client_monitors_config_unsupported_by_device(qxl->id, + qxl->revision); + return 0; + } + /* + * Older windows drivers set int_mask to 0 when their ISR is called, + * then later set it to ~0. So it doesn't relate to the actual interrupts + * handled. However, they are old, so clearly they don't support this + * interrupt + */ + if (qxl->ram->int_mask == 0 || qxl->ram->int_mask == ~0 || + !(qxl->ram->int_mask & QXL_INTERRUPT_CLIENT_MONITORS_CONFIG)) { + trace_qxl_client_monitors_config_unsupported_by_guest(qxl->id, + qxl->ram->int_mask, + monitors_config); + return 0; + } + if (!monitors_config) { + return 1; + } + memset(&rom->client_monitors_config, 0, + sizeof(rom->client_monitors_config)); + rom->client_monitors_config.count = monitors_config->num_of_monitors; + /* monitors_config->flags ignored */ + if (rom->client_monitors_config.count >= + ARRAY_SIZE(rom->client_monitors_config.heads)) { + trace_qxl_client_monitors_config_capped(qxl->id, + monitors_config->num_of_monitors, + ARRAY_SIZE(rom->client_monitors_config.heads)); + rom->client_monitors_config.count = + ARRAY_SIZE(rom->client_monitors_config.heads); + } + for (i = 0 ; i < rom->client_monitors_config.count ; ++i) { + VDAgentMonConfig *monitor = &monitors_config->monitors[i]; + QXLURect *rect = &rom->client_monitors_config.heads[i]; + /* monitor->depth ignored */ + rect->left = monitor->x; + rect->top = monitor->y; + rect->right = monitor->x + monitor->width; + rect->bottom = monitor->y + monitor->height; + } + rom->client_monitors_config_crc = qxl_crc32( + (const uint8_t *)&rom->client_monitors_config, + sizeof(rom->client_monitors_config)); + trace_qxl_client_monitors_config_crc(qxl->id, + sizeof(rom->client_monitors_config), + rom->client_monitors_config_crc); + + trace_qxl_interrupt_client_monitors_config(qxl->id, + rom->client_monitors_config.count, + rom->client_monitors_config.heads); + qxl_send_events(qxl, QXL_INTERRUPT_CLIENT_MONITORS_CONFIG); + return 1; +} + +static const QXLInterface qxl_interface = { + .base.type = SPICE_INTERFACE_QXL, + .base.description = "qxl gpu", + .base.major_version = SPICE_INTERFACE_QXL_MAJOR, + .base.minor_version = SPICE_INTERFACE_QXL_MINOR, + + .attache_worker = interface_attach_worker, + .set_compression_level = interface_set_compression_level, + .set_mm_time = interface_set_mm_time, + .get_init_info = interface_get_init_info, + + /* the callbacks below are called from spice server thread context */ + .get_command = interface_get_command, + .req_cmd_notification = interface_req_cmd_notification, + .release_resource = interface_release_resource, + .get_cursor_command = interface_get_cursor_command, + .req_cursor_notification = interface_req_cursor_notification, + .notify_update = interface_notify_update, + .flush_resources = interface_flush_resources, + .async_complete = interface_async_complete, + .update_area_complete = interface_update_area_complete, + .set_client_capabilities = interface_set_client_capabilities, + .client_monitors_config = interface_client_monitors_config, +}; + +static void qxl_enter_vga_mode(PCIQXLDevice *d) +{ + if (d->mode == QXL_MODE_VGA) { + return; + } + trace_qxl_enter_vga_mode(d->id); + qemu_spice_create_host_primary(&d->ssd); + d->mode = QXL_MODE_VGA; + vga_dirty_log_start(&d->vga); + vga_hw_update(); +} + +static void qxl_exit_vga_mode(PCIQXLDevice *d) +{ + if (d->mode != QXL_MODE_VGA) { + return; + } + trace_qxl_exit_vga_mode(d->id); + vga_dirty_log_stop(&d->vga); + qxl_destroy_primary(d, QXL_SYNC); +} + +static void qxl_update_irq(PCIQXLDevice *d) +{ + uint32_t pending = le32_to_cpu(d->ram->int_pending); + uint32_t mask = le32_to_cpu(d->ram->int_mask); + int level = !!(pending & mask); + qemu_set_irq(d->pci.irq[0], level); + qxl_ring_set_dirty(d); +} + +static void qxl_check_state(PCIQXLDevice *d) +{ + QXLRam *ram = d->ram; + int spice_display_running = qemu_spice_display_is_running(&d->ssd); + + assert(!spice_display_running || SPICE_RING_IS_EMPTY(&ram->cmd_ring)); + assert(!spice_display_running || SPICE_RING_IS_EMPTY(&ram->cursor_ring)); +} + +static void qxl_reset_state(PCIQXLDevice *d) +{ + QXLRom *rom = d->rom; + + qxl_check_state(d); + d->shadow_rom.update_id = cpu_to_le32(0); + *rom = d->shadow_rom; + qxl_rom_set_dirty(d); + init_qxl_ram(d); + d->num_free_res = 0; + d->last_release = NULL; + memset(&d->ssd.dirty, 0, sizeof(d->ssd.dirty)); +} + +static void qxl_soft_reset(PCIQXLDevice *d) +{ + trace_qxl_soft_reset(d->id); + qxl_check_state(d); + qxl_clear_guest_bug(d); + d->current_async = QXL_UNDEFINED_IO; + + if (d->id == 0) { + qxl_enter_vga_mode(d); + } else { + d->mode = QXL_MODE_UNDEFINED; + } +} + +static void qxl_hard_reset(PCIQXLDevice *d, int loadvm) +{ + trace_qxl_hard_reset(d->id, loadvm); + + qxl_spice_reset_cursor(d); + qxl_spice_reset_image_cache(d); + qxl_reset_surfaces(d); + qxl_reset_memslots(d); + + /* pre loadvm reset must not touch QXLRam. This lives in + * device memory, is migrated together with RAM and thus + * already loaded at this point */ + if (!loadvm) { + qxl_reset_state(d); + } + qemu_spice_create_host_memslot(&d->ssd); + qxl_soft_reset(d); +} + +static void qxl_reset_handler(DeviceState *dev) +{ + PCIQXLDevice *d = DO_UPCAST(PCIQXLDevice, pci.qdev, dev); + + qxl_hard_reset(d, 0); +} + +static void qxl_vga_ioport_write(void *opaque, uint32_t addr, uint32_t val) +{ + VGACommonState *vga = opaque; + PCIQXLDevice *qxl = container_of(vga, PCIQXLDevice, vga); + + trace_qxl_io_write_vga(qxl->id, qxl_mode_to_string(qxl->mode), addr, val); + if (qxl->mode != QXL_MODE_VGA) { + qxl_destroy_primary(qxl, QXL_SYNC); + qxl_soft_reset(qxl); + } + vga_ioport_write(opaque, addr, val); +} + +static const MemoryRegionPortio qxl_vga_portio_list[] = { + { 0x04, 2, 1, .read = vga_ioport_read, + .write = qxl_vga_ioport_write }, /* 3b4 */ + { 0x0a, 1, 1, .read = vga_ioport_read, + .write = qxl_vga_ioport_write }, /* 3ba */ + { 0x10, 16, 1, .read = vga_ioport_read, + .write = qxl_vga_ioport_write }, /* 3c0 */ + { 0x24, 2, 1, .read = vga_ioport_read, + .write = qxl_vga_ioport_write }, /* 3d4 */ + { 0x2a, 1, 1, .read = vga_ioport_read, + .write = qxl_vga_ioport_write }, /* 3da */ + PORTIO_END_OF_LIST(), +}; + +static int qxl_add_memslot(PCIQXLDevice *d, uint32_t slot_id, uint64_t delta, + qxl_async_io async) +{ + static const int regions[] = { + QXL_RAM_RANGE_INDEX, + QXL_VRAM_RANGE_INDEX, + QXL_VRAM64_RANGE_INDEX, + }; + uint64_t guest_start; + uint64_t guest_end; + int pci_region; + pcibus_t pci_start; + pcibus_t pci_end; + intptr_t virt_start; + QXLDevMemSlot memslot; + int i; + + guest_start = le64_to_cpu(d->guest_slots[slot_id].slot.mem_start); + guest_end = le64_to_cpu(d->guest_slots[slot_id].slot.mem_end); + + trace_qxl_memslot_add_guest(d->id, slot_id, guest_start, guest_end); + + if (slot_id >= NUM_MEMSLOTS) { + qxl_set_guest_bug(d, "%s: slot_id >= NUM_MEMSLOTS %d >= %d", __func__, + slot_id, NUM_MEMSLOTS); + return 1; + } + if (guest_start > guest_end) { + qxl_set_guest_bug(d, "%s: guest_start > guest_end 0x%" PRIx64 + " > 0x%" PRIx64, __func__, guest_start, guest_end); + return 1; + } + + for (i = 0; i < ARRAY_SIZE(regions); i++) { + pci_region = regions[i]; + pci_start = d->pci.io_regions[pci_region].addr; + pci_end = pci_start + d->pci.io_regions[pci_region].size; + /* mapped? */ + if (pci_start == -1) { + continue; + } + /* start address in range ? */ + if (guest_start < pci_start || guest_start > pci_end) { + continue; + } + /* end address in range ? */ + if (guest_end > pci_end) { + continue; + } + /* passed */ + break; + } + if (i == ARRAY_SIZE(regions)) { + qxl_set_guest_bug(d, "%s: finished loop without match", __func__); + return 1; + } + + switch (pci_region) { + case QXL_RAM_RANGE_INDEX: + virt_start = (intptr_t)memory_region_get_ram_ptr(&d->vga.vram); + break; + case QXL_VRAM_RANGE_INDEX: + case 4 /* vram 64bit */: + virt_start = (intptr_t)memory_region_get_ram_ptr(&d->vram_bar); + break; + default: + /* should not happen */ + qxl_set_guest_bug(d, "%s: pci_region = %d", __func__, pci_region); + return 1; + } + + memslot.slot_id = slot_id; + memslot.slot_group_id = MEMSLOT_GROUP_GUEST; /* guest group */ + memslot.virt_start = virt_start + (guest_start - pci_start); + memslot.virt_end = virt_start + (guest_end - pci_start); + memslot.addr_delta = memslot.virt_start - delta; + memslot.generation = d->rom->slot_generation = 0; + qxl_rom_set_dirty(d); + + qemu_spice_add_memslot(&d->ssd, &memslot, async); + d->guest_slots[slot_id].ptr = (void*)memslot.virt_start; + d->guest_slots[slot_id].size = memslot.virt_end - memslot.virt_start; + d->guest_slots[slot_id].delta = delta; + d->guest_slots[slot_id].active = 1; + return 0; +} + +static void qxl_del_memslot(PCIQXLDevice *d, uint32_t slot_id) +{ + qemu_spice_del_memslot(&d->ssd, MEMSLOT_GROUP_HOST, slot_id); + d->guest_slots[slot_id].active = 0; +} + +static void qxl_reset_memslots(PCIQXLDevice *d) +{ + qxl_spice_reset_memslots(d); + memset(&d->guest_slots, 0, sizeof(d->guest_slots)); +} + +static void qxl_reset_surfaces(PCIQXLDevice *d) +{ + trace_qxl_reset_surfaces(d->id); + d->mode = QXL_MODE_UNDEFINED; + qxl_spice_destroy_surfaces(d, QXL_SYNC); +} + +/* can be also called from spice server thread context */ +void *qxl_phys2virt(PCIQXLDevice *qxl, QXLPHYSICAL pqxl, int group_id) +{ + uint64_t phys = le64_to_cpu(pqxl); + uint32_t slot = (phys >> (64 - 8)) & 0xff; + uint64_t offset = phys & 0xffffffffffff; + + switch (group_id) { + case MEMSLOT_GROUP_HOST: + return (void *)(intptr_t)offset; + case MEMSLOT_GROUP_GUEST: + if (slot >= NUM_MEMSLOTS) { + qxl_set_guest_bug(qxl, "slot too large %d >= %d", slot, + NUM_MEMSLOTS); + return NULL; + } + if (!qxl->guest_slots[slot].active) { + qxl_set_guest_bug(qxl, "inactive slot %d\n", slot); + return NULL; + } + if (offset < qxl->guest_slots[slot].delta) { + qxl_set_guest_bug(qxl, + "slot %d offset %"PRIu64" < delta %"PRIu64"\n", + slot, offset, qxl->guest_slots[slot].delta); + return NULL; + } + offset -= qxl->guest_slots[slot].delta; + if (offset > qxl->guest_slots[slot].size) { + qxl_set_guest_bug(qxl, + "slot %d offset %"PRIu64" > size %"PRIu64"\n", + slot, offset, qxl->guest_slots[slot].size); + return NULL; + } + return qxl->guest_slots[slot].ptr + offset; + } + return NULL; +} + +static void qxl_create_guest_primary_complete(PCIQXLDevice *qxl) +{ + /* for local rendering */ + qxl_render_resize(qxl); +} + +static void qxl_create_guest_primary(PCIQXLDevice *qxl, int loadvm, + qxl_async_io async) +{ + QXLDevSurfaceCreate surface; + QXLSurfaceCreate *sc = &qxl->guest_primary.surface; + int size; + int requested_height = le32_to_cpu(sc->height); + int requested_stride = le32_to_cpu(sc->stride); + + size = abs(requested_stride) * requested_height; + if (size > qxl->vgamem_size) { + qxl_set_guest_bug(qxl, "%s: requested primary larger then framebuffer" + " size", __func__); + return; + } + + if (qxl->mode == QXL_MODE_NATIVE) { + qxl_set_guest_bug(qxl, "%s: nop since already in QXL_MODE_NATIVE", + __func__); + } + qxl_exit_vga_mode(qxl); + + surface.format = le32_to_cpu(sc->format); + surface.height = le32_to_cpu(sc->height); + surface.mem = le64_to_cpu(sc->mem); + surface.position = le32_to_cpu(sc->position); + surface.stride = le32_to_cpu(sc->stride); + surface.width = le32_to_cpu(sc->width); + surface.type = le32_to_cpu(sc->type); + surface.flags = le32_to_cpu(sc->flags); + trace_qxl_create_guest_primary(qxl->id, sc->width, sc->height, sc->mem, + sc->format, sc->position); + trace_qxl_create_guest_primary_rest(qxl->id, sc->stride, sc->type, + sc->flags); + + if ((surface.stride & 0x3) != 0) { + qxl_set_guest_bug(qxl, "primary surface stride = %d %% 4 != 0", + surface.stride); + return; + } + + surface.mouse_mode = true; + surface.group_id = MEMSLOT_GROUP_GUEST; + if (loadvm) { + surface.flags |= QXL_SURF_FLAG_KEEP_DATA; + } + + qxl->mode = QXL_MODE_NATIVE; + qxl->cmdflags = 0; + qemu_spice_create_primary_surface(&qxl->ssd, 0, &surface, async); + + if (async == QXL_SYNC) { + qxl_create_guest_primary_complete(qxl); + } +} + +/* return 1 if surface destoy was initiated (in QXL_ASYNC case) or + * done (in QXL_SYNC case), 0 otherwise. */ +static int qxl_destroy_primary(PCIQXLDevice *d, qxl_async_io async) +{ + if (d->mode == QXL_MODE_UNDEFINED) { + return 0; + } + trace_qxl_destroy_primary(d->id); + d->mode = QXL_MODE_UNDEFINED; + qemu_spice_destroy_primary_surface(&d->ssd, 0, async); + qxl_spice_reset_cursor(d); + return 1; +} + +static void qxl_set_mode(PCIQXLDevice *d, int modenr, int loadvm) +{ + pcibus_t start = d->pci.io_regions[QXL_RAM_RANGE_INDEX].addr; + pcibus_t end = d->pci.io_regions[QXL_RAM_RANGE_INDEX].size + start; + QXLMode *mode = d->modes->modes + modenr; + uint64_t devmem = d->pci.io_regions[QXL_RAM_RANGE_INDEX].addr; + QXLMemSlot slot = { + .mem_start = start, + .mem_end = end + }; + QXLSurfaceCreate surface = { + .width = mode->x_res, + .height = mode->y_res, + .stride = -mode->x_res * 4, + .format = SPICE_SURFACE_FMT_32_xRGB, + .flags = loadvm ? QXL_SURF_FLAG_KEEP_DATA : 0, + .mouse_mode = true, + .mem = devmem + d->shadow_rom.draw_area_offset, + }; + + trace_qxl_set_mode(d->id, modenr, mode->x_res, mode->y_res, mode->bits, + devmem); + if (!loadvm) { + qxl_hard_reset(d, 0); + } + + d->guest_slots[0].slot = slot; + assert(qxl_add_memslot(d, 0, devmem, QXL_SYNC) == 0); + + d->guest_primary.surface = surface; + qxl_create_guest_primary(d, 0, QXL_SYNC); + + d->mode = QXL_MODE_COMPAT; + d->cmdflags = QXL_COMMAND_FLAG_COMPAT; + if (mode->bits == 16) { + d->cmdflags |= QXL_COMMAND_FLAG_COMPAT_16BPP; + } + d->shadow_rom.mode = cpu_to_le32(modenr); + d->rom->mode = cpu_to_le32(modenr); + qxl_rom_set_dirty(d); +} + +static void ioport_write(void *opaque, hwaddr addr, + uint64_t val, unsigned size) +{ + PCIQXLDevice *d = opaque; + uint32_t io_port = addr; + qxl_async_io async = QXL_SYNC; + uint32_t orig_io_port = io_port; + + if (d->guest_bug && io_port != QXL_IO_RESET) { + return; + } + + if (d->revision <= QXL_REVISION_STABLE_V10 && + io_port > QXL_IO_FLUSH_RELEASE) { + qxl_set_guest_bug(d, "unsupported io %d for revision %d\n", + io_port, d->revision); + return; + } + + switch (io_port) { + case QXL_IO_RESET: + case QXL_IO_SET_MODE: + case QXL_IO_MEMSLOT_ADD: + case QXL_IO_MEMSLOT_DEL: + case QXL_IO_CREATE_PRIMARY: + case QXL_IO_UPDATE_IRQ: + case QXL_IO_LOG: + case QXL_IO_MEMSLOT_ADD_ASYNC: + case QXL_IO_CREATE_PRIMARY_ASYNC: + break; + default: + if (d->mode != QXL_MODE_VGA) { + break; + } + trace_qxl_io_unexpected_vga_mode(d->id, + addr, val, io_port_to_string(io_port)); + /* be nice to buggy guest drivers */ + if (io_port >= QXL_IO_UPDATE_AREA_ASYNC && + io_port < QXL_IO_RANGE_SIZE) { + qxl_send_events(d, QXL_INTERRUPT_IO_CMD); + } + return; + } + + /* we change the io_port to avoid ifdeffery in the main switch */ + orig_io_port = io_port; + switch (io_port) { + case QXL_IO_UPDATE_AREA_ASYNC: + io_port = QXL_IO_UPDATE_AREA; + goto async_common; + case QXL_IO_MEMSLOT_ADD_ASYNC: + io_port = QXL_IO_MEMSLOT_ADD; + goto async_common; + case QXL_IO_CREATE_PRIMARY_ASYNC: + io_port = QXL_IO_CREATE_PRIMARY; + goto async_common; + case QXL_IO_DESTROY_PRIMARY_ASYNC: + io_port = QXL_IO_DESTROY_PRIMARY; + goto async_common; + case QXL_IO_DESTROY_SURFACE_ASYNC: + io_port = QXL_IO_DESTROY_SURFACE_WAIT; + goto async_common; + case QXL_IO_DESTROY_ALL_SURFACES_ASYNC: + io_port = QXL_IO_DESTROY_ALL_SURFACES; + goto async_common; + case QXL_IO_FLUSH_SURFACES_ASYNC: + case QXL_IO_MONITORS_CONFIG_ASYNC: +async_common: + async = QXL_ASYNC; + qemu_mutex_lock(&d->async_lock); + if (d->current_async != QXL_UNDEFINED_IO) { + qxl_set_guest_bug(d, "%d async started before last (%d) complete", + io_port, d->current_async); + qemu_mutex_unlock(&d->async_lock); + return; + } + d->current_async = orig_io_port; + qemu_mutex_unlock(&d->async_lock); + break; + default: + break; + } + trace_qxl_io_write(d->id, qxl_mode_to_string(d->mode), addr, val, size, + async); + + switch (io_port) { + case QXL_IO_UPDATE_AREA: + { + QXLCookie *cookie = NULL; + QXLRect update = d->ram->update_area; + + if (d->ram->update_surface > d->ssd.num_surfaces) { + qxl_set_guest_bug(d, "QXL_IO_UPDATE_AREA: invalid surface id %d\n", + d->ram->update_surface); + break; + } + if (update.left >= update.right || update.top >= update.bottom || + update.left < 0 || update.top < 0) { + qxl_set_guest_bug(d, + "QXL_IO_UPDATE_AREA: invalid area (%ux%u)x(%ux%u)\n", + update.left, update.top, update.right, update.bottom); + break; + } + if (async == QXL_ASYNC) { + cookie = qxl_cookie_new(QXL_COOKIE_TYPE_IO, + QXL_IO_UPDATE_AREA_ASYNC); + cookie->u.area = update; + } + qxl_spice_update_area(d, d->ram->update_surface, + cookie ? &cookie->u.area : &update, + NULL, 0, 0, async, cookie); + break; + } + case QXL_IO_NOTIFY_CMD: + qemu_spice_wakeup(&d->ssd); + break; + case QXL_IO_NOTIFY_CURSOR: + qemu_spice_wakeup(&d->ssd); + break; + case QXL_IO_UPDATE_IRQ: + qxl_update_irq(d); + break; + case QXL_IO_NOTIFY_OOM: + if (!SPICE_RING_IS_EMPTY(&d->ram->release_ring)) { + break; + } + d->oom_running = 1; + qxl_spice_oom(d); + d->oom_running = 0; + break; + case QXL_IO_SET_MODE: + qxl_set_mode(d, val, 0); + break; + case QXL_IO_LOG: + trace_qxl_io_log(d->id, d->ram->log_buf); + if (d->guestdebug) { + fprintf(stderr, "qxl/guest-%d: %" PRId64 ": %s", d->id, + qemu_get_clock_ns(vm_clock), d->ram->log_buf); + } + break; + case QXL_IO_RESET: + qxl_hard_reset(d, 0); + break; + case QXL_IO_MEMSLOT_ADD: + if (val >= NUM_MEMSLOTS) { + qxl_set_guest_bug(d, "QXL_IO_MEMSLOT_ADD: val out of range"); + break; + } + if (d->guest_slots[val].active) { + qxl_set_guest_bug(d, + "QXL_IO_MEMSLOT_ADD: memory slot already active"); + break; + } + d->guest_slots[val].slot = d->ram->mem_slot; + qxl_add_memslot(d, val, 0, async); + break; + case QXL_IO_MEMSLOT_DEL: + if (val >= NUM_MEMSLOTS) { + qxl_set_guest_bug(d, "QXL_IO_MEMSLOT_DEL: val out of range"); + break; + } + qxl_del_memslot(d, val); + break; + case QXL_IO_CREATE_PRIMARY: + if (val != 0) { + qxl_set_guest_bug(d, "QXL_IO_CREATE_PRIMARY (async=%d): val != 0", + async); + goto cancel_async; + } + d->guest_primary.surface = d->ram->create_surface; + qxl_create_guest_primary(d, 0, async); + break; + case QXL_IO_DESTROY_PRIMARY: + if (val != 0) { + qxl_set_guest_bug(d, "QXL_IO_DESTROY_PRIMARY (async=%d): val != 0", + async); + goto cancel_async; + } + if (!qxl_destroy_primary(d, async)) { + trace_qxl_io_destroy_primary_ignored(d->id, + qxl_mode_to_string(d->mode)); + goto cancel_async; + } + break; + case QXL_IO_DESTROY_SURFACE_WAIT: + if (val >= d->ssd.num_surfaces) { + qxl_set_guest_bug(d, "QXL_IO_DESTROY_SURFACE (async=%d):" + "%" PRIu64 " >= NUM_SURFACES", async, val); + goto cancel_async; + } + qxl_spice_destroy_surface_wait(d, val, async); + break; + case QXL_IO_FLUSH_RELEASE: { + QXLReleaseRing *ring = &d->ram->release_ring; + if (ring->prod - ring->cons + 1 == ring->num_items) { + fprintf(stderr, + "ERROR: no flush, full release ring [p%d,%dc]\n", + ring->prod, ring->cons); + } + qxl_push_free_res(d, 1 /* flush */); + break; + } + case QXL_IO_FLUSH_SURFACES_ASYNC: + qxl_spice_flush_surfaces_async(d); + break; + case QXL_IO_DESTROY_ALL_SURFACES: + d->mode = QXL_MODE_UNDEFINED; + qxl_spice_destroy_surfaces(d, async); + break; + case QXL_IO_MONITORS_CONFIG_ASYNC: + qxl_spice_monitors_config_async(d, 0); + break; + default: + qxl_set_guest_bug(d, "%s: unexpected ioport=0x%x\n", __func__, io_port); + } + return; +cancel_async: + if (async) { + qxl_send_events(d, QXL_INTERRUPT_IO_CMD); + qemu_mutex_lock(&d->async_lock); + d->current_async = QXL_UNDEFINED_IO; + qemu_mutex_unlock(&d->async_lock); + } +} + +static uint64_t ioport_read(void *opaque, hwaddr addr, + unsigned size) +{ + PCIQXLDevice *qxl = opaque; + + trace_qxl_io_read_unexpected(qxl->id); + return 0xff; +} + +static const MemoryRegionOps qxl_io_ops = { + .read = ioport_read, + .write = ioport_write, + .valid = { + .min_access_size = 1, + .max_access_size = 1, + }, +}; + +static void pipe_read(void *opaque) +{ + PCIQXLDevice *d = opaque; + char dummy; + int len; + + do { + len = read(d->pipe[0], &dummy, sizeof(dummy)); + } while (len == sizeof(dummy)); + qxl_update_irq(d); +} + +static void qxl_send_events(PCIQXLDevice *d, uint32_t events) +{ + uint32_t old_pending; + uint32_t le_events = cpu_to_le32(events); + + trace_qxl_send_events(d->id, events); + if (!qemu_spice_display_is_running(&d->ssd)) { + /* spice-server tracks guest running state and should not do this */ + fprintf(stderr, "%s: spice-server bug: guest stopped, ignoring\n", + __func__); + trace_qxl_send_events_vm_stopped(d->id, events); + return; + } + old_pending = __sync_fetch_and_or(&d->ram->int_pending, le_events); + if ((old_pending & le_events) == le_events) { + return; + } + if (qemu_thread_is_self(&d->main)) { + qxl_update_irq(d); + } else { + if (write(d->pipe[1], d, 1) != 1) { + dprint(d, 1, "%s: write to pipe failed\n", __func__); + } + } +} + +static void init_pipe_signaling(PCIQXLDevice *d) +{ + if (pipe(d->pipe) < 0) { + fprintf(stderr, "%s:%s: qxl pipe creation failed\n", + __FILE__, __func__); + exit(1); + } + fcntl(d->pipe[0], F_SETFL, O_NONBLOCK); + fcntl(d->pipe[1], F_SETFL, O_NONBLOCK); + fcntl(d->pipe[0], F_SETOWN, getpid()); + + qemu_thread_get_self(&d->main); + qemu_set_fd_handler(d->pipe[0], pipe_read, NULL, d); +} + +/* graphics console */ + +static void qxl_hw_update(void *opaque) +{ + PCIQXLDevice *qxl = opaque; + VGACommonState *vga = &qxl->vga; + + switch (qxl->mode) { + case QXL_MODE_VGA: + vga->update(vga); + break; + case QXL_MODE_COMPAT: + case QXL_MODE_NATIVE: + qxl_render_update(qxl); + break; + default: + break; + } +} + +static void qxl_hw_invalidate(void *opaque) +{ + PCIQXLDevice *qxl = opaque; + VGACommonState *vga = &qxl->vga; + + vga->invalidate(vga); +} + +static void qxl_hw_screen_dump(void *opaque, const char *filename, bool cswitch, + Error **errp) +{ + PCIQXLDevice *qxl = opaque; + VGACommonState *vga = &qxl->vga; + + switch (qxl->mode) { + case QXL_MODE_COMPAT: + case QXL_MODE_NATIVE: + qxl_render_update(qxl); + ppm_save(filename, qxl->ssd.ds, errp); + break; + case QXL_MODE_VGA: + vga->screen_dump(vga, filename, cswitch, errp); + break; + default: + break; + } +} + +static void qxl_hw_text_update(void *opaque, console_ch_t *chardata) +{ + PCIQXLDevice *qxl = opaque; + VGACommonState *vga = &qxl->vga; + + if (qxl->mode == QXL_MODE_VGA) { + vga->text_update(vga, chardata); + return; + } +} + +static void qxl_dirty_surfaces(PCIQXLDevice *qxl) +{ + uintptr_t vram_start; + int i; + + if (qxl->mode != QXL_MODE_NATIVE && qxl->mode != QXL_MODE_COMPAT) { + return; + } + + /* dirty the primary surface */ + qxl_set_dirty(&qxl->vga.vram, qxl->shadow_rom.draw_area_offset, + qxl->shadow_rom.surface0_area_size); + + vram_start = (uintptr_t)memory_region_get_ram_ptr(&qxl->vram_bar); + + /* dirty the off-screen surfaces */ + for (i = 0; i < qxl->ssd.num_surfaces; i++) { + QXLSurfaceCmd *cmd; + intptr_t surface_offset; + int surface_size; + + if (qxl->guest_surfaces.cmds[i] == 0) { + continue; + } + + cmd = qxl_phys2virt(qxl, qxl->guest_surfaces.cmds[i], + MEMSLOT_GROUP_GUEST); + assert(cmd); + assert(cmd->type == QXL_SURFACE_CMD_CREATE); + surface_offset = (intptr_t)qxl_phys2virt(qxl, + cmd->u.surface_create.data, + MEMSLOT_GROUP_GUEST); + assert(surface_offset); + surface_offset -= vram_start; + surface_size = cmd->u.surface_create.height * + abs(cmd->u.surface_create.stride); + trace_qxl_surfaces_dirty(qxl->id, i, (int)surface_offset, surface_size); + qxl_set_dirty(&qxl->vram_bar, surface_offset, surface_size); + } +} + +static void qxl_vm_change_state_handler(void *opaque, int running, + RunState state) +{ + PCIQXLDevice *qxl = opaque; + + if (running) { + /* + * if qxl_send_events was called from spice server context before + * migration ended, qxl_update_irq for these events might not have been + * called + */ + qxl_update_irq(qxl); + } else { + /* make sure surfaces are saved before migration */ + qxl_dirty_surfaces(qxl); + } +} + +/* display change listener */ + +static void display_update(DisplayChangeListener *dcl, + int x, int y, int w, int h) +{ + PCIQXLDevice *qxl = container_of(dcl, PCIQXLDevice, ssd.dcl); + + if (qxl->mode == QXL_MODE_VGA) { + qemu_spice_display_update(&qxl->ssd, x, y, w, h); + } +} + +static void display_switch(DisplayChangeListener *dcl, + struct DisplaySurface *surface) +{ + PCIQXLDevice *qxl = container_of(dcl, PCIQXLDevice, ssd.dcl); + + qxl->ssd.ds = surface; + if (qxl->mode == QXL_MODE_VGA) { + qemu_spice_display_switch(&qxl->ssd, surface); + } +} + +static void display_refresh(DisplayChangeListener *dcl) +{ + PCIQXLDevice *qxl = container_of(dcl, PCIQXLDevice, ssd.dcl); + + if (qxl->mode == QXL_MODE_VGA) { + qemu_spice_display_refresh(&qxl->ssd); + } else { + qemu_mutex_lock(&qxl->ssd.lock); + qemu_spice_cursor_refresh_unlocked(&qxl->ssd); + qemu_mutex_unlock(&qxl->ssd.lock); + } +} + +static DisplayChangeListenerOps display_listener_ops = { + .dpy_name = "spice/qxl", + .dpy_gfx_update = display_update, + .dpy_gfx_switch = display_switch, + .dpy_refresh = display_refresh, +}; + +static void qxl_init_ramsize(PCIQXLDevice *qxl) +{ + /* vga mode framebuffer / primary surface (bar 0, first part) */ + if (qxl->vgamem_size_mb < 8) { + qxl->vgamem_size_mb = 8; + } + qxl->vgamem_size = qxl->vgamem_size_mb * 1024 * 1024; + + /* vga ram (bar 0, total) */ + if (qxl->ram_size_mb != -1) { + qxl->vga.vram_size = qxl->ram_size_mb * 1024 * 1024; + } + if (qxl->vga.vram_size < qxl->vgamem_size * 2) { + qxl->vga.vram_size = qxl->vgamem_size * 2; + } + + /* vram32 (surfaces, 32bit, bar 1) */ + if (qxl->vram32_size_mb != -1) { + qxl->vram32_size = qxl->vram32_size_mb * 1024 * 1024; + } + if (qxl->vram32_size < 4096) { + qxl->vram32_size = 4096; + } + + /* vram (surfaces, 64bit, bar 4+5) */ + if (qxl->vram_size_mb != -1) { + qxl->vram_size = qxl->vram_size_mb * 1024 * 1024; + } + if (qxl->vram_size < qxl->vram32_size) { + qxl->vram_size = qxl->vram32_size; + } + + if (qxl->revision == 1) { + qxl->vram32_size = 4096; + qxl->vram_size = 4096; + } + qxl->vgamem_size = msb_mask(qxl->vgamem_size * 2 - 1); + qxl->vga.vram_size = msb_mask(qxl->vga.vram_size * 2 - 1); + qxl->vram32_size = msb_mask(qxl->vram32_size * 2 - 1); + qxl->vram_size = msb_mask(qxl->vram_size * 2 - 1); +} + +static int qxl_init_common(PCIQXLDevice *qxl) +{ + uint8_t* config = qxl->pci.config; + uint32_t pci_device_rev; + uint32_t io_size; + + qxl->mode = QXL_MODE_UNDEFINED; + qxl->generation = 1; + qxl->num_memslots = NUM_MEMSLOTS; + qemu_mutex_init(&qxl->track_lock); + qemu_mutex_init(&qxl->async_lock); + qxl->current_async = QXL_UNDEFINED_IO; + qxl->guest_bug = 0; + + switch (qxl->revision) { + case 1: /* spice 0.4 -- qxl-1 */ + pci_device_rev = QXL_REVISION_STABLE_V04; + io_size = 8; + break; + case 2: /* spice 0.6 -- qxl-2 */ + pci_device_rev = QXL_REVISION_STABLE_V06; + io_size = 16; + break; + case 3: /* qxl-3 */ + pci_device_rev = QXL_REVISION_STABLE_V10; + io_size = 32; /* PCI region size must be pow2 */ + break; + case 4: /* qxl-4 */ + pci_device_rev = QXL_REVISION_STABLE_V12; + io_size = msb_mask(QXL_IO_RANGE_SIZE * 2 - 1); + break; + default: + error_report("Invalid revision %d for qxl device (max %d)", + qxl->revision, QXL_DEFAULT_REVISION); + return -1; + } + + pci_set_byte(&config[PCI_REVISION_ID], pci_device_rev); + pci_set_byte(&config[PCI_INTERRUPT_PIN], 1); + + qxl->rom_size = qxl_rom_size(); + memory_region_init_ram(&qxl->rom_bar, "qxl.vrom", qxl->rom_size); + vmstate_register_ram(&qxl->rom_bar, &qxl->pci.qdev); + init_qxl_rom(qxl); + init_qxl_ram(qxl); + + qxl->guest_surfaces.cmds = g_new0(QXLPHYSICAL, qxl->ssd.num_surfaces); + memory_region_init_ram(&qxl->vram_bar, "qxl.vram", qxl->vram_size); + vmstate_register_ram(&qxl->vram_bar, &qxl->pci.qdev); + memory_region_init_alias(&qxl->vram32_bar, "qxl.vram32", &qxl->vram_bar, + 0, qxl->vram32_size); + + memory_region_init_io(&qxl->io_bar, &qxl_io_ops, qxl, + "qxl-ioports", io_size); + if (qxl->id == 0) { + vga_dirty_log_start(&qxl->vga); + } + memory_region_set_flush_coalesced(&qxl->io_bar); + + + pci_register_bar(&qxl->pci, QXL_IO_RANGE_INDEX, + PCI_BASE_ADDRESS_SPACE_IO, &qxl->io_bar); + + pci_register_bar(&qxl->pci, QXL_ROM_RANGE_INDEX, + PCI_BASE_ADDRESS_SPACE_MEMORY, &qxl->rom_bar); + + pci_register_bar(&qxl->pci, QXL_RAM_RANGE_INDEX, + PCI_BASE_ADDRESS_SPACE_MEMORY, &qxl->vga.vram); + + pci_register_bar(&qxl->pci, QXL_VRAM_RANGE_INDEX, + PCI_BASE_ADDRESS_SPACE_MEMORY, &qxl->vram32_bar); + + if (qxl->vram32_size < qxl->vram_size) { + /* + * Make the 64bit vram bar show up only in case it is + * configured to be larger than the 32bit vram bar. + */ + pci_register_bar(&qxl->pci, QXL_VRAM64_RANGE_INDEX, + PCI_BASE_ADDRESS_SPACE_MEMORY | + PCI_BASE_ADDRESS_MEM_TYPE_64 | + PCI_BASE_ADDRESS_MEM_PREFETCH, + &qxl->vram_bar); + } + + /* print pci bar details */ + dprint(qxl, 1, "ram/%s: %d MB [region 0]\n", + qxl->id == 0 ? "pri" : "sec", + qxl->vga.vram_size / (1024*1024)); + dprint(qxl, 1, "vram/32: %d MB [region 1]\n", + qxl->vram32_size / (1024*1024)); + dprint(qxl, 1, "vram/64: %d MB %s\n", + qxl->vram_size / (1024*1024), + qxl->vram32_size < qxl->vram_size ? "[region 4]" : "[unmapped]"); + + qxl->ssd.qxl.base.sif = &qxl_interface.base; + qxl->ssd.qxl.id = qxl->id; + if (qemu_spice_add_interface(&qxl->ssd.qxl.base) != 0) { + error_report("qxl interface %d.%d not supported by spice-server", + SPICE_INTERFACE_QXL_MAJOR, SPICE_INTERFACE_QXL_MINOR); + return -1; + } + qemu_add_vm_change_state_handler(qxl_vm_change_state_handler, qxl); + + init_pipe_signaling(qxl); + qxl_reset_state(qxl); + + qxl->update_area_bh = qemu_bh_new(qxl_render_update_area_bh, qxl); + + return 0; +} + +static int qxl_init_primary(PCIDevice *dev) +{ + PCIQXLDevice *qxl = DO_UPCAST(PCIQXLDevice, pci, dev); + VGACommonState *vga = &qxl->vga; + PortioList *qxl_vga_port_list = g_new(PortioList, 1); + DisplayState *ds; + int rc; + + qxl->id = 0; + qxl_init_ramsize(qxl); + vga->vram_size_mb = qxl->vga.vram_size >> 20; + vga_common_init(vga); + vga_init(vga, pci_address_space(dev), pci_address_space_io(dev), false); + portio_list_init(qxl_vga_port_list, qxl_vga_portio_list, vga, "vga"); + portio_list_add(qxl_vga_port_list, pci_address_space_io(dev), 0x3b0); + + vga->con = graphic_console_init(qxl_hw_update, qxl_hw_invalidate, + qxl_hw_screen_dump, qxl_hw_text_update, + qxl); + qxl->ssd.con = vga->con, + qemu_spice_display_init_common(&qxl->ssd); + + rc = qxl_init_common(qxl); + if (rc != 0) { + return rc; + } + + qxl->ssd.dcl.ops = &display_listener_ops; + ds = qemu_console_displaystate(vga->con); + register_displaychangelistener(ds, &qxl->ssd.dcl); + return rc; +} + +static int qxl_init_secondary(PCIDevice *dev) +{ + static int device_id = 1; + PCIQXLDevice *qxl = DO_UPCAST(PCIQXLDevice, pci, dev); + + qxl->id = device_id++; + qxl_init_ramsize(qxl); + memory_region_init_ram(&qxl->vga.vram, "qxl.vgavram", qxl->vga.vram_size); + vmstate_register_ram(&qxl->vga.vram, &qxl->pci.qdev); + qxl->vga.vram_ptr = memory_region_get_ram_ptr(&qxl->vga.vram); + + return qxl_init_common(qxl); +} + +static void qxl_pre_save(void *opaque) +{ + PCIQXLDevice* d = opaque; + uint8_t *ram_start = d->vga.vram_ptr; + + trace_qxl_pre_save(d->id); + if (d->last_release == NULL) { + d->last_release_offset = 0; + } else { + d->last_release_offset = (uint8_t *)d->last_release - ram_start; + } + assert(d->last_release_offset < d->vga.vram_size); +} + +static int qxl_pre_load(void *opaque) +{ + PCIQXLDevice* d = opaque; + + trace_qxl_pre_load(d->id); + qxl_hard_reset(d, 1); + qxl_exit_vga_mode(d); + return 0; +} + +static void qxl_create_memslots(PCIQXLDevice *d) +{ + int i; + + for (i = 0; i < NUM_MEMSLOTS; i++) { + if (!d->guest_slots[i].active) { + continue; + } + qxl_add_memslot(d, i, 0, QXL_SYNC); + } +} + +static int qxl_post_load(void *opaque, int version) +{ + PCIQXLDevice* d = opaque; + uint8_t *ram_start = d->vga.vram_ptr; + QXLCommandExt *cmds; + int in, out, newmode; + + assert(d->last_release_offset < d->vga.vram_size); + if (d->last_release_offset == 0) { + d->last_release = NULL; + } else { + d->last_release = (QXLReleaseInfo *)(ram_start + d->last_release_offset); + } + + d->modes = (QXLModes*)((uint8_t*)d->rom + d->rom->modes_offset); + + trace_qxl_post_load(d->id, qxl_mode_to_string(d->mode)); + newmode = d->mode; + d->mode = QXL_MODE_UNDEFINED; + + switch (newmode) { + case QXL_MODE_UNDEFINED: + qxl_create_memslots(d); + break; + case QXL_MODE_VGA: + qxl_create_memslots(d); + qxl_enter_vga_mode(d); + break; + case QXL_MODE_NATIVE: + qxl_create_memslots(d); + qxl_create_guest_primary(d, 1, QXL_SYNC); + + /* replay surface-create and cursor-set commands */ + cmds = g_malloc0(sizeof(QXLCommandExt) * (d->ssd.num_surfaces + 1)); + for (in = 0, out = 0; in < d->ssd.num_surfaces; in++) { + if (d->guest_surfaces.cmds[in] == 0) { + continue; + } + cmds[out].cmd.data = d->guest_surfaces.cmds[in]; + cmds[out].cmd.type = QXL_CMD_SURFACE; + cmds[out].group_id = MEMSLOT_GROUP_GUEST; + out++; + } + if (d->guest_cursor) { + cmds[out].cmd.data = d->guest_cursor; + cmds[out].cmd.type = QXL_CMD_CURSOR; + cmds[out].group_id = MEMSLOT_GROUP_GUEST; + out++; + } + qxl_spice_loadvm_commands(d, cmds, out); + g_free(cmds); + if (d->guest_monitors_config) { + qxl_spice_monitors_config_async(d, 1); + } + break; + case QXL_MODE_COMPAT: + /* note: no need to call qxl_create_memslots, qxl_set_mode + * creates the mem slot. */ + qxl_set_mode(d, d->shadow_rom.mode, 1); + break; + } + return 0; +} + +#define QXL_SAVE_VERSION 21 + +static bool qxl_monitors_config_needed(void *opaque) +{ + PCIQXLDevice *qxl = opaque; + + return qxl->guest_monitors_config != 0; +} + + +static VMStateDescription qxl_memslot = { + .name = "qxl-memslot", + .version_id = QXL_SAVE_VERSION, + .minimum_version_id = QXL_SAVE_VERSION, + .fields = (VMStateField[]) { + VMSTATE_UINT64(slot.mem_start, struct guest_slots), + VMSTATE_UINT64(slot.mem_end, struct guest_slots), + VMSTATE_UINT32(active, struct guest_slots), + VMSTATE_END_OF_LIST() + } +}; + +static VMStateDescription qxl_surface = { + .name = "qxl-surface", + .version_id = QXL_SAVE_VERSION, + .minimum_version_id = QXL_SAVE_VERSION, + .fields = (VMStateField[]) { + VMSTATE_UINT32(width, QXLSurfaceCreate), + VMSTATE_UINT32(height, QXLSurfaceCreate), + VMSTATE_INT32(stride, QXLSurfaceCreate), + VMSTATE_UINT32(format, QXLSurfaceCreate), + VMSTATE_UINT32(position, QXLSurfaceCreate), + VMSTATE_UINT32(mouse_mode, QXLSurfaceCreate), + VMSTATE_UINT32(flags, QXLSurfaceCreate), + VMSTATE_UINT32(type, QXLSurfaceCreate), + VMSTATE_UINT64(mem, QXLSurfaceCreate), + VMSTATE_END_OF_LIST() + } +}; + +static VMStateDescription qxl_vmstate_monitors_config = { + .name = "qxl/monitors-config", + .version_id = 1, + .minimum_version_id = 1, + .fields = (VMStateField[]) { + VMSTATE_UINT64(guest_monitors_config, PCIQXLDevice), + VMSTATE_END_OF_LIST() + }, +}; + +static VMStateDescription qxl_vmstate = { + .name = "qxl", + .version_id = QXL_SAVE_VERSION, + .minimum_version_id = QXL_SAVE_VERSION, + .pre_save = qxl_pre_save, + .pre_load = qxl_pre_load, + .post_load = qxl_post_load, + .fields = (VMStateField[]) { + VMSTATE_PCI_DEVICE(pci, PCIQXLDevice), + VMSTATE_STRUCT(vga, PCIQXLDevice, 0, vmstate_vga_common, VGACommonState), + VMSTATE_UINT32(shadow_rom.mode, PCIQXLDevice), + VMSTATE_UINT32(num_free_res, PCIQXLDevice), + VMSTATE_UINT32(last_release_offset, PCIQXLDevice), + VMSTATE_UINT32(mode, PCIQXLDevice), + VMSTATE_UINT32(ssd.unique, PCIQXLDevice), + VMSTATE_INT32_EQUAL(num_memslots, PCIQXLDevice), + VMSTATE_STRUCT_ARRAY(guest_slots, PCIQXLDevice, NUM_MEMSLOTS, 0, + qxl_memslot, struct guest_slots), + VMSTATE_STRUCT(guest_primary.surface, PCIQXLDevice, 0, + qxl_surface, QXLSurfaceCreate), + VMSTATE_INT32_EQUAL(ssd.num_surfaces, PCIQXLDevice), + VMSTATE_VARRAY_INT32(guest_surfaces.cmds, PCIQXLDevice, + ssd.num_surfaces, 0, + vmstate_info_uint64, uint64_t), + VMSTATE_UINT64(guest_cursor, PCIQXLDevice), + VMSTATE_END_OF_LIST() + }, + .subsections = (VMStateSubsection[]) { + { + .vmsd = &qxl_vmstate_monitors_config, + .needed = qxl_monitors_config_needed, + }, { + /* empty */ + } + } +}; + +static Property qxl_properties[] = { + DEFINE_PROP_UINT32("ram_size", PCIQXLDevice, vga.vram_size, + 64 * 1024 * 1024), + DEFINE_PROP_UINT32("vram_size", PCIQXLDevice, vram32_size, + 64 * 1024 * 1024), + DEFINE_PROP_UINT32("revision", PCIQXLDevice, revision, + QXL_DEFAULT_REVISION), + DEFINE_PROP_UINT32("debug", PCIQXLDevice, debug, 0), + DEFINE_PROP_UINT32("guestdebug", PCIQXLDevice, guestdebug, 0), + DEFINE_PROP_UINT32("cmdlog", PCIQXLDevice, cmdlog, 0), + DEFINE_PROP_UINT32("ram_size_mb", PCIQXLDevice, ram_size_mb, -1), + DEFINE_PROP_UINT32("vram_size_mb", PCIQXLDevice, vram32_size_mb, -1), + DEFINE_PROP_UINT32("vram64_size_mb", PCIQXLDevice, vram_size_mb, -1), + DEFINE_PROP_UINT32("vgamem_mb", PCIQXLDevice, vgamem_size_mb, 16), + DEFINE_PROP_INT32("surfaces", PCIQXLDevice, ssd.num_surfaces, 1024), + DEFINE_PROP_END_OF_LIST(), +}; + +static void qxl_primary_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + PCIDeviceClass *k = PCI_DEVICE_CLASS(klass); + + k->no_hotplug = 1; + k->init = qxl_init_primary; + k->romfile = "vgabios-qxl.bin"; + k->vendor_id = REDHAT_PCI_VENDOR_ID; + k->device_id = QXL_DEVICE_ID_STABLE; + k->class_id = PCI_CLASS_DISPLAY_VGA; + dc->desc = "Spice QXL GPU (primary, vga compatible)"; + dc->reset = qxl_reset_handler; + dc->vmsd = &qxl_vmstate; + dc->props = qxl_properties; +} + +static const TypeInfo qxl_primary_info = { + .name = "qxl-vga", + .parent = TYPE_PCI_DEVICE, + .instance_size = sizeof(PCIQXLDevice), + .class_init = qxl_primary_class_init, +}; + +static void qxl_secondary_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + PCIDeviceClass *k = PCI_DEVICE_CLASS(klass); + + k->init = qxl_init_secondary; + k->vendor_id = REDHAT_PCI_VENDOR_ID; + k->device_id = QXL_DEVICE_ID_STABLE; + k->class_id = PCI_CLASS_DISPLAY_OTHER; + dc->desc = "Spice QXL GPU (secondary)"; + dc->reset = qxl_reset_handler; + dc->vmsd = &qxl_vmstate; + dc->props = qxl_properties; +} + +static const TypeInfo qxl_secondary_info = { + .name = "qxl", + .parent = TYPE_PCI_DEVICE, + .instance_size = sizeof(PCIQXLDevice), + .class_init = qxl_secondary_class_init, +}; + +static void qxl_register_types(void) +{ + type_register_static(&qxl_primary_info); + type_register_static(&qxl_secondary_info); +} + +type_init(qxl_register_types) diff --git a/hw/display/qxl.h b/hw/display/qxl.h new file mode 100644 index 0000000000..8e9b0c299e --- /dev/null +++ b/hw/display/qxl.h @@ -0,0 +1,165 @@ +#ifndef HW_QXL_H +#define HW_QXL_H 1 + +#include "qemu-common.h" + +#include "ui/console.h" +#include "hw/hw.h" +#include "hw/pci/pci.h" +#include "vga_int.h" +#include "qemu/thread.h" + +#include "ui/qemu-spice.h" +#include "ui/spice-display.h" + +enum qxl_mode { + QXL_MODE_UNDEFINED, + QXL_MODE_VGA, + QXL_MODE_COMPAT, /* spice 0.4.x */ + QXL_MODE_NATIVE, +}; + +#ifndef QXL_VRAM64_RANGE_INDEX +#define QXL_VRAM64_RANGE_INDEX 4 +#endif + +#define QXL_UNDEFINED_IO UINT32_MAX + +#define QXL_NUM_DIRTY_RECTS 64 + +typedef struct PCIQXLDevice { + PCIDevice pci; + SimpleSpiceDisplay ssd; + int id; + uint32_t debug; + uint32_t guestdebug; + uint32_t cmdlog; + + uint32_t guest_bug; + + enum qxl_mode mode; + uint32_t cmdflags; + int generation; + uint32_t revision; + + int32_t num_memslots; + + uint32_t current_async; + QemuMutex async_lock; + + struct guest_slots { + QXLMemSlot slot; + void *ptr; + uint64_t size; + uint64_t delta; + uint32_t active; + } guest_slots[NUM_MEMSLOTS]; + + struct guest_primary { + QXLSurfaceCreate surface; + uint32_t commands; + uint32_t resized; + int32_t qxl_stride; + uint32_t abs_stride; + uint32_t bits_pp; + uint32_t bytes_pp; + uint8_t *data; + } guest_primary; + + struct surfaces { + QXLPHYSICAL *cmds; + uint32_t count; + uint32_t max; + } guest_surfaces; + QXLPHYSICAL guest_cursor; + + QXLPHYSICAL guest_monitors_config; + + QemuMutex track_lock; + + /* thread signaling */ + QemuThread main; + int pipe[2]; + + /* ram pci bar */ + QXLRam *ram; + VGACommonState vga; + uint32_t num_free_res; + QXLReleaseInfo *last_release; + uint32_t last_release_offset; + uint32_t oom_running; + uint32_t vgamem_size; + + /* rom pci bar */ + QXLRom shadow_rom; + QXLRom *rom; + QXLModes *modes; + uint32_t rom_size; + MemoryRegion rom_bar; + + /* vram pci bar */ + uint32_t vram_size; + MemoryRegion vram_bar; + uint32_t vram32_size; + MemoryRegion vram32_bar; + + /* io bar */ + MemoryRegion io_bar; + + /* user-friendly properties (in megabytes) */ + uint32_t ram_size_mb; + uint32_t vram_size_mb; + uint32_t vram32_size_mb; + uint32_t vgamem_size_mb; + + /* qxl_render_update state */ + int render_update_cookie_num; + int num_dirty_rects; + QXLRect dirty[QXL_NUM_DIRTY_RECTS]; + QEMUBH *update_area_bh; +} PCIQXLDevice; + +#define PANIC_ON(x) if ((x)) { \ + printf("%s: PANIC %s failed\n", __FUNCTION__, #x); \ + abort(); \ +} + +#define dprint(_qxl, _level, _fmt, ...) \ + do { \ + if (_qxl->debug >= _level) { \ + fprintf(stderr, "qxl-%d: ", _qxl->id); \ + fprintf(stderr, _fmt, ## __VA_ARGS__); \ + } \ + } while (0) + +#define QXL_DEFAULT_REVISION QXL_REVISION_STABLE_V12 + +/* qxl.c */ +void *qxl_phys2virt(PCIQXLDevice *qxl, QXLPHYSICAL phys, int group_id); +void qxl_set_guest_bug(PCIQXLDevice *qxl, const char *msg, ...) + GCC_FMT_ATTR(2, 3); + +void qxl_spice_update_area(PCIQXLDevice *qxl, uint32_t surface_id, + struct QXLRect *area, struct QXLRect *dirty_rects, + uint32_t num_dirty_rects, + uint32_t clear_dirty_region, + qxl_async_io async, QXLCookie *cookie); +void qxl_spice_loadvm_commands(PCIQXLDevice *qxl, struct QXLCommandExt *ext, + uint32_t count); +void qxl_spice_oom(PCIQXLDevice *qxl); +void qxl_spice_reset_memslots(PCIQXLDevice *qxl); +void qxl_spice_reset_image_cache(PCIQXLDevice *qxl); +void qxl_spice_reset_cursor(PCIQXLDevice *qxl); + +/* qxl-logger.c */ +int qxl_log_cmd_cursor(PCIQXLDevice *qxl, QXLCursorCmd *cmd, int group_id); +int qxl_log_command(PCIQXLDevice *qxl, const char *ring, QXLCommandExt *ext); + +/* qxl-render.c */ +void qxl_render_resize(PCIQXLDevice *qxl); +void qxl_render_update(PCIQXLDevice *qxl); +int qxl_render_cursor(PCIQXLDevice *qxl, QXLCommandExt *ext); +void qxl_render_update_area_done(PCIQXLDevice *qxl, QXLCookie *cookie); +void qxl_render_update_area_bh(void *opaque); + +#endif diff --git a/hw/display/sm501.c b/hw/display/sm501.c new file mode 100644 index 0000000000..6b660ac1c0 --- /dev/null +++ b/hw/display/sm501.c @@ -0,0 +1,1450 @@ +/* + * QEMU SM501 Device + * + * Copyright (c) 2008 Shin-ichiro KAWASAKI + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include <stdio.h> +#include "hw/hw.h" +#include "hw/char/serial.h" +#include "ui/console.h" +#include "hw/arm/devices.h" +#include "hw/sysbus.h" +#include "hw/qdev-addr.h" +#include "qemu/range.h" +#include "ui/pixel_ops.h" + +/* + * Status: 2010/05/07 + * - Minimum implementation for Linux console : mmio regs and CRT layer. + * - 2D grapihcs acceleration partially supported : only fill rectangle. + * + * TODO: + * - Panel support + * - Touch panel support + * - USB support + * - UART support + * - More 2D graphics engine support + * - Performance tuning + */ + +//#define DEBUG_SM501 +//#define DEBUG_BITBLT + +#ifdef DEBUG_SM501 +#define SM501_DPRINTF(fmt, ...) printf(fmt, ## __VA_ARGS__) +#else +#define SM501_DPRINTF(fmt, ...) do {} while(0) +#endif + + +#define MMIO_BASE_OFFSET 0x3e00000 + +/* SM501 register definitions taken from "linux/include/linux/sm501-regs.h" */ + +/* System Configuration area */ +/* System config base */ +#define SM501_SYS_CONFIG (0x000000) + +/* config 1 */ +#define SM501_SYSTEM_CONTROL (0x000000) + +#define SM501_SYSCTRL_PANEL_TRISTATE (1<<0) +#define SM501_SYSCTRL_MEM_TRISTATE (1<<1) +#define SM501_SYSCTRL_CRT_TRISTATE (1<<2) + +#define SM501_SYSCTRL_PCI_SLAVE_BURST_MASK (3<<4) +#define SM501_SYSCTRL_PCI_SLAVE_BURST_1 (0<<4) +#define SM501_SYSCTRL_PCI_SLAVE_BURST_2 (1<<4) +#define SM501_SYSCTRL_PCI_SLAVE_BURST_4 (2<<4) +#define SM501_SYSCTRL_PCI_SLAVE_BURST_8 (3<<4) + +#define SM501_SYSCTRL_PCI_CLOCK_RUN_EN (1<<6) +#define SM501_SYSCTRL_PCI_RETRY_DISABLE (1<<7) +#define SM501_SYSCTRL_PCI_SUBSYS_LOCK (1<<11) +#define SM501_SYSCTRL_PCI_BURST_READ_EN (1<<15) + +/* miscellaneous control */ + +#define SM501_MISC_CONTROL (0x000004) + +#define SM501_MISC_BUS_SH (0x0) +#define SM501_MISC_BUS_PCI (0x1) +#define SM501_MISC_BUS_XSCALE (0x2) +#define SM501_MISC_BUS_NEC (0x6) +#define SM501_MISC_BUS_MASK (0x7) + +#define SM501_MISC_VR_62MB (1<<3) +#define SM501_MISC_CDR_RESET (1<<7) +#define SM501_MISC_USB_LB (1<<8) +#define SM501_MISC_USB_SLAVE (1<<9) +#define SM501_MISC_BL_1 (1<<10) +#define SM501_MISC_MC (1<<11) +#define SM501_MISC_DAC_POWER (1<<12) +#define SM501_MISC_IRQ_INVERT (1<<16) +#define SM501_MISC_SH (1<<17) + +#define SM501_MISC_HOLD_EMPTY (0<<18) +#define SM501_MISC_HOLD_8 (1<<18) +#define SM501_MISC_HOLD_16 (2<<18) +#define SM501_MISC_HOLD_24 (3<<18) +#define SM501_MISC_HOLD_32 (4<<18) +#define SM501_MISC_HOLD_MASK (7<<18) + +#define SM501_MISC_FREQ_12 (1<<24) +#define SM501_MISC_PNL_24BIT (1<<25) +#define SM501_MISC_8051_LE (1<<26) + + + +#define SM501_GPIO31_0_CONTROL (0x000008) +#define SM501_GPIO63_32_CONTROL (0x00000C) +#define SM501_DRAM_CONTROL (0x000010) + +/* command list */ +#define SM501_ARBTRTN_CONTROL (0x000014) + +/* command list */ +#define SM501_COMMAND_LIST_STATUS (0x000024) + +/* interrupt debug */ +#define SM501_RAW_IRQ_STATUS (0x000028) +#define SM501_RAW_IRQ_CLEAR (0x000028) +#define SM501_IRQ_STATUS (0x00002C) +#define SM501_IRQ_MASK (0x000030) +#define SM501_DEBUG_CONTROL (0x000034) + +/* power management */ +#define SM501_POWERMODE_P2X_SRC (1<<29) +#define SM501_POWERMODE_V2X_SRC (1<<20) +#define SM501_POWERMODE_M_SRC (1<<12) +#define SM501_POWERMODE_M1_SRC (1<<4) + +#define SM501_CURRENT_GATE (0x000038) +#define SM501_CURRENT_CLOCK (0x00003C) +#define SM501_POWER_MODE_0_GATE (0x000040) +#define SM501_POWER_MODE_0_CLOCK (0x000044) +#define SM501_POWER_MODE_1_GATE (0x000048) +#define SM501_POWER_MODE_1_CLOCK (0x00004C) +#define SM501_SLEEP_MODE_GATE (0x000050) +#define SM501_POWER_MODE_CONTROL (0x000054) + +/* power gates for units within the 501 */ +#define SM501_GATE_HOST (0) +#define SM501_GATE_MEMORY (1) +#define SM501_GATE_DISPLAY (2) +#define SM501_GATE_2D_ENGINE (3) +#define SM501_GATE_CSC (4) +#define SM501_GATE_ZVPORT (5) +#define SM501_GATE_GPIO (6) +#define SM501_GATE_UART0 (7) +#define SM501_GATE_UART1 (8) +#define SM501_GATE_SSP (10) +#define SM501_GATE_USB_HOST (11) +#define SM501_GATE_USB_GADGET (12) +#define SM501_GATE_UCONTROLLER (17) +#define SM501_GATE_AC97 (18) + +/* panel clock */ +#define SM501_CLOCK_P2XCLK (24) +/* crt clock */ +#define SM501_CLOCK_V2XCLK (16) +/* main clock */ +#define SM501_CLOCK_MCLK (8) +/* SDRAM controller clock */ +#define SM501_CLOCK_M1XCLK (0) + +/* config 2 */ +#define SM501_PCI_MASTER_BASE (0x000058) +#define SM501_ENDIAN_CONTROL (0x00005C) +#define SM501_DEVICEID (0x000060) +/* 0x050100A0 */ + +#define SM501_DEVICEID_SM501 (0x05010000) +#define SM501_DEVICEID_IDMASK (0xffff0000) +#define SM501_DEVICEID_REVMASK (0x000000ff) + +#define SM501_PLLCLOCK_COUNT (0x000064) +#define SM501_MISC_TIMING (0x000068) +#define SM501_CURRENT_SDRAM_CLOCK (0x00006C) + +#define SM501_PROGRAMMABLE_PLL_CONTROL (0x000074) + +/* GPIO base */ +#define SM501_GPIO (0x010000) +#define SM501_GPIO_DATA_LOW (0x00) +#define SM501_GPIO_DATA_HIGH (0x04) +#define SM501_GPIO_DDR_LOW (0x08) +#define SM501_GPIO_DDR_HIGH (0x0C) +#define SM501_GPIO_IRQ_SETUP (0x10) +#define SM501_GPIO_IRQ_STATUS (0x14) +#define SM501_GPIO_IRQ_RESET (0x14) + +/* I2C controller base */ +#define SM501_I2C (0x010040) +#define SM501_I2C_BYTE_COUNT (0x00) +#define SM501_I2C_CONTROL (0x01) +#define SM501_I2C_STATUS (0x02) +#define SM501_I2C_RESET (0x02) +#define SM501_I2C_SLAVE_ADDRESS (0x03) +#define SM501_I2C_DATA (0x04) + +/* SSP base */ +#define SM501_SSP (0x020000) + +/* Uart 0 base */ +#define SM501_UART0 (0x030000) + +/* Uart 1 base */ +#define SM501_UART1 (0x030020) + +/* USB host port base */ +#define SM501_USB_HOST (0x040000) + +/* USB slave/gadget base */ +#define SM501_USB_GADGET (0x060000) + +/* USB slave/gadget data port base */ +#define SM501_USB_GADGET_DATA (0x070000) + +/* Display controller/video engine base */ +#define SM501_DC (0x080000) + +/* common defines for the SM501 address registers */ +#define SM501_ADDR_FLIP (1<<31) +#define SM501_ADDR_EXT (1<<27) +#define SM501_ADDR_CS1 (1<<26) +#define SM501_ADDR_MASK (0x3f << 26) + +#define SM501_FIFO_MASK (0x3 << 16) +#define SM501_FIFO_1 (0x0 << 16) +#define SM501_FIFO_3 (0x1 << 16) +#define SM501_FIFO_7 (0x2 << 16) +#define SM501_FIFO_11 (0x3 << 16) + +/* common registers for panel and the crt */ +#define SM501_OFF_DC_H_TOT (0x000) +#define SM501_OFF_DC_V_TOT (0x008) +#define SM501_OFF_DC_H_SYNC (0x004) +#define SM501_OFF_DC_V_SYNC (0x00C) + +#define SM501_DC_PANEL_CONTROL (0x000) + +#define SM501_DC_PANEL_CONTROL_FPEN (1<<27) +#define SM501_DC_PANEL_CONTROL_BIAS (1<<26) +#define SM501_DC_PANEL_CONTROL_DATA (1<<25) +#define SM501_DC_PANEL_CONTROL_VDD (1<<24) +#define SM501_DC_PANEL_CONTROL_DP (1<<23) + +#define SM501_DC_PANEL_CONTROL_TFT_888 (0<<21) +#define SM501_DC_PANEL_CONTROL_TFT_333 (1<<21) +#define SM501_DC_PANEL_CONTROL_TFT_444 (2<<21) + +#define SM501_DC_PANEL_CONTROL_DE (1<<20) + +#define SM501_DC_PANEL_CONTROL_LCD_TFT (0<<18) +#define SM501_DC_PANEL_CONTROL_LCD_STN8 (1<<18) +#define SM501_DC_PANEL_CONTROL_LCD_STN12 (2<<18) + +#define SM501_DC_PANEL_CONTROL_CP (1<<14) +#define SM501_DC_PANEL_CONTROL_VSP (1<<13) +#define SM501_DC_PANEL_CONTROL_HSP (1<<12) +#define SM501_DC_PANEL_CONTROL_CK (1<<9) +#define SM501_DC_PANEL_CONTROL_TE (1<<8) +#define SM501_DC_PANEL_CONTROL_VPD (1<<7) +#define SM501_DC_PANEL_CONTROL_VP (1<<6) +#define SM501_DC_PANEL_CONTROL_HPD (1<<5) +#define SM501_DC_PANEL_CONTROL_HP (1<<4) +#define SM501_DC_PANEL_CONTROL_GAMMA (1<<3) +#define SM501_DC_PANEL_CONTROL_EN (1<<2) + +#define SM501_DC_PANEL_CONTROL_8BPP (0<<0) +#define SM501_DC_PANEL_CONTROL_16BPP (1<<0) +#define SM501_DC_PANEL_CONTROL_32BPP (2<<0) + + +#define SM501_DC_PANEL_PANNING_CONTROL (0x004) +#define SM501_DC_PANEL_COLOR_KEY (0x008) +#define SM501_DC_PANEL_FB_ADDR (0x00C) +#define SM501_DC_PANEL_FB_OFFSET (0x010) +#define SM501_DC_PANEL_FB_WIDTH (0x014) +#define SM501_DC_PANEL_FB_HEIGHT (0x018) +#define SM501_DC_PANEL_TL_LOC (0x01C) +#define SM501_DC_PANEL_BR_LOC (0x020) +#define SM501_DC_PANEL_H_TOT (0x024) +#define SM501_DC_PANEL_H_SYNC (0x028) +#define SM501_DC_PANEL_V_TOT (0x02C) +#define SM501_DC_PANEL_V_SYNC (0x030) +#define SM501_DC_PANEL_CUR_LINE (0x034) + +#define SM501_DC_VIDEO_CONTROL (0x040) +#define SM501_DC_VIDEO_FB0_ADDR (0x044) +#define SM501_DC_VIDEO_FB_WIDTH (0x048) +#define SM501_DC_VIDEO_FB0_LAST_ADDR (0x04C) +#define SM501_DC_VIDEO_TL_LOC (0x050) +#define SM501_DC_VIDEO_BR_LOC (0x054) +#define SM501_DC_VIDEO_SCALE (0x058) +#define SM501_DC_VIDEO_INIT_SCALE (0x05C) +#define SM501_DC_VIDEO_YUV_CONSTANTS (0x060) +#define SM501_DC_VIDEO_FB1_ADDR (0x064) +#define SM501_DC_VIDEO_FB1_LAST_ADDR (0x068) + +#define SM501_DC_VIDEO_ALPHA_CONTROL (0x080) +#define SM501_DC_VIDEO_ALPHA_FB_ADDR (0x084) +#define SM501_DC_VIDEO_ALPHA_FB_OFFSET (0x088) +#define SM501_DC_VIDEO_ALPHA_FB_LAST_ADDR (0x08C) +#define SM501_DC_VIDEO_ALPHA_TL_LOC (0x090) +#define SM501_DC_VIDEO_ALPHA_BR_LOC (0x094) +#define SM501_DC_VIDEO_ALPHA_SCALE (0x098) +#define SM501_DC_VIDEO_ALPHA_INIT_SCALE (0x09C) +#define SM501_DC_VIDEO_ALPHA_CHROMA_KEY (0x0A0) +#define SM501_DC_VIDEO_ALPHA_COLOR_LOOKUP (0x0A4) + +#define SM501_DC_PANEL_HWC_BASE (0x0F0) +#define SM501_DC_PANEL_HWC_ADDR (0x0F0) +#define SM501_DC_PANEL_HWC_LOC (0x0F4) +#define SM501_DC_PANEL_HWC_COLOR_1_2 (0x0F8) +#define SM501_DC_PANEL_HWC_COLOR_3 (0x0FC) + +#define SM501_HWC_EN (1<<31) + +#define SM501_OFF_HWC_ADDR (0x00) +#define SM501_OFF_HWC_LOC (0x04) +#define SM501_OFF_HWC_COLOR_1_2 (0x08) +#define SM501_OFF_HWC_COLOR_3 (0x0C) + +#define SM501_DC_ALPHA_CONTROL (0x100) +#define SM501_DC_ALPHA_FB_ADDR (0x104) +#define SM501_DC_ALPHA_FB_OFFSET (0x108) +#define SM501_DC_ALPHA_TL_LOC (0x10C) +#define SM501_DC_ALPHA_BR_LOC (0x110) +#define SM501_DC_ALPHA_CHROMA_KEY (0x114) +#define SM501_DC_ALPHA_COLOR_LOOKUP (0x118) + +#define SM501_DC_CRT_CONTROL (0x200) + +#define SM501_DC_CRT_CONTROL_TVP (1<<15) +#define SM501_DC_CRT_CONTROL_CP (1<<14) +#define SM501_DC_CRT_CONTROL_VSP (1<<13) +#define SM501_DC_CRT_CONTROL_HSP (1<<12) +#define SM501_DC_CRT_CONTROL_VS (1<<11) +#define SM501_DC_CRT_CONTROL_BLANK (1<<10) +#define SM501_DC_CRT_CONTROL_SEL (1<<9) +#define SM501_DC_CRT_CONTROL_TE (1<<8) +#define SM501_DC_CRT_CONTROL_PIXEL_MASK (0xF << 4) +#define SM501_DC_CRT_CONTROL_GAMMA (1<<3) +#define SM501_DC_CRT_CONTROL_ENABLE (1<<2) + +#define SM501_DC_CRT_CONTROL_8BPP (0<<0) +#define SM501_DC_CRT_CONTROL_16BPP (1<<0) +#define SM501_DC_CRT_CONTROL_32BPP (2<<0) + +#define SM501_DC_CRT_FB_ADDR (0x204) +#define SM501_DC_CRT_FB_OFFSET (0x208) +#define SM501_DC_CRT_H_TOT (0x20C) +#define SM501_DC_CRT_H_SYNC (0x210) +#define SM501_DC_CRT_V_TOT (0x214) +#define SM501_DC_CRT_V_SYNC (0x218) +#define SM501_DC_CRT_SIGNATURE_ANALYZER (0x21C) +#define SM501_DC_CRT_CUR_LINE (0x220) +#define SM501_DC_CRT_MONITOR_DETECT (0x224) + +#define SM501_DC_CRT_HWC_BASE (0x230) +#define SM501_DC_CRT_HWC_ADDR (0x230) +#define SM501_DC_CRT_HWC_LOC (0x234) +#define SM501_DC_CRT_HWC_COLOR_1_2 (0x238) +#define SM501_DC_CRT_HWC_COLOR_3 (0x23C) + +#define SM501_DC_PANEL_PALETTE (0x400) + +#define SM501_DC_VIDEO_PALETTE (0x800) + +#define SM501_DC_CRT_PALETTE (0xC00) + +/* Zoom Video port base */ +#define SM501_ZVPORT (0x090000) + +/* AC97/I2S base */ +#define SM501_AC97 (0x0A0000) + +/* 8051 micro controller base */ +#define SM501_UCONTROLLER (0x0B0000) + +/* 8051 micro controller SRAM base */ +#define SM501_UCONTROLLER_SRAM (0x0C0000) + +/* DMA base */ +#define SM501_DMA (0x0D0000) + +/* 2d engine base */ +#define SM501_2D_ENGINE (0x100000) +#define SM501_2D_SOURCE (0x00) +#define SM501_2D_DESTINATION (0x04) +#define SM501_2D_DIMENSION (0x08) +#define SM501_2D_CONTROL (0x0C) +#define SM501_2D_PITCH (0x10) +#define SM501_2D_FOREGROUND (0x14) +#define SM501_2D_BACKGROUND (0x18) +#define SM501_2D_STRETCH (0x1C) +#define SM501_2D_COLOR_COMPARE (0x20) +#define SM501_2D_COLOR_COMPARE_MASK (0x24) +#define SM501_2D_MASK (0x28) +#define SM501_2D_CLIP_TL (0x2C) +#define SM501_2D_CLIP_BR (0x30) +#define SM501_2D_MONO_PATTERN_LOW (0x34) +#define SM501_2D_MONO_PATTERN_HIGH (0x38) +#define SM501_2D_WINDOW_WIDTH (0x3C) +#define SM501_2D_SOURCE_BASE (0x40) +#define SM501_2D_DESTINATION_BASE (0x44) +#define SM501_2D_ALPHA (0x48) +#define SM501_2D_WRAP (0x4C) +#define SM501_2D_STATUS (0x50) + +#define SM501_CSC_Y_SOURCE_BASE (0xC8) +#define SM501_CSC_CONSTANTS (0xCC) +#define SM501_CSC_Y_SOURCE_X (0xD0) +#define SM501_CSC_Y_SOURCE_Y (0xD4) +#define SM501_CSC_U_SOURCE_BASE (0xD8) +#define SM501_CSC_V_SOURCE_BASE (0xDC) +#define SM501_CSC_SOURCE_DIMENSION (0xE0) +#define SM501_CSC_SOURCE_PITCH (0xE4) +#define SM501_CSC_DESTINATION (0xE8) +#define SM501_CSC_DESTINATION_DIMENSION (0xEC) +#define SM501_CSC_DESTINATION_PITCH (0xF0) +#define SM501_CSC_SCALE_FACTOR (0xF4) +#define SM501_CSC_DESTINATION_BASE (0xF8) +#define SM501_CSC_CONTROL (0xFC) + +/* 2d engine data port base */ +#define SM501_2D_ENGINE_DATA (0x110000) + +/* end of register definitions */ + +#define SM501_HWC_WIDTH (64) +#define SM501_HWC_HEIGHT (64) + +/* SM501 local memory size taken from "linux/drivers/mfd/sm501.c" */ +static const uint32_t sm501_mem_local_size[] = { + [0] = 4*1024*1024, + [1] = 8*1024*1024, + [2] = 16*1024*1024, + [3] = 32*1024*1024, + [4] = 64*1024*1024, + [5] = 2*1024*1024, +}; +#define get_local_mem_size(s) sm501_mem_local_size[(s)->local_mem_size_index] + +typedef struct SM501State { + /* graphic console status */ + QemuConsole *con; + + /* status & internal resources */ + hwaddr base; + uint32_t local_mem_size_index; + uint8_t * local_mem; + MemoryRegion local_mem_region; + uint32_t last_width; + uint32_t last_height; + + /* mmio registers */ + uint32_t system_control; + uint32_t misc_control; + uint32_t gpio_31_0_control; + uint32_t gpio_63_32_control; + uint32_t dram_control; + uint32_t irq_mask; + uint32_t misc_timing; + uint32_t power_mode_control; + + uint32_t uart0_ier; + uint32_t uart0_lcr; + uint32_t uart0_mcr; + uint32_t uart0_scr; + + uint8_t dc_palette[0x400 * 3]; + + uint32_t dc_panel_control; + uint32_t dc_panel_panning_control; + uint32_t dc_panel_fb_addr; + uint32_t dc_panel_fb_offset; + uint32_t dc_panel_fb_width; + uint32_t dc_panel_fb_height; + uint32_t dc_panel_tl_location; + uint32_t dc_panel_br_location; + uint32_t dc_panel_h_total; + uint32_t dc_panel_h_sync; + uint32_t dc_panel_v_total; + uint32_t dc_panel_v_sync; + + uint32_t dc_panel_hwc_addr; + uint32_t dc_panel_hwc_location; + uint32_t dc_panel_hwc_color_1_2; + uint32_t dc_panel_hwc_color_3; + + uint32_t dc_crt_control; + uint32_t dc_crt_fb_addr; + uint32_t dc_crt_fb_offset; + uint32_t dc_crt_h_total; + uint32_t dc_crt_h_sync; + uint32_t dc_crt_v_total; + uint32_t dc_crt_v_sync; + + uint32_t dc_crt_hwc_addr; + uint32_t dc_crt_hwc_location; + uint32_t dc_crt_hwc_color_1_2; + uint32_t dc_crt_hwc_color_3; + + uint32_t twoD_source; + uint32_t twoD_destination; + uint32_t twoD_dimension; + uint32_t twoD_control; + uint32_t twoD_pitch; + uint32_t twoD_foreground; + uint32_t twoD_stretch; + uint32_t twoD_color_compare_mask; + uint32_t twoD_mask; + uint32_t twoD_window_width; + uint32_t twoD_source_base; + uint32_t twoD_destination_base; + +} SM501State; + +static uint32_t get_local_mem_size_index(uint32_t size) +{ + uint32_t norm_size = 0; + int i, index = 0; + + for (i = 0; i < ARRAY_SIZE(sm501_mem_local_size); i++) { + uint32_t new_size = sm501_mem_local_size[i]; + if (new_size >= size) { + if (norm_size == 0 || norm_size > new_size) { + norm_size = new_size; + index = i; + } + } + } + + return index; +} + +/** + * Check the availability of hardware cursor. + * @param crt 0 for PANEL, 1 for CRT. + */ +static inline int is_hwc_enabled(SM501State *state, int crt) +{ + uint32_t addr = crt ? state->dc_crt_hwc_addr : state->dc_panel_hwc_addr; + return addr & 0x80000000; +} + +/** + * Get the address which holds cursor pattern data. + * @param crt 0 for PANEL, 1 for CRT. + */ +static inline uint32_t get_hwc_address(SM501State *state, int crt) +{ + uint32_t addr = crt ? state->dc_crt_hwc_addr : state->dc_panel_hwc_addr; + return (addr & 0x03FFFFF0)/* >> 4*/; +} + +/** + * Get the cursor position in y coordinate. + * @param crt 0 for PANEL, 1 for CRT. + */ +static inline uint32_t get_hwc_y(SM501State *state, int crt) +{ + uint32_t location = crt ? state->dc_crt_hwc_location + : state->dc_panel_hwc_location; + return (location & 0x07FF0000) >> 16; +} + +/** + * Get the cursor position in x coordinate. + * @param crt 0 for PANEL, 1 for CRT. + */ +static inline uint32_t get_hwc_x(SM501State *state, int crt) +{ + uint32_t location = crt ? state->dc_crt_hwc_location + : state->dc_panel_hwc_location; + return location & 0x000007FF; +} + +/** + * Get the cursor position in x coordinate. + * @param crt 0 for PANEL, 1 for CRT. + * @param index 0, 1, 2 or 3 which specifies color of corsor dot. + */ +static inline uint16_t get_hwc_color(SM501State *state, int crt, int index) +{ + uint32_t color_reg = 0; + uint16_t color_565 = 0; + + if (index == 0) { + return 0; + } + + switch (index) { + case 1: + case 2: + color_reg = crt ? state->dc_crt_hwc_color_1_2 + : state->dc_panel_hwc_color_1_2; + break; + case 3: + color_reg = crt ? state->dc_crt_hwc_color_3 + : state->dc_panel_hwc_color_3; + break; + default: + printf("invalid hw cursor color.\n"); + abort(); + } + + switch (index) { + case 1: + case 3: + color_565 = (uint16_t)(color_reg & 0xFFFF); + break; + case 2: + color_565 = (uint16_t)((color_reg >> 16) & 0xFFFF); + break; + } + return color_565; +} + +static int within_hwc_y_range(SM501State *state, int y, int crt) +{ + int hwc_y = get_hwc_y(state, crt); + return (hwc_y <= y && y < hwc_y + SM501_HWC_HEIGHT); +} + +static void sm501_2d_operation(SM501State * s) +{ + /* obtain operation parameters */ + int operation = (s->twoD_control >> 16) & 0x1f; + int rtl = s->twoD_control & 0x8000000; + int src_x = (s->twoD_source >> 16) & 0x01FFF; + int src_y = s->twoD_source & 0xFFFF; + int dst_x = (s->twoD_destination >> 16) & 0x01FFF; + int dst_y = s->twoD_destination & 0xFFFF; + int operation_width = (s->twoD_dimension >> 16) & 0x1FFF; + int operation_height = s->twoD_dimension & 0xFFFF; + uint32_t color = s->twoD_foreground; + int format_flags = (s->twoD_stretch >> 20) & 0x3; + int addressing = (s->twoD_stretch >> 16) & 0xF; + + /* get frame buffer info */ + uint8_t * src = s->local_mem + (s->twoD_source_base & 0x03FFFFFF); + uint8_t * dst = s->local_mem + (s->twoD_destination_base & 0x03FFFFFF); + int src_width = (s->dc_crt_h_total & 0x00000FFF) + 1; + int dst_width = (s->dc_crt_h_total & 0x00000FFF) + 1; + + if (addressing != 0x0) { + printf("%s: only XY addressing is supported.\n", __func__); + abort(); + } + + if ((s->twoD_source_base & 0x08000000) || + (s->twoD_destination_base & 0x08000000)) { + printf("%s: only local memory is supported.\n", __func__); + abort(); + } + + switch (operation) { + case 0x00: /* copy area */ +#define COPY_AREA(_bpp, _pixel_type, rtl) { \ + int y, x, index_d, index_s; \ + for (y = 0; y < operation_height; y++) { \ + for (x = 0; x < operation_width; x++) { \ + if (rtl) { \ + index_s = ((src_y - y) * src_width + src_x - x) * _bpp; \ + index_d = ((dst_y - y) * dst_width + dst_x - x) * _bpp; \ + } else { \ + index_s = ((src_y + y) * src_width + src_x + x) * _bpp; \ + index_d = ((dst_y + y) * dst_width + dst_x + x) * _bpp; \ + } \ + *(_pixel_type*)&dst[index_d] = *(_pixel_type*)&src[index_s];\ + } \ + } \ + } + switch (format_flags) { + case 0: + COPY_AREA(1, uint8_t, rtl); + break; + case 1: + COPY_AREA(2, uint16_t, rtl); + break; + case 2: + COPY_AREA(4, uint32_t, rtl); + break; + } + break; + + case 0x01: /* fill rectangle */ +#define FILL_RECT(_bpp, _pixel_type) { \ + int y, x; \ + for (y = 0; y < operation_height; y++) { \ + for (x = 0; x < operation_width; x++) { \ + int index = ((dst_y + y) * dst_width + dst_x + x) * _bpp; \ + *(_pixel_type*)&dst[index] = (_pixel_type)color; \ + } \ + } \ + } + + switch (format_flags) { + case 0: + FILL_RECT(1, uint8_t); + break; + case 1: + FILL_RECT(2, uint16_t); + break; + case 2: + FILL_RECT(4, uint32_t); + break; + } + break; + + default: + printf("non-implemented SM501 2D operation. %d\n", operation); + abort(); + break; + } +} + +static uint64_t sm501_system_config_read(void *opaque, hwaddr addr, + unsigned size) +{ + SM501State * s = (SM501State *)opaque; + uint32_t ret = 0; + SM501_DPRINTF("sm501 system config regs : read addr=%x\n", (int)addr); + + switch(addr) { + case SM501_SYSTEM_CONTROL: + ret = s->system_control; + break; + case SM501_MISC_CONTROL: + ret = s->misc_control; + break; + case SM501_GPIO31_0_CONTROL: + ret = s->gpio_31_0_control; + break; + case SM501_GPIO63_32_CONTROL: + ret = s->gpio_63_32_control; + break; + case SM501_DEVICEID: + ret = 0x050100A0; + break; + case SM501_DRAM_CONTROL: + ret = (s->dram_control & 0x07F107C0) | s->local_mem_size_index << 13; + break; + case SM501_IRQ_MASK: + ret = s->irq_mask; + break; + case SM501_MISC_TIMING: + /* TODO : simulate gate control */ + ret = s->misc_timing; + break; + case SM501_CURRENT_GATE: + /* TODO : simulate gate control */ + ret = 0x00021807; + break; + case SM501_CURRENT_CLOCK: + ret = 0x2A1A0A09; + break; + case SM501_POWER_MODE_CONTROL: + ret = s->power_mode_control; + break; + + default: + printf("sm501 system config : not implemented register read." + " addr=%x\n", (int)addr); + abort(); + } + + return ret; +} + +static void sm501_system_config_write(void *opaque, hwaddr addr, + uint64_t value, unsigned size) +{ + SM501State * s = (SM501State *)opaque; + SM501_DPRINTF("sm501 system config regs : write addr=%x, val=%x\n", + (uint32_t)addr, (uint32_t)value); + + switch(addr) { + case SM501_SYSTEM_CONTROL: + s->system_control = value & 0xE300B8F7; + break; + case SM501_MISC_CONTROL: + s->misc_control = value & 0xFF7FFF20; + break; + case SM501_GPIO31_0_CONTROL: + s->gpio_31_0_control = value; + break; + case SM501_GPIO63_32_CONTROL: + s->gpio_63_32_control = value; + break; + case SM501_DRAM_CONTROL: + s->local_mem_size_index = (value >> 13) & 0x7; + /* rODO : check validity of size change */ + s->dram_control |= value & 0x7FFFFFC3; + break; + case SM501_IRQ_MASK: + s->irq_mask = value; + break; + case SM501_MISC_TIMING: + s->misc_timing = value & 0xF31F1FFF; + break; + case SM501_POWER_MODE_0_GATE: + case SM501_POWER_MODE_1_GATE: + case SM501_POWER_MODE_0_CLOCK: + case SM501_POWER_MODE_1_CLOCK: + /* TODO : simulate gate & clock control */ + break; + case SM501_POWER_MODE_CONTROL: + s->power_mode_control = value & 0x00000003; + break; + + default: + printf("sm501 system config : not implemented register write." + " addr=%x, val=%x\n", (int)addr, (uint32_t)value); + abort(); + } +} + +static const MemoryRegionOps sm501_system_config_ops = { + .read = sm501_system_config_read, + .write = sm501_system_config_write, + .valid = { + .min_access_size = 4, + .max_access_size = 4, + }, + .endianness = DEVICE_NATIVE_ENDIAN, +}; + +static uint32_t sm501_palette_read(void *opaque, hwaddr addr) +{ + SM501State * s = (SM501State *)opaque; + SM501_DPRINTF("sm501 palette read addr=%x\n", (int)addr); + + /* TODO : consider BYTE/WORD access */ + /* TODO : consider endian */ + + assert(range_covers_byte(0, 0x400 * 3, addr)); + return *(uint32_t*)&s->dc_palette[addr]; +} + +static void sm501_palette_write(void *opaque, + hwaddr addr, uint32_t value) +{ + SM501State * s = (SM501State *)opaque; + SM501_DPRINTF("sm501 palette write addr=%x, val=%x\n", + (int)addr, value); + + /* TODO : consider BYTE/WORD access */ + /* TODO : consider endian */ + + assert(range_covers_byte(0, 0x400 * 3, addr)); + *(uint32_t*)&s->dc_palette[addr] = value; +} + +static uint64_t sm501_disp_ctrl_read(void *opaque, hwaddr addr, + unsigned size) +{ + SM501State * s = (SM501State *)opaque; + uint32_t ret = 0; + SM501_DPRINTF("sm501 disp ctrl regs : read addr=%x\n", (int)addr); + + switch(addr) { + + case SM501_DC_PANEL_CONTROL: + ret = s->dc_panel_control; + break; + case SM501_DC_PANEL_PANNING_CONTROL: + ret = s->dc_panel_panning_control; + break; + case SM501_DC_PANEL_FB_ADDR: + ret = s->dc_panel_fb_addr; + break; + case SM501_DC_PANEL_FB_OFFSET: + ret = s->dc_panel_fb_offset; + break; + case SM501_DC_PANEL_FB_WIDTH: + ret = s->dc_panel_fb_width; + break; + case SM501_DC_PANEL_FB_HEIGHT: + ret = s->dc_panel_fb_height; + break; + case SM501_DC_PANEL_TL_LOC: + ret = s->dc_panel_tl_location; + break; + case SM501_DC_PANEL_BR_LOC: + ret = s->dc_panel_br_location; + break; + + case SM501_DC_PANEL_H_TOT: + ret = s->dc_panel_h_total; + break; + case SM501_DC_PANEL_H_SYNC: + ret = s->dc_panel_h_sync; + break; + case SM501_DC_PANEL_V_TOT: + ret = s->dc_panel_v_total; + break; + case SM501_DC_PANEL_V_SYNC: + ret = s->dc_panel_v_sync; + break; + + case SM501_DC_CRT_CONTROL: + ret = s->dc_crt_control; + break; + case SM501_DC_CRT_FB_ADDR: + ret = s->dc_crt_fb_addr; + break; + case SM501_DC_CRT_FB_OFFSET: + ret = s->dc_crt_fb_offset; + break; + case SM501_DC_CRT_H_TOT: + ret = s->dc_crt_h_total; + break; + case SM501_DC_CRT_H_SYNC: + ret = s->dc_crt_h_sync; + break; + case SM501_DC_CRT_V_TOT: + ret = s->dc_crt_v_total; + break; + case SM501_DC_CRT_V_SYNC: + ret = s->dc_crt_v_sync; + break; + + case SM501_DC_CRT_HWC_ADDR: + ret = s->dc_crt_hwc_addr; + break; + case SM501_DC_CRT_HWC_LOC: + ret = s->dc_crt_hwc_location; + break; + case SM501_DC_CRT_HWC_COLOR_1_2: + ret = s->dc_crt_hwc_color_1_2; + break; + case SM501_DC_CRT_HWC_COLOR_3: + ret = s->dc_crt_hwc_color_3; + break; + + case SM501_DC_PANEL_PALETTE ... SM501_DC_PANEL_PALETTE + 0x400*3 - 4: + ret = sm501_palette_read(opaque, addr - SM501_DC_PANEL_PALETTE); + break; + + default: + printf("sm501 disp ctrl : not implemented register read." + " addr=%x\n", (int)addr); + abort(); + } + + return ret; +} + +static void sm501_disp_ctrl_write(void *opaque, hwaddr addr, + uint64_t value, unsigned size) +{ + SM501State * s = (SM501State *)opaque; + SM501_DPRINTF("sm501 disp ctrl regs : write addr=%x, val=%x\n", + (unsigned)addr, (unsigned)value); + + switch(addr) { + case SM501_DC_PANEL_CONTROL: + s->dc_panel_control = value & 0x0FFF73FF; + break; + case SM501_DC_PANEL_PANNING_CONTROL: + s->dc_panel_panning_control = value & 0xFF3FFF3F; + break; + case SM501_DC_PANEL_FB_ADDR: + s->dc_panel_fb_addr = value & 0x8FFFFFF0; + break; + case SM501_DC_PANEL_FB_OFFSET: + s->dc_panel_fb_offset = value & 0x3FF03FF0; + break; + case SM501_DC_PANEL_FB_WIDTH: + s->dc_panel_fb_width = value & 0x0FFF0FFF; + break; + case SM501_DC_PANEL_FB_HEIGHT: + s->dc_panel_fb_height = value & 0x0FFF0FFF; + break; + case SM501_DC_PANEL_TL_LOC: + s->dc_panel_tl_location = value & 0x07FF07FF; + break; + case SM501_DC_PANEL_BR_LOC: + s->dc_panel_br_location = value & 0x07FF07FF; + break; + + case SM501_DC_PANEL_H_TOT: + s->dc_panel_h_total = value & 0x0FFF0FFF; + break; + case SM501_DC_PANEL_H_SYNC: + s->dc_panel_h_sync = value & 0x00FF0FFF; + break; + case SM501_DC_PANEL_V_TOT: + s->dc_panel_v_total = value & 0x0FFF0FFF; + break; + case SM501_DC_PANEL_V_SYNC: + s->dc_panel_v_sync = value & 0x003F0FFF; + break; + + case SM501_DC_PANEL_HWC_ADDR: + s->dc_panel_hwc_addr = value & 0x8FFFFFF0; + break; + case SM501_DC_PANEL_HWC_LOC: + s->dc_panel_hwc_location = value & 0x0FFF0FFF; + break; + case SM501_DC_PANEL_HWC_COLOR_1_2: + s->dc_panel_hwc_color_1_2 = value; + break; + case SM501_DC_PANEL_HWC_COLOR_3: + s->dc_panel_hwc_color_3 = value & 0x0000FFFF; + break; + + case SM501_DC_CRT_CONTROL: + s->dc_crt_control = value & 0x0003FFFF; + break; + case SM501_DC_CRT_FB_ADDR: + s->dc_crt_fb_addr = value & 0x8FFFFFF0; + break; + case SM501_DC_CRT_FB_OFFSET: + s->dc_crt_fb_offset = value & 0x3FF03FF0; + break; + case SM501_DC_CRT_H_TOT: + s->dc_crt_h_total = value & 0x0FFF0FFF; + break; + case SM501_DC_CRT_H_SYNC: + s->dc_crt_h_sync = value & 0x00FF0FFF; + break; + case SM501_DC_CRT_V_TOT: + s->dc_crt_v_total = value & 0x0FFF0FFF; + break; + case SM501_DC_CRT_V_SYNC: + s->dc_crt_v_sync = value & 0x003F0FFF; + break; + + case SM501_DC_CRT_HWC_ADDR: + s->dc_crt_hwc_addr = value & 0x8FFFFFF0; + break; + case SM501_DC_CRT_HWC_LOC: + s->dc_crt_hwc_location = value & 0x0FFF0FFF; + break; + case SM501_DC_CRT_HWC_COLOR_1_2: + s->dc_crt_hwc_color_1_2 = value; + break; + case SM501_DC_CRT_HWC_COLOR_3: + s->dc_crt_hwc_color_3 = value & 0x0000FFFF; + break; + + case SM501_DC_PANEL_PALETTE ... SM501_DC_PANEL_PALETTE + 0x400*3 - 4: + sm501_palette_write(opaque, addr - SM501_DC_PANEL_PALETTE, value); + break; + + default: + printf("sm501 disp ctrl : not implemented register write." + " addr=%x, val=%x\n", (int)addr, (unsigned)value); + abort(); + } +} + +static const MemoryRegionOps sm501_disp_ctrl_ops = { + .read = sm501_disp_ctrl_read, + .write = sm501_disp_ctrl_write, + .valid = { + .min_access_size = 4, + .max_access_size = 4, + }, + .endianness = DEVICE_NATIVE_ENDIAN, +}; + +static uint64_t sm501_2d_engine_read(void *opaque, hwaddr addr, + unsigned size) +{ + SM501State * s = (SM501State *)opaque; + uint32_t ret = 0; + SM501_DPRINTF("sm501 2d engine regs : read addr=%x\n", (int)addr); + + switch(addr) { + case SM501_2D_SOURCE_BASE: + ret = s->twoD_source_base; + break; + default: + printf("sm501 disp ctrl : not implemented register read." + " addr=%x\n", (int)addr); + abort(); + } + + return ret; +} + +static void sm501_2d_engine_write(void *opaque, hwaddr addr, + uint64_t value, unsigned size) +{ + SM501State * s = (SM501State *)opaque; + SM501_DPRINTF("sm501 2d engine regs : write addr=%x, val=%x\n", + (unsigned)addr, (unsigned)value); + + switch(addr) { + case SM501_2D_SOURCE: + s->twoD_source = value; + break; + case SM501_2D_DESTINATION: + s->twoD_destination = value; + break; + case SM501_2D_DIMENSION: + s->twoD_dimension = value; + break; + case SM501_2D_CONTROL: + s->twoD_control = value; + + /* do 2d operation if start flag is set. */ + if (value & 0x80000000) { + sm501_2d_operation(s); + s->twoD_control &= ~0x80000000; /* start flag down */ + } + + break; + case SM501_2D_PITCH: + s->twoD_pitch = value; + break; + case SM501_2D_FOREGROUND: + s->twoD_foreground = value; + break; + case SM501_2D_STRETCH: + s->twoD_stretch = value; + break; + case SM501_2D_COLOR_COMPARE_MASK: + s->twoD_color_compare_mask = value; + break; + case SM501_2D_MASK: + s->twoD_mask = value; + break; + case SM501_2D_WINDOW_WIDTH: + s->twoD_window_width = value; + break; + case SM501_2D_SOURCE_BASE: + s->twoD_source_base = value; + break; + case SM501_2D_DESTINATION_BASE: + s->twoD_destination_base = value; + break; + default: + printf("sm501 2d engine : not implemented register write." + " addr=%x, val=%x\n", (int)addr, (unsigned)value); + abort(); + } +} + +static const MemoryRegionOps sm501_2d_engine_ops = { + .read = sm501_2d_engine_read, + .write = sm501_2d_engine_write, + .valid = { + .min_access_size = 4, + .max_access_size = 4, + }, + .endianness = DEVICE_NATIVE_ENDIAN, +}; + +/* draw line functions for all console modes */ + +typedef void draw_line_func(uint8_t *d, const uint8_t *s, + int width, const uint32_t *pal); + +typedef void draw_hwc_line_func(SM501State * s, int crt, uint8_t * palette, + int c_y, uint8_t *d, int width); + +#define DEPTH 8 +#include "sm501_template.h" + +#define DEPTH 15 +#include "sm501_template.h" + +#define BGR_FORMAT +#define DEPTH 15 +#include "sm501_template.h" + +#define DEPTH 16 +#include "sm501_template.h" + +#define BGR_FORMAT +#define DEPTH 16 +#include "sm501_template.h" + +#define DEPTH 32 +#include "sm501_template.h" + +#define BGR_FORMAT +#define DEPTH 32 +#include "sm501_template.h" + +static draw_line_func * draw_line8_funcs[] = { + draw_line8_8, + draw_line8_15, + draw_line8_16, + draw_line8_32, + draw_line8_32bgr, + draw_line8_15bgr, + draw_line8_16bgr, +}; + +static draw_line_func * draw_line16_funcs[] = { + draw_line16_8, + draw_line16_15, + draw_line16_16, + draw_line16_32, + draw_line16_32bgr, + draw_line16_15bgr, + draw_line16_16bgr, +}; + +static draw_line_func * draw_line32_funcs[] = { + draw_line32_8, + draw_line32_15, + draw_line32_16, + draw_line32_32, + draw_line32_32bgr, + draw_line32_15bgr, + draw_line32_16bgr, +}; + +static draw_hwc_line_func * draw_hwc_line_funcs[] = { + draw_hwc_line_8, + draw_hwc_line_15, + draw_hwc_line_16, + draw_hwc_line_32, + draw_hwc_line_32bgr, + draw_hwc_line_15bgr, + draw_hwc_line_16bgr, +}; + +static inline int get_depth_index(DisplaySurface *surface) +{ + switch (surface_bits_per_pixel(surface)) { + default: + case 8: + return 0; + case 15: + return 1; + case 16: + return 2; + case 32: + if (is_surface_bgr(surface)) { + return 4; + } else { + return 3; + } + } +} + +static void sm501_draw_crt(SM501State * s) +{ + DisplaySurface *surface = qemu_console_surface(s->con); + int y; + int width = (s->dc_crt_h_total & 0x00000FFF) + 1; + int height = (s->dc_crt_v_total & 0x00000FFF) + 1; + + uint8_t * src = s->local_mem; + int src_bpp = 0; + int dst_bpp = surface_bytes_per_pixel(surface); + uint32_t * palette = (uint32_t *)&s->dc_palette[SM501_DC_CRT_PALETTE + - SM501_DC_PANEL_PALETTE]; + uint8_t hwc_palette[3 * 3]; + int ds_depth_index = get_depth_index(surface); + draw_line_func * draw_line = NULL; + draw_hwc_line_func * draw_hwc_line = NULL; + int full_update = 0; + int y_start = -1; + ram_addr_t page_min = ~0l; + ram_addr_t page_max = 0l; + ram_addr_t offset = 0; + + /* choose draw_line function */ + switch (s->dc_crt_control & 3) { + case SM501_DC_CRT_CONTROL_8BPP: + src_bpp = 1; + draw_line = draw_line8_funcs[ds_depth_index]; + break; + case SM501_DC_CRT_CONTROL_16BPP: + src_bpp = 2; + draw_line = draw_line16_funcs[ds_depth_index]; + break; + case SM501_DC_CRT_CONTROL_32BPP: + src_bpp = 4; + draw_line = draw_line32_funcs[ds_depth_index]; + break; + default: + printf("sm501 draw crt : invalid DC_CRT_CONTROL=%x.\n", + s->dc_crt_control); + abort(); + break; + } + + /* set up to draw hardware cursor */ + if (is_hwc_enabled(s, 1)) { + int i; + + /* get cursor palette */ + for (i = 0; i < 3; i++) { + uint16_t rgb565 = get_hwc_color(s, 1, i + 1); + hwc_palette[i * 3 + 0] = (rgb565 & 0xf800) >> 8; /* red */ + hwc_palette[i * 3 + 1] = (rgb565 & 0x07e0) >> 3; /* green */ + hwc_palette[i * 3 + 2] = (rgb565 & 0x001f) << 3; /* blue */ + } + + /* choose cursor draw line function */ + draw_hwc_line = draw_hwc_line_funcs[ds_depth_index]; + } + + /* adjust console size */ + if (s->last_width != width || s->last_height != height) { + qemu_console_resize(s->con, width, height); + surface = qemu_console_surface(s->con); + s->last_width = width; + s->last_height = height; + full_update = 1; + } + + /* draw each line according to conditions */ + for (y = 0; y < height; y++) { + int update_hwc = draw_hwc_line ? within_hwc_y_range(s, y, 1) : 0; + int update = full_update || update_hwc; + ram_addr_t page0 = offset; + ram_addr_t page1 = offset + width * src_bpp - 1; + + /* check dirty flags for each line */ + update = memory_region_get_dirty(&s->local_mem_region, page0, + page1 - page0, DIRTY_MEMORY_VGA); + + /* draw line and change status */ + if (update) { + uint8_t *d = surface_data(surface); + d += y * width * dst_bpp; + + /* draw graphics layer */ + draw_line(d, src, width, palette); + + /* draw haredware cursor */ + if (update_hwc) { + draw_hwc_line(s, 1, hwc_palette, y - get_hwc_y(s, 1), d, width); + } + + if (y_start < 0) + y_start = y; + if (page0 < page_min) + page_min = page0; + if (page1 > page_max) + page_max = page1; + } else { + if (y_start >= 0) { + /* flush to display */ + dpy_gfx_update(s->con, 0, y_start, width, y - y_start); + y_start = -1; + } + } + + src += width * src_bpp; + offset += width * src_bpp; + } + + /* complete flush to display */ + if (y_start >= 0) + dpy_gfx_update(s->con, 0, y_start, width, y - y_start); + + /* clear dirty flags */ + if (page_min != ~0l) { + memory_region_reset_dirty(&s->local_mem_region, + page_min, page_max + TARGET_PAGE_SIZE, + DIRTY_MEMORY_VGA); + } +} + +static void sm501_update_display(void *opaque) +{ + SM501State * s = (SM501State *)opaque; + + if (s->dc_crt_control & SM501_DC_CRT_CONTROL_ENABLE) + sm501_draw_crt(s); +} + +void sm501_init(MemoryRegion *address_space_mem, uint32_t base, + uint32_t local_mem_bytes, qemu_irq irq, CharDriverState *chr) +{ + SM501State * s; + DeviceState *dev; + MemoryRegion *sm501_system_config = g_new(MemoryRegion, 1); + MemoryRegion *sm501_disp_ctrl = g_new(MemoryRegion, 1); + MemoryRegion *sm501_2d_engine = g_new(MemoryRegion, 1); + + /* allocate management data region */ + s = (SM501State *)g_malloc0(sizeof(SM501State)); + s->base = base; + s->local_mem_size_index + = get_local_mem_size_index(local_mem_bytes); + SM501_DPRINTF("local mem size=%x. index=%d\n", get_local_mem_size(s), + s->local_mem_size_index); + s->system_control = 0x00100000; + s->misc_control = 0x00001000; /* assumes SH, active=low */ + s->dc_panel_control = 0x00010000; + s->dc_crt_control = 0x00010000; + + /* allocate local memory */ + memory_region_init_ram(&s->local_mem_region, "sm501.local", + local_mem_bytes); + vmstate_register_ram_global(&s->local_mem_region); + s->local_mem = memory_region_get_ram_ptr(&s->local_mem_region); + memory_region_add_subregion(address_space_mem, base, &s->local_mem_region); + + /* map mmio */ + memory_region_init_io(sm501_system_config, &sm501_system_config_ops, s, + "sm501-system-config", 0x6c); + memory_region_add_subregion(address_space_mem, base + MMIO_BASE_OFFSET, + sm501_system_config); + memory_region_init_io(sm501_disp_ctrl, &sm501_disp_ctrl_ops, s, + "sm501-disp-ctrl", 0x1000); + memory_region_add_subregion(address_space_mem, + base + MMIO_BASE_OFFSET + SM501_DC, + sm501_disp_ctrl); + memory_region_init_io(sm501_2d_engine, &sm501_2d_engine_ops, s, + "sm501-2d-engine", 0x54); + memory_region_add_subregion(address_space_mem, + base + MMIO_BASE_OFFSET + SM501_2D_ENGINE, + sm501_2d_engine); + + /* bridge to usb host emulation module */ + dev = qdev_create(NULL, "sysbus-ohci"); + qdev_prop_set_uint32(dev, "num-ports", 2); + qdev_prop_set_taddr(dev, "dma-offset", base); + qdev_init_nofail(dev); + sysbus_mmio_map(SYS_BUS_DEVICE(dev), 0, + base + MMIO_BASE_OFFSET + SM501_USB_HOST); + sysbus_connect_irq(SYS_BUS_DEVICE(dev), 0, irq); + + /* bridge to serial emulation module */ + if (chr) { + serial_mm_init(address_space_mem, + base + MMIO_BASE_OFFSET + SM501_UART0, 2, + NULL, /* TODO : chain irq to IRL */ + 115200, chr, DEVICE_NATIVE_ENDIAN); + } + + /* create qemu graphic console */ + s->con = graphic_console_init(sm501_update_display, NULL, + NULL, NULL, s); +} diff --git a/hw/display/sm501_template.h b/hw/display/sm501_template.h new file mode 100644 index 0000000000..2d4a3d8b48 --- /dev/null +++ b/hw/display/sm501_template.h @@ -0,0 +1,145 @@ +/* + * Pixel drawing function templates for QEMU SM501 Device + * + * Copyright (c) 2008 Shin-ichiro KAWASAKI + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#if DEPTH == 8 +#define BPP 1 +#define PIXEL_TYPE uint8_t +#elif DEPTH == 15 || DEPTH == 16 +#define BPP 2 +#define PIXEL_TYPE uint16_t +#elif DEPTH == 32 +#define BPP 4 +#define PIXEL_TYPE uint32_t +#else +#error unsupport depth +#endif + +#ifdef BGR_FORMAT +#define PIXEL_NAME glue(DEPTH, bgr) +#else +#define PIXEL_NAME DEPTH +#endif /* BGR_FORMAT */ + + +static void glue(draw_line8_, PIXEL_NAME)( + uint8_t *d, const uint8_t *s, int width, const uint32_t *pal) +{ + uint8_t v, r, g, b; + do { + v = ldub_raw(s); + r = (pal[v] >> 16) & 0xff; + g = (pal[v] >> 8) & 0xff; + b = (pal[v] >> 0) & 0xff; + ((PIXEL_TYPE *) d)[0] = glue(rgb_to_pixel, PIXEL_NAME)(r, g, b); + s ++; + d += BPP; + } while (-- width != 0); +} + +static void glue(draw_line16_, PIXEL_NAME)( + uint8_t *d, const uint8_t *s, int width, const uint32_t *pal) +{ + uint16_t rgb565; + uint8_t r, g, b; + + do { + rgb565 = lduw_raw(s); + r = ((rgb565 >> 11) & 0x1f) << 3; + g = ((rgb565 >> 5) & 0x3f) << 2; + b = ((rgb565 >> 0) & 0x1f) << 3; + ((PIXEL_TYPE *) d)[0] = glue(rgb_to_pixel, PIXEL_NAME)(r, g, b); + s += 2; + d += BPP; + } while (-- width != 0); +} + +static void glue(draw_line32_, PIXEL_NAME)( + uint8_t *d, const uint8_t *s, int width, const uint32_t *pal) +{ + uint8_t r, g, b; + + do { + ldub_raw(s); +#if defined(TARGET_WORDS_BIGENDIAN) + r = s[1]; + g = s[2]; + b = s[3]; +#else + b = s[0]; + g = s[1]; + r = s[2]; +#endif + ((PIXEL_TYPE *) d)[0] = glue(rgb_to_pixel, PIXEL_NAME)(r, g, b); + s += 4; + d += BPP; + } while (-- width != 0); +} + +/** + * Draw hardware cursor image on the given line. + */ +static void glue(draw_hwc_line_, PIXEL_NAME)(SM501State * s, int crt, + uint8_t * palette, int c_y, uint8_t *d, int width) +{ + int x, i; + uint8_t bitset = 0; + + /* get hardware cursor pattern */ + uint32_t cursor_addr = get_hwc_address(s, crt); + assert(0 <= c_y && c_y < SM501_HWC_HEIGHT); + cursor_addr += 64 * c_y / 4; /* 4 pixels per byte */ + cursor_addr += s->base; + + /* get cursor position */ + x = get_hwc_x(s, crt); + d += x * BPP; + + for (i = 0; i < SM501_HWC_WIDTH && x + i < width; i++) { + uint8_t v; + + /* get pixel value */ + if (i % 4 == 0) { + bitset = ldub_phys(cursor_addr); + cursor_addr++; + } + v = bitset & 3; + bitset >>= 2; + + /* write pixel */ + if (v) { + v--; + uint8_t r = palette[v * 3 + 0]; + uint8_t g = palette[v * 3 + 1]; + uint8_t b = palette[v * 3 + 2]; + ((PIXEL_TYPE *) d)[0] = glue(rgb_to_pixel, PIXEL_NAME)(r, g, b); + } + d += BPP; + } +} + +#undef DEPTH +#undef BPP +#undef PIXEL_TYPE +#undef PIXEL_NAME +#undef BGR_FORMAT diff --git a/hw/display/ssd0303.c b/hw/display/ssd0303.c new file mode 100644 index 0000000000..183a87835c --- /dev/null +++ b/hw/display/ssd0303.c @@ -0,0 +1,322 @@ +/* + * SSD0303 OLED controller with OSRAM Pictiva 96x16 display. + * + * Copyright (c) 2006-2007 CodeSourcery. + * Written by Paul Brook + * + * This code is licensed under the GPL. + */ + +/* The controller can support a variety of different displays, but we only + implement one. Most of the commends relating to brightness and geometry + setup are ignored. */ +#include "hw/i2c/i2c.h" +#include "ui/console.h" + +//#define DEBUG_SSD0303 1 + +#ifdef DEBUG_SSD0303 +#define DPRINTF(fmt, ...) \ +do { printf("ssd0303: " fmt , ## __VA_ARGS__); } while (0) +#define BADF(fmt, ...) \ +do { fprintf(stderr, "ssd0303: error: " fmt , ## __VA_ARGS__); exit(1);} while (0) +#else +#define DPRINTF(fmt, ...) do {} while(0) +#define BADF(fmt, ...) \ +do { fprintf(stderr, "ssd0303: error: " fmt , ## __VA_ARGS__);} while (0) +#endif + +/* Scaling factor for pixels. */ +#define MAGNIFY 4 + +enum ssd0303_mode +{ + SSD0303_IDLE, + SSD0303_DATA, + SSD0303_CMD +}; + +enum ssd0303_cmd { + SSD0303_CMD_NONE, + SSD0303_CMD_SKIP1 +}; + +typedef struct { + I2CSlave i2c; + QemuConsole *con; + int row; + int col; + int start_line; + int mirror; + int flash; + int enabled; + int inverse; + int redraw; + enum ssd0303_mode mode; + enum ssd0303_cmd cmd_state; + uint8_t framebuffer[132*8]; +} ssd0303_state; + +static int ssd0303_recv(I2CSlave *i2c) +{ + BADF("Reads not implemented\n"); + return -1; +} + +static int ssd0303_send(I2CSlave *i2c, uint8_t data) +{ + ssd0303_state *s = (ssd0303_state *)i2c; + enum ssd0303_cmd old_cmd_state; + switch (s->mode) { + case SSD0303_IDLE: + DPRINTF("byte 0x%02x\n", data); + if (data == 0x80) + s->mode = SSD0303_CMD; + else if (data == 0x40) + s->mode = SSD0303_DATA; + else + BADF("Unexpected byte 0x%x\n", data); + break; + case SSD0303_DATA: + DPRINTF("data 0x%02x\n", data); + if (s->col < 132) { + s->framebuffer[s->col + s->row * 132] = data; + s->col++; + s->redraw = 1; + } + break; + case SSD0303_CMD: + old_cmd_state = s->cmd_state; + s->cmd_state = SSD0303_CMD_NONE; + switch (old_cmd_state) { + case SSD0303_CMD_NONE: + DPRINTF("cmd 0x%02x\n", data); + s->mode = SSD0303_IDLE; + switch (data) { + case 0x00 ... 0x0f: /* Set lower column address. */ + s->col = (s->col & 0xf0) | (data & 0xf); + break; + case 0x10 ... 0x20: /* Set higher column address. */ + s->col = (s->col & 0x0f) | ((data & 0xf) << 4); + break; + case 0x40 ... 0x7f: /* Set start line. */ + s->start_line = 0; + break; + case 0x81: /* Set contrast (Ignored). */ + s->cmd_state = SSD0303_CMD_SKIP1; + break; + case 0xa0: /* Mirror off. */ + s->mirror = 0; + break; + case 0xa1: /* Mirror off. */ + s->mirror = 1; + break; + case 0xa4: /* Entire display off. */ + s->flash = 0; + break; + case 0xa5: /* Entire display on. */ + s->flash = 1; + break; + case 0xa6: /* Inverse off. */ + s->inverse = 0; + break; + case 0xa7: /* Inverse on. */ + s->inverse = 1; + break; + case 0xa8: /* Set multiplied ratio (Ignored). */ + s->cmd_state = SSD0303_CMD_SKIP1; + break; + case 0xad: /* DC-DC power control. */ + s->cmd_state = SSD0303_CMD_SKIP1; + break; + case 0xae: /* Display off. */ + s->enabled = 0; + break; + case 0xaf: /* Display on. */ + s->enabled = 1; + break; + case 0xb0 ... 0xbf: /* Set Page address. */ + s->row = data & 7; + break; + case 0xc0 ... 0xc8: /* Set COM output direction (Ignored). */ + break; + case 0xd3: /* Set display offset (Ignored). */ + s->cmd_state = SSD0303_CMD_SKIP1; + break; + case 0xd5: /* Set display clock (Ignored). */ + s->cmd_state = SSD0303_CMD_SKIP1; + break; + case 0xd8: /* Set color and power mode (Ignored). */ + s->cmd_state = SSD0303_CMD_SKIP1; + break; + case 0xd9: /* Set pre-charge period (Ignored). */ + s->cmd_state = SSD0303_CMD_SKIP1; + break; + case 0xda: /* Set COM pin configuration (Ignored). */ + s->cmd_state = SSD0303_CMD_SKIP1; + break; + case 0xdb: /* Set VCOM dselect level (Ignored). */ + s->cmd_state = SSD0303_CMD_SKIP1; + break; + case 0xe3: /* no-op. */ + break; + default: + BADF("Unknown command: 0x%x\n", data); + } + break; + case SSD0303_CMD_SKIP1: + DPRINTF("skip 0x%02x\n", data); + break; + } + break; + } + return 0; +} + +static void ssd0303_event(I2CSlave *i2c, enum i2c_event event) +{ + ssd0303_state *s = (ssd0303_state *)i2c; + switch (event) { + case I2C_FINISH: + s->mode = SSD0303_IDLE; + break; + case I2C_START_RECV: + case I2C_START_SEND: + case I2C_NACK: + /* Nothing to do. */ + break; + } +} + +static void ssd0303_update_display(void *opaque) +{ + ssd0303_state *s = (ssd0303_state *)opaque; + DisplaySurface *surface = qemu_console_surface(s->con); + uint8_t *dest; + uint8_t *src; + int x; + int y; + int line; + char *colors[2]; + char colortab[MAGNIFY * 8]; + int dest_width; + uint8_t mask; + + if (!s->redraw) + return; + + switch (surface_bits_per_pixel(surface)) { + case 0: + return; + case 15: + dest_width = 2; + break; + case 16: + dest_width = 2; + break; + case 24: + dest_width = 3; + break; + case 32: + dest_width = 4; + break; + default: + BADF("Bad color depth\n"); + return; + } + dest_width *= MAGNIFY; + memset(colortab, 0xff, dest_width); + memset(colortab + dest_width, 0, dest_width); + if (s->flash) { + colors[0] = colortab; + colors[1] = colortab; + } else if (s->inverse) { + colors[0] = colortab; + colors[1] = colortab + dest_width; + } else { + colors[0] = colortab + dest_width; + colors[1] = colortab; + } + dest = surface_data(surface); + for (y = 0; y < 16; y++) { + line = (y + s->start_line) & 63; + src = s->framebuffer + 132 * (line >> 3) + 36; + mask = 1 << (line & 7); + for (x = 0; x < 96; x++) { + memcpy(dest, colors[(*src & mask) != 0], dest_width); + dest += dest_width; + src++; + } + for (x = 1; x < MAGNIFY; x++) { + memcpy(dest, dest - dest_width * 96, dest_width * 96); + dest += dest_width * 96; + } + } + s->redraw = 0; + dpy_gfx_update(s->con, 0, 0, 96 * MAGNIFY, 16 * MAGNIFY); +} + +static void ssd0303_invalidate_display(void * opaque) +{ + ssd0303_state *s = (ssd0303_state *)opaque; + s->redraw = 1; +} + +static const VMStateDescription vmstate_ssd0303 = { + .name = "ssd0303_oled", + .version_id = 1, + .minimum_version_id = 1, + .minimum_version_id_old = 1, + .fields = (VMStateField []) { + VMSTATE_INT32(row, ssd0303_state), + VMSTATE_INT32(col, ssd0303_state), + VMSTATE_INT32(start_line, ssd0303_state), + VMSTATE_INT32(mirror, ssd0303_state), + VMSTATE_INT32(flash, ssd0303_state), + VMSTATE_INT32(enabled, ssd0303_state), + VMSTATE_INT32(inverse, ssd0303_state), + VMSTATE_INT32(redraw, ssd0303_state), + VMSTATE_UINT32(mode, ssd0303_state), + VMSTATE_UINT32(cmd_state, ssd0303_state), + VMSTATE_BUFFER(framebuffer, ssd0303_state), + VMSTATE_I2C_SLAVE(i2c, ssd0303_state), + VMSTATE_END_OF_LIST() + } +}; + +static int ssd0303_init(I2CSlave *i2c) +{ + ssd0303_state *s = FROM_I2C_SLAVE(ssd0303_state, i2c); + + s->con = graphic_console_init(ssd0303_update_display, + ssd0303_invalidate_display, + NULL, NULL, s); + qemu_console_resize(s->con, 96 * MAGNIFY, 16 * MAGNIFY); + return 0; +} + +static void ssd0303_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + I2CSlaveClass *k = I2C_SLAVE_CLASS(klass); + + k->init = ssd0303_init; + k->event = ssd0303_event; + k->recv = ssd0303_recv; + k->send = ssd0303_send; + dc->vmsd = &vmstate_ssd0303; +} + +static const TypeInfo ssd0303_info = { + .name = "ssd0303", + .parent = TYPE_I2C_SLAVE, + .instance_size = sizeof(ssd0303_state), + .class_init = ssd0303_class_init, +}; + +static void ssd0303_register_types(void) +{ + type_register_static(&ssd0303_info); +} + +type_init(ssd0303_register_types) diff --git a/hw/display/ssd0323.c b/hw/display/ssd0323.c new file mode 100644 index 0000000000..5cf2f7058f --- /dev/null +++ b/hw/display/ssd0323.c @@ -0,0 +1,373 @@ +/* + * SSD0323 OLED controller with OSRAM Pictiva 128x64 display. + * + * Copyright (c) 2006-2007 CodeSourcery. + * Written by Paul Brook + * + * This code is licensed under the GPL. + */ + +/* The controller can support a variety of different displays, but we only + implement one. Most of the commends relating to brightness and geometry + setup are ignored. */ +#include "hw/ssi.h" +#include "ui/console.h" + +//#define DEBUG_SSD0323 1 + +#ifdef DEBUG_SSD0323 +#define DPRINTF(fmt, ...) \ +do { printf("ssd0323: " fmt , ## __VA_ARGS__); } while (0) +#define BADF(fmt, ...) \ +do { \ + fprintf(stderr, "ssd0323: error: " fmt , ## __VA_ARGS__); abort(); \ +} while (0) +#else +#define DPRINTF(fmt, ...) do {} while(0) +#define BADF(fmt, ...) \ +do { fprintf(stderr, "ssd0323: error: " fmt , ## __VA_ARGS__);} while (0) +#endif + +/* Scaling factor for pixels. */ +#define MAGNIFY 4 + +#define REMAP_SWAP_COLUMN 0x01 +#define REMAP_SWAP_NYBBLE 0x02 +#define REMAP_VERTICAL 0x04 +#define REMAP_SWAP_COM 0x10 +#define REMAP_SPLIT_COM 0x40 + +enum ssd0323_mode +{ + SSD0323_CMD, + SSD0323_DATA +}; + +typedef struct { + SSISlave ssidev; + QemuConsole *con; + + int cmd_len; + int cmd; + int cmd_data[8]; + int row; + int row_start; + int row_end; + int col; + int col_start; + int col_end; + int redraw; + int remap; + enum ssd0323_mode mode; + uint8_t framebuffer[128 * 80 / 2]; +} ssd0323_state; + +static uint32_t ssd0323_transfer(SSISlave *dev, uint32_t data) +{ + ssd0323_state *s = FROM_SSI_SLAVE(ssd0323_state, dev); + + switch (s->mode) { + case SSD0323_DATA: + DPRINTF("data 0x%02x\n", data); + s->framebuffer[s->col + s->row * 64] = data; + if (s->remap & REMAP_VERTICAL) { + s->row++; + if (s->row > s->row_end) { + s->row = s->row_start; + s->col++; + } + if (s->col > s->col_end) { + s->col = s->col_start; + } + } else { + s->col++; + if (s->col > s->col_end) { + s->row++; + s->col = s->col_start; + } + if (s->row > s->row_end) { + s->row = s->row_start; + } + } + s->redraw = 1; + break; + case SSD0323_CMD: + DPRINTF("cmd 0x%02x\n", data); + if (s->cmd_len == 0) { + s->cmd = data; + } else { + s->cmd_data[s->cmd_len - 1] = data; + } + s->cmd_len++; + switch (s->cmd) { +#define DATA(x) if (s->cmd_len <= (x)) return 0 + case 0x15: /* Set column. */ + DATA(2); + s->col = s->col_start = s->cmd_data[0] % 64; + s->col_end = s->cmd_data[1] % 64; + break; + case 0x75: /* Set row. */ + DATA(2); + s->row = s->row_start = s->cmd_data[0] % 80; + s->row_end = s->cmd_data[1] % 80; + break; + case 0x81: /* Set contrast */ + DATA(1); + break; + case 0x84: case 0x85: case 0x86: /* Max current. */ + DATA(0); + break; + case 0xa0: /* Set remapping. */ + /* FIXME: Implement this. */ + DATA(1); + s->remap = s->cmd_data[0]; + break; + case 0xa1: /* Set display start line. */ + case 0xa2: /* Set display offset. */ + /* FIXME: Implement these. */ + DATA(1); + break; + case 0xa4: /* Normal mode. */ + case 0xa5: /* All on. */ + case 0xa6: /* All off. */ + case 0xa7: /* Inverse. */ + /* FIXME: Implement these. */ + DATA(0); + break; + case 0xa8: /* Set multiplex ratio. */ + case 0xad: /* Set DC-DC converter. */ + DATA(1); + /* Ignored. Don't care. */ + break; + case 0xae: /* Display off. */ + case 0xaf: /* Display on. */ + DATA(0); + /* TODO: Implement power control. */ + break; + case 0xb1: /* Set phase length. */ + case 0xb2: /* Set row period. */ + case 0xb3: /* Set clock rate. */ + case 0xbc: /* Set precharge. */ + case 0xbe: /* Set VCOMH. */ + case 0xbf: /* Set segment low. */ + DATA(1); + /* Ignored. Don't care. */ + break; + case 0xb8: /* Set grey scale table. */ + /* FIXME: Implement this. */ + DATA(8); + break; + case 0xe3: /* NOP. */ + DATA(0); + break; + case 0xff: /* Nasty hack because we don't handle chip selects + properly. */ + break; + default: + BADF("Unknown command: 0x%x\n", data); + } + s->cmd_len = 0; + return 0; + } + return 0; +} + +static void ssd0323_update_display(void *opaque) +{ + ssd0323_state *s = (ssd0323_state *)opaque; + DisplaySurface *surface = qemu_console_surface(s->con); + uint8_t *dest; + uint8_t *src; + int x; + int y; + int i; + int line; + char *colors[16]; + char colortab[MAGNIFY * 64]; + char *p; + int dest_width; + + if (!s->redraw) + return; + + switch (surface_bits_per_pixel(surface)) { + case 0: + return; + case 15: + dest_width = 2; + break; + case 16: + dest_width = 2; + break; + case 24: + dest_width = 3; + break; + case 32: + dest_width = 4; + break; + default: + BADF("Bad color depth\n"); + return; + } + p = colortab; + for (i = 0; i < 16; i++) { + int n; + colors[i] = p; + switch (surface_bits_per_pixel(surface)) { + case 15: + n = i * 2 + (i >> 3); + p[0] = n | (n << 5); + p[1] = (n << 2) | (n >> 3); + break; + case 16: + n = i * 2 + (i >> 3); + p[0] = n | (n << 6) | ((n << 1) & 0x20); + p[1] = (n << 3) | (n >> 2); + break; + case 24: + case 32: + n = (i << 4) | i; + p[0] = p[1] = p[2] = n; + break; + default: + BADF("Bad color depth\n"); + return; + } + p += dest_width; + } + /* TODO: Implement row/column remapping. */ + dest = surface_data(surface); + for (y = 0; y < 64; y++) { + line = y; + src = s->framebuffer + 64 * line; + for (x = 0; x < 64; x++) { + int val; + val = *src >> 4; + for (i = 0; i < MAGNIFY; i++) { + memcpy(dest, colors[val], dest_width); + dest += dest_width; + } + val = *src & 0xf; + for (i = 0; i < MAGNIFY; i++) { + memcpy(dest, colors[val], dest_width); + dest += dest_width; + } + src++; + } + for (i = 1; i < MAGNIFY; i++) { + memcpy(dest, dest - dest_width * MAGNIFY * 128, + dest_width * 128 * MAGNIFY); + dest += dest_width * 128 * MAGNIFY; + } + } + s->redraw = 0; + dpy_gfx_update(s->con, 0, 0, 128 * MAGNIFY, 64 * MAGNIFY); +} + +static void ssd0323_invalidate_display(void * opaque) +{ + ssd0323_state *s = (ssd0323_state *)opaque; + s->redraw = 1; +} + +/* Command/data input. */ +static void ssd0323_cd(void *opaque, int n, int level) +{ + ssd0323_state *s = (ssd0323_state *)opaque; + DPRINTF("%s mode\n", level ? "Data" : "Command"); + s->mode = level ? SSD0323_DATA : SSD0323_CMD; +} + +static void ssd0323_save(QEMUFile *f, void *opaque) +{ + SSISlave *ss = SSI_SLAVE(opaque); + ssd0323_state *s = (ssd0323_state *)opaque; + int i; + + qemu_put_be32(f, s->cmd_len); + qemu_put_be32(f, s->cmd); + for (i = 0; i < 8; i++) + qemu_put_be32(f, s->cmd_data[i]); + qemu_put_be32(f, s->row); + qemu_put_be32(f, s->row_start); + qemu_put_be32(f, s->row_end); + qemu_put_be32(f, s->col); + qemu_put_be32(f, s->col_start); + qemu_put_be32(f, s->col_end); + qemu_put_be32(f, s->redraw); + qemu_put_be32(f, s->remap); + qemu_put_be32(f, s->mode); + qemu_put_buffer(f, s->framebuffer, sizeof(s->framebuffer)); + + qemu_put_be32(f, ss->cs); +} + +static int ssd0323_load(QEMUFile *f, void *opaque, int version_id) +{ + SSISlave *ss = SSI_SLAVE(opaque); + ssd0323_state *s = (ssd0323_state *)opaque; + int i; + + if (version_id != 1) + return -EINVAL; + + s->cmd_len = qemu_get_be32(f); + s->cmd = qemu_get_be32(f); + for (i = 0; i < 8; i++) + s->cmd_data[i] = qemu_get_be32(f); + s->row = qemu_get_be32(f); + s->row_start = qemu_get_be32(f); + s->row_end = qemu_get_be32(f); + s->col = qemu_get_be32(f); + s->col_start = qemu_get_be32(f); + s->col_end = qemu_get_be32(f); + s->redraw = qemu_get_be32(f); + s->remap = qemu_get_be32(f); + s->mode = qemu_get_be32(f); + qemu_get_buffer(f, s->framebuffer, sizeof(s->framebuffer)); + + ss->cs = qemu_get_be32(f); + + return 0; +} + +static int ssd0323_init(SSISlave *dev) +{ + ssd0323_state *s = FROM_SSI_SLAVE(ssd0323_state, dev); + + s->col_end = 63; + s->row_end = 79; + s->con = graphic_console_init(ssd0323_update_display, + ssd0323_invalidate_display, + NULL, NULL, s); + qemu_console_resize(s->con, 128 * MAGNIFY, 64 * MAGNIFY); + + qdev_init_gpio_in(&dev->qdev, ssd0323_cd, 1); + + register_savevm(&dev->qdev, "ssd0323_oled", -1, 1, + ssd0323_save, ssd0323_load, s); + return 0; +} + +static void ssd0323_class_init(ObjectClass *klass, void *data) +{ + SSISlaveClass *k = SSI_SLAVE_CLASS(klass); + + k->init = ssd0323_init; + k->transfer = ssd0323_transfer; + k->cs_polarity = SSI_CS_HIGH; +} + +static const TypeInfo ssd0323_info = { + .name = "ssd0323", + .parent = TYPE_SSI_SLAVE, + .instance_size = sizeof(ssd0323_state), + .class_init = ssd0323_class_init, +}; + +static void ssd03232_register_types(void) +{ + type_register_static(&ssd0323_info); +} + +type_init(ssd03232_register_types) diff --git a/hw/display/tc6393xb.c b/hw/display/tc6393xb.c new file mode 100644 index 0000000000..e252ce945f --- /dev/null +++ b/hw/display/tc6393xb.c @@ -0,0 +1,593 @@ +/* + * Toshiba TC6393XB I/O Controller. + * Found in Sharp Zaurus SL-6000 (tosa) or some + * Toshiba e-Series PDAs. + * + * Most features are currently unsupported!!! + * + * This code is licensed under the GNU GPL v2. + * + * Contributions after 2012-01-13 are licensed under the terms of the + * GNU GPL, version 2 or (at your option) any later version. + */ +#include "hw/hw.h" +#include "hw/arm/devices.h" +#include "hw/block/flash.h" +#include "ui/console.h" +#include "ui/pixel_ops.h" +#include "sysemu/blockdev.h" + +#define IRQ_TC6393_NAND 0 +#define IRQ_TC6393_MMC 1 +#define IRQ_TC6393_OHCI 2 +#define IRQ_TC6393_SERIAL 3 +#define IRQ_TC6393_FB 4 + +#define TC6393XB_NR_IRQS 8 + +#define TC6393XB_GPIOS 16 + +#define SCR_REVID 0x08 /* b Revision ID */ +#define SCR_ISR 0x50 /* b Interrupt Status */ +#define SCR_IMR 0x52 /* b Interrupt Mask */ +#define SCR_IRR 0x54 /* b Interrupt Routing */ +#define SCR_GPER 0x60 /* w GP Enable */ +#define SCR_GPI_SR(i) (0x64 + (i)) /* b3 GPI Status */ +#define SCR_GPI_IMR(i) (0x68 + (i)) /* b3 GPI INT Mask */ +#define SCR_GPI_EDER(i) (0x6c + (i)) /* b3 GPI Edge Detect Enable */ +#define SCR_GPI_LIR(i) (0x70 + (i)) /* b3 GPI Level Invert */ +#define SCR_GPO_DSR(i) (0x78 + (i)) /* b3 GPO Data Set */ +#define SCR_GPO_DOECR(i) (0x7c + (i)) /* b3 GPO Data OE Control */ +#define SCR_GP_IARCR(i) (0x80 + (i)) /* b3 GP Internal Active Register Control */ +#define SCR_GP_IARLCR(i) (0x84 + (i)) /* b3 GP INTERNAL Active Register Level Control */ +#define SCR_GPI_BCR(i) (0x88 + (i)) /* b3 GPI Buffer Control */ +#define SCR_GPA_IARCR 0x8c /* w GPa Internal Active Register Control */ +#define SCR_GPA_IARLCR 0x90 /* w GPa Internal Active Register Level Control */ +#define SCR_GPA_BCR 0x94 /* w GPa Buffer Control */ +#define SCR_CCR 0x98 /* w Clock Control */ +#define SCR_PLL2CR 0x9a /* w PLL2 Control */ +#define SCR_PLL1CR 0x9c /* l PLL1 Control */ +#define SCR_DIARCR 0xa0 /* b Device Internal Active Register Control */ +#define SCR_DBOCR 0xa1 /* b Device Buffer Off Control */ +#define SCR_FER 0xe0 /* b Function Enable */ +#define SCR_MCR 0xe4 /* w Mode Control */ +#define SCR_CONFIG 0xfc /* b Configuration Control */ +#define SCR_DEBUG 0xff /* b Debug */ + +#define NAND_CFG_COMMAND 0x04 /* w Command */ +#define NAND_CFG_BASE 0x10 /* l Control Base Address */ +#define NAND_CFG_INTP 0x3d /* b Interrupt Pin */ +#define NAND_CFG_INTE 0x48 /* b Int Enable */ +#define NAND_CFG_EC 0x4a /* b Event Control */ +#define NAND_CFG_ICC 0x4c /* b Internal Clock Control */ +#define NAND_CFG_ECCC 0x5b /* b ECC Control */ +#define NAND_CFG_NFTC 0x60 /* b NAND Flash Transaction Control */ +#define NAND_CFG_NFM 0x61 /* b NAND Flash Monitor */ +#define NAND_CFG_NFPSC 0x62 /* b NAND Flash Power Supply Control */ +#define NAND_CFG_NFDC 0x63 /* b NAND Flash Detect Control */ + +#define NAND_DATA 0x00 /* l Data */ +#define NAND_MODE 0x04 /* b Mode */ +#define NAND_STATUS 0x05 /* b Status */ +#define NAND_ISR 0x06 /* b Interrupt Status */ +#define NAND_IMR 0x07 /* b Interrupt Mask */ + +#define NAND_MODE_WP 0x80 +#define NAND_MODE_CE 0x10 +#define NAND_MODE_ALE 0x02 +#define NAND_MODE_CLE 0x01 +#define NAND_MODE_ECC_MASK 0x60 +#define NAND_MODE_ECC_EN 0x20 +#define NAND_MODE_ECC_READ 0x40 +#define NAND_MODE_ECC_RST 0x60 + +struct TC6393xbState { + MemoryRegion iomem; + qemu_irq irq; + qemu_irq *sub_irqs; + struct { + uint8_t ISR; + uint8_t IMR; + uint8_t IRR; + uint16_t GPER; + uint8_t GPI_SR[3]; + uint8_t GPI_IMR[3]; + uint8_t GPI_EDER[3]; + uint8_t GPI_LIR[3]; + uint8_t GP_IARCR[3]; + uint8_t GP_IARLCR[3]; + uint8_t GPI_BCR[3]; + uint16_t GPA_IARCR; + uint16_t GPA_IARLCR; + uint16_t CCR; + uint16_t PLL2CR; + uint32_t PLL1CR; + uint8_t DIARCR; + uint8_t DBOCR; + uint8_t FER; + uint16_t MCR; + uint8_t CONFIG; + uint8_t DEBUG; + } scr; + uint32_t gpio_dir; + uint32_t gpio_level; + uint32_t prev_level; + qemu_irq handler[TC6393XB_GPIOS]; + qemu_irq *gpio_in; + + struct { + uint8_t mode; + uint8_t isr; + uint8_t imr; + } nand; + int nand_enable; + uint32_t nand_phys; + DeviceState *flash; + ECCState ecc; + + QemuConsole *con; + MemoryRegion vram; + uint16_t *vram_ptr; + uint32_t scr_width, scr_height; /* in pixels */ + qemu_irq l3v; + unsigned blank : 1, + blanked : 1; +}; + +qemu_irq *tc6393xb_gpio_in_get(TC6393xbState *s) +{ + return s->gpio_in; +} + +static void tc6393xb_gpio_set(void *opaque, int line, int level) +{ +// TC6393xbState *s = opaque; + + if (line > TC6393XB_GPIOS) { + printf("%s: No GPIO pin %i\n", __FUNCTION__, line); + return; + } + + // FIXME: how does the chip reflect the GPIO input level change? +} + +void tc6393xb_gpio_out_set(TC6393xbState *s, int line, + qemu_irq handler) +{ + if (line >= TC6393XB_GPIOS) { + fprintf(stderr, "TC6393xb: no GPIO pin %d\n", line); + return; + } + + s->handler[line] = handler; +} + +static void tc6393xb_gpio_handler_update(TC6393xbState *s) +{ + uint32_t level, diff; + int bit; + + level = s->gpio_level & s->gpio_dir; + + for (diff = s->prev_level ^ level; diff; diff ^= 1 << bit) { + bit = ffs(diff) - 1; + qemu_set_irq(s->handler[bit], (level >> bit) & 1); + } + + s->prev_level = level; +} + +qemu_irq tc6393xb_l3v_get(TC6393xbState *s) +{ + return s->l3v; +} + +static void tc6393xb_l3v(void *opaque, int line, int level) +{ + TC6393xbState *s = opaque; + s->blank = !level; + fprintf(stderr, "L3V: %d\n", level); +} + +static void tc6393xb_sub_irq(void *opaque, int line, int level) { + TC6393xbState *s = opaque; + uint8_t isr = s->scr.ISR; + if (level) + isr |= 1 << line; + else + isr &= ~(1 << line); + s->scr.ISR = isr; + qemu_set_irq(s->irq, isr & s->scr.IMR); +} + +#define SCR_REG_B(N) \ + case SCR_ ##N: return s->scr.N +#define SCR_REG_W(N) \ + case SCR_ ##N: return s->scr.N; \ + case SCR_ ##N + 1: return s->scr.N >> 8; +#define SCR_REG_L(N) \ + case SCR_ ##N: return s->scr.N; \ + case SCR_ ##N + 1: return s->scr.N >> 8; \ + case SCR_ ##N + 2: return s->scr.N >> 16; \ + case SCR_ ##N + 3: return s->scr.N >> 24; +#define SCR_REG_A(N) \ + case SCR_ ##N(0): return s->scr.N[0]; \ + case SCR_ ##N(1): return s->scr.N[1]; \ + case SCR_ ##N(2): return s->scr.N[2] + +static uint32_t tc6393xb_scr_readb(TC6393xbState *s, hwaddr addr) +{ + switch (addr) { + case SCR_REVID: + return 3; + case SCR_REVID+1: + return 0; + SCR_REG_B(ISR); + SCR_REG_B(IMR); + SCR_REG_B(IRR); + SCR_REG_W(GPER); + SCR_REG_A(GPI_SR); + SCR_REG_A(GPI_IMR); + SCR_REG_A(GPI_EDER); + SCR_REG_A(GPI_LIR); + case SCR_GPO_DSR(0): + case SCR_GPO_DSR(1): + case SCR_GPO_DSR(2): + return (s->gpio_level >> ((addr - SCR_GPO_DSR(0)) * 8)) & 0xff; + case SCR_GPO_DOECR(0): + case SCR_GPO_DOECR(1): + case SCR_GPO_DOECR(2): + return (s->gpio_dir >> ((addr - SCR_GPO_DOECR(0)) * 8)) & 0xff; + SCR_REG_A(GP_IARCR); + SCR_REG_A(GP_IARLCR); + SCR_REG_A(GPI_BCR); + SCR_REG_W(GPA_IARCR); + SCR_REG_W(GPA_IARLCR); + SCR_REG_W(CCR); + SCR_REG_W(PLL2CR); + SCR_REG_L(PLL1CR); + SCR_REG_B(DIARCR); + SCR_REG_B(DBOCR); + SCR_REG_B(FER); + SCR_REG_W(MCR); + SCR_REG_B(CONFIG); + SCR_REG_B(DEBUG); + } + fprintf(stderr, "tc6393xb_scr: unhandled read at %08x\n", (uint32_t) addr); + return 0; +} +#undef SCR_REG_B +#undef SCR_REG_W +#undef SCR_REG_L +#undef SCR_REG_A + +#define SCR_REG_B(N) \ + case SCR_ ##N: s->scr.N = value; return; +#define SCR_REG_W(N) \ + case SCR_ ##N: s->scr.N = (s->scr.N & ~0xff) | (value & 0xff); return; \ + case SCR_ ##N + 1: s->scr.N = (s->scr.N & 0xff) | (value << 8); return +#define SCR_REG_L(N) \ + case SCR_ ##N: s->scr.N = (s->scr.N & ~0xff) | (value & 0xff); return; \ + case SCR_ ##N + 1: s->scr.N = (s->scr.N & ~(0xff << 8)) | (value & (0xff << 8)); return; \ + case SCR_ ##N + 2: s->scr.N = (s->scr.N & ~(0xff << 16)) | (value & (0xff << 16)); return; \ + case SCR_ ##N + 3: s->scr.N = (s->scr.N & ~(0xff << 24)) | (value & (0xff << 24)); return; +#define SCR_REG_A(N) \ + case SCR_ ##N(0): s->scr.N[0] = value; return; \ + case SCR_ ##N(1): s->scr.N[1] = value; return; \ + case SCR_ ##N(2): s->scr.N[2] = value; return + +static void tc6393xb_scr_writeb(TC6393xbState *s, hwaddr addr, uint32_t value) +{ + switch (addr) { + SCR_REG_B(ISR); + SCR_REG_B(IMR); + SCR_REG_B(IRR); + SCR_REG_W(GPER); + SCR_REG_A(GPI_SR); + SCR_REG_A(GPI_IMR); + SCR_REG_A(GPI_EDER); + SCR_REG_A(GPI_LIR); + case SCR_GPO_DSR(0): + case SCR_GPO_DSR(1): + case SCR_GPO_DSR(2): + s->gpio_level = (s->gpio_level & ~(0xff << ((addr - SCR_GPO_DSR(0))*8))) | ((value & 0xff) << ((addr - SCR_GPO_DSR(0))*8)); + tc6393xb_gpio_handler_update(s); + return; + case SCR_GPO_DOECR(0): + case SCR_GPO_DOECR(1): + case SCR_GPO_DOECR(2): + s->gpio_dir = (s->gpio_dir & ~(0xff << ((addr - SCR_GPO_DOECR(0))*8))) | ((value & 0xff) << ((addr - SCR_GPO_DOECR(0))*8)); + tc6393xb_gpio_handler_update(s); + return; + SCR_REG_A(GP_IARCR); + SCR_REG_A(GP_IARLCR); + SCR_REG_A(GPI_BCR); + SCR_REG_W(GPA_IARCR); + SCR_REG_W(GPA_IARLCR); + SCR_REG_W(CCR); + SCR_REG_W(PLL2CR); + SCR_REG_L(PLL1CR); + SCR_REG_B(DIARCR); + SCR_REG_B(DBOCR); + SCR_REG_B(FER); + SCR_REG_W(MCR); + SCR_REG_B(CONFIG); + SCR_REG_B(DEBUG); + } + fprintf(stderr, "tc6393xb_scr: unhandled write at %08x: %02x\n", + (uint32_t) addr, value & 0xff); +} +#undef SCR_REG_B +#undef SCR_REG_W +#undef SCR_REG_L +#undef SCR_REG_A + +static void tc6393xb_nand_irq(TC6393xbState *s) { + qemu_set_irq(s->sub_irqs[IRQ_TC6393_NAND], + (s->nand.imr & 0x80) && (s->nand.imr & s->nand.isr)); +} + +static uint32_t tc6393xb_nand_cfg_readb(TC6393xbState *s, hwaddr addr) { + switch (addr) { + case NAND_CFG_COMMAND: + return s->nand_enable ? 2 : 0; + case NAND_CFG_BASE: + case NAND_CFG_BASE + 1: + case NAND_CFG_BASE + 2: + case NAND_CFG_BASE + 3: + return s->nand_phys >> (addr - NAND_CFG_BASE); + } + fprintf(stderr, "tc6393xb_nand_cfg: unhandled read at %08x\n", (uint32_t) addr); + return 0; +} +static void tc6393xb_nand_cfg_writeb(TC6393xbState *s, hwaddr addr, uint32_t value) { + switch (addr) { + case NAND_CFG_COMMAND: + s->nand_enable = (value & 0x2); + return; + case NAND_CFG_BASE: + case NAND_CFG_BASE + 1: + case NAND_CFG_BASE + 2: + case NAND_CFG_BASE + 3: + s->nand_phys &= ~(0xff << ((addr - NAND_CFG_BASE) * 8)); + s->nand_phys |= (value & 0xff) << ((addr - NAND_CFG_BASE) * 8); + return; + } + fprintf(stderr, "tc6393xb_nand_cfg: unhandled write at %08x: %02x\n", + (uint32_t) addr, value & 0xff); +} + +static uint32_t tc6393xb_nand_readb(TC6393xbState *s, hwaddr addr) { + switch (addr) { + case NAND_DATA + 0: + case NAND_DATA + 1: + case NAND_DATA + 2: + case NAND_DATA + 3: + return nand_getio(s->flash); + case NAND_MODE: + return s->nand.mode; + case NAND_STATUS: + return 0x14; + case NAND_ISR: + return s->nand.isr; + case NAND_IMR: + return s->nand.imr; + } + fprintf(stderr, "tc6393xb_nand: unhandled read at %08x\n", (uint32_t) addr); + return 0; +} +static void tc6393xb_nand_writeb(TC6393xbState *s, hwaddr addr, uint32_t value) { +// fprintf(stderr, "tc6393xb_nand: write at %08x: %02x\n", +// (uint32_t) addr, value & 0xff); + switch (addr) { + case NAND_DATA + 0: + case NAND_DATA + 1: + case NAND_DATA + 2: + case NAND_DATA + 3: + nand_setio(s->flash, value); + s->nand.isr |= 1; + tc6393xb_nand_irq(s); + return; + case NAND_MODE: + s->nand.mode = value; + nand_setpins(s->flash, + value & NAND_MODE_CLE, + value & NAND_MODE_ALE, + !(value & NAND_MODE_CE), + value & NAND_MODE_WP, + 0); // FIXME: gnd + switch (value & NAND_MODE_ECC_MASK) { + case NAND_MODE_ECC_RST: + ecc_reset(&s->ecc); + break; + case NAND_MODE_ECC_READ: + // FIXME + break; + case NAND_MODE_ECC_EN: + ecc_reset(&s->ecc); + } + return; + case NAND_ISR: + s->nand.isr = value; + tc6393xb_nand_irq(s); + return; + case NAND_IMR: + s->nand.imr = value; + tc6393xb_nand_irq(s); + return; + } + fprintf(stderr, "tc6393xb_nand: unhandled write at %08x: %02x\n", + (uint32_t) addr, value & 0xff); +} + +#define BITS 8 +#include "tc6393xb_template.h" +#define BITS 15 +#include "tc6393xb_template.h" +#define BITS 16 +#include "tc6393xb_template.h" +#define BITS 24 +#include "tc6393xb_template.h" +#define BITS 32 +#include "tc6393xb_template.h" + +static void tc6393xb_draw_graphic(TC6393xbState *s, int full_update) +{ + DisplaySurface *surface = qemu_console_surface(s->con); + + switch (surface_bits_per_pixel(surface)) { + case 8: + tc6393xb_draw_graphic8(s); + break; + case 15: + tc6393xb_draw_graphic15(s); + break; + case 16: + tc6393xb_draw_graphic16(s); + break; + case 24: + tc6393xb_draw_graphic24(s); + break; + case 32: + tc6393xb_draw_graphic32(s); + break; + default: + printf("tc6393xb: unknown depth %d\n", + surface_bits_per_pixel(surface)); + return; + } + + dpy_gfx_update(s->con, 0, 0, s->scr_width, s->scr_height); +} + +static void tc6393xb_draw_blank(TC6393xbState *s, int full_update) +{ + DisplaySurface *surface = qemu_console_surface(s->con); + int i, w; + uint8_t *d; + + if (!full_update) + return; + + w = s->scr_width * surface_bytes_per_pixel(surface); + d = surface_data(surface); + for(i = 0; i < s->scr_height; i++) { + memset(d, 0, w); + d += surface_stride(surface); + } + + dpy_gfx_update(s->con, 0, 0, s->scr_width, s->scr_height); +} + +static void tc6393xb_update_display(void *opaque) +{ + TC6393xbState *s = opaque; + DisplaySurface *surface = qemu_console_surface(s->con); + int full_update; + + if (s->scr_width == 0 || s->scr_height == 0) + return; + + full_update = 0; + if (s->blanked != s->blank) { + s->blanked = s->blank; + full_update = 1; + } + if (s->scr_width != surface_width(surface) || + s->scr_height != surface_height(surface)) { + qemu_console_resize(s->con, s->scr_width, s->scr_height); + full_update = 1; + } + if (s->blanked) + tc6393xb_draw_blank(s, full_update); + else + tc6393xb_draw_graphic(s, full_update); +} + + +static uint64_t tc6393xb_readb(void *opaque, hwaddr addr, + unsigned size) +{ + TC6393xbState *s = opaque; + + switch (addr >> 8) { + case 0: + return tc6393xb_scr_readb(s, addr & 0xff); + case 1: + return tc6393xb_nand_cfg_readb(s, addr & 0xff); + }; + + if ((addr &~0xff) == s->nand_phys && s->nand_enable) { +// return tc6393xb_nand_readb(s, addr & 0xff); + uint8_t d = tc6393xb_nand_readb(s, addr & 0xff); +// fprintf(stderr, "tc6393xb_nand: read at %08x: %02hhx\n", (uint32_t) addr, d); + return d; + } + +// fprintf(stderr, "tc6393xb: unhandled read at %08x\n", (uint32_t) addr); + return 0; +} + +static void tc6393xb_writeb(void *opaque, hwaddr addr, + uint64_t value, unsigned size) { + TC6393xbState *s = opaque; + + switch (addr >> 8) { + case 0: + tc6393xb_scr_writeb(s, addr & 0xff, value); + return; + case 1: + tc6393xb_nand_cfg_writeb(s, addr & 0xff, value); + return; + }; + + if ((addr &~0xff) == s->nand_phys && s->nand_enable) + tc6393xb_nand_writeb(s, addr & 0xff, value); + else + fprintf(stderr, "tc6393xb: unhandled write at %08x: %02x\n", + (uint32_t) addr, (int)value & 0xff); +} + +TC6393xbState *tc6393xb_init(MemoryRegion *sysmem, uint32_t base, qemu_irq irq) +{ + TC6393xbState *s; + DriveInfo *nand; + static const MemoryRegionOps tc6393xb_ops = { + .read = tc6393xb_readb, + .write = tc6393xb_writeb, + .endianness = DEVICE_NATIVE_ENDIAN, + .impl = { + .min_access_size = 1, + .max_access_size = 1, + }, + }; + + s = (TC6393xbState *) g_malloc0(sizeof(TC6393xbState)); + s->irq = irq; + s->gpio_in = qemu_allocate_irqs(tc6393xb_gpio_set, s, TC6393XB_GPIOS); + + s->l3v = *qemu_allocate_irqs(tc6393xb_l3v, s, 1); + s->blanked = 1; + + s->sub_irqs = qemu_allocate_irqs(tc6393xb_sub_irq, s, TC6393XB_NR_IRQS); + + nand = drive_get(IF_MTD, 0, 0); + s->flash = nand_init(nand ? nand->bdrv : NULL, NAND_MFR_TOSHIBA, 0x76); + + memory_region_init_io(&s->iomem, &tc6393xb_ops, s, "tc6393xb", 0x10000); + memory_region_add_subregion(sysmem, base, &s->iomem); + + memory_region_init_ram(&s->vram, "tc6393xb.vram", 0x100000); + vmstate_register_ram_global(&s->vram); + s->vram_ptr = memory_region_get_ram_ptr(&s->vram); + memory_region_add_subregion(sysmem, base + 0x100000, &s->vram); + s->scr_width = 480; + s->scr_height = 640; + s->con = graphic_console_init(tc6393xb_update_display, + NULL, /* invalidate */ + NULL, /* screen_dump */ + NULL, /* text_update */ + s); + + return s; +} diff --git a/hw/display/tc6393xb_template.h b/hw/display/tc6393xb_template.h new file mode 100644 index 0000000000..154aafd400 --- /dev/null +++ b/hw/display/tc6393xb_template.h @@ -0,0 +1,68 @@ +/* + * Toshiba TC6393XB I/O Controller. + * Found in Sharp Zaurus SL-6000 (tosa) or some + * Toshiba e-Series PDAs. + * + * FB support code. Based on G364 fb emulator + * + * Copyright (c) 2007 Hervé Poussineau + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, see <http://www.gnu.org/licenses/>. + */ + +#if BITS == 8 +# define SET_PIXEL(addr, color) *(uint8_t*)addr = color; +#elif BITS == 15 || BITS == 16 +# define SET_PIXEL(addr, color) *(uint16_t*)addr = color; +#elif BITS == 24 +# define SET_PIXEL(addr, color) \ + addr[0] = color; addr[1] = (color) >> 8; addr[2] = (color) >> 16; +#elif BITS == 32 +# define SET_PIXEL(addr, color) *(uint32_t*)addr = color; +#else +# error unknown bit depth +#endif + + +static void glue(tc6393xb_draw_graphic, BITS)(TC6393xbState *s) +{ + DisplaySurface *surface = qemu_console_surface(s->con); + int i; + uint16_t *data_buffer; + uint8_t *data_display; + + data_buffer = s->vram_ptr; + data_display = surface_data(surface); + for(i = 0; i < s->scr_height; i++) { +#if (BITS == 16) + memcpy(data_display, data_buffer, s->scr_width * 2); + data_buffer += s->scr_width; + data_display += surface_stride(surface); +#else + int j; + for (j = 0; j < s->scr_width; j++, data_display += BITS / 8, data_buffer++) { + uint16_t color = *data_buffer; + uint32_t dest_color = glue(rgb_to_pixel, BITS)( + ((color & 0xf800) * 0x108) >> 11, + ((color & 0x7e0) * 0x41) >> 9, + ((color & 0x1f) * 0x21) >> 2 + ); + SET_PIXEL(data_display, dest_color); + } +#endif + } +} + +#undef BITS +#undef SET_PIXEL diff --git a/hw/display/tcx.c b/hw/display/tcx.c new file mode 100644 index 0000000000..c44068e7c9 --- /dev/null +++ b/hw/display/tcx.c @@ -0,0 +1,739 @@ +/* + * QEMU TCX Frame buffer + * + * Copyright (c) 2003-2005 Fabrice Bellard + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "qemu-common.h" +#include "ui/console.h" +#include "ui/pixel_ops.h" +#include "hw/sysbus.h" +#include "hw/qdev-addr.h" + +#define MAXX 1024 +#define MAXY 768 +#define TCX_DAC_NREGS 16 +#define TCX_THC_NREGS_8 0x081c +#define TCX_THC_NREGS_24 0x1000 +#define TCX_TEC_NREGS 0x1000 + +typedef struct TCXState { + SysBusDevice busdev; + QemuConsole *con; + uint8_t *vram; + uint32_t *vram24, *cplane; + MemoryRegion vram_mem; + MemoryRegion vram_8bit; + MemoryRegion vram_24bit; + MemoryRegion vram_cplane; + MemoryRegion dac; + MemoryRegion tec; + MemoryRegion thc24; + MemoryRegion thc8; + ram_addr_t vram24_offset, cplane_offset; + uint32_t vram_size; + uint32_t palette[256]; + uint8_t r[256], g[256], b[256]; + uint16_t width, height, depth; + uint8_t dac_index, dac_state; +} TCXState; + +static void tcx_screen_dump(void *opaque, const char *filename, bool cswitch, + Error **errp); +static void tcx24_screen_dump(void *opaque, const char *filename, bool cswitch, + Error **errp); + +static void tcx_set_dirty(TCXState *s) +{ + memory_region_set_dirty(&s->vram_mem, 0, MAXX * MAXY); +} + +static void tcx24_set_dirty(TCXState *s) +{ + memory_region_set_dirty(&s->vram_mem, s->vram24_offset, MAXX * MAXY * 4); + memory_region_set_dirty(&s->vram_mem, s->cplane_offset, MAXX * MAXY * 4); +} + +static void update_palette_entries(TCXState *s, int start, int end) +{ + DisplaySurface *surface = qemu_console_surface(s->con); + int i; + + for (i = start; i < end; i++) { + switch (surface_bits_per_pixel(surface)) { + default: + case 8: + s->palette[i] = rgb_to_pixel8(s->r[i], s->g[i], s->b[i]); + break; + case 15: + s->palette[i] = rgb_to_pixel15(s->r[i], s->g[i], s->b[i]); + break; + case 16: + s->palette[i] = rgb_to_pixel16(s->r[i], s->g[i], s->b[i]); + break; + case 32: + if (is_surface_bgr(surface)) { + s->palette[i] = rgb_to_pixel32bgr(s->r[i], s->g[i], s->b[i]); + } else { + s->palette[i] = rgb_to_pixel32(s->r[i], s->g[i], s->b[i]); + } + break; + } + } + if (s->depth == 24) { + tcx24_set_dirty(s); + } else { + tcx_set_dirty(s); + } +} + +static void tcx_draw_line32(TCXState *s1, uint8_t *d, + const uint8_t *s, int width) +{ + int x; + uint8_t val; + uint32_t *p = (uint32_t *)d; + + for(x = 0; x < width; x++) { + val = *s++; + *p++ = s1->palette[val]; + } +} + +static void tcx_draw_line16(TCXState *s1, uint8_t *d, + const uint8_t *s, int width) +{ + int x; + uint8_t val; + uint16_t *p = (uint16_t *)d; + + for(x = 0; x < width; x++) { + val = *s++; + *p++ = s1->palette[val]; + } +} + +static void tcx_draw_line8(TCXState *s1, uint8_t *d, + const uint8_t *s, int width) +{ + int x; + uint8_t val; + + for(x = 0; x < width; x++) { + val = *s++; + *d++ = s1->palette[val]; + } +} + +/* + XXX Could be much more optimal: + * detect if line/page/whole screen is in 24 bit mode + * if destination is also BGR, use memcpy + */ +static inline void tcx24_draw_line32(TCXState *s1, uint8_t *d, + const uint8_t *s, int width, + const uint32_t *cplane, + const uint32_t *s24) +{ + DisplaySurface *surface = qemu_console_surface(s1->con); + int x, bgr, r, g, b; + uint8_t val, *p8; + uint32_t *p = (uint32_t *)d; + uint32_t dval; + + bgr = is_surface_bgr(surface); + for(x = 0; x < width; x++, s++, s24++) { + if ((be32_to_cpu(*cplane++) & 0xff000000) == 0x03000000) { + // 24-bit direct, BGR order + p8 = (uint8_t *)s24; + p8++; + b = *p8++; + g = *p8++; + r = *p8; + if (bgr) + dval = rgb_to_pixel32bgr(r, g, b); + else + dval = rgb_to_pixel32(r, g, b); + } else { + val = *s; + dval = s1->palette[val]; + } + *p++ = dval; + } +} + +static inline int check_dirty(TCXState *s, ram_addr_t page, ram_addr_t page24, + ram_addr_t cpage) +{ + int ret; + + ret = memory_region_get_dirty(&s->vram_mem, page, TARGET_PAGE_SIZE, + DIRTY_MEMORY_VGA); + ret |= memory_region_get_dirty(&s->vram_mem, page24, TARGET_PAGE_SIZE * 4, + DIRTY_MEMORY_VGA); + ret |= memory_region_get_dirty(&s->vram_mem, cpage, TARGET_PAGE_SIZE * 4, + DIRTY_MEMORY_VGA); + return ret; +} + +static inline void reset_dirty(TCXState *ts, ram_addr_t page_min, + ram_addr_t page_max, ram_addr_t page24, + ram_addr_t cpage) +{ + memory_region_reset_dirty(&ts->vram_mem, + page_min, page_max + TARGET_PAGE_SIZE, + DIRTY_MEMORY_VGA); + memory_region_reset_dirty(&ts->vram_mem, + page24 + page_min * 4, + page24 + page_max * 4 + TARGET_PAGE_SIZE, + DIRTY_MEMORY_VGA); + memory_region_reset_dirty(&ts->vram_mem, + cpage + page_min * 4, + cpage + page_max * 4 + TARGET_PAGE_SIZE, + DIRTY_MEMORY_VGA); +} + +/* Fixed line length 1024 allows us to do nice tricks not possible on + VGA... */ +static void tcx_update_display(void *opaque) +{ + TCXState *ts = opaque; + DisplaySurface *surface = qemu_console_surface(ts->con); + ram_addr_t page, page_min, page_max; + int y, y_start, dd, ds; + uint8_t *d, *s; + void (*f)(TCXState *s1, uint8_t *dst, const uint8_t *src, int width); + + if (surface_bits_per_pixel(surface) == 0) { + return; + } + + page = 0; + y_start = -1; + page_min = -1; + page_max = 0; + d = surface_data(surface); + s = ts->vram; + dd = surface_stride(surface); + ds = 1024; + + switch (surface_bits_per_pixel(surface)) { + case 32: + f = tcx_draw_line32; + break; + case 15: + case 16: + f = tcx_draw_line16; + break; + default: + case 8: + f = tcx_draw_line8; + break; + case 0: + return; + } + + for(y = 0; y < ts->height; y += 4, page += TARGET_PAGE_SIZE) { + if (memory_region_get_dirty(&ts->vram_mem, page, TARGET_PAGE_SIZE, + DIRTY_MEMORY_VGA)) { + if (y_start < 0) + y_start = y; + if (page < page_min) + page_min = page; + if (page > page_max) + page_max = page; + f(ts, d, s, ts->width); + d += dd; + s += ds; + f(ts, d, s, ts->width); + d += dd; + s += ds; + f(ts, d, s, ts->width); + d += dd; + s += ds; + f(ts, d, s, ts->width); + d += dd; + s += ds; + } else { + if (y_start >= 0) { + /* flush to display */ + dpy_gfx_update(ts->con, 0, y_start, + ts->width, y - y_start); + y_start = -1; + } + d += dd * 4; + s += ds * 4; + } + } + if (y_start >= 0) { + /* flush to display */ + dpy_gfx_update(ts->con, 0, y_start, + ts->width, y - y_start); + } + /* reset modified pages */ + if (page_max >= page_min) { + memory_region_reset_dirty(&ts->vram_mem, + page_min, page_max + TARGET_PAGE_SIZE, + DIRTY_MEMORY_VGA); + } +} + +static void tcx24_update_display(void *opaque) +{ + TCXState *ts = opaque; + DisplaySurface *surface = qemu_console_surface(ts->con); + ram_addr_t page, page_min, page_max, cpage, page24; + int y, y_start, dd, ds; + uint8_t *d, *s; + uint32_t *cptr, *s24; + + if (surface_bits_per_pixel(surface) != 32) { + return; + } + + page = 0; + page24 = ts->vram24_offset; + cpage = ts->cplane_offset; + y_start = -1; + page_min = -1; + page_max = 0; + d = surface_data(surface); + s = ts->vram; + s24 = ts->vram24; + cptr = ts->cplane; + dd = surface_stride(surface); + ds = 1024; + + for(y = 0; y < ts->height; y += 4, page += TARGET_PAGE_SIZE, + page24 += TARGET_PAGE_SIZE, cpage += TARGET_PAGE_SIZE) { + if (check_dirty(ts, page, page24, cpage)) { + if (y_start < 0) + y_start = y; + if (page < page_min) + page_min = page; + if (page > page_max) + page_max = page; + tcx24_draw_line32(ts, d, s, ts->width, cptr, s24); + d += dd; + s += ds; + cptr += ds; + s24 += ds; + tcx24_draw_line32(ts, d, s, ts->width, cptr, s24); + d += dd; + s += ds; + cptr += ds; + s24 += ds; + tcx24_draw_line32(ts, d, s, ts->width, cptr, s24); + d += dd; + s += ds; + cptr += ds; + s24 += ds; + tcx24_draw_line32(ts, d, s, ts->width, cptr, s24); + d += dd; + s += ds; + cptr += ds; + s24 += ds; + } else { + if (y_start >= 0) { + /* flush to display */ + dpy_gfx_update(ts->con, 0, y_start, + ts->width, y - y_start); + y_start = -1; + } + d += dd * 4; + s += ds * 4; + cptr += ds * 4; + s24 += ds * 4; + } + } + if (y_start >= 0) { + /* flush to display */ + dpy_gfx_update(ts->con, 0, y_start, + ts->width, y - y_start); + } + /* reset modified pages */ + if (page_max >= page_min) { + reset_dirty(ts, page_min, page_max, page24, cpage); + } +} + +static void tcx_invalidate_display(void *opaque) +{ + TCXState *s = opaque; + + tcx_set_dirty(s); + qemu_console_resize(s->con, s->width, s->height); +} + +static void tcx24_invalidate_display(void *opaque) +{ + TCXState *s = opaque; + + tcx_set_dirty(s); + tcx24_set_dirty(s); + qemu_console_resize(s->con, s->width, s->height); +} + +static int vmstate_tcx_post_load(void *opaque, int version_id) +{ + TCXState *s = opaque; + + update_palette_entries(s, 0, 256); + if (s->depth == 24) { + tcx24_set_dirty(s); + } else { + tcx_set_dirty(s); + } + + return 0; +} + +static const VMStateDescription vmstate_tcx = { + .name ="tcx", + .version_id = 4, + .minimum_version_id = 4, + .minimum_version_id_old = 4, + .post_load = vmstate_tcx_post_load, + .fields = (VMStateField []) { + VMSTATE_UINT16(height, TCXState), + VMSTATE_UINT16(width, TCXState), + VMSTATE_UINT16(depth, TCXState), + VMSTATE_BUFFER(r, TCXState), + VMSTATE_BUFFER(g, TCXState), + VMSTATE_BUFFER(b, TCXState), + VMSTATE_UINT8(dac_index, TCXState), + VMSTATE_UINT8(dac_state, TCXState), + VMSTATE_END_OF_LIST() + } +}; + +static void tcx_reset(DeviceState *d) +{ + TCXState *s = container_of(d, TCXState, busdev.qdev); + + /* Initialize palette */ + memset(s->r, 0, 256); + memset(s->g, 0, 256); + memset(s->b, 0, 256); + s->r[255] = s->g[255] = s->b[255] = 255; + update_palette_entries(s, 0, 256); + memset(s->vram, 0, MAXX*MAXY); + memory_region_reset_dirty(&s->vram_mem, 0, MAXX * MAXY * (1 + 4 + 4), + DIRTY_MEMORY_VGA); + s->dac_index = 0; + s->dac_state = 0; +} + +static uint64_t tcx_dac_readl(void *opaque, hwaddr addr, + unsigned size) +{ + return 0; +} + +static void tcx_dac_writel(void *opaque, hwaddr addr, uint64_t val, + unsigned size) +{ + TCXState *s = opaque; + + switch (addr) { + case 0: + s->dac_index = val >> 24; + s->dac_state = 0; + break; + case 4: + switch (s->dac_state) { + case 0: + s->r[s->dac_index] = val >> 24; + update_palette_entries(s, s->dac_index, s->dac_index + 1); + s->dac_state++; + break; + case 1: + s->g[s->dac_index] = val >> 24; + update_palette_entries(s, s->dac_index, s->dac_index + 1); + s->dac_state++; + break; + case 2: + s->b[s->dac_index] = val >> 24; + update_palette_entries(s, s->dac_index, s->dac_index + 1); + s->dac_index = (s->dac_index + 1) & 255; // Index autoincrement + default: + s->dac_state = 0; + break; + } + break; + default: + break; + } +} + +static const MemoryRegionOps tcx_dac_ops = { + .read = tcx_dac_readl, + .write = tcx_dac_writel, + .endianness = DEVICE_NATIVE_ENDIAN, + .valid = { + .min_access_size = 4, + .max_access_size = 4, + }, +}; + +static uint64_t dummy_readl(void *opaque, hwaddr addr, + unsigned size) +{ + return 0; +} + +static void dummy_writel(void *opaque, hwaddr addr, + uint64_t val, unsigned size) +{ +} + +static const MemoryRegionOps dummy_ops = { + .read = dummy_readl, + .write = dummy_writel, + .endianness = DEVICE_NATIVE_ENDIAN, + .valid = { + .min_access_size = 4, + .max_access_size = 4, + }, +}; + +static int tcx_init1(SysBusDevice *dev) +{ + TCXState *s = FROM_SYSBUS(TCXState, dev); + ram_addr_t vram_offset = 0; + int size; + uint8_t *vram_base; + + memory_region_init_ram(&s->vram_mem, "tcx.vram", + s->vram_size * (1 + 4 + 4)); + vmstate_register_ram_global(&s->vram_mem); + vram_base = memory_region_get_ram_ptr(&s->vram_mem); + + /* 8-bit plane */ + s->vram = vram_base; + size = s->vram_size; + memory_region_init_alias(&s->vram_8bit, "tcx.vram.8bit", + &s->vram_mem, vram_offset, size); + sysbus_init_mmio(dev, &s->vram_8bit); + vram_offset += size; + vram_base += size; + + /* DAC */ + memory_region_init_io(&s->dac, &tcx_dac_ops, s, "tcx.dac", TCX_DAC_NREGS); + sysbus_init_mmio(dev, &s->dac); + + /* TEC (dummy) */ + memory_region_init_io(&s->tec, &dummy_ops, s, "tcx.tec", TCX_TEC_NREGS); + sysbus_init_mmio(dev, &s->tec); + /* THC: NetBSD writes here even with 8-bit display: dummy */ + memory_region_init_io(&s->thc24, &dummy_ops, s, "tcx.thc24", + TCX_THC_NREGS_24); + sysbus_init_mmio(dev, &s->thc24); + + if (s->depth == 24) { + /* 24-bit plane */ + size = s->vram_size * 4; + s->vram24 = (uint32_t *)vram_base; + s->vram24_offset = vram_offset; + memory_region_init_alias(&s->vram_24bit, "tcx.vram.24bit", + &s->vram_mem, vram_offset, size); + sysbus_init_mmio(dev, &s->vram_24bit); + vram_offset += size; + vram_base += size; + + /* Control plane */ + size = s->vram_size * 4; + s->cplane = (uint32_t *)vram_base; + s->cplane_offset = vram_offset; + memory_region_init_alias(&s->vram_cplane, "tcx.vram.cplane", + &s->vram_mem, vram_offset, size); + sysbus_init_mmio(dev, &s->vram_cplane); + + s->con = graphic_console_init(tcx24_update_display, + tcx24_invalidate_display, + tcx24_screen_dump, NULL, s); + } else { + /* THC 8 bit (dummy) */ + memory_region_init_io(&s->thc8, &dummy_ops, s, "tcx.thc8", + TCX_THC_NREGS_8); + sysbus_init_mmio(dev, &s->thc8); + + s->con = graphic_console_init(tcx_update_display, + tcx_invalidate_display, + tcx_screen_dump, NULL, s); + } + + qemu_console_resize(s->con, s->width, s->height); + return 0; +} + +static void tcx_screen_dump(void *opaque, const char *filename, bool cswitch, + Error **errp) +{ + TCXState *s = opaque; + FILE *f; + uint8_t *d, *d1, v; + int ret, y, x; + + f = fopen(filename, "wb"); + if (!f) { + error_setg(errp, "failed to open file '%s': %s", filename, + strerror(errno)); + return; + } + ret = fprintf(f, "P6\n%d %d\n%d\n", s->width, s->height, 255); + if (ret < 0) { + goto write_err; + } + d1 = s->vram; + for(y = 0; y < s->height; y++) { + d = d1; + for(x = 0; x < s->width; x++) { + v = *d; + ret = fputc(s->r[v], f); + if (ret == EOF) { + goto write_err; + } + ret = fputc(s->g[v], f); + if (ret == EOF) { + goto write_err; + } + ret = fputc(s->b[v], f); + if (ret == EOF) { + goto write_err; + } + d++; + } + d1 += MAXX; + } + +out: + fclose(f); + return; + +write_err: + error_setg(errp, "failed to write to file '%s': %s", filename, + strerror(errno)); + unlink(filename); + goto out; +} + +static void tcx24_screen_dump(void *opaque, const char *filename, bool cswitch, + Error **errp) +{ + TCXState *s = opaque; + FILE *f; + uint8_t *d, *d1, v; + uint32_t *s24, *cptr, dval; + int ret, y, x; + + f = fopen(filename, "wb"); + if (!f) { + error_setg(errp, "failed to open file '%s': %s", filename, + strerror(errno)); + return; + } + ret = fprintf(f, "P6\n%d %d\n%d\n", s->width, s->height, 255); + if (ret < 0) { + goto write_err; + } + d1 = s->vram; + s24 = s->vram24; + cptr = s->cplane; + for(y = 0; y < s->height; y++) { + d = d1; + for(x = 0; x < s->width; x++, d++, s24++) { + if ((*cptr++ & 0xff000000) == 0x03000000) { // 24-bit direct + dval = *s24 & 0x00ffffff; + ret = fputc((dval >> 16) & 0xff, f); + if (ret == EOF) { + goto write_err; + } + ret = fputc((dval >> 8) & 0xff, f); + if (ret == EOF) { + goto write_err; + } + ret = fputc(dval & 0xff, f); + if (ret == EOF) { + goto write_err; + } + } else { + v = *d; + ret = fputc(s->r[v], f); + if (ret == EOF) { + goto write_err; + } + ret = fputc(s->g[v], f); + if (ret == EOF) { + goto write_err; + } + ret = fputc(s->b[v], f); + if (ret == EOF) { + goto write_err; + } + } + } + d1 += MAXX; + } + +out: + fclose(f); + return; + +write_err: + error_setg(errp, "failed to write to file '%s': %s", filename, + strerror(errno)); + unlink(filename); + goto out; +} + +static Property tcx_properties[] = { + DEFINE_PROP_HEX32("vram_size", TCXState, vram_size, -1), + DEFINE_PROP_UINT16("width", TCXState, width, -1), + DEFINE_PROP_UINT16("height", TCXState, height, -1), + DEFINE_PROP_UINT16("depth", TCXState, depth, -1), + DEFINE_PROP_END_OF_LIST(), +}; + +static void tcx_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass); + + k->init = tcx_init1; + dc->reset = tcx_reset; + dc->vmsd = &vmstate_tcx; + dc->props = tcx_properties; +} + +static const TypeInfo tcx_info = { + .name = "SUNW,tcx", + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(TCXState), + .class_init = tcx_class_init, +}; + +static void tcx_register_types(void) +{ + type_register_static(&tcx_info); +} + +type_init(tcx_register_types) diff --git a/hw/display/vga-isa-mm.c b/hw/display/vga-isa-mm.c new file mode 100644 index 0000000000..1c50070216 --- /dev/null +++ b/hw/display/vga-isa-mm.c @@ -0,0 +1,144 @@ +/* + * QEMU ISA MM VGA Emulator. + * + * Copyright (c) 2003 Fabrice Bellard + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#include "hw/hw.h" +#include "ui/console.h" +#include "hw/i386/pc.h" +#include "vga_int.h" +#include "ui/pixel_ops.h" +#include "qemu/timer.h" + +#define VGA_RAM_SIZE (8192 * 1024) + +typedef struct ISAVGAMMState { + VGACommonState vga; + int it_shift; +} ISAVGAMMState; + +/* Memory mapped interface */ +static uint32_t vga_mm_readb (void *opaque, hwaddr addr) +{ + ISAVGAMMState *s = opaque; + + return vga_ioport_read(&s->vga, addr >> s->it_shift) & 0xff; +} + +static void vga_mm_writeb (void *opaque, + hwaddr addr, uint32_t value) +{ + ISAVGAMMState *s = opaque; + + vga_ioport_write(&s->vga, addr >> s->it_shift, value & 0xff); +} + +static uint32_t vga_mm_readw (void *opaque, hwaddr addr) +{ + ISAVGAMMState *s = opaque; + + return vga_ioport_read(&s->vga, addr >> s->it_shift) & 0xffff; +} + +static void vga_mm_writew (void *opaque, + hwaddr addr, uint32_t value) +{ + ISAVGAMMState *s = opaque; + + vga_ioport_write(&s->vga, addr >> s->it_shift, value & 0xffff); +} + +static uint32_t vga_mm_readl (void *opaque, hwaddr addr) +{ + ISAVGAMMState *s = opaque; + + return vga_ioport_read(&s->vga, addr >> s->it_shift); +} + +static void vga_mm_writel (void *opaque, + hwaddr addr, uint32_t value) +{ + ISAVGAMMState *s = opaque; + + vga_ioport_write(&s->vga, addr >> s->it_shift, value); +} + +static const MemoryRegionOps vga_mm_ctrl_ops = { + .old_mmio = { + .read = { + vga_mm_readb, + vga_mm_readw, + vga_mm_readl, + }, + .write = { + vga_mm_writeb, + vga_mm_writew, + vga_mm_writel, + }, + }, + .endianness = DEVICE_NATIVE_ENDIAN, +}; + +static void vga_mm_init(ISAVGAMMState *s, hwaddr vram_base, + hwaddr ctrl_base, int it_shift, + MemoryRegion *address_space) +{ + MemoryRegion *s_ioport_ctrl, *vga_io_memory; + + s->it_shift = it_shift; + s_ioport_ctrl = g_malloc(sizeof(*s_ioport_ctrl)); + memory_region_init_io(s_ioport_ctrl, &vga_mm_ctrl_ops, s, + "vga-mm-ctrl", 0x100000); + memory_region_set_flush_coalesced(s_ioport_ctrl); + + vga_io_memory = g_malloc(sizeof(*vga_io_memory)); + /* XXX: endianness? */ + memory_region_init_io(vga_io_memory, &vga_mem_ops, &s->vga, + "vga-mem", 0x20000); + + vmstate_register(NULL, 0, &vmstate_vga_common, s); + + memory_region_add_subregion(address_space, ctrl_base, s_ioport_ctrl); + s->vga.bank_offset = 0; + memory_region_add_subregion(address_space, + vram_base + 0x000a0000, vga_io_memory); + memory_region_set_coalescing(vga_io_memory); +} + +int isa_vga_mm_init(hwaddr vram_base, + hwaddr ctrl_base, int it_shift, + MemoryRegion *address_space) +{ + ISAVGAMMState *s; + + s = g_malloc0(sizeof(*s)); + + s->vga.vram_size_mb = VGA_RAM_SIZE >> 20; + vga_common_init(&s->vga); + vga_mm_init(s, vram_base, ctrl_base, it_shift, address_space); + + s->vga.con = graphic_console_init(s->vga.update, s->vga.invalidate, + s->vga.screen_dump, s->vga.text_update, + s); + + vga_init_vbe(&s->vga, address_space); + return 0; +} diff --git a/hw/display/vga-isa.c b/hw/display/vga-isa.c new file mode 100644 index 0000000000..90959ebc2c --- /dev/null +++ b/hw/display/vga-isa.c @@ -0,0 +1,101 @@ +/* + * QEMU ISA VGA Emulator. + * + * see docs/specs/standard-vga.txt for virtual hardware specs. + * + * Copyright (c) 2003 Fabrice Bellard + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#include "hw/hw.h" +#include "ui/console.h" +#include "hw/i386/pc.h" +#include "vga_int.h" +#include "ui/pixel_ops.h" +#include "qemu/timer.h" +#include "hw/loader.h" + +typedef struct ISAVGAState { + ISADevice dev; + struct VGACommonState state; +} ISAVGAState; + +static void vga_reset_isa(DeviceState *dev) +{ + ISAVGAState *d = container_of(dev, ISAVGAState, dev.qdev); + VGACommonState *s = &d->state; + + vga_common_reset(s); +} + +static int vga_initfn(ISADevice *dev) +{ + ISAVGAState *d = DO_UPCAST(ISAVGAState, dev, dev); + VGACommonState *s = &d->state; + MemoryRegion *vga_io_memory; + const MemoryRegionPortio *vga_ports, *vbe_ports; + + vga_common_init(s); + s->legacy_address_space = isa_address_space(dev); + vga_io_memory = vga_init_io(s, &vga_ports, &vbe_ports); + isa_register_portio_list(dev, 0x3b0, vga_ports, s, "vga"); + if (vbe_ports) { + isa_register_portio_list(dev, 0x1ce, vbe_ports, s, "vbe"); + } + memory_region_add_subregion_overlap(isa_address_space(dev), + isa_mem_base + 0x000a0000, + vga_io_memory, 1); + memory_region_set_coalescing(vga_io_memory); + s->con = graphic_console_init(s->update, s->invalidate, + s->screen_dump, s->text_update, s); + + vga_init_vbe(s, isa_address_space(dev)); + /* ROM BIOS */ + rom_add_vga(VGABIOS_FILENAME); + return 0; +} + +static Property vga_isa_properties[] = { + DEFINE_PROP_UINT32("vgamem_mb", ISAVGAState, state.vram_size_mb, 8), + DEFINE_PROP_END_OF_LIST(), +}; + +static void vga_class_initfn(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + ISADeviceClass *ic = ISA_DEVICE_CLASS(klass); + ic->init = vga_initfn; + dc->reset = vga_reset_isa; + dc->vmsd = &vmstate_vga_common; + dc->props = vga_isa_properties; +} + +static const TypeInfo vga_info = { + .name = "isa-vga", + .parent = TYPE_ISA_DEVICE, + .instance_size = sizeof(ISAVGAState), + .class_init = vga_class_initfn, +}; + +static void vga_register_types(void) +{ + type_register_static(&vga_info); +} + +type_init(vga_register_types) diff --git a/hw/display/vga-pci.c b/hw/display/vga-pci.c new file mode 100644 index 0000000000..a9c69b6ac7 --- /dev/null +++ b/hw/display/vga-pci.c @@ -0,0 +1,215 @@ +/* + * QEMU PCI VGA Emulator. + * + * see docs/specs/standard-vga.txt for virtual hardware specs. + * + * Copyright (c) 2003 Fabrice Bellard + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#include "hw/hw.h" +#include "ui/console.h" +#include "hw/pci/pci.h" +#include "vga_int.h" +#include "ui/pixel_ops.h" +#include "qemu/timer.h" +#include "hw/loader.h" + +#define PCI_VGA_IOPORT_OFFSET 0x400 +#define PCI_VGA_IOPORT_SIZE (0x3e0 - 0x3c0) +#define PCI_VGA_BOCHS_OFFSET 0x500 +#define PCI_VGA_BOCHS_SIZE (0x0b * 2) +#define PCI_VGA_MMIO_SIZE 0x1000 + +enum vga_pci_flags { + PCI_VGA_FLAG_ENABLE_MMIO = 1, +}; + +typedef struct PCIVGAState { + PCIDevice dev; + VGACommonState vga; + uint32_t flags; + MemoryRegion mmio; + MemoryRegion ioport; + MemoryRegion bochs; +} PCIVGAState; + +static const VMStateDescription vmstate_vga_pci = { + .name = "vga", + .version_id = 2, + .minimum_version_id = 2, + .minimum_version_id_old = 2, + .fields = (VMStateField []) { + VMSTATE_PCI_DEVICE(dev, PCIVGAState), + VMSTATE_STRUCT(vga, PCIVGAState, 0, vmstate_vga_common, VGACommonState), + VMSTATE_END_OF_LIST() + } +}; + +static uint64_t pci_vga_ioport_read(void *ptr, hwaddr addr, + unsigned size) +{ + PCIVGAState *d = ptr; + uint64_t ret = 0; + + switch (size) { + case 1: + ret = vga_ioport_read(&d->vga, addr); + break; + case 2: + ret = vga_ioport_read(&d->vga, addr); + ret |= vga_ioport_read(&d->vga, addr+1) << 8; + break; + } + return ret; +} + +static void pci_vga_ioport_write(void *ptr, hwaddr addr, + uint64_t val, unsigned size) +{ + PCIVGAState *d = ptr; + + switch (size) { + case 1: + vga_ioport_write(&d->vga, addr + 0x3c0, val); + break; + case 2: + /* + * Update bytes in little endian order. Allows to update + * indexed registers with a single word write because the + * index byte is updated first. + */ + vga_ioport_write(&d->vga, addr + 0x3c0, val & 0xff); + vga_ioport_write(&d->vga, addr + 0x3c1, (val >> 8) & 0xff); + break; + } +} + +static const MemoryRegionOps pci_vga_ioport_ops = { + .read = pci_vga_ioport_read, + .write = pci_vga_ioport_write, + .valid.min_access_size = 1, + .valid.max_access_size = 4, + .impl.min_access_size = 1, + .impl.max_access_size = 2, + .endianness = DEVICE_LITTLE_ENDIAN, +}; + +static uint64_t pci_vga_bochs_read(void *ptr, hwaddr addr, + unsigned size) +{ + PCIVGAState *d = ptr; + int index = addr >> 1; + + vbe_ioport_write_index(&d->vga, 0, index); + return vbe_ioport_read_data(&d->vga, 0); +} + +static void pci_vga_bochs_write(void *ptr, hwaddr addr, + uint64_t val, unsigned size) +{ + PCIVGAState *d = ptr; + int index = addr >> 1; + + vbe_ioport_write_index(&d->vga, 0, index); + vbe_ioport_write_data(&d->vga, 0, val); +} + +static const MemoryRegionOps pci_vga_bochs_ops = { + .read = pci_vga_bochs_read, + .write = pci_vga_bochs_write, + .valid.min_access_size = 1, + .valid.max_access_size = 4, + .impl.min_access_size = 2, + .impl.max_access_size = 2, + .endianness = DEVICE_LITTLE_ENDIAN, +}; + +static int pci_std_vga_initfn(PCIDevice *dev) +{ + PCIVGAState *d = DO_UPCAST(PCIVGAState, dev, dev); + VGACommonState *s = &d->vga; + + /* vga + console init */ + vga_common_init(s); + vga_init(s, pci_address_space(dev), pci_address_space_io(dev), true); + + s->con = graphic_console_init(s->update, s->invalidate, + s->screen_dump, s->text_update, s); + + /* XXX: VGA_RAM_SIZE must be a power of two */ + pci_register_bar(&d->dev, 0, PCI_BASE_ADDRESS_MEM_PREFETCH, &s->vram); + + /* mmio bar for vga register access */ + if (d->flags & (1 << PCI_VGA_FLAG_ENABLE_MMIO)) { + memory_region_init(&d->mmio, "vga.mmio", 4096); + memory_region_init_io(&d->ioport, &pci_vga_ioport_ops, d, + "vga ioports remapped", PCI_VGA_IOPORT_SIZE); + memory_region_init_io(&d->bochs, &pci_vga_bochs_ops, d, + "bochs dispi interface", PCI_VGA_BOCHS_SIZE); + + memory_region_add_subregion(&d->mmio, PCI_VGA_IOPORT_OFFSET, + &d->ioport); + memory_region_add_subregion(&d->mmio, PCI_VGA_BOCHS_OFFSET, + &d->bochs); + pci_register_bar(&d->dev, 2, PCI_BASE_ADDRESS_SPACE_MEMORY, &d->mmio); + } + + if (!dev->rom_bar) { + /* compatibility with pc-0.13 and older */ + vga_init_vbe(s, pci_address_space(dev)); + } + + return 0; +} + +static Property vga_pci_properties[] = { + DEFINE_PROP_UINT32("vgamem_mb", PCIVGAState, vga.vram_size_mb, 16), + DEFINE_PROP_BIT("mmio", PCIVGAState, flags, PCI_VGA_FLAG_ENABLE_MMIO, true), + DEFINE_PROP_END_OF_LIST(), +}; + +static void vga_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + PCIDeviceClass *k = PCI_DEVICE_CLASS(klass); + + k->no_hotplug = 1; + k->init = pci_std_vga_initfn; + k->romfile = "vgabios-stdvga.bin"; + k->vendor_id = PCI_VENDOR_ID_QEMU; + k->device_id = PCI_DEVICE_ID_QEMU_VGA; + k->class_id = PCI_CLASS_DISPLAY_VGA; + dc->vmsd = &vmstate_vga_pci; + dc->props = vga_pci_properties; +} + +static const TypeInfo vga_info = { + .name = "VGA", + .parent = TYPE_PCI_DEVICE, + .instance_size = sizeof(PCIVGAState), + .class_init = vga_class_init, +}; + +static void vga_register_types(void) +{ + type_register_static(&vga_info); +} + +type_init(vga_register_types) diff --git a/hw/display/vga.c b/hw/display/vga.c new file mode 100644 index 0000000000..c1b67bbbf8 --- /dev/null +++ b/hw/display/vga.c @@ -0,0 +1,2457 @@ +/* + * QEMU VGA Emulator. + * + * Copyright (c) 2003 Fabrice Bellard + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#include "hw/hw.h" +#include "vga.h" +#include "ui/console.h" +#include "hw/i386/pc.h" +#include "hw/pci/pci.h" +#include "vga_int.h" +#include "ui/pixel_ops.h" +#include "qemu/timer.h" +#include "hw/xen/xen.h" +#include "trace.h" + +//#define DEBUG_VGA +//#define DEBUG_VGA_MEM +//#define DEBUG_VGA_REG + +//#define DEBUG_BOCHS_VBE + +/* 16 state changes per vertical frame @60 Hz */ +#define VGA_TEXT_CURSOR_PERIOD_MS (1000 * 2 * 16 / 60) + +/* + * Video Graphics Array (VGA) + * + * Chipset docs for original IBM VGA: + * http://www.mcamafia.de/pdf/ibm_vgaxga_trm2.pdf + * + * FreeVGA site: + * http://www.osdever.net/FreeVGA/home.htm + * + * Standard VGA features and Bochs VBE extensions are implemented. + */ + +/* force some bits to zero */ +const uint8_t sr_mask[8] = { + 0x03, + 0x3d, + 0x0f, + 0x3f, + 0x0e, + 0x00, + 0x00, + 0xff, +}; + +const uint8_t gr_mask[16] = { + 0x0f, /* 0x00 */ + 0x0f, /* 0x01 */ + 0x0f, /* 0x02 */ + 0x1f, /* 0x03 */ + 0x03, /* 0x04 */ + 0x7b, /* 0x05 */ + 0x0f, /* 0x06 */ + 0x0f, /* 0x07 */ + 0xff, /* 0x08 */ + 0x00, /* 0x09 */ + 0x00, /* 0x0a */ + 0x00, /* 0x0b */ + 0x00, /* 0x0c */ + 0x00, /* 0x0d */ + 0x00, /* 0x0e */ + 0x00, /* 0x0f */ +}; + +#define cbswap_32(__x) \ +((uint32_t)( \ + (((uint32_t)(__x) & (uint32_t)0x000000ffUL) << 24) | \ + (((uint32_t)(__x) & (uint32_t)0x0000ff00UL) << 8) | \ + (((uint32_t)(__x) & (uint32_t)0x00ff0000UL) >> 8) | \ + (((uint32_t)(__x) & (uint32_t)0xff000000UL) >> 24) )) + +#ifdef HOST_WORDS_BIGENDIAN +#define PAT(x) cbswap_32(x) +#else +#define PAT(x) (x) +#endif + +#ifdef HOST_WORDS_BIGENDIAN +#define BIG 1 +#else +#define BIG 0 +#endif + +#ifdef HOST_WORDS_BIGENDIAN +#define GET_PLANE(data, p) (((data) >> (24 - (p) * 8)) & 0xff) +#else +#define GET_PLANE(data, p) (((data) >> ((p) * 8)) & 0xff) +#endif + +static const uint32_t mask16[16] = { + PAT(0x00000000), + PAT(0x000000ff), + PAT(0x0000ff00), + PAT(0x0000ffff), + PAT(0x00ff0000), + PAT(0x00ff00ff), + PAT(0x00ffff00), + PAT(0x00ffffff), + PAT(0xff000000), + PAT(0xff0000ff), + PAT(0xff00ff00), + PAT(0xff00ffff), + PAT(0xffff0000), + PAT(0xffff00ff), + PAT(0xffffff00), + PAT(0xffffffff), +}; + +#undef PAT + +#ifdef HOST_WORDS_BIGENDIAN +#define PAT(x) (x) +#else +#define PAT(x) cbswap_32(x) +#endif + +static const uint32_t dmask16[16] = { + PAT(0x00000000), + PAT(0x000000ff), + PAT(0x0000ff00), + PAT(0x0000ffff), + PAT(0x00ff0000), + PAT(0x00ff00ff), + PAT(0x00ffff00), + PAT(0x00ffffff), + PAT(0xff000000), + PAT(0xff0000ff), + PAT(0xff00ff00), + PAT(0xff00ffff), + PAT(0xffff0000), + PAT(0xffff00ff), + PAT(0xffffff00), + PAT(0xffffffff), +}; + +static const uint32_t dmask4[4] = { + PAT(0x00000000), + PAT(0x0000ffff), + PAT(0xffff0000), + PAT(0xffffffff), +}; + +static uint32_t expand4[256]; +static uint16_t expand2[256]; +static uint8_t expand4to8[16]; + +static void vga_screen_dump(void *opaque, const char *filename, bool cswitch, + Error **errp); + +static void vga_update_memory_access(VGACommonState *s) +{ + MemoryRegion *region, *old_region = s->chain4_alias; + hwaddr base, offset, size; + + s->chain4_alias = NULL; + + if ((s->sr[VGA_SEQ_PLANE_WRITE] & VGA_SR02_ALL_PLANES) == + VGA_SR02_ALL_PLANES && s->sr[VGA_SEQ_MEMORY_MODE] & VGA_SR04_CHN_4M) { + offset = 0; + switch ((s->gr[VGA_GFX_MISC] >> 2) & 3) { + case 0: + base = 0xa0000; + size = 0x20000; + break; + case 1: + base = 0xa0000; + size = 0x10000; + offset = s->bank_offset; + break; + case 2: + base = 0xb0000; + size = 0x8000; + break; + case 3: + default: + base = 0xb8000; + size = 0x8000; + break; + } + base += isa_mem_base; + region = g_malloc(sizeof(*region)); + memory_region_init_alias(region, "vga.chain4", &s->vram, offset, size); + memory_region_add_subregion_overlap(s->legacy_address_space, base, + region, 2); + s->chain4_alias = region; + } + if (old_region) { + memory_region_del_subregion(s->legacy_address_space, old_region); + memory_region_destroy(old_region); + g_free(old_region); + s->plane_updated = 0xf; + } +} + +static void vga_dumb_update_retrace_info(VGACommonState *s) +{ + (void) s; +} + +static void vga_precise_update_retrace_info(VGACommonState *s) +{ + int htotal_chars; + int hretr_start_char; + int hretr_skew_chars; + int hretr_end_char; + + int vtotal_lines; + int vretr_start_line; + int vretr_end_line; + + int dots; +#if 0 + int div2, sldiv2; +#endif + int clocking_mode; + int clock_sel; + const int clk_hz[] = {25175000, 28322000, 25175000, 25175000}; + int64_t chars_per_sec; + struct vga_precise_retrace *r = &s->retrace_info.precise; + + htotal_chars = s->cr[VGA_CRTC_H_TOTAL] + 5; + hretr_start_char = s->cr[VGA_CRTC_H_SYNC_START]; + hretr_skew_chars = (s->cr[VGA_CRTC_H_SYNC_END] >> 5) & 3; + hretr_end_char = s->cr[VGA_CRTC_H_SYNC_END] & 0x1f; + + vtotal_lines = (s->cr[VGA_CRTC_V_TOTAL] | + (((s->cr[VGA_CRTC_OVERFLOW] & 1) | + ((s->cr[VGA_CRTC_OVERFLOW] >> 4) & 2)) << 8)) + 2; + vretr_start_line = s->cr[VGA_CRTC_V_SYNC_START] | + ((((s->cr[VGA_CRTC_OVERFLOW] >> 2) & 1) | + ((s->cr[VGA_CRTC_OVERFLOW] >> 6) & 2)) << 8); + vretr_end_line = s->cr[VGA_CRTC_V_SYNC_END] & 0xf; + + clocking_mode = (s->sr[VGA_SEQ_CLOCK_MODE] >> 3) & 1; + clock_sel = (s->msr >> 2) & 3; + dots = (s->msr & 1) ? 8 : 9; + + chars_per_sec = clk_hz[clock_sel] / dots; + + htotal_chars <<= clocking_mode; + + r->total_chars = vtotal_lines * htotal_chars; + if (r->freq) { + r->ticks_per_char = get_ticks_per_sec() / (r->total_chars * r->freq); + } else { + r->ticks_per_char = get_ticks_per_sec() / chars_per_sec; + } + + r->vstart = vretr_start_line; + r->vend = r->vstart + vretr_end_line + 1; + + r->hstart = hretr_start_char + hretr_skew_chars; + r->hend = r->hstart + hretr_end_char + 1; + r->htotal = htotal_chars; + +#if 0 + div2 = (s->cr[VGA_CRTC_MODE] >> 2) & 1; + sldiv2 = (s->cr[VGA_CRTC_MODE] >> 3) & 1; + printf ( + "hz=%f\n" + "htotal = %d\n" + "hretr_start = %d\n" + "hretr_skew = %d\n" + "hretr_end = %d\n" + "vtotal = %d\n" + "vretr_start = %d\n" + "vretr_end = %d\n" + "div2 = %d sldiv2 = %d\n" + "clocking_mode = %d\n" + "clock_sel = %d %d\n" + "dots = %d\n" + "ticks/char = %" PRId64 "\n" + "\n", + (double) get_ticks_per_sec() / (r->ticks_per_char * r->total_chars), + htotal_chars, + hretr_start_char, + hretr_skew_chars, + hretr_end_char, + vtotal_lines, + vretr_start_line, + vretr_end_line, + div2, sldiv2, + clocking_mode, + clock_sel, + clk_hz[clock_sel], + dots, + r->ticks_per_char + ); +#endif +} + +static uint8_t vga_precise_retrace(VGACommonState *s) +{ + struct vga_precise_retrace *r = &s->retrace_info.precise; + uint8_t val = s->st01 & ~(ST01_V_RETRACE | ST01_DISP_ENABLE); + + if (r->total_chars) { + int cur_line, cur_line_char, cur_char; + int64_t cur_tick; + + cur_tick = qemu_get_clock_ns(vm_clock); + + cur_char = (cur_tick / r->ticks_per_char) % r->total_chars; + cur_line = cur_char / r->htotal; + + if (cur_line >= r->vstart && cur_line <= r->vend) { + val |= ST01_V_RETRACE | ST01_DISP_ENABLE; + } else { + cur_line_char = cur_char % r->htotal; + if (cur_line_char >= r->hstart && cur_line_char <= r->hend) { + val |= ST01_DISP_ENABLE; + } + } + + return val; + } else { + return s->st01 ^ (ST01_V_RETRACE | ST01_DISP_ENABLE); + } +} + +static uint8_t vga_dumb_retrace(VGACommonState *s) +{ + return s->st01 ^ (ST01_V_RETRACE | ST01_DISP_ENABLE); +} + +int vga_ioport_invalid(VGACommonState *s, uint32_t addr) +{ + if (s->msr & VGA_MIS_COLOR) { + /* Color */ + return (addr >= 0x3b0 && addr <= 0x3bf); + } else { + /* Monochrome */ + return (addr >= 0x3d0 && addr <= 0x3df); + } +} + +uint32_t vga_ioport_read(void *opaque, uint32_t addr) +{ + VGACommonState *s = opaque; + int val, index; + + qemu_flush_coalesced_mmio_buffer(); + + if (vga_ioport_invalid(s, addr)) { + val = 0xff; + } else { + switch(addr) { + case VGA_ATT_W: + if (s->ar_flip_flop == 0) { + val = s->ar_index; + } else { + val = 0; + } + break; + case VGA_ATT_R: + index = s->ar_index & 0x1f; + if (index < VGA_ATT_C) { + val = s->ar[index]; + } else { + val = 0; + } + break; + case VGA_MIS_W: + val = s->st00; + break; + case VGA_SEQ_I: + val = s->sr_index; + break; + case VGA_SEQ_D: + val = s->sr[s->sr_index]; +#ifdef DEBUG_VGA_REG + printf("vga: read SR%x = 0x%02x\n", s->sr_index, val); +#endif + break; + case VGA_PEL_IR: + val = s->dac_state; + break; + case VGA_PEL_IW: + val = s->dac_write_index; + break; + case VGA_PEL_D: + val = s->palette[s->dac_read_index * 3 + s->dac_sub_index]; + if (++s->dac_sub_index == 3) { + s->dac_sub_index = 0; + s->dac_read_index++; + } + break; + case VGA_FTC_R: + val = s->fcr; + break; + case VGA_MIS_R: + val = s->msr; + break; + case VGA_GFX_I: + val = s->gr_index; + break; + case VGA_GFX_D: + val = s->gr[s->gr_index]; +#ifdef DEBUG_VGA_REG + printf("vga: read GR%x = 0x%02x\n", s->gr_index, val); +#endif + break; + case VGA_CRT_IM: + case VGA_CRT_IC: + val = s->cr_index; + break; + case VGA_CRT_DM: + case VGA_CRT_DC: + val = s->cr[s->cr_index]; +#ifdef DEBUG_VGA_REG + printf("vga: read CR%x = 0x%02x\n", s->cr_index, val); +#endif + break; + case VGA_IS1_RM: + case VGA_IS1_RC: + /* just toggle to fool polling */ + val = s->st01 = s->retrace(s); + s->ar_flip_flop = 0; + break; + default: + val = 0x00; + break; + } + } +#if defined(DEBUG_VGA) + printf("VGA: read addr=0x%04x data=0x%02x\n", addr, val); +#endif + return val; +} + +void vga_ioport_write(void *opaque, uint32_t addr, uint32_t val) +{ + VGACommonState *s = opaque; + int index; + + qemu_flush_coalesced_mmio_buffer(); + + /* check port range access depending on color/monochrome mode */ + if (vga_ioport_invalid(s, addr)) { + return; + } +#ifdef DEBUG_VGA + printf("VGA: write addr=0x%04x data=0x%02x\n", addr, val); +#endif + + switch(addr) { + case VGA_ATT_W: + if (s->ar_flip_flop == 0) { + val &= 0x3f; + s->ar_index = val; + } else { + index = s->ar_index & 0x1f; + switch(index) { + case VGA_ATC_PALETTE0 ... VGA_ATC_PALETTEF: + s->ar[index] = val & 0x3f; + break; + case VGA_ATC_MODE: + s->ar[index] = val & ~0x10; + break; + case VGA_ATC_OVERSCAN: + s->ar[index] = val; + break; + case VGA_ATC_PLANE_ENABLE: + s->ar[index] = val & ~0xc0; + break; + case VGA_ATC_PEL: + s->ar[index] = val & ~0xf0; + break; + case VGA_ATC_COLOR_PAGE: + s->ar[index] = val & ~0xf0; + break; + default: + break; + } + } + s->ar_flip_flop ^= 1; + break; + case VGA_MIS_W: + s->msr = val & ~0x10; + s->update_retrace_info(s); + break; + case VGA_SEQ_I: + s->sr_index = val & 7; + break; + case VGA_SEQ_D: +#ifdef DEBUG_VGA_REG + printf("vga: write SR%x = 0x%02x\n", s->sr_index, val); +#endif + s->sr[s->sr_index] = val & sr_mask[s->sr_index]; + if (s->sr_index == VGA_SEQ_CLOCK_MODE) { + s->update_retrace_info(s); + } + vga_update_memory_access(s); + break; + case VGA_PEL_IR: + s->dac_read_index = val; + s->dac_sub_index = 0; + s->dac_state = 3; + break; + case VGA_PEL_IW: + s->dac_write_index = val; + s->dac_sub_index = 0; + s->dac_state = 0; + break; + case VGA_PEL_D: + s->dac_cache[s->dac_sub_index] = val; + if (++s->dac_sub_index == 3) { + memcpy(&s->palette[s->dac_write_index * 3], s->dac_cache, 3); + s->dac_sub_index = 0; + s->dac_write_index++; + } + break; + case VGA_GFX_I: + s->gr_index = val & 0x0f; + break; + case VGA_GFX_D: +#ifdef DEBUG_VGA_REG + printf("vga: write GR%x = 0x%02x\n", s->gr_index, val); +#endif + s->gr[s->gr_index] = val & gr_mask[s->gr_index]; + vga_update_memory_access(s); + break; + case VGA_CRT_IM: + case VGA_CRT_IC: + s->cr_index = val; + break; + case VGA_CRT_DM: + case VGA_CRT_DC: +#ifdef DEBUG_VGA_REG + printf("vga: write CR%x = 0x%02x\n", s->cr_index, val); +#endif + /* handle CR0-7 protection */ + if ((s->cr[VGA_CRTC_V_SYNC_END] & VGA_CR11_LOCK_CR0_CR7) && + s->cr_index <= VGA_CRTC_OVERFLOW) { + /* can always write bit 4 of CR7 */ + if (s->cr_index == VGA_CRTC_OVERFLOW) { + s->cr[VGA_CRTC_OVERFLOW] = (s->cr[VGA_CRTC_OVERFLOW] & ~0x10) | + (val & 0x10); + } + return; + } + s->cr[s->cr_index] = val; + + switch(s->cr_index) { + case VGA_CRTC_H_TOTAL: + case VGA_CRTC_H_SYNC_START: + case VGA_CRTC_H_SYNC_END: + case VGA_CRTC_V_TOTAL: + case VGA_CRTC_OVERFLOW: + case VGA_CRTC_V_SYNC_END: + case VGA_CRTC_MODE: + s->update_retrace_info(s); + break; + } + break; + case VGA_IS1_RM: + case VGA_IS1_RC: + s->fcr = val & 0x10; + break; + } +} + +static uint32_t vbe_ioport_read_index(void *opaque, uint32_t addr) +{ + VGACommonState *s = opaque; + uint32_t val; + val = s->vbe_index; + return val; +} + +uint32_t vbe_ioport_read_data(void *opaque, uint32_t addr) +{ + VGACommonState *s = opaque; + uint32_t val; + + if (s->vbe_index < VBE_DISPI_INDEX_NB) { + if (s->vbe_regs[VBE_DISPI_INDEX_ENABLE] & VBE_DISPI_GETCAPS) { + switch(s->vbe_index) { + /* XXX: do not hardcode ? */ + case VBE_DISPI_INDEX_XRES: + val = VBE_DISPI_MAX_XRES; + break; + case VBE_DISPI_INDEX_YRES: + val = VBE_DISPI_MAX_YRES; + break; + case VBE_DISPI_INDEX_BPP: + val = VBE_DISPI_MAX_BPP; + break; + default: + val = s->vbe_regs[s->vbe_index]; + break; + } + } else { + val = s->vbe_regs[s->vbe_index]; + } + } else if (s->vbe_index == VBE_DISPI_INDEX_VIDEO_MEMORY_64K) { + val = s->vram_size / (64 * 1024); + } else { + val = 0; + } +#ifdef DEBUG_BOCHS_VBE + printf("VBE: read index=0x%x val=0x%x\n", s->vbe_index, val); +#endif + return val; +} + +void vbe_ioport_write_index(void *opaque, uint32_t addr, uint32_t val) +{ + VGACommonState *s = opaque; + s->vbe_index = val; +} + +void vbe_ioport_write_data(void *opaque, uint32_t addr, uint32_t val) +{ + VGACommonState *s = opaque; + + if (s->vbe_index <= VBE_DISPI_INDEX_NB) { +#ifdef DEBUG_BOCHS_VBE + printf("VBE: write index=0x%x val=0x%x\n", s->vbe_index, val); +#endif + switch(s->vbe_index) { + case VBE_DISPI_INDEX_ID: + if (val == VBE_DISPI_ID0 || + val == VBE_DISPI_ID1 || + val == VBE_DISPI_ID2 || + val == VBE_DISPI_ID3 || + val == VBE_DISPI_ID4) { + s->vbe_regs[s->vbe_index] = val; + } + break; + case VBE_DISPI_INDEX_XRES: + if ((val <= VBE_DISPI_MAX_XRES) && ((val & 7) == 0)) { + s->vbe_regs[s->vbe_index] = val; + } + break; + case VBE_DISPI_INDEX_YRES: + if (val <= VBE_DISPI_MAX_YRES) { + s->vbe_regs[s->vbe_index] = val; + } + break; + case VBE_DISPI_INDEX_BPP: + if (val == 0) + val = 8; + if (val == 4 || val == 8 || val == 15 || + val == 16 || val == 24 || val == 32) { + s->vbe_regs[s->vbe_index] = val; + } + break; + case VBE_DISPI_INDEX_BANK: + if (s->vbe_regs[VBE_DISPI_INDEX_BPP] == 4) { + val &= (s->vbe_bank_mask >> 2); + } else { + val &= s->vbe_bank_mask; + } + s->vbe_regs[s->vbe_index] = val; + s->bank_offset = (val << 16); + vga_update_memory_access(s); + break; + case VBE_DISPI_INDEX_ENABLE: + if ((val & VBE_DISPI_ENABLED) && + !(s->vbe_regs[VBE_DISPI_INDEX_ENABLE] & VBE_DISPI_ENABLED)) { + int h, shift_control; + + s->vbe_regs[VBE_DISPI_INDEX_VIRT_WIDTH] = + s->vbe_regs[VBE_DISPI_INDEX_XRES]; + s->vbe_regs[VBE_DISPI_INDEX_VIRT_HEIGHT] = + s->vbe_regs[VBE_DISPI_INDEX_YRES]; + s->vbe_regs[VBE_DISPI_INDEX_X_OFFSET] = 0; + s->vbe_regs[VBE_DISPI_INDEX_Y_OFFSET] = 0; + + if (s->vbe_regs[VBE_DISPI_INDEX_BPP] == 4) + s->vbe_line_offset = s->vbe_regs[VBE_DISPI_INDEX_XRES] >> 1; + else + s->vbe_line_offset = s->vbe_regs[VBE_DISPI_INDEX_XRES] * + ((s->vbe_regs[VBE_DISPI_INDEX_BPP] + 7) >> 3); + s->vbe_start_addr = 0; + + /* clear the screen (should be done in BIOS) */ + if (!(val & VBE_DISPI_NOCLEARMEM)) { + memset(s->vram_ptr, 0, + s->vbe_regs[VBE_DISPI_INDEX_YRES] * s->vbe_line_offset); + } + + /* we initialize the VGA graphic mode (should be done + in BIOS) */ + /* graphic mode + memory map 1 */ + s->gr[VGA_GFX_MISC] = (s->gr[VGA_GFX_MISC] & ~0x0c) | 0x04 | + VGA_GR06_GRAPHICS_MODE; + s->cr[VGA_CRTC_MODE] |= 3; /* no CGA modes */ + s->cr[VGA_CRTC_OFFSET] = s->vbe_line_offset >> 3; + /* width */ + s->cr[VGA_CRTC_H_DISP] = + (s->vbe_regs[VBE_DISPI_INDEX_XRES] >> 3) - 1; + /* height (only meaningful if < 1024) */ + h = s->vbe_regs[VBE_DISPI_INDEX_YRES] - 1; + s->cr[VGA_CRTC_V_DISP_END] = h; + s->cr[VGA_CRTC_OVERFLOW] = (s->cr[VGA_CRTC_OVERFLOW] & ~0x42) | + ((h >> 7) & 0x02) | ((h >> 3) & 0x40); + /* line compare to 1023 */ + s->cr[VGA_CRTC_LINE_COMPARE] = 0xff; + s->cr[VGA_CRTC_OVERFLOW] |= 0x10; + s->cr[VGA_CRTC_MAX_SCAN] |= 0x40; + + if (s->vbe_regs[VBE_DISPI_INDEX_BPP] == 4) { + shift_control = 0; + s->sr[VGA_SEQ_CLOCK_MODE] &= ~8; /* no double line */ + } else { + shift_control = 2; + /* set chain 4 mode */ + s->sr[VGA_SEQ_MEMORY_MODE] |= VGA_SR04_CHN_4M; + /* activate all planes */ + s->sr[VGA_SEQ_PLANE_WRITE] |= VGA_SR02_ALL_PLANES; + } + s->gr[VGA_GFX_MODE] = (s->gr[VGA_GFX_MODE] & ~0x60) | + (shift_control << 5); + s->cr[VGA_CRTC_MAX_SCAN] &= ~0x9f; /* no double scan */ + } else { + /* XXX: the bios should do that */ + s->bank_offset = 0; + } + s->dac_8bit = (val & VBE_DISPI_8BIT_DAC) > 0; + s->vbe_regs[s->vbe_index] = val; + vga_update_memory_access(s); + break; + case VBE_DISPI_INDEX_VIRT_WIDTH: + { + int w, h, line_offset; + + if (val < s->vbe_regs[VBE_DISPI_INDEX_XRES]) + return; + w = val; + if (s->vbe_regs[VBE_DISPI_INDEX_BPP] == 4) + line_offset = w >> 1; + else + line_offset = w * ((s->vbe_regs[VBE_DISPI_INDEX_BPP] + 7) >> 3); + h = s->vram_size / line_offset; + /* XXX: support weird bochs semantics ? */ + if (h < s->vbe_regs[VBE_DISPI_INDEX_YRES]) + return; + s->vbe_regs[VBE_DISPI_INDEX_VIRT_WIDTH] = w; + s->vbe_regs[VBE_DISPI_INDEX_VIRT_HEIGHT] = h; + s->vbe_line_offset = line_offset; + } + break; + case VBE_DISPI_INDEX_X_OFFSET: + case VBE_DISPI_INDEX_Y_OFFSET: + { + int x; + s->vbe_regs[s->vbe_index] = val; + s->vbe_start_addr = s->vbe_line_offset * s->vbe_regs[VBE_DISPI_INDEX_Y_OFFSET]; + x = s->vbe_regs[VBE_DISPI_INDEX_X_OFFSET]; + if (s->vbe_regs[VBE_DISPI_INDEX_BPP] == 4) + s->vbe_start_addr += x >> 1; + else + s->vbe_start_addr += x * ((s->vbe_regs[VBE_DISPI_INDEX_BPP] + 7) >> 3); + s->vbe_start_addr >>= 2; + } + break; + default: + break; + } + } +} + +/* called for accesses between 0xa0000 and 0xc0000 */ +uint32_t vga_mem_readb(VGACommonState *s, hwaddr addr) +{ + int memory_map_mode, plane; + uint32_t ret; + + /* convert to VGA memory offset */ + memory_map_mode = (s->gr[VGA_GFX_MISC] >> 2) & 3; + addr &= 0x1ffff; + switch(memory_map_mode) { + case 0: + break; + case 1: + if (addr >= 0x10000) + return 0xff; + addr += s->bank_offset; + break; + case 2: + addr -= 0x10000; + if (addr >= 0x8000) + return 0xff; + break; + default: + case 3: + addr -= 0x18000; + if (addr >= 0x8000) + return 0xff; + break; + } + + if (s->sr[VGA_SEQ_MEMORY_MODE] & VGA_SR04_CHN_4M) { + /* chain 4 mode : simplest access */ + ret = s->vram_ptr[addr]; + } else if (s->gr[VGA_GFX_MODE] & 0x10) { + /* odd/even mode (aka text mode mapping) */ + plane = (s->gr[VGA_GFX_PLANE_READ] & 2) | (addr & 1); + ret = s->vram_ptr[((addr & ~1) << 1) | plane]; + } else { + /* standard VGA latched access */ + s->latch = ((uint32_t *)s->vram_ptr)[addr]; + + if (!(s->gr[VGA_GFX_MODE] & 0x08)) { + /* read mode 0 */ + plane = s->gr[VGA_GFX_PLANE_READ]; + ret = GET_PLANE(s->latch, plane); + } else { + /* read mode 1 */ + ret = (s->latch ^ mask16[s->gr[VGA_GFX_COMPARE_VALUE]]) & + mask16[s->gr[VGA_GFX_COMPARE_MASK]]; + ret |= ret >> 16; + ret |= ret >> 8; + ret = (~ret) & 0xff; + } + } + return ret; +} + +/* called for accesses between 0xa0000 and 0xc0000 */ +void vga_mem_writeb(VGACommonState *s, hwaddr addr, uint32_t val) +{ + int memory_map_mode, plane, write_mode, b, func_select, mask; + uint32_t write_mask, bit_mask, set_mask; + +#ifdef DEBUG_VGA_MEM + printf("vga: [0x" TARGET_FMT_plx "] = 0x%02x\n", addr, val); +#endif + /* convert to VGA memory offset */ + memory_map_mode = (s->gr[VGA_GFX_MISC] >> 2) & 3; + addr &= 0x1ffff; + switch(memory_map_mode) { + case 0: + break; + case 1: + if (addr >= 0x10000) + return; + addr += s->bank_offset; + break; + case 2: + addr -= 0x10000; + if (addr >= 0x8000) + return; + break; + default: + case 3: + addr -= 0x18000; + if (addr >= 0x8000) + return; + break; + } + + if (s->sr[VGA_SEQ_MEMORY_MODE] & VGA_SR04_CHN_4M) { + /* chain 4 mode : simplest access */ + plane = addr & 3; + mask = (1 << plane); + if (s->sr[VGA_SEQ_PLANE_WRITE] & mask) { + s->vram_ptr[addr] = val; +#ifdef DEBUG_VGA_MEM + printf("vga: chain4: [0x" TARGET_FMT_plx "]\n", addr); +#endif + s->plane_updated |= mask; /* only used to detect font change */ + memory_region_set_dirty(&s->vram, addr, 1); + } + } else if (s->gr[VGA_GFX_MODE] & 0x10) { + /* odd/even mode (aka text mode mapping) */ + plane = (s->gr[VGA_GFX_PLANE_READ] & 2) | (addr & 1); + mask = (1 << plane); + if (s->sr[VGA_SEQ_PLANE_WRITE] & mask) { + addr = ((addr & ~1) << 1) | plane; + s->vram_ptr[addr] = val; +#ifdef DEBUG_VGA_MEM + printf("vga: odd/even: [0x" TARGET_FMT_plx "]\n", addr); +#endif + s->plane_updated |= mask; /* only used to detect font change */ + memory_region_set_dirty(&s->vram, addr, 1); + } + } else { + /* standard VGA latched access */ + write_mode = s->gr[VGA_GFX_MODE] & 3; + switch(write_mode) { + default: + case 0: + /* rotate */ + b = s->gr[VGA_GFX_DATA_ROTATE] & 7; + val = ((val >> b) | (val << (8 - b))) & 0xff; + val |= val << 8; + val |= val << 16; + + /* apply set/reset mask */ + set_mask = mask16[s->gr[VGA_GFX_SR_ENABLE]]; + val = (val & ~set_mask) | + (mask16[s->gr[VGA_GFX_SR_VALUE]] & set_mask); + bit_mask = s->gr[VGA_GFX_BIT_MASK]; + break; + case 1: + val = s->latch; + goto do_write; + case 2: + val = mask16[val & 0x0f]; + bit_mask = s->gr[VGA_GFX_BIT_MASK]; + break; + case 3: + /* rotate */ + b = s->gr[VGA_GFX_DATA_ROTATE] & 7; + val = (val >> b) | (val << (8 - b)); + + bit_mask = s->gr[VGA_GFX_BIT_MASK] & val; + val = mask16[s->gr[VGA_GFX_SR_VALUE]]; + break; + } + + /* apply logical operation */ + func_select = s->gr[VGA_GFX_DATA_ROTATE] >> 3; + switch(func_select) { + case 0: + default: + /* nothing to do */ + break; + case 1: + /* and */ + val &= s->latch; + break; + case 2: + /* or */ + val |= s->latch; + break; + case 3: + /* xor */ + val ^= s->latch; + break; + } + + /* apply bit mask */ + bit_mask |= bit_mask << 8; + bit_mask |= bit_mask << 16; + val = (val & bit_mask) | (s->latch & ~bit_mask); + + do_write: + /* mask data according to sr[2] */ + mask = s->sr[VGA_SEQ_PLANE_WRITE]; + s->plane_updated |= mask; /* only used to detect font change */ + write_mask = mask16[mask]; + ((uint32_t *)s->vram_ptr)[addr] = + (((uint32_t *)s->vram_ptr)[addr] & ~write_mask) | + (val & write_mask); +#ifdef DEBUG_VGA_MEM + printf("vga: latch: [0x" TARGET_FMT_plx "] mask=0x%08x val=0x%08x\n", + addr * 4, write_mask, val); +#endif + memory_region_set_dirty(&s->vram, addr << 2, sizeof(uint32_t)); + } +} + +typedef void vga_draw_glyph8_func(uint8_t *d, int linesize, + const uint8_t *font_ptr, int h, + uint32_t fgcol, uint32_t bgcol); +typedef void vga_draw_glyph9_func(uint8_t *d, int linesize, + const uint8_t *font_ptr, int h, + uint32_t fgcol, uint32_t bgcol, int dup9); +typedef void vga_draw_line_func(VGACommonState *s1, uint8_t *d, + const uint8_t *s, int width); + +#define DEPTH 8 +#include "vga_template.h" + +#define DEPTH 15 +#include "vga_template.h" + +#define BGR_FORMAT +#define DEPTH 15 +#include "vga_template.h" + +#define DEPTH 16 +#include "vga_template.h" + +#define BGR_FORMAT +#define DEPTH 16 +#include "vga_template.h" + +#define DEPTH 32 +#include "vga_template.h" + +#define BGR_FORMAT +#define DEPTH 32 +#include "vga_template.h" + +static unsigned int rgb_to_pixel8_dup(unsigned int r, unsigned int g, unsigned b) +{ + unsigned int col; + col = rgb_to_pixel8(r, g, b); + col |= col << 8; + col |= col << 16; + return col; +} + +static unsigned int rgb_to_pixel15_dup(unsigned int r, unsigned int g, unsigned b) +{ + unsigned int col; + col = rgb_to_pixel15(r, g, b); + col |= col << 16; + return col; +} + +static unsigned int rgb_to_pixel15bgr_dup(unsigned int r, unsigned int g, + unsigned int b) +{ + unsigned int col; + col = rgb_to_pixel15bgr(r, g, b); + col |= col << 16; + return col; +} + +static unsigned int rgb_to_pixel16_dup(unsigned int r, unsigned int g, unsigned b) +{ + unsigned int col; + col = rgb_to_pixel16(r, g, b); + col |= col << 16; + return col; +} + +static unsigned int rgb_to_pixel16bgr_dup(unsigned int r, unsigned int g, + unsigned int b) +{ + unsigned int col; + col = rgb_to_pixel16bgr(r, g, b); + col |= col << 16; + return col; +} + +static unsigned int rgb_to_pixel32_dup(unsigned int r, unsigned int g, unsigned b) +{ + unsigned int col; + col = rgb_to_pixel32(r, g, b); + return col; +} + +static unsigned int rgb_to_pixel32bgr_dup(unsigned int r, unsigned int g, unsigned b) +{ + unsigned int col; + col = rgb_to_pixel32bgr(r, g, b); + return col; +} + +/* return true if the palette was modified */ +static int update_palette16(VGACommonState *s) +{ + int full_update, i; + uint32_t v, col, *palette; + + full_update = 0; + palette = s->last_palette; + for(i = 0; i < 16; i++) { + v = s->ar[i]; + if (s->ar[VGA_ATC_MODE] & 0x80) { + v = ((s->ar[VGA_ATC_COLOR_PAGE] & 0xf) << 4) | (v & 0xf); + } else { + v = ((s->ar[VGA_ATC_COLOR_PAGE] & 0xc) << 4) | (v & 0x3f); + } + v = v * 3; + col = s->rgb_to_pixel(c6_to_8(s->palette[v]), + c6_to_8(s->palette[v + 1]), + c6_to_8(s->palette[v + 2])); + if (col != palette[i]) { + full_update = 1; + palette[i] = col; + } + } + return full_update; +} + +/* return true if the palette was modified */ +static int update_palette256(VGACommonState *s) +{ + int full_update, i; + uint32_t v, col, *palette; + + full_update = 0; + palette = s->last_palette; + v = 0; + for(i = 0; i < 256; i++) { + if (s->dac_8bit) { + col = s->rgb_to_pixel(s->palette[v], + s->palette[v + 1], + s->palette[v + 2]); + } else { + col = s->rgb_to_pixel(c6_to_8(s->palette[v]), + c6_to_8(s->palette[v + 1]), + c6_to_8(s->palette[v + 2])); + } + if (col != palette[i]) { + full_update = 1; + palette[i] = col; + } + v += 3; + } + return full_update; +} + +static void vga_get_offsets(VGACommonState *s, + uint32_t *pline_offset, + uint32_t *pstart_addr, + uint32_t *pline_compare) +{ + uint32_t start_addr, line_offset, line_compare; + + if (s->vbe_regs[VBE_DISPI_INDEX_ENABLE] & VBE_DISPI_ENABLED) { + line_offset = s->vbe_line_offset; + start_addr = s->vbe_start_addr; + line_compare = 65535; + } else { + /* compute line_offset in bytes */ + line_offset = s->cr[VGA_CRTC_OFFSET]; + line_offset <<= 3; + + /* starting address */ + start_addr = s->cr[VGA_CRTC_START_LO] | + (s->cr[VGA_CRTC_START_HI] << 8); + + /* line compare */ + line_compare = s->cr[VGA_CRTC_LINE_COMPARE] | + ((s->cr[VGA_CRTC_OVERFLOW] & 0x10) << 4) | + ((s->cr[VGA_CRTC_MAX_SCAN] & 0x40) << 3); + } + *pline_offset = line_offset; + *pstart_addr = start_addr; + *pline_compare = line_compare; +} + +/* update start_addr and line_offset. Return TRUE if modified */ +static int update_basic_params(VGACommonState *s) +{ + int full_update; + uint32_t start_addr, line_offset, line_compare; + + full_update = 0; + + s->get_offsets(s, &line_offset, &start_addr, &line_compare); + + if (line_offset != s->line_offset || + start_addr != s->start_addr || + line_compare != s->line_compare) { + s->line_offset = line_offset; + s->start_addr = start_addr; + s->line_compare = line_compare; + full_update = 1; + } + return full_update; +} + +#define NB_DEPTHS 7 + +static inline int get_depth_index(DisplaySurface *s) +{ + switch (surface_bits_per_pixel(s)) { + default: + case 8: + return 0; + case 15: + return 1; + case 16: + return 2; + case 32: + if (is_surface_bgr(s)) { + return 4; + } else { + return 3; + } + } +} + +static vga_draw_glyph8_func * const vga_draw_glyph8_table[NB_DEPTHS] = { + vga_draw_glyph8_8, + vga_draw_glyph8_16, + vga_draw_glyph8_16, + vga_draw_glyph8_32, + vga_draw_glyph8_32, + vga_draw_glyph8_16, + vga_draw_glyph8_16, +}; + +static vga_draw_glyph8_func * const vga_draw_glyph16_table[NB_DEPTHS] = { + vga_draw_glyph16_8, + vga_draw_glyph16_16, + vga_draw_glyph16_16, + vga_draw_glyph16_32, + vga_draw_glyph16_32, + vga_draw_glyph16_16, + vga_draw_glyph16_16, +}; + +static vga_draw_glyph9_func * const vga_draw_glyph9_table[NB_DEPTHS] = { + vga_draw_glyph9_8, + vga_draw_glyph9_16, + vga_draw_glyph9_16, + vga_draw_glyph9_32, + vga_draw_glyph9_32, + vga_draw_glyph9_16, + vga_draw_glyph9_16, +}; + +static const uint8_t cursor_glyph[32 * 4] = { + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, +}; + +static void vga_get_text_resolution(VGACommonState *s, int *pwidth, int *pheight, + int *pcwidth, int *pcheight) +{ + int width, cwidth, height, cheight; + + /* total width & height */ + cheight = (s->cr[VGA_CRTC_MAX_SCAN] & 0x1f) + 1; + cwidth = 8; + if (!(s->sr[VGA_SEQ_CLOCK_MODE] & VGA_SR01_CHAR_CLK_8DOTS)) { + cwidth = 9; + } + if (s->sr[VGA_SEQ_CLOCK_MODE] & 0x08) { + cwidth = 16; /* NOTE: no 18 pixel wide */ + } + width = (s->cr[VGA_CRTC_H_DISP] + 1); + if (s->cr[VGA_CRTC_V_TOTAL] == 100) { + /* ugly hack for CGA 160x100x16 - explain me the logic */ + height = 100; + } else { + height = s->cr[VGA_CRTC_V_DISP_END] | + ((s->cr[VGA_CRTC_OVERFLOW] & 0x02) << 7) | + ((s->cr[VGA_CRTC_OVERFLOW] & 0x40) << 3); + height = (height + 1) / cheight; + } + + *pwidth = width; + *pheight = height; + *pcwidth = cwidth; + *pcheight = cheight; +} + +typedef unsigned int rgb_to_pixel_dup_func(unsigned int r, unsigned int g, unsigned b); + +static rgb_to_pixel_dup_func * const rgb_to_pixel_dup_table[NB_DEPTHS] = { + rgb_to_pixel8_dup, + rgb_to_pixel15_dup, + rgb_to_pixel16_dup, + rgb_to_pixel32_dup, + rgb_to_pixel32bgr_dup, + rgb_to_pixel15bgr_dup, + rgb_to_pixel16bgr_dup, +}; + +/* + * Text mode update + * Missing: + * - double scan + * - double width + * - underline + * - flashing + */ +static void vga_draw_text(VGACommonState *s, int full_update) +{ + DisplaySurface *surface = qemu_console_surface(s->con); + int cx, cy, cheight, cw, ch, cattr, height, width, ch_attr; + int cx_min, cx_max, linesize, x_incr, line, line1; + uint32_t offset, fgcol, bgcol, v, cursor_offset; + uint8_t *d1, *d, *src, *dest, *cursor_ptr; + const uint8_t *font_ptr, *font_base[2]; + int dup9, line_offset, depth_index; + uint32_t *palette; + uint32_t *ch_attr_ptr; + vga_draw_glyph8_func *vga_draw_glyph8; + vga_draw_glyph9_func *vga_draw_glyph9; + int64_t now = qemu_get_clock_ms(vm_clock); + + /* compute font data address (in plane 2) */ + v = s->sr[VGA_SEQ_CHARACTER_MAP]; + offset = (((v >> 4) & 1) | ((v << 1) & 6)) * 8192 * 4 + 2; + if (offset != s->font_offsets[0]) { + s->font_offsets[0] = offset; + full_update = 1; + } + font_base[0] = s->vram_ptr + offset; + + offset = (((v >> 5) & 1) | ((v >> 1) & 6)) * 8192 * 4 + 2; + font_base[1] = s->vram_ptr + offset; + if (offset != s->font_offsets[1]) { + s->font_offsets[1] = offset; + full_update = 1; + } + if (s->plane_updated & (1 << 2) || s->chain4_alias) { + /* if the plane 2 was modified since the last display, it + indicates the font may have been modified */ + s->plane_updated = 0; + full_update = 1; + } + full_update |= update_basic_params(s); + + line_offset = s->line_offset; + + vga_get_text_resolution(s, &width, &height, &cw, &cheight); + if ((height * width) <= 1) { + /* better than nothing: exit if transient size is too small */ + return; + } + if ((height * width) > CH_ATTR_SIZE) { + /* better than nothing: exit if transient size is too big */ + return; + } + + if (width != s->last_width || height != s->last_height || + cw != s->last_cw || cheight != s->last_ch || s->last_depth) { + s->last_scr_width = width * cw; + s->last_scr_height = height * cheight; + qemu_console_resize(s->con, s->last_scr_width, s->last_scr_height); + surface = qemu_console_surface(s->con); + dpy_text_resize(s->con, width, height); + s->last_depth = 0; + s->last_width = width; + s->last_height = height; + s->last_ch = cheight; + s->last_cw = cw; + full_update = 1; + } + s->rgb_to_pixel = + rgb_to_pixel_dup_table[get_depth_index(surface)]; + full_update |= update_palette16(s); + palette = s->last_palette; + x_incr = cw * surface_bytes_per_pixel(surface); + + if (full_update) { + s->full_update_text = 1; + } + if (s->full_update_gfx) { + s->full_update_gfx = 0; + full_update |= 1; + } + + cursor_offset = ((s->cr[VGA_CRTC_CURSOR_HI] << 8) | + s->cr[VGA_CRTC_CURSOR_LO]) - s->start_addr; + if (cursor_offset != s->cursor_offset || + s->cr[VGA_CRTC_CURSOR_START] != s->cursor_start || + s->cr[VGA_CRTC_CURSOR_END] != s->cursor_end) { + /* if the cursor position changed, we update the old and new + chars */ + if (s->cursor_offset < CH_ATTR_SIZE) + s->last_ch_attr[s->cursor_offset] = -1; + if (cursor_offset < CH_ATTR_SIZE) + s->last_ch_attr[cursor_offset] = -1; + s->cursor_offset = cursor_offset; + s->cursor_start = s->cr[VGA_CRTC_CURSOR_START]; + s->cursor_end = s->cr[VGA_CRTC_CURSOR_END]; + } + cursor_ptr = s->vram_ptr + (s->start_addr + cursor_offset) * 4; + if (now >= s->cursor_blink_time) { + s->cursor_blink_time = now + VGA_TEXT_CURSOR_PERIOD_MS / 2; + s->cursor_visible_phase = !s->cursor_visible_phase; + } + + depth_index = get_depth_index(surface); + if (cw == 16) + vga_draw_glyph8 = vga_draw_glyph16_table[depth_index]; + else + vga_draw_glyph8 = vga_draw_glyph8_table[depth_index]; + vga_draw_glyph9 = vga_draw_glyph9_table[depth_index]; + + dest = surface_data(surface); + linesize = surface_stride(surface); + ch_attr_ptr = s->last_ch_attr; + line = 0; + offset = s->start_addr * 4; + for(cy = 0; cy < height; cy++) { + d1 = dest; + src = s->vram_ptr + offset; + cx_min = width; + cx_max = -1; + for(cx = 0; cx < width; cx++) { + ch_attr = *(uint16_t *)src; + if (full_update || ch_attr != *ch_attr_ptr || src == cursor_ptr) { + if (cx < cx_min) + cx_min = cx; + if (cx > cx_max) + cx_max = cx; + *ch_attr_ptr = ch_attr; +#ifdef HOST_WORDS_BIGENDIAN + ch = ch_attr >> 8; + cattr = ch_attr & 0xff; +#else + ch = ch_attr & 0xff; + cattr = ch_attr >> 8; +#endif + font_ptr = font_base[(cattr >> 3) & 1]; + font_ptr += 32 * 4 * ch; + bgcol = palette[cattr >> 4]; + fgcol = palette[cattr & 0x0f]; + if (cw != 9) { + vga_draw_glyph8(d1, linesize, + font_ptr, cheight, fgcol, bgcol); + } else { + dup9 = 0; + if (ch >= 0xb0 && ch <= 0xdf && + (s->ar[VGA_ATC_MODE] & 0x04)) { + dup9 = 1; + } + vga_draw_glyph9(d1, linesize, + font_ptr, cheight, fgcol, bgcol, dup9); + } + if (src == cursor_ptr && + !(s->cr[VGA_CRTC_CURSOR_START] & 0x20) && + s->cursor_visible_phase) { + int line_start, line_last, h; + /* draw the cursor */ + line_start = s->cr[VGA_CRTC_CURSOR_START] & 0x1f; + line_last = s->cr[VGA_CRTC_CURSOR_END] & 0x1f; + /* XXX: check that */ + if (line_last > cheight - 1) + line_last = cheight - 1; + if (line_last >= line_start && line_start < cheight) { + h = line_last - line_start + 1; + d = d1 + linesize * line_start; + if (cw != 9) { + vga_draw_glyph8(d, linesize, + cursor_glyph, h, fgcol, bgcol); + } else { + vga_draw_glyph9(d, linesize, + cursor_glyph, h, fgcol, bgcol, 1); + } + } + } + } + d1 += x_incr; + src += 4; + ch_attr_ptr++; + } + if (cx_max != -1) { + dpy_gfx_update(s->con, cx_min * cw, cy * cheight, + (cx_max - cx_min + 1) * cw, cheight); + } + dest += linesize * cheight; + line1 = line + cheight; + offset += line_offset; + if (line < s->line_compare && line1 >= s->line_compare) { + offset = 0; + } + line = line1; + } +} + +enum { + VGA_DRAW_LINE2, + VGA_DRAW_LINE2D2, + VGA_DRAW_LINE4, + VGA_DRAW_LINE4D2, + VGA_DRAW_LINE8D2, + VGA_DRAW_LINE8, + VGA_DRAW_LINE15, + VGA_DRAW_LINE16, + VGA_DRAW_LINE24, + VGA_DRAW_LINE32, + VGA_DRAW_LINE_NB, +}; + +static vga_draw_line_func * const vga_draw_line_table[NB_DEPTHS * VGA_DRAW_LINE_NB] = { + vga_draw_line2_8, + vga_draw_line2_16, + vga_draw_line2_16, + vga_draw_line2_32, + vga_draw_line2_32, + vga_draw_line2_16, + vga_draw_line2_16, + + vga_draw_line2d2_8, + vga_draw_line2d2_16, + vga_draw_line2d2_16, + vga_draw_line2d2_32, + vga_draw_line2d2_32, + vga_draw_line2d2_16, + vga_draw_line2d2_16, + + vga_draw_line4_8, + vga_draw_line4_16, + vga_draw_line4_16, + vga_draw_line4_32, + vga_draw_line4_32, + vga_draw_line4_16, + vga_draw_line4_16, + + vga_draw_line4d2_8, + vga_draw_line4d2_16, + vga_draw_line4d2_16, + vga_draw_line4d2_32, + vga_draw_line4d2_32, + vga_draw_line4d2_16, + vga_draw_line4d2_16, + + vga_draw_line8d2_8, + vga_draw_line8d2_16, + vga_draw_line8d2_16, + vga_draw_line8d2_32, + vga_draw_line8d2_32, + vga_draw_line8d2_16, + vga_draw_line8d2_16, + + vga_draw_line8_8, + vga_draw_line8_16, + vga_draw_line8_16, + vga_draw_line8_32, + vga_draw_line8_32, + vga_draw_line8_16, + vga_draw_line8_16, + + vga_draw_line15_8, + vga_draw_line15_15, + vga_draw_line15_16, + vga_draw_line15_32, + vga_draw_line15_32bgr, + vga_draw_line15_15bgr, + vga_draw_line15_16bgr, + + vga_draw_line16_8, + vga_draw_line16_15, + vga_draw_line16_16, + vga_draw_line16_32, + vga_draw_line16_32bgr, + vga_draw_line16_15bgr, + vga_draw_line16_16bgr, + + vga_draw_line24_8, + vga_draw_line24_15, + vga_draw_line24_16, + vga_draw_line24_32, + vga_draw_line24_32bgr, + vga_draw_line24_15bgr, + vga_draw_line24_16bgr, + + vga_draw_line32_8, + vga_draw_line32_15, + vga_draw_line32_16, + vga_draw_line32_32, + vga_draw_line32_32bgr, + vga_draw_line32_15bgr, + vga_draw_line32_16bgr, +}; + +static int vga_get_bpp(VGACommonState *s) +{ + int ret; + + if (s->vbe_regs[VBE_DISPI_INDEX_ENABLE] & VBE_DISPI_ENABLED) { + ret = s->vbe_regs[VBE_DISPI_INDEX_BPP]; + } else { + ret = 0; + } + return ret; +} + +static void vga_get_resolution(VGACommonState *s, int *pwidth, int *pheight) +{ + int width, height; + + if (s->vbe_regs[VBE_DISPI_INDEX_ENABLE] & VBE_DISPI_ENABLED) { + width = s->vbe_regs[VBE_DISPI_INDEX_XRES]; + height = s->vbe_regs[VBE_DISPI_INDEX_YRES]; + } else { + width = (s->cr[VGA_CRTC_H_DISP] + 1) * 8; + height = s->cr[VGA_CRTC_V_DISP_END] | + ((s->cr[VGA_CRTC_OVERFLOW] & 0x02) << 7) | + ((s->cr[VGA_CRTC_OVERFLOW] & 0x40) << 3); + height = (height + 1); + } + *pwidth = width; + *pheight = height; +} + +void vga_invalidate_scanlines(VGACommonState *s, int y1, int y2) +{ + int y; + if (y1 >= VGA_MAX_HEIGHT) + return; + if (y2 >= VGA_MAX_HEIGHT) + y2 = VGA_MAX_HEIGHT; + for(y = y1; y < y2; y++) { + s->invalidated_y_table[y >> 5] |= 1 << (y & 0x1f); + } +} + +void vga_sync_dirty_bitmap(VGACommonState *s) +{ + memory_region_sync_dirty_bitmap(&s->vram); +} + +void vga_dirty_log_start(VGACommonState *s) +{ + memory_region_set_log(&s->vram, true, DIRTY_MEMORY_VGA); +} + +void vga_dirty_log_stop(VGACommonState *s) +{ + memory_region_set_log(&s->vram, false, DIRTY_MEMORY_VGA); +} + +/* + * graphic modes + */ +static void vga_draw_graphic(VGACommonState *s, int full_update) +{ + DisplaySurface *surface = qemu_console_surface(s->con); + int y1, y, update, linesize, y_start, double_scan, mask, depth; + int width, height, shift_control, line_offset, bwidth, bits; + ram_addr_t page0, page1, page_min, page_max; + int disp_width, multi_scan, multi_run; + uint8_t *d; + uint32_t v, addr1, addr; + vga_draw_line_func *vga_draw_line; +#if defined(HOST_WORDS_BIGENDIAN) == defined(TARGET_WORDS_BIGENDIAN) + static const bool byteswap = false; +#else + static const bool byteswap = true; +#endif + + full_update |= update_basic_params(s); + + if (!full_update) + vga_sync_dirty_bitmap(s); + + s->get_resolution(s, &width, &height); + disp_width = width; + + shift_control = (s->gr[VGA_GFX_MODE] >> 5) & 3; + double_scan = (s->cr[VGA_CRTC_MAX_SCAN] >> 7); + if (shift_control != 1) { + multi_scan = (((s->cr[VGA_CRTC_MAX_SCAN] & 0x1f) + 1) << double_scan) + - 1; + } else { + /* in CGA modes, multi_scan is ignored */ + /* XXX: is it correct ? */ + multi_scan = double_scan; + } + multi_run = multi_scan; + if (shift_control != s->shift_control || + double_scan != s->double_scan) { + full_update = 1; + s->shift_control = shift_control; + s->double_scan = double_scan; + } + + if (shift_control == 0) { + if (s->sr[VGA_SEQ_CLOCK_MODE] & 8) { + disp_width <<= 1; + } + } else if (shift_control == 1) { + if (s->sr[VGA_SEQ_CLOCK_MODE] & 8) { + disp_width <<= 1; + } + } + + depth = s->get_bpp(s); + if (s->line_offset != s->last_line_offset || + disp_width != s->last_width || + height != s->last_height || + s->last_depth != depth) { + if (depth == 32 || (depth == 16 && !byteswap)) { + surface = qemu_create_displaysurface_from(disp_width, + height, depth, s->line_offset, + s->vram_ptr + (s->start_addr * 4), byteswap); + dpy_gfx_replace_surface(s->con, surface); + } else { + qemu_console_resize(s->con, disp_width, height); + surface = qemu_console_surface(s->con); + } + s->last_scr_width = disp_width; + s->last_scr_height = height; + s->last_width = disp_width; + s->last_height = height; + s->last_line_offset = s->line_offset; + s->last_depth = depth; + full_update = 1; + } else if (is_buffer_shared(surface) && + (full_update || surface_data(surface) != s->vram_ptr + + (s->start_addr * 4))) { + DisplaySurface *surface; + surface = qemu_create_displaysurface_from(disp_width, + height, depth, s->line_offset, + s->vram_ptr + (s->start_addr * 4), byteswap); + dpy_gfx_replace_surface(s->con, surface); + } + + s->rgb_to_pixel = + rgb_to_pixel_dup_table[get_depth_index(surface)]; + + if (shift_control == 0) { + full_update |= update_palette16(s); + if (s->sr[VGA_SEQ_CLOCK_MODE] & 8) { + v = VGA_DRAW_LINE4D2; + } else { + v = VGA_DRAW_LINE4; + } + bits = 4; + } else if (shift_control == 1) { + full_update |= update_palette16(s); + if (s->sr[VGA_SEQ_CLOCK_MODE] & 8) { + v = VGA_DRAW_LINE2D2; + } else { + v = VGA_DRAW_LINE2; + } + bits = 4; + } else { + switch(s->get_bpp(s)) { + default: + case 0: + full_update |= update_palette256(s); + v = VGA_DRAW_LINE8D2; + bits = 4; + break; + case 8: + full_update |= update_palette256(s); + v = VGA_DRAW_LINE8; + bits = 8; + break; + case 15: + v = VGA_DRAW_LINE15; + bits = 16; + break; + case 16: + v = VGA_DRAW_LINE16; + bits = 16; + break; + case 24: + v = VGA_DRAW_LINE24; + bits = 24; + break; + case 32: + v = VGA_DRAW_LINE32; + bits = 32; + break; + } + } + vga_draw_line = vga_draw_line_table[v * NB_DEPTHS + + get_depth_index(surface)]; + + if (!is_buffer_shared(surface) && s->cursor_invalidate) { + s->cursor_invalidate(s); + } + + line_offset = s->line_offset; +#if 0 + printf("w=%d h=%d v=%d line_offset=%d cr[0x09]=0x%02x cr[0x17]=0x%02x linecmp=%d sr[0x01]=0x%02x\n", + width, height, v, line_offset, s->cr[9], s->cr[VGA_CRTC_MODE], + s->line_compare, s->sr[VGA_SEQ_CLOCK_MODE]); +#endif + addr1 = (s->start_addr * 4); + bwidth = (width * bits + 7) / 8; + y_start = -1; + page_min = -1; + page_max = 0; + d = surface_data(surface); + linesize = surface_stride(surface); + y1 = 0; + for(y = 0; y < height; y++) { + addr = addr1; + if (!(s->cr[VGA_CRTC_MODE] & 1)) { + int shift; + /* CGA compatibility handling */ + shift = 14 + ((s->cr[VGA_CRTC_MODE] >> 6) & 1); + addr = (addr & ~(1 << shift)) | ((y1 & 1) << shift); + } + if (!(s->cr[VGA_CRTC_MODE] & 2)) { + addr = (addr & ~0x8000) | ((y1 & 2) << 14); + } + update = full_update; + page0 = addr; + page1 = addr + bwidth - 1; + update |= memory_region_get_dirty(&s->vram, page0, page1 - page0, + DIRTY_MEMORY_VGA); + /* explicit invalidation for the hardware cursor */ + update |= (s->invalidated_y_table[y >> 5] >> (y & 0x1f)) & 1; + if (update) { + if (y_start < 0) + y_start = y; + if (page0 < page_min) + page_min = page0; + if (page1 > page_max) + page_max = page1; + if (!(is_buffer_shared(surface))) { + vga_draw_line(s, d, s->vram_ptr + addr, width); + if (s->cursor_draw_line) + s->cursor_draw_line(s, d, y); + } + } else { + if (y_start >= 0) { + /* flush to display */ + dpy_gfx_update(s->con, 0, y_start, + disp_width, y - y_start); + y_start = -1; + } + } + if (!multi_run) { + mask = (s->cr[VGA_CRTC_MODE] & 3) ^ 3; + if ((y1 & mask) == mask) + addr1 += line_offset; + y1++; + multi_run = multi_scan; + } else { + multi_run--; + } + /* line compare acts on the displayed lines */ + if (y == s->line_compare) + addr1 = 0; + d += linesize; + } + if (y_start >= 0) { + /* flush to display */ + dpy_gfx_update(s->con, 0, y_start, + disp_width, y - y_start); + } + /* reset modified pages */ + if (page_max >= page_min) { + memory_region_reset_dirty(&s->vram, + page_min, + page_max - page_min, + DIRTY_MEMORY_VGA); + } + memset(s->invalidated_y_table, 0, ((height + 31) >> 5) * 4); +} + +static void vga_draw_blank(VGACommonState *s, int full_update) +{ + DisplaySurface *surface = qemu_console_surface(s->con); + int i, w, val; + uint8_t *d; + + if (!full_update) + return; + if (s->last_scr_width <= 0 || s->last_scr_height <= 0) + return; + + s->rgb_to_pixel = + rgb_to_pixel_dup_table[get_depth_index(surface)]; + if (surface_bits_per_pixel(surface) == 8) { + val = s->rgb_to_pixel(0, 0, 0); + } else { + val = 0; + } + w = s->last_scr_width * surface_bytes_per_pixel(surface); + d = surface_data(surface); + for(i = 0; i < s->last_scr_height; i++) { + memset(d, val, w); + d += surface_stride(surface); + } + dpy_gfx_update(s->con, 0, 0, + s->last_scr_width, s->last_scr_height); +} + +#define GMODE_TEXT 0 +#define GMODE_GRAPH 1 +#define GMODE_BLANK 2 + +static void vga_update_display(void *opaque) +{ + VGACommonState *s = opaque; + DisplaySurface *surface = qemu_console_surface(s->con); + int full_update, graphic_mode; + + qemu_flush_coalesced_mmio_buffer(); + + if (surface_bits_per_pixel(surface) == 0) { + /* nothing to do */ + } else { + full_update = 0; + if (!(s->ar_index & 0x20)) { + graphic_mode = GMODE_BLANK; + } else { + graphic_mode = s->gr[VGA_GFX_MISC] & VGA_GR06_GRAPHICS_MODE; + } + if (graphic_mode != s->graphic_mode) { + s->graphic_mode = graphic_mode; + s->cursor_blink_time = qemu_get_clock_ms(vm_clock); + full_update = 1; + } + switch(graphic_mode) { + case GMODE_TEXT: + vga_draw_text(s, full_update); + break; + case GMODE_GRAPH: + vga_draw_graphic(s, full_update); + break; + case GMODE_BLANK: + default: + vga_draw_blank(s, full_update); + break; + } + } +} + +/* force a full display refresh */ +static void vga_invalidate_display(void *opaque) +{ + VGACommonState *s = opaque; + + s->last_width = -1; + s->last_height = -1; +} + +void vga_common_reset(VGACommonState *s) +{ + s->sr_index = 0; + memset(s->sr, '\0', sizeof(s->sr)); + s->gr_index = 0; + memset(s->gr, '\0', sizeof(s->gr)); + s->ar_index = 0; + memset(s->ar, '\0', sizeof(s->ar)); + s->ar_flip_flop = 0; + s->cr_index = 0; + memset(s->cr, '\0', sizeof(s->cr)); + s->msr = 0; + s->fcr = 0; + s->st00 = 0; + s->st01 = 0; + s->dac_state = 0; + s->dac_sub_index = 0; + s->dac_read_index = 0; + s->dac_write_index = 0; + memset(s->dac_cache, '\0', sizeof(s->dac_cache)); + s->dac_8bit = 0; + memset(s->palette, '\0', sizeof(s->palette)); + s->bank_offset = 0; + s->vbe_index = 0; + memset(s->vbe_regs, '\0', sizeof(s->vbe_regs)); + s->vbe_regs[VBE_DISPI_INDEX_ID] = VBE_DISPI_ID5; + s->vbe_start_addr = 0; + s->vbe_line_offset = 0; + s->vbe_bank_mask = (s->vram_size >> 16) - 1; + memset(s->font_offsets, '\0', sizeof(s->font_offsets)); + s->graphic_mode = -1; /* force full update */ + s->shift_control = 0; + s->double_scan = 0; + s->line_offset = 0; + s->line_compare = 0; + s->start_addr = 0; + s->plane_updated = 0; + s->last_cw = 0; + s->last_ch = 0; + s->last_width = 0; + s->last_height = 0; + s->last_scr_width = 0; + s->last_scr_height = 0; + s->cursor_start = 0; + s->cursor_end = 0; + s->cursor_offset = 0; + memset(s->invalidated_y_table, '\0', sizeof(s->invalidated_y_table)); + memset(s->last_palette, '\0', sizeof(s->last_palette)); + memset(s->last_ch_attr, '\0', sizeof(s->last_ch_attr)); + switch (vga_retrace_method) { + case VGA_RETRACE_DUMB: + break; + case VGA_RETRACE_PRECISE: + memset(&s->retrace_info, 0, sizeof (s->retrace_info)); + break; + } + vga_update_memory_access(s); +} + +static void vga_reset(void *opaque) +{ + VGACommonState *s = opaque; + vga_common_reset(s); +} + +#define TEXTMODE_X(x) ((x) % width) +#define TEXTMODE_Y(x) ((x) / width) +#define VMEM2CHTYPE(v) ((v & 0xff0007ff) | \ + ((v & 0x00000800) << 10) | ((v & 0x00007000) >> 1)) +/* relay text rendering to the display driver + * instead of doing a full vga_update_display() */ +static void vga_update_text(void *opaque, console_ch_t *chardata) +{ + VGACommonState *s = opaque; + int graphic_mode, i, cursor_offset, cursor_visible; + int cw, cheight, width, height, size, c_min, c_max; + uint32_t *src; + console_ch_t *dst, val; + char msg_buffer[80]; + int full_update = 0; + + qemu_flush_coalesced_mmio_buffer(); + + if (!(s->ar_index & 0x20)) { + graphic_mode = GMODE_BLANK; + } else { + graphic_mode = s->gr[VGA_GFX_MISC] & VGA_GR06_GRAPHICS_MODE; + } + if (graphic_mode != s->graphic_mode) { + s->graphic_mode = graphic_mode; + full_update = 1; + } + if (s->last_width == -1) { + s->last_width = 0; + full_update = 1; + } + + switch (graphic_mode) { + case GMODE_TEXT: + /* TODO: update palette */ + full_update |= update_basic_params(s); + + /* total width & height */ + cheight = (s->cr[VGA_CRTC_MAX_SCAN] & 0x1f) + 1; + cw = 8; + if (!(s->sr[VGA_SEQ_CLOCK_MODE] & VGA_SR01_CHAR_CLK_8DOTS)) { + cw = 9; + } + if (s->sr[VGA_SEQ_CLOCK_MODE] & 0x08) { + cw = 16; /* NOTE: no 18 pixel wide */ + } + width = (s->cr[VGA_CRTC_H_DISP] + 1); + if (s->cr[VGA_CRTC_V_TOTAL] == 100) { + /* ugly hack for CGA 160x100x16 - explain me the logic */ + height = 100; + } else { + height = s->cr[VGA_CRTC_V_DISP_END] | + ((s->cr[VGA_CRTC_OVERFLOW] & 0x02) << 7) | + ((s->cr[VGA_CRTC_OVERFLOW] & 0x40) << 3); + height = (height + 1) / cheight; + } + + size = (height * width); + if (size > CH_ATTR_SIZE) { + if (!full_update) + return; + + snprintf(msg_buffer, sizeof(msg_buffer), "%i x %i Text mode", + width, height); + break; + } + + if (width != s->last_width || height != s->last_height || + cw != s->last_cw || cheight != s->last_ch) { + s->last_scr_width = width * cw; + s->last_scr_height = height * cheight; + qemu_console_resize(s->con, s->last_scr_width, s->last_scr_height); + dpy_text_resize(s->con, width, height); + s->last_depth = 0; + s->last_width = width; + s->last_height = height; + s->last_ch = cheight; + s->last_cw = cw; + full_update = 1; + } + + if (full_update) { + s->full_update_gfx = 1; + } + if (s->full_update_text) { + s->full_update_text = 0; + full_update |= 1; + } + + /* Update "hardware" cursor */ + cursor_offset = ((s->cr[VGA_CRTC_CURSOR_HI] << 8) | + s->cr[VGA_CRTC_CURSOR_LO]) - s->start_addr; + if (cursor_offset != s->cursor_offset || + s->cr[VGA_CRTC_CURSOR_START] != s->cursor_start || + s->cr[VGA_CRTC_CURSOR_END] != s->cursor_end || full_update) { + cursor_visible = !(s->cr[VGA_CRTC_CURSOR_START] & 0x20); + if (cursor_visible && cursor_offset < size && cursor_offset >= 0) + dpy_text_cursor(s->con, + TEXTMODE_X(cursor_offset), + TEXTMODE_Y(cursor_offset)); + else + dpy_text_cursor(s->con, -1, -1); + s->cursor_offset = cursor_offset; + s->cursor_start = s->cr[VGA_CRTC_CURSOR_START]; + s->cursor_end = s->cr[VGA_CRTC_CURSOR_END]; + } + + src = (uint32_t *) s->vram_ptr + s->start_addr; + dst = chardata; + + if (full_update) { + for (i = 0; i < size; src ++, dst ++, i ++) + console_write_ch(dst, VMEM2CHTYPE(le32_to_cpu(*src))); + + dpy_text_update(s->con, 0, 0, width, height); + } else { + c_max = 0; + + for (i = 0; i < size; src ++, dst ++, i ++) { + console_write_ch(&val, VMEM2CHTYPE(le32_to_cpu(*src))); + if (*dst != val) { + *dst = val; + c_max = i; + break; + } + } + c_min = i; + for (; i < size; src ++, dst ++, i ++) { + console_write_ch(&val, VMEM2CHTYPE(le32_to_cpu(*src))); + if (*dst != val) { + *dst = val; + c_max = i; + } + } + + if (c_min <= c_max) { + i = TEXTMODE_Y(c_min); + dpy_text_update(s->con, 0, i, width, TEXTMODE_Y(c_max) - i + 1); + } + } + + return; + case GMODE_GRAPH: + if (!full_update) + return; + + s->get_resolution(s, &width, &height); + snprintf(msg_buffer, sizeof(msg_buffer), "%i x %i Graphic mode", + width, height); + break; + case GMODE_BLANK: + default: + if (!full_update) + return; + + snprintf(msg_buffer, sizeof(msg_buffer), "VGA Blank mode"); + break; + } + + /* Display a message */ + s->last_width = 60; + s->last_height = height = 3; + dpy_text_cursor(s->con, -1, -1); + dpy_text_resize(s->con, s->last_width, height); + + for (dst = chardata, i = 0; i < s->last_width * height; i ++) + console_write_ch(dst ++, ' '); + + size = strlen(msg_buffer); + width = (s->last_width - size) / 2; + dst = chardata + s->last_width + width; + for (i = 0; i < size; i ++) + console_write_ch(dst ++, 0x00200100 | msg_buffer[i]); + + dpy_text_update(s->con, 0, 0, s->last_width, height); +} + +static uint64_t vga_mem_read(void *opaque, hwaddr addr, + unsigned size) +{ + VGACommonState *s = opaque; + + return vga_mem_readb(s, addr); +} + +static void vga_mem_write(void *opaque, hwaddr addr, + uint64_t data, unsigned size) +{ + VGACommonState *s = opaque; + + return vga_mem_writeb(s, addr, data); +} + +const MemoryRegionOps vga_mem_ops = { + .read = vga_mem_read, + .write = vga_mem_write, + .endianness = DEVICE_LITTLE_ENDIAN, + .impl = { + .min_access_size = 1, + .max_access_size = 1, + }, +}; + +static int vga_common_post_load(void *opaque, int version_id) +{ + VGACommonState *s = opaque; + + /* force refresh */ + s->graphic_mode = -1; + return 0; +} + +const VMStateDescription vmstate_vga_common = { + .name = "vga", + .version_id = 2, + .minimum_version_id = 2, + .minimum_version_id_old = 2, + .post_load = vga_common_post_load, + .fields = (VMStateField []) { + VMSTATE_UINT32(latch, VGACommonState), + VMSTATE_UINT8(sr_index, VGACommonState), + VMSTATE_PARTIAL_BUFFER(sr, VGACommonState, 8), + VMSTATE_UINT8(gr_index, VGACommonState), + VMSTATE_PARTIAL_BUFFER(gr, VGACommonState, 16), + VMSTATE_UINT8(ar_index, VGACommonState), + VMSTATE_BUFFER(ar, VGACommonState), + VMSTATE_INT32(ar_flip_flop, VGACommonState), + VMSTATE_UINT8(cr_index, VGACommonState), + VMSTATE_BUFFER(cr, VGACommonState), + VMSTATE_UINT8(msr, VGACommonState), + VMSTATE_UINT8(fcr, VGACommonState), + VMSTATE_UINT8(st00, VGACommonState), + VMSTATE_UINT8(st01, VGACommonState), + + VMSTATE_UINT8(dac_state, VGACommonState), + VMSTATE_UINT8(dac_sub_index, VGACommonState), + VMSTATE_UINT8(dac_read_index, VGACommonState), + VMSTATE_UINT8(dac_write_index, VGACommonState), + VMSTATE_BUFFER(dac_cache, VGACommonState), + VMSTATE_BUFFER(palette, VGACommonState), + + VMSTATE_INT32(bank_offset, VGACommonState), + VMSTATE_UINT8_EQUAL(is_vbe_vmstate, VGACommonState), + VMSTATE_UINT16(vbe_index, VGACommonState), + VMSTATE_UINT16_ARRAY(vbe_regs, VGACommonState, VBE_DISPI_INDEX_NB), + VMSTATE_UINT32(vbe_start_addr, VGACommonState), + VMSTATE_UINT32(vbe_line_offset, VGACommonState), + VMSTATE_UINT32(vbe_bank_mask, VGACommonState), + VMSTATE_END_OF_LIST() + } +}; + +void vga_common_init(VGACommonState *s) +{ + int i, j, v, b; + + for(i = 0;i < 256; i++) { + v = 0; + for(j = 0; j < 8; j++) { + v |= ((i >> j) & 1) << (j * 4); + } + expand4[i] = v; + + v = 0; + for(j = 0; j < 4; j++) { + v |= ((i >> (2 * j)) & 3) << (j * 4); + } + expand2[i] = v; + } + for(i = 0; i < 16; i++) { + v = 0; + for(j = 0; j < 4; j++) { + b = ((i >> j) & 1); + v |= b << (2 * j); + v |= b << (2 * j + 1); + } + expand4to8[i] = v; + } + + /* valid range: 1 MB -> 256 MB */ + s->vram_size = 1024 * 1024; + while (s->vram_size < (s->vram_size_mb << 20) && + s->vram_size < (256 << 20)) { + s->vram_size <<= 1; + } + s->vram_size_mb = s->vram_size >> 20; + + s->is_vbe_vmstate = 1; + memory_region_init_ram(&s->vram, "vga.vram", s->vram_size); + vmstate_register_ram_global(&s->vram); + xen_register_framebuffer(&s->vram); + s->vram_ptr = memory_region_get_ram_ptr(&s->vram); + s->get_bpp = vga_get_bpp; + s->get_offsets = vga_get_offsets; + s->get_resolution = vga_get_resolution; + s->update = vga_update_display; + s->invalidate = vga_invalidate_display; + s->screen_dump = vga_screen_dump; + s->text_update = vga_update_text; + switch (vga_retrace_method) { + case VGA_RETRACE_DUMB: + s->retrace = vga_dumb_retrace; + s->update_retrace_info = vga_dumb_update_retrace_info; + break; + + case VGA_RETRACE_PRECISE: + s->retrace = vga_precise_retrace; + s->update_retrace_info = vga_precise_update_retrace_info; + break; + } + vga_dirty_log_start(s); +} + +static const MemoryRegionPortio vga_portio_list[] = { + { 0x04, 2, 1, .read = vga_ioport_read, .write = vga_ioport_write }, /* 3b4 */ + { 0x0a, 1, 1, .read = vga_ioport_read, .write = vga_ioport_write }, /* 3ba */ + { 0x10, 16, 1, .read = vga_ioport_read, .write = vga_ioport_write }, /* 3c0 */ + { 0x24, 2, 1, .read = vga_ioport_read, .write = vga_ioport_write }, /* 3d4 */ + { 0x2a, 1, 1, .read = vga_ioport_read, .write = vga_ioport_write }, /* 3da */ + PORTIO_END_OF_LIST(), +}; + +static const MemoryRegionPortio vbe_portio_list[] = { + { 0, 1, 2, .read = vbe_ioport_read_index, .write = vbe_ioport_write_index }, +# ifdef TARGET_I386 + { 1, 1, 2, .read = vbe_ioport_read_data, .write = vbe_ioport_write_data }, +# endif + { 2, 1, 2, .read = vbe_ioport_read_data, .write = vbe_ioport_write_data }, + PORTIO_END_OF_LIST(), +}; + +/* Used by both ISA and PCI */ +MemoryRegion *vga_init_io(VGACommonState *s, + const MemoryRegionPortio **vga_ports, + const MemoryRegionPortio **vbe_ports) +{ + MemoryRegion *vga_mem; + + *vga_ports = vga_portio_list; + *vbe_ports = vbe_portio_list; + + vga_mem = g_malloc(sizeof(*vga_mem)); + memory_region_init_io(vga_mem, &vga_mem_ops, s, + "vga-lowmem", 0x20000); + memory_region_set_flush_coalesced(vga_mem); + + return vga_mem; +} + +void vga_init(VGACommonState *s, MemoryRegion *address_space, + MemoryRegion *address_space_io, bool init_vga_ports) +{ + MemoryRegion *vga_io_memory; + const MemoryRegionPortio *vga_ports, *vbe_ports; + PortioList *vga_port_list = g_new(PortioList, 1); + PortioList *vbe_port_list = g_new(PortioList, 1); + + qemu_register_reset(vga_reset, s); + + s->bank_offset = 0; + + s->legacy_address_space = address_space; + + vga_io_memory = vga_init_io(s, &vga_ports, &vbe_ports); + memory_region_add_subregion_overlap(address_space, + isa_mem_base + 0x000a0000, + vga_io_memory, + 1); + memory_region_set_coalescing(vga_io_memory); + if (init_vga_ports) { + portio_list_init(vga_port_list, vga_ports, s, "vga"); + portio_list_add(vga_port_list, address_space_io, 0x3b0); + } + if (vbe_ports) { + portio_list_init(vbe_port_list, vbe_ports, s, "vbe"); + portio_list_add(vbe_port_list, address_space_io, 0x1ce); + } +} + +void vga_init_vbe(VGACommonState *s, MemoryRegion *system_memory) +{ + /* With pc-0.12 and below we map both the PCI BAR and the fixed VBE region, + * so use an alias to avoid double-mapping the same region. + */ + memory_region_init_alias(&s->vram_vbe, "vram.vbe", + &s->vram, 0, memory_region_size(&s->vram)); + /* XXX: use optimized standard vga accesses */ + memory_region_add_subregion(system_memory, + VBE_DISPI_LFB_PHYSICAL_ADDRESS, + &s->vram_vbe); + s->vbe_mapped = 1; +} +/********************************************************/ +/* vga screen dump */ + +void ppm_save(const char *filename, struct DisplaySurface *ds, Error **errp) +{ + int width = pixman_image_get_width(ds->image); + int height = pixman_image_get_height(ds->image); + FILE *f; + int y; + int ret; + pixman_image_t *linebuf; + + trace_ppm_save(filename, ds); + f = fopen(filename, "wb"); + if (!f) { + error_setg(errp, "failed to open file '%s': %s", filename, + strerror(errno)); + return; + } + ret = fprintf(f, "P6\n%d %d\n%d\n", width, height, 255); + if (ret < 0) { + linebuf = NULL; + goto write_err; + } + linebuf = qemu_pixman_linebuf_create(PIXMAN_BE_r8g8b8, width); + for (y = 0; y < height; y++) { + qemu_pixman_linebuf_fill(linebuf, ds->image, width, 0, y); + clearerr(f); + ret = fwrite(pixman_image_get_data(linebuf), 1, + pixman_image_get_stride(linebuf), f); + (void)ret; + if (ferror(f)) { + goto write_err; + } + } + +out: + qemu_pixman_image_unref(linebuf); + fclose(f); + return; + +write_err: + error_setg(errp, "failed to write to file '%s': %s", filename, + strerror(errno)); + unlink(filename); + goto out; +} + +/* save the vga display in a PPM image even if no display is + available */ +static void vga_screen_dump(void *opaque, const char *filename, bool cswitch, + Error **errp) +{ + VGACommonState *s = opaque; + DisplaySurface *surface = qemu_console_surface(s->con); + + if (cswitch) { + vga_invalidate_display(s); + } + vga_hw_update(); + ppm_save(filename, surface, errp); +} diff --git a/hw/display/vga.h b/hw/display/vga.h new file mode 100644 index 0000000000..d917046da6 --- /dev/null +++ b/hw/display/vga.h @@ -0,0 +1,159 @@ +/* + * linux/include/video/vga.h -- standard VGA chipset interaction + * + * Copyright 1999 Jeff Garzik <jgarzik@pobox.com> + * + * Copyright history from vga16fb.c: + * Copyright 1999 Ben Pfaff and Petr Vandrovec + * Based on VGA info at http://www.osdever.net/FreeVGA/home.htm + * Based on VESA framebuffer (c) 1998 Gerd Knorr + * + * This file is subject to the terms and conditions of the GNU General + * Public License. See the file COPYING in the main directory of this + * archive for more details. + * + */ + +#ifndef __linux_video_vga_h__ +#define __linux_video_vga_h__ + +/* Some of the code below is taken from SVGAlib. The original, + unmodified copyright notice for that code is below. */ +/* VGAlib version 1.2 - (c) 1993 Tommy Frandsen */ +/* */ +/* This library is free software; you can redistribute it and/or */ +/* modify it without any restrictions. This library is distributed */ +/* in the hope that it will be useful, but without any warranty. */ + +/* Multi-chipset support Copyright 1993 Harm Hanemaayer */ +/* partially copyrighted (C) 1993 by Hartmut Schirmer */ + +/* VGA data register ports */ +#define VGA_CRT_DC 0x3D5 /* CRT Controller Data Register - color emulation */ +#define VGA_CRT_DM 0x3B5 /* CRT Controller Data Register - mono emulation */ +#define VGA_ATT_R 0x3C1 /* Attribute Controller Data Read Register */ +#define VGA_ATT_W 0x3C0 /* Attribute Controller Data Write Register */ +#define VGA_GFX_D 0x3CF /* Graphics Controller Data Register */ +#define VGA_SEQ_D 0x3C5 /* Sequencer Data Register */ +#define VGA_MIS_R 0x3CC /* Misc Output Read Register */ +#define VGA_MIS_W 0x3C2 /* Misc Output Write Register */ +#define VGA_FTC_R 0x3CA /* Feature Control Read Register */ +#define VGA_IS1_RC 0x3DA /* Input Status Register 1 - color emulation */ +#define VGA_IS1_RM 0x3BA /* Input Status Register 1 - mono emulation */ +#define VGA_PEL_D 0x3C9 /* PEL Data Register */ +#define VGA_PEL_MSK 0x3C6 /* PEL mask register */ + +/* EGA-specific registers */ +#define EGA_GFX_E0 0x3CC /* Graphics enable processor 0 */ +#define EGA_GFX_E1 0x3CA /* Graphics enable processor 1 */ + +/* VGA index register ports */ +#define VGA_CRT_IC 0x3D4 /* CRT Controller Index - color emulation */ +#define VGA_CRT_IM 0x3B4 /* CRT Controller Index - mono emulation */ +#define VGA_ATT_IW 0x3C0 /* Attribute Controller Index & Data Write Register */ +#define VGA_GFX_I 0x3CE /* Graphics Controller Index */ +#define VGA_SEQ_I 0x3C4 /* Sequencer Index */ +#define VGA_PEL_IW 0x3C8 /* PEL Write Index */ +#define VGA_PEL_IR 0x3C7 /* PEL Read Index */ + +/* standard VGA indexes max counts */ +#define VGA_CRT_C 0x19 /* Number of CRT Controller Registers */ +#define VGA_ATT_C 0x15 /* Number of Attribute Controller Registers */ +#define VGA_GFX_C 0x09 /* Number of Graphics Controller Registers */ +#define VGA_SEQ_C 0x05 /* Number of Sequencer Registers */ +#define VGA_MIS_C 0x01 /* Number of Misc Output Register */ + +/* VGA misc register bit masks */ +#define VGA_MIS_COLOR 0x01 +#define VGA_MIS_ENB_MEM_ACCESS 0x02 +#define VGA_MIS_DCLK_28322_720 0x04 +#define VGA_MIS_ENB_PLL_LOAD (0x04 | 0x08) +#define VGA_MIS_SEL_HIGH_PAGE 0x20 + +/* VGA CRT controller register indices */ +#define VGA_CRTC_H_TOTAL 0 +#define VGA_CRTC_H_DISP 1 +#define VGA_CRTC_H_BLANK_START 2 +#define VGA_CRTC_H_BLANK_END 3 +#define VGA_CRTC_H_SYNC_START 4 +#define VGA_CRTC_H_SYNC_END 5 +#define VGA_CRTC_V_TOTAL 6 +#define VGA_CRTC_OVERFLOW 7 +#define VGA_CRTC_PRESET_ROW 8 +#define VGA_CRTC_MAX_SCAN 9 +#define VGA_CRTC_CURSOR_START 0x0A +#define VGA_CRTC_CURSOR_END 0x0B +#define VGA_CRTC_START_HI 0x0C +#define VGA_CRTC_START_LO 0x0D +#define VGA_CRTC_CURSOR_HI 0x0E +#define VGA_CRTC_CURSOR_LO 0x0F +#define VGA_CRTC_V_SYNC_START 0x10 +#define VGA_CRTC_V_SYNC_END 0x11 +#define VGA_CRTC_V_DISP_END 0x12 +#define VGA_CRTC_OFFSET 0x13 +#define VGA_CRTC_UNDERLINE 0x14 +#define VGA_CRTC_V_BLANK_START 0x15 +#define VGA_CRTC_V_BLANK_END 0x16 +#define VGA_CRTC_MODE 0x17 +#define VGA_CRTC_LINE_COMPARE 0x18 +#define VGA_CRTC_REGS VGA_CRT_C + +/* VGA CRT controller bit masks */ +#define VGA_CR11_LOCK_CR0_CR7 0x80 /* lock writes to CR0 - CR7 */ +#define VGA_CR17_H_V_SIGNALS_ENABLED 0x80 + +/* VGA attribute controller register indices */ +#define VGA_ATC_PALETTE0 0x00 +#define VGA_ATC_PALETTE1 0x01 +#define VGA_ATC_PALETTE2 0x02 +#define VGA_ATC_PALETTE3 0x03 +#define VGA_ATC_PALETTE4 0x04 +#define VGA_ATC_PALETTE5 0x05 +#define VGA_ATC_PALETTE6 0x06 +#define VGA_ATC_PALETTE7 0x07 +#define VGA_ATC_PALETTE8 0x08 +#define VGA_ATC_PALETTE9 0x09 +#define VGA_ATC_PALETTEA 0x0A +#define VGA_ATC_PALETTEB 0x0B +#define VGA_ATC_PALETTEC 0x0C +#define VGA_ATC_PALETTED 0x0D +#define VGA_ATC_PALETTEE 0x0E +#define VGA_ATC_PALETTEF 0x0F +#define VGA_ATC_MODE 0x10 +#define VGA_ATC_OVERSCAN 0x11 +#define VGA_ATC_PLANE_ENABLE 0x12 +#define VGA_ATC_PEL 0x13 +#define VGA_ATC_COLOR_PAGE 0x14 + +#define VGA_AR_ENABLE_DISPLAY 0x20 + +/* VGA sequencer register indices */ +#define VGA_SEQ_RESET 0x00 +#define VGA_SEQ_CLOCK_MODE 0x01 +#define VGA_SEQ_PLANE_WRITE 0x02 +#define VGA_SEQ_CHARACTER_MAP 0x03 +#define VGA_SEQ_MEMORY_MODE 0x04 + +/* VGA sequencer register bit masks */ +#define VGA_SR01_CHAR_CLK_8DOTS 0x01 /* bit 0: character clocks 8 dots wide are generated */ +#define VGA_SR01_SCREEN_OFF 0x20 /* bit 5: Screen is off */ +#define VGA_SR02_ALL_PLANES 0x0F /* bits 3-0: enable access to all planes */ +#define VGA_SR04_EXT_MEM 0x02 /* bit 1: allows complete mem access to 256K */ +#define VGA_SR04_SEQ_MODE 0x04 /* bit 2: directs system to use a sequential addressing mode */ +#define VGA_SR04_CHN_4M 0x08 /* bit 3: selects modulo 4 addressing for CPU access to display memory */ + +/* VGA graphics controller register indices */ +#define VGA_GFX_SR_VALUE 0x00 +#define VGA_GFX_SR_ENABLE 0x01 +#define VGA_GFX_COMPARE_VALUE 0x02 +#define VGA_GFX_DATA_ROTATE 0x03 +#define VGA_GFX_PLANE_READ 0x04 +#define VGA_GFX_MODE 0x05 +#define VGA_GFX_MISC 0x06 +#define VGA_GFX_COMPARE_MASK 0x07 +#define VGA_GFX_BIT_MASK 0x08 + +/* VGA graphics controller bit masks */ +#define VGA_GR06_GRAPHICS_MODE 0x01 + +#endif /* __linux_video_vga_h__ */ diff --git a/hw/display/vga_int.h b/hw/display/vga_int.h new file mode 100644 index 0000000000..260f7d6948 --- /dev/null +++ b/hw/display/vga_int.h @@ -0,0 +1,218 @@ +/* + * QEMU internal VGA defines. + * + * Copyright (c) 2003-2004 Fabrice Bellard + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#ifndef HW_VGA_INT_H +#define HW_VGA_INT_H 1 + +#include <hw/hw.h> +#include "qapi/error.h" +#include "exec/memory.h" + +#define ST01_V_RETRACE 0x08 +#define ST01_DISP_ENABLE 0x01 + +#define VBE_DISPI_MAX_XRES 16000 +#define VBE_DISPI_MAX_YRES 12000 +#define VBE_DISPI_MAX_BPP 32 + +#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_NB 0xa /* size of vbe_regs[] */ +#define VBE_DISPI_INDEX_VIDEO_MEMORY_64K 0xa /* read-only, not in vbe_regs */ + +#define VBE_DISPI_ID0 0xB0C0 +#define VBE_DISPI_ID1 0xB0C1 +#define VBE_DISPI_ID2 0xB0C2 +#define VBE_DISPI_ID3 0xB0C3 +#define VBE_DISPI_ID4 0xB0C4 +#define VBE_DISPI_ID5 0xB0C5 + +#define VBE_DISPI_DISABLED 0x00 +#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 + +#define VBE_DISPI_LFB_PHYSICAL_ADDRESS 0xE0000000 + +#define CH_ATTR_SIZE (160 * 100) +#define VGA_MAX_HEIGHT 2048 + +struct vga_precise_retrace { + int64_t ticks_per_char; + int64_t total_chars; + int htotal; + int hstart; + int hend; + int vstart; + int vend; + int freq; +}; + +union vga_retrace { + struct vga_precise_retrace precise; +}; + +struct VGACommonState; +typedef uint8_t (* vga_retrace_fn)(struct VGACommonState *s); +typedef void (* vga_update_retrace_info_fn)(struct VGACommonState *s); + +typedef struct VGACommonState { + MemoryRegion *legacy_address_space; + uint8_t *vram_ptr; + MemoryRegion vram; + MemoryRegion vram_vbe; + uint32_t vram_size; + uint32_t vram_size_mb; /* property */ + uint32_t latch; + MemoryRegion *chain4_alias; + uint8_t sr_index; + uint8_t sr[256]; + uint8_t gr_index; + uint8_t gr[256]; + uint8_t ar_index; + uint8_t ar[21]; + int ar_flip_flop; + uint8_t cr_index; + uint8_t cr[256]; /* CRT registers */ + uint8_t msr; /* Misc Output Register */ + uint8_t fcr; /* Feature Control Register */ + uint8_t st00; /* status 0 */ + uint8_t st01; /* status 1 */ + uint8_t dac_state; + uint8_t dac_sub_index; + uint8_t dac_read_index; + uint8_t dac_write_index; + uint8_t dac_cache[3]; /* used when writing */ + int dac_8bit; + uint8_t palette[768]; + int32_t bank_offset; + int (*get_bpp)(struct VGACommonState *s); + void (*get_offsets)(struct VGACommonState *s, + uint32_t *pline_offset, + uint32_t *pstart_addr, + uint32_t *pline_compare); + void (*get_resolution)(struct VGACommonState *s, + int *pwidth, + int *pheight); + /* bochs vbe state */ + uint16_t vbe_index; + uint16_t vbe_regs[VBE_DISPI_INDEX_NB]; + uint32_t vbe_start_addr; + uint32_t vbe_line_offset; + uint32_t vbe_bank_mask; + int vbe_mapped; + /* display refresh support */ + QemuConsole *con; + uint32_t font_offsets[2]; + int graphic_mode; + uint8_t shift_control; + uint8_t double_scan; + uint32_t line_offset; + uint32_t line_compare; + uint32_t start_addr; + uint32_t plane_updated; + uint32_t last_line_offset; + uint8_t last_cw, last_ch; + uint32_t last_width, last_height; /* in chars or pixels */ + uint32_t last_scr_width, last_scr_height; /* in pixels */ + uint32_t last_depth; /* in bits */ + uint8_t cursor_start, cursor_end; + bool cursor_visible_phase; + int64_t cursor_blink_time; + uint32_t cursor_offset; + unsigned int (*rgb_to_pixel)(unsigned int r, + unsigned int g, unsigned b); + vga_hw_update_ptr update; + vga_hw_invalidate_ptr invalidate; + vga_hw_screen_dump_ptr screen_dump; + vga_hw_text_update_ptr text_update; + bool full_update_text; + bool full_update_gfx; + /* hardware mouse cursor support */ + uint32_t invalidated_y_table[VGA_MAX_HEIGHT / 32]; + void (*cursor_invalidate)(struct VGACommonState *s); + void (*cursor_draw_line)(struct VGACommonState *s, uint8_t *d, int y); + /* tell for each page if it has been updated since the last time */ + uint32_t last_palette[256]; + uint32_t last_ch_attr[CH_ATTR_SIZE]; /* XXX: make it dynamic */ + /* retrace */ + vga_retrace_fn retrace; + vga_update_retrace_info_fn update_retrace_info; + union vga_retrace retrace_info; + uint8_t is_vbe_vmstate; +} VGACommonState; + +static inline int c6_to_8(int v) +{ + int b; + v &= 0x3f; + b = v & 1; + return (v << 2) | (b << 1) | b; +} + +void vga_common_init(VGACommonState *s); +void vga_init(VGACommonState *s, MemoryRegion *address_space, + MemoryRegion *address_space_io, bool init_vga_ports); +MemoryRegion *vga_init_io(VGACommonState *s, + const MemoryRegionPortio **vga_ports, + const MemoryRegionPortio **vbe_ports); +void vga_common_reset(VGACommonState *s); + +void vga_sync_dirty_bitmap(VGACommonState *s); +void vga_dirty_log_start(VGACommonState *s); +void vga_dirty_log_stop(VGACommonState *s); + +extern const VMStateDescription vmstate_vga_common; +uint32_t vga_ioport_read(void *opaque, uint32_t addr); +void vga_ioport_write(void *opaque, uint32_t addr, uint32_t val); +uint32_t vga_mem_readb(VGACommonState *s, hwaddr addr); +void vga_mem_writeb(VGACommonState *s, hwaddr addr, uint32_t val); +void vga_invalidate_scanlines(VGACommonState *s, int y1, int y2); +void ppm_save(const char *filename, struct DisplaySurface *ds, Error **errp); + +int vga_ioport_invalid(VGACommonState *s, uint32_t addr); + +void vga_init_vbe(VGACommonState *s, MemoryRegion *address_space); +uint32_t vbe_ioport_read_data(void *opaque, uint32_t addr); +void vbe_ioport_write_index(void *opaque, uint32_t addr, uint32_t val); +void vbe_ioport_write_data(void *opaque, uint32_t addr, uint32_t val); + +extern const uint8_t sr_mask[8]; +extern const uint8_t gr_mask[16]; + +#define VGABIOS_FILENAME "vgabios.bin" +#define VGABIOS_CIRRUS_FILENAME "vgabios-cirrus.bin" + +extern const MemoryRegionOps vga_mem_ops; + +#endif diff --git a/hw/display/vga_template.h b/hw/display/vga_template.h new file mode 100644 index 0000000000..f6f6a01d84 --- /dev/null +++ b/hw/display/vga_template.h @@ -0,0 +1,459 @@ +/* + * QEMU VGA Emulator templates + * + * Copyright (c) 2003 Fabrice Bellard + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#if DEPTH == 8 +#define BPP 1 +#define PIXEL_TYPE uint8_t +#elif DEPTH == 15 || DEPTH == 16 +#define BPP 2 +#define PIXEL_TYPE uint16_t +#elif DEPTH == 32 +#define BPP 4 +#define PIXEL_TYPE uint32_t +#else +#error unsupport depth +#endif + +#ifdef BGR_FORMAT +#define PIXEL_NAME glue(DEPTH, bgr) +#else +#define PIXEL_NAME DEPTH +#endif /* BGR_FORMAT */ + +#if DEPTH != 15 && !defined(BGR_FORMAT) + +static inline void glue(vga_draw_glyph_line_, DEPTH)(uint8_t *d, + uint32_t font_data, + uint32_t xorcol, + uint32_t bgcol) +{ +#if BPP == 1 + ((uint32_t *)d)[0] = (dmask16[(font_data >> 4)] & xorcol) ^ bgcol; + ((uint32_t *)d)[1] = (dmask16[(font_data >> 0) & 0xf] & xorcol) ^ bgcol; +#elif BPP == 2 + ((uint32_t *)d)[0] = (dmask4[(font_data >> 6)] & xorcol) ^ bgcol; + ((uint32_t *)d)[1] = (dmask4[(font_data >> 4) & 3] & xorcol) ^ bgcol; + ((uint32_t *)d)[2] = (dmask4[(font_data >> 2) & 3] & xorcol) ^ bgcol; + ((uint32_t *)d)[3] = (dmask4[(font_data >> 0) & 3] & xorcol) ^ bgcol; +#else + ((uint32_t *)d)[0] = (-((font_data >> 7)) & xorcol) ^ bgcol; + ((uint32_t *)d)[1] = (-((font_data >> 6) & 1) & xorcol) ^ bgcol; + ((uint32_t *)d)[2] = (-((font_data >> 5) & 1) & xorcol) ^ bgcol; + ((uint32_t *)d)[3] = (-((font_data >> 4) & 1) & xorcol) ^ bgcol; + ((uint32_t *)d)[4] = (-((font_data >> 3) & 1) & xorcol) ^ bgcol; + ((uint32_t *)d)[5] = (-((font_data >> 2) & 1) & xorcol) ^ bgcol; + ((uint32_t *)d)[6] = (-((font_data >> 1) & 1) & xorcol) ^ bgcol; + ((uint32_t *)d)[7] = (-((font_data >> 0) & 1) & xorcol) ^ bgcol; +#endif +} + +static void glue(vga_draw_glyph8_, DEPTH)(uint8_t *d, int linesize, + const uint8_t *font_ptr, int h, + uint32_t fgcol, uint32_t bgcol) +{ + uint32_t font_data, xorcol; + + xorcol = bgcol ^ fgcol; + do { + font_data = font_ptr[0]; + glue(vga_draw_glyph_line_, DEPTH)(d, font_data, xorcol, bgcol); + font_ptr += 4; + d += linesize; + } while (--h); +} + +static void glue(vga_draw_glyph16_, DEPTH)(uint8_t *d, int linesize, + const uint8_t *font_ptr, int h, + uint32_t fgcol, uint32_t bgcol) +{ + uint32_t font_data, xorcol; + + xorcol = bgcol ^ fgcol; + do { + font_data = font_ptr[0]; + glue(vga_draw_glyph_line_, DEPTH)(d, + expand4to8[font_data >> 4], + xorcol, bgcol); + glue(vga_draw_glyph_line_, DEPTH)(d + 8 * BPP, + expand4to8[font_data & 0x0f], + xorcol, bgcol); + font_ptr += 4; + d += linesize; + } while (--h); +} + +static void glue(vga_draw_glyph9_, DEPTH)(uint8_t *d, int linesize, + const uint8_t *font_ptr, int h, + uint32_t fgcol, uint32_t bgcol, int dup9) +{ + uint32_t font_data, xorcol, v; + + xorcol = bgcol ^ fgcol; + do { + font_data = font_ptr[0]; +#if BPP == 1 + cpu_to_32wu((uint32_t *)d, (dmask16[(font_data >> 4)] & xorcol) ^ bgcol); + v = (dmask16[(font_data >> 0) & 0xf] & xorcol) ^ bgcol; + cpu_to_32wu(((uint32_t *)d)+1, v); + if (dup9) + ((uint8_t *)d)[8] = v >> (24 * (1 - BIG)); + else + ((uint8_t *)d)[8] = bgcol; + +#elif BPP == 2 + cpu_to_32wu(((uint32_t *)d)+0, (dmask4[(font_data >> 6)] & xorcol) ^ bgcol); + cpu_to_32wu(((uint32_t *)d)+1, (dmask4[(font_data >> 4) & 3] & xorcol) ^ bgcol); + cpu_to_32wu(((uint32_t *)d)+2, (dmask4[(font_data >> 2) & 3] & xorcol) ^ bgcol); + v = (dmask4[(font_data >> 0) & 3] & xorcol) ^ bgcol; + cpu_to_32wu(((uint32_t *)d)+3, v); + if (dup9) + ((uint16_t *)d)[8] = v >> (16 * (1 - BIG)); + else + ((uint16_t *)d)[8] = bgcol; +#else + ((uint32_t *)d)[0] = (-((font_data >> 7)) & xorcol) ^ bgcol; + ((uint32_t *)d)[1] = (-((font_data >> 6) & 1) & xorcol) ^ bgcol; + ((uint32_t *)d)[2] = (-((font_data >> 5) & 1) & xorcol) ^ bgcol; + ((uint32_t *)d)[3] = (-((font_data >> 4) & 1) & xorcol) ^ bgcol; + ((uint32_t *)d)[4] = (-((font_data >> 3) & 1) & xorcol) ^ bgcol; + ((uint32_t *)d)[5] = (-((font_data >> 2) & 1) & xorcol) ^ bgcol; + ((uint32_t *)d)[6] = (-((font_data >> 1) & 1) & xorcol) ^ bgcol; + v = (-((font_data >> 0) & 1) & xorcol) ^ bgcol; + ((uint32_t *)d)[7] = v; + if (dup9) + ((uint32_t *)d)[8] = v; + else + ((uint32_t *)d)[8] = bgcol; +#endif + font_ptr += 4; + d += linesize; + } while (--h); +} + +/* + * 4 color mode + */ +static void glue(vga_draw_line2_, DEPTH)(VGACommonState *s1, uint8_t *d, + const uint8_t *s, int width) +{ + uint32_t plane_mask, *palette, data, v; + int x; + + palette = s1->last_palette; + plane_mask = mask16[s1->ar[VGA_ATC_PLANE_ENABLE] & 0xf]; + width >>= 3; + for(x = 0; x < width; x++) { + data = ((uint32_t *)s)[0]; + data &= plane_mask; + v = expand2[GET_PLANE(data, 0)]; + v |= expand2[GET_PLANE(data, 2)] << 2; + ((PIXEL_TYPE *)d)[0] = palette[v >> 12]; + ((PIXEL_TYPE *)d)[1] = palette[(v >> 8) & 0xf]; + ((PIXEL_TYPE *)d)[2] = palette[(v >> 4) & 0xf]; + ((PIXEL_TYPE *)d)[3] = palette[(v >> 0) & 0xf]; + + v = expand2[GET_PLANE(data, 1)]; + v |= expand2[GET_PLANE(data, 3)] << 2; + ((PIXEL_TYPE *)d)[4] = palette[v >> 12]; + ((PIXEL_TYPE *)d)[5] = palette[(v >> 8) & 0xf]; + ((PIXEL_TYPE *)d)[6] = palette[(v >> 4) & 0xf]; + ((PIXEL_TYPE *)d)[7] = palette[(v >> 0) & 0xf]; + d += BPP * 8; + s += 4; + } +} + +#if BPP == 1 +#define PUT_PIXEL2(d, n, v) ((uint16_t *)d)[(n)] = (v) +#elif BPP == 2 +#define PUT_PIXEL2(d, n, v) ((uint32_t *)d)[(n)] = (v) +#else +#define PUT_PIXEL2(d, n, v) \ +((uint32_t *)d)[2*(n)] = ((uint32_t *)d)[2*(n)+1] = (v) +#endif + +/* + * 4 color mode, dup2 horizontal + */ +static void glue(vga_draw_line2d2_, DEPTH)(VGACommonState *s1, uint8_t *d, + const uint8_t *s, int width) +{ + uint32_t plane_mask, *palette, data, v; + int x; + + palette = s1->last_palette; + plane_mask = mask16[s1->ar[VGA_ATC_PLANE_ENABLE] & 0xf]; + width >>= 3; + for(x = 0; x < width; x++) { + data = ((uint32_t *)s)[0]; + data &= plane_mask; + v = expand2[GET_PLANE(data, 0)]; + v |= expand2[GET_PLANE(data, 2)] << 2; + PUT_PIXEL2(d, 0, palette[v >> 12]); + PUT_PIXEL2(d, 1, palette[(v >> 8) & 0xf]); + PUT_PIXEL2(d, 2, palette[(v >> 4) & 0xf]); + PUT_PIXEL2(d, 3, palette[(v >> 0) & 0xf]); + + v = expand2[GET_PLANE(data, 1)]; + v |= expand2[GET_PLANE(data, 3)] << 2; + PUT_PIXEL2(d, 4, palette[v >> 12]); + PUT_PIXEL2(d, 5, palette[(v >> 8) & 0xf]); + PUT_PIXEL2(d, 6, palette[(v >> 4) & 0xf]); + PUT_PIXEL2(d, 7, palette[(v >> 0) & 0xf]); + d += BPP * 16; + s += 4; + } +} + +/* + * 16 color mode + */ +static void glue(vga_draw_line4_, DEPTH)(VGACommonState *s1, uint8_t *d, + const uint8_t *s, int width) +{ + uint32_t plane_mask, data, v, *palette; + int x; + + palette = s1->last_palette; + plane_mask = mask16[s1->ar[VGA_ATC_PLANE_ENABLE] & 0xf]; + width >>= 3; + for(x = 0; x < width; x++) { + data = ((uint32_t *)s)[0]; + data &= plane_mask; + v = expand4[GET_PLANE(data, 0)]; + v |= expand4[GET_PLANE(data, 1)] << 1; + v |= expand4[GET_PLANE(data, 2)] << 2; + v |= expand4[GET_PLANE(data, 3)] << 3; + ((PIXEL_TYPE *)d)[0] = palette[v >> 28]; + ((PIXEL_TYPE *)d)[1] = palette[(v >> 24) & 0xf]; + ((PIXEL_TYPE *)d)[2] = palette[(v >> 20) & 0xf]; + ((PIXEL_TYPE *)d)[3] = palette[(v >> 16) & 0xf]; + ((PIXEL_TYPE *)d)[4] = palette[(v >> 12) & 0xf]; + ((PIXEL_TYPE *)d)[5] = palette[(v >> 8) & 0xf]; + ((PIXEL_TYPE *)d)[6] = palette[(v >> 4) & 0xf]; + ((PIXEL_TYPE *)d)[7] = palette[(v >> 0) & 0xf]; + d += BPP * 8; + s += 4; + } +} + +/* + * 16 color mode, dup2 horizontal + */ +static void glue(vga_draw_line4d2_, DEPTH)(VGACommonState *s1, uint8_t *d, + const uint8_t *s, int width) +{ + uint32_t plane_mask, data, v, *palette; + int x; + + palette = s1->last_palette; + plane_mask = mask16[s1->ar[VGA_ATC_PLANE_ENABLE] & 0xf]; + width >>= 3; + for(x = 0; x < width; x++) { + data = ((uint32_t *)s)[0]; + data &= plane_mask; + v = expand4[GET_PLANE(data, 0)]; + v |= expand4[GET_PLANE(data, 1)] << 1; + v |= expand4[GET_PLANE(data, 2)] << 2; + v |= expand4[GET_PLANE(data, 3)] << 3; + PUT_PIXEL2(d, 0, palette[v >> 28]); + PUT_PIXEL2(d, 1, palette[(v >> 24) & 0xf]); + PUT_PIXEL2(d, 2, palette[(v >> 20) & 0xf]); + PUT_PIXEL2(d, 3, palette[(v >> 16) & 0xf]); + PUT_PIXEL2(d, 4, palette[(v >> 12) & 0xf]); + PUT_PIXEL2(d, 5, palette[(v >> 8) & 0xf]); + PUT_PIXEL2(d, 6, palette[(v >> 4) & 0xf]); + PUT_PIXEL2(d, 7, palette[(v >> 0) & 0xf]); + d += BPP * 16; + s += 4; + } +} + +/* + * 256 color mode, double pixels + * + * XXX: add plane_mask support (never used in standard VGA modes) + */ +static void glue(vga_draw_line8d2_, DEPTH)(VGACommonState *s1, uint8_t *d, + const uint8_t *s, int width) +{ + uint32_t *palette; + int x; + + palette = s1->last_palette; + width >>= 3; + for(x = 0; x < width; x++) { + PUT_PIXEL2(d, 0, palette[s[0]]); + PUT_PIXEL2(d, 1, palette[s[1]]); + PUT_PIXEL2(d, 2, palette[s[2]]); + PUT_PIXEL2(d, 3, palette[s[3]]); + d += BPP * 8; + s += 4; + } +} + +/* + * standard 256 color mode + * + * XXX: add plane_mask support (never used in standard VGA modes) + */ +static void glue(vga_draw_line8_, DEPTH)(VGACommonState *s1, uint8_t *d, + const uint8_t *s, int width) +{ + uint32_t *palette; + int x; + + palette = s1->last_palette; + width >>= 3; + for(x = 0; x < width; x++) { + ((PIXEL_TYPE *)d)[0] = palette[s[0]]; + ((PIXEL_TYPE *)d)[1] = palette[s[1]]; + ((PIXEL_TYPE *)d)[2] = palette[s[2]]; + ((PIXEL_TYPE *)d)[3] = palette[s[3]]; + ((PIXEL_TYPE *)d)[4] = palette[s[4]]; + ((PIXEL_TYPE *)d)[5] = palette[s[5]]; + ((PIXEL_TYPE *)d)[6] = palette[s[6]]; + ((PIXEL_TYPE *)d)[7] = palette[s[7]]; + d += BPP * 8; + s += 8; + } +} + +#endif /* DEPTH != 15 */ + + +/* XXX: optimize */ + +/* + * 15 bit color + */ +static void glue(vga_draw_line15_, PIXEL_NAME)(VGACommonState *s1, uint8_t *d, + const uint8_t *s, int width) +{ +#if DEPTH == 15 && defined(HOST_WORDS_BIGENDIAN) == defined(TARGET_WORDS_BIGENDIAN) + memcpy(d, s, width * 2); +#else + int w; + uint32_t v, r, g, b; + + w = width; + do { + v = lduw_raw((void *)s); + r = (v >> 7) & 0xf8; + g = (v >> 2) & 0xf8; + b = (v << 3) & 0xf8; + ((PIXEL_TYPE *)d)[0] = glue(rgb_to_pixel, PIXEL_NAME)(r, g, b); + s += 2; + d += BPP; + } while (--w != 0); +#endif +} + +/* + * 16 bit color + */ +static void glue(vga_draw_line16_, PIXEL_NAME)(VGACommonState *s1, uint8_t *d, + const uint8_t *s, int width) +{ +#if DEPTH == 16 && defined(HOST_WORDS_BIGENDIAN) == defined(TARGET_WORDS_BIGENDIAN) + memcpy(d, s, width * 2); +#else + int w; + uint32_t v, r, g, b; + + w = width; + do { + v = lduw_raw((void *)s); + r = (v >> 8) & 0xf8; + g = (v >> 3) & 0xfc; + b = (v << 3) & 0xf8; + ((PIXEL_TYPE *)d)[0] = glue(rgb_to_pixel, PIXEL_NAME)(r, g, b); + s += 2; + d += BPP; + } while (--w != 0); +#endif +} + +/* + * 24 bit color + */ +static void glue(vga_draw_line24_, PIXEL_NAME)(VGACommonState *s1, uint8_t *d, + const uint8_t *s, int width) +{ + int w; + uint32_t r, g, b; + + w = width; + do { +#if defined(TARGET_WORDS_BIGENDIAN) + r = s[0]; + g = s[1]; + b = s[2]; +#else + b = s[0]; + g = s[1]; + r = s[2]; +#endif + ((PIXEL_TYPE *)d)[0] = glue(rgb_to_pixel, PIXEL_NAME)(r, g, b); + s += 3; + d += BPP; + } while (--w != 0); +} + +/* + * 32 bit color + */ +static void glue(vga_draw_line32_, PIXEL_NAME)(VGACommonState *s1, uint8_t *d, + const uint8_t *s, int width) +{ +#if DEPTH == 32 && defined(HOST_WORDS_BIGENDIAN) == defined(TARGET_WORDS_BIGENDIAN) && !defined(BGR_FORMAT) + memcpy(d, s, width * 4); +#else + int w; + uint32_t r, g, b; + + w = width; + do { +#if defined(TARGET_WORDS_BIGENDIAN) + r = s[1]; + g = s[2]; + b = s[3]; +#else + b = s[0]; + g = s[1]; + r = s[2]; +#endif + ((PIXEL_TYPE *)d)[0] = glue(rgb_to_pixel, PIXEL_NAME)(r, g, b); + s += 4; + d += BPP; + } while (--w != 0); +#endif +} + +#undef PUT_PIXEL2 +#undef DEPTH +#undef BPP +#undef PIXEL_TYPE +#undef PIXEL_NAME +#undef BGR_FORMAT diff --git a/hw/display/vmware_vga.c b/hw/display/vmware_vga.c new file mode 100644 index 0000000000..bcad47a68d --- /dev/null +++ b/hw/display/vmware_vga.c @@ -0,0 +1,1282 @@ +/* + * QEMU VMware-SVGA "chipset". + * + * Copyright (c) 2007 Andrzej Zaborowski <balrog@zabor.org> + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#include "hw/hw.h" +#include "hw/loader.h" +#include "ui/console.h" +#include "hw/pci/pci.h" + +#undef VERBOSE +#define HW_RECT_ACCEL +#define HW_FILL_ACCEL +#define HW_MOUSE_ACCEL + +#include "vga_int.h" + +/* See http://vmware-svga.sf.net/ for some documentation on VMWare SVGA */ + +struct vmsvga_state_s { + VGACommonState vga; + + int invalidated; + int depth; + int bypp; + int enable; + int config; + struct { + int id; + int x; + int y; + int on; + } cursor; + + int index; + int scratch_size; + uint32_t *scratch; + int new_width; + int new_height; + uint32_t guest; + uint32_t svgaid; + int syncing; + + MemoryRegion fifo_ram; + uint8_t *fifo_ptr; + unsigned int fifo_size; + + union { + uint32_t *fifo; + struct QEMU_PACKED { + uint32_t min; + uint32_t max; + uint32_t next_cmd; + uint32_t stop; + /* Add registers here when adding capabilities. */ + uint32_t fifo[0]; + } *cmd; + }; + +#define REDRAW_FIFO_LEN 512 + struct vmsvga_rect_s { + int x, y, w, h; + } redraw_fifo[REDRAW_FIFO_LEN]; + int redraw_fifo_first, redraw_fifo_last; +}; + +struct pci_vmsvga_state_s { + PCIDevice card; + struct vmsvga_state_s chip; + MemoryRegion io_bar; +}; + +#define SVGA_MAGIC 0x900000UL +#define SVGA_MAKE_ID(ver) (SVGA_MAGIC << 8 | (ver)) +#define SVGA_ID_0 SVGA_MAKE_ID(0) +#define SVGA_ID_1 SVGA_MAKE_ID(1) +#define SVGA_ID_2 SVGA_MAKE_ID(2) + +#define SVGA_LEGACY_BASE_PORT 0x4560 +#define SVGA_INDEX_PORT 0x0 +#define SVGA_VALUE_PORT 0x1 +#define SVGA_BIOS_PORT 0x2 + +#define SVGA_VERSION_2 + +#ifdef SVGA_VERSION_2 +# define SVGA_ID SVGA_ID_2 +# define SVGA_IO_BASE SVGA_LEGACY_BASE_PORT +# define SVGA_IO_MUL 1 +# define SVGA_FIFO_SIZE 0x10000 +# define SVGA_PCI_DEVICE_ID PCI_DEVICE_ID_VMWARE_SVGA2 +#else +# define SVGA_ID SVGA_ID_1 +# define SVGA_IO_BASE SVGA_LEGACY_BASE_PORT +# define SVGA_IO_MUL 4 +# define SVGA_FIFO_SIZE 0x10000 +# define SVGA_PCI_DEVICE_ID PCI_DEVICE_ID_VMWARE_SVGA +#endif + +enum { + /* ID 0, 1 and 2 registers */ + SVGA_REG_ID = 0, + SVGA_REG_ENABLE = 1, + SVGA_REG_WIDTH = 2, + SVGA_REG_HEIGHT = 3, + SVGA_REG_MAX_WIDTH = 4, + SVGA_REG_MAX_HEIGHT = 5, + SVGA_REG_DEPTH = 6, + SVGA_REG_BITS_PER_PIXEL = 7, /* Current bpp in the guest */ + SVGA_REG_PSEUDOCOLOR = 8, + SVGA_REG_RED_MASK = 9, + SVGA_REG_GREEN_MASK = 10, + SVGA_REG_BLUE_MASK = 11, + SVGA_REG_BYTES_PER_LINE = 12, + SVGA_REG_FB_START = 13, + SVGA_REG_FB_OFFSET = 14, + SVGA_REG_VRAM_SIZE = 15, + SVGA_REG_FB_SIZE = 16, + + /* ID 1 and 2 registers */ + SVGA_REG_CAPABILITIES = 17, + SVGA_REG_MEM_START = 18, /* Memory for command FIFO */ + SVGA_REG_MEM_SIZE = 19, + SVGA_REG_CONFIG_DONE = 20, /* Set when memory area configured */ + SVGA_REG_SYNC = 21, /* Write to force synchronization */ + SVGA_REG_BUSY = 22, /* Read to check if sync is done */ + SVGA_REG_GUEST_ID = 23, /* Set guest OS identifier */ + SVGA_REG_CURSOR_ID = 24, /* ID of cursor */ + SVGA_REG_CURSOR_X = 25, /* Set cursor X position */ + SVGA_REG_CURSOR_Y = 26, /* Set cursor Y position */ + SVGA_REG_CURSOR_ON = 27, /* Turn cursor on/off */ + SVGA_REG_HOST_BITS_PER_PIXEL = 28, /* Current bpp in the host */ + SVGA_REG_SCRATCH_SIZE = 29, /* Number of scratch registers */ + SVGA_REG_MEM_REGS = 30, /* Number of FIFO registers */ + SVGA_REG_NUM_DISPLAYS = 31, /* Number of guest displays */ + SVGA_REG_PITCHLOCK = 32, /* Fixed pitch for all modes */ + + SVGA_PALETTE_BASE = 1024, /* Base of SVGA color map */ + SVGA_PALETTE_END = SVGA_PALETTE_BASE + 767, + SVGA_SCRATCH_BASE = SVGA_PALETTE_BASE + 768, +}; + +#define SVGA_CAP_NONE 0 +#define SVGA_CAP_RECT_FILL (1 << 0) +#define SVGA_CAP_RECT_COPY (1 << 1) +#define SVGA_CAP_RECT_PAT_FILL (1 << 2) +#define SVGA_CAP_LEGACY_OFFSCREEN (1 << 3) +#define SVGA_CAP_RASTER_OP (1 << 4) +#define SVGA_CAP_CURSOR (1 << 5) +#define SVGA_CAP_CURSOR_BYPASS (1 << 6) +#define SVGA_CAP_CURSOR_BYPASS_2 (1 << 7) +#define SVGA_CAP_8BIT_EMULATION (1 << 8) +#define SVGA_CAP_ALPHA_CURSOR (1 << 9) +#define SVGA_CAP_GLYPH (1 << 10) +#define SVGA_CAP_GLYPH_CLIPPING (1 << 11) +#define SVGA_CAP_OFFSCREEN_1 (1 << 12) +#define SVGA_CAP_ALPHA_BLEND (1 << 13) +#define SVGA_CAP_3D (1 << 14) +#define SVGA_CAP_EXTENDED_FIFO (1 << 15) +#define SVGA_CAP_MULTIMON (1 << 16) +#define SVGA_CAP_PITCHLOCK (1 << 17) + +/* + * FIFO offsets (seen as an array of 32-bit words) + */ +enum { + /* + * The original defined FIFO offsets + */ + SVGA_FIFO_MIN = 0, + SVGA_FIFO_MAX, /* The distance from MIN to MAX must be at least 10K */ + SVGA_FIFO_NEXT_CMD, + SVGA_FIFO_STOP, + + /* + * Additional offsets added as of SVGA_CAP_EXTENDED_FIFO + */ + SVGA_FIFO_CAPABILITIES = 4, + SVGA_FIFO_FLAGS, + SVGA_FIFO_FENCE, + SVGA_FIFO_3D_HWVERSION, + SVGA_FIFO_PITCHLOCK, +}; + +#define SVGA_FIFO_CAP_NONE 0 +#define SVGA_FIFO_CAP_FENCE (1 << 0) +#define SVGA_FIFO_CAP_ACCELFRONT (1 << 1) +#define SVGA_FIFO_CAP_PITCHLOCK (1 << 2) + +#define SVGA_FIFO_FLAG_NONE 0 +#define SVGA_FIFO_FLAG_ACCELFRONT (1 << 0) + +/* These values can probably be changed arbitrarily. */ +#define SVGA_SCRATCH_SIZE 0x8000 +#define SVGA_MAX_WIDTH 2360 +#define SVGA_MAX_HEIGHT 1770 + +#ifdef VERBOSE +# define GUEST_OS_BASE 0x5001 +static const char *vmsvga_guest_id[] = { + [0x00] = "Dos", + [0x01] = "Windows 3.1", + [0x02] = "Windows 95", + [0x03] = "Windows 98", + [0x04] = "Windows ME", + [0x05] = "Windows NT", + [0x06] = "Windows 2000", + [0x07] = "Linux", + [0x08] = "OS/2", + [0x09] = "an unknown OS", + [0x0a] = "BSD", + [0x0b] = "Whistler", + [0x0c] = "an unknown OS", + [0x0d] = "an unknown OS", + [0x0e] = "an unknown OS", + [0x0f] = "an unknown OS", + [0x10] = "an unknown OS", + [0x11] = "an unknown OS", + [0x12] = "an unknown OS", + [0x13] = "an unknown OS", + [0x14] = "an unknown OS", + [0x15] = "Windows 2003", +}; +#endif + +enum { + SVGA_CMD_INVALID_CMD = 0, + SVGA_CMD_UPDATE = 1, + SVGA_CMD_RECT_FILL = 2, + SVGA_CMD_RECT_COPY = 3, + SVGA_CMD_DEFINE_BITMAP = 4, + SVGA_CMD_DEFINE_BITMAP_SCANLINE = 5, + SVGA_CMD_DEFINE_PIXMAP = 6, + SVGA_CMD_DEFINE_PIXMAP_SCANLINE = 7, + SVGA_CMD_RECT_BITMAP_FILL = 8, + SVGA_CMD_RECT_PIXMAP_FILL = 9, + SVGA_CMD_RECT_BITMAP_COPY = 10, + SVGA_CMD_RECT_PIXMAP_COPY = 11, + SVGA_CMD_FREE_OBJECT = 12, + SVGA_CMD_RECT_ROP_FILL = 13, + SVGA_CMD_RECT_ROP_COPY = 14, + SVGA_CMD_RECT_ROP_BITMAP_FILL = 15, + SVGA_CMD_RECT_ROP_PIXMAP_FILL = 16, + SVGA_CMD_RECT_ROP_BITMAP_COPY = 17, + SVGA_CMD_RECT_ROP_PIXMAP_COPY = 18, + SVGA_CMD_DEFINE_CURSOR = 19, + SVGA_CMD_DISPLAY_CURSOR = 20, + SVGA_CMD_MOVE_CURSOR = 21, + SVGA_CMD_DEFINE_ALPHA_CURSOR = 22, + SVGA_CMD_DRAW_GLYPH = 23, + SVGA_CMD_DRAW_GLYPH_CLIPPED = 24, + SVGA_CMD_UPDATE_VERBOSE = 25, + SVGA_CMD_SURFACE_FILL = 26, + SVGA_CMD_SURFACE_COPY = 27, + SVGA_CMD_SURFACE_ALPHA_BLEND = 28, + SVGA_CMD_FRONT_ROP_FILL = 29, + SVGA_CMD_FENCE = 30, +}; + +/* Legal values for the SVGA_REG_CURSOR_ON register in cursor bypass mode */ +enum { + SVGA_CURSOR_ON_HIDE = 0, + SVGA_CURSOR_ON_SHOW = 1, + SVGA_CURSOR_ON_REMOVE_FROM_FB = 2, + SVGA_CURSOR_ON_RESTORE_TO_FB = 3, +}; + +static inline void vmsvga_update_rect(struct vmsvga_state_s *s, + int x, int y, int w, int h) +{ + DisplaySurface *surface = qemu_console_surface(s->vga.con); + int line; + int bypl; + int width; + int start; + uint8_t *src; + uint8_t *dst; + + if (x < 0) { + fprintf(stderr, "%s: update x was < 0 (%d)\n", __func__, x); + w += x; + x = 0; + } + if (w < 0) { + fprintf(stderr, "%s: update w was < 0 (%d)\n", __func__, w); + w = 0; + } + if (x + w > surface_width(surface)) { + fprintf(stderr, "%s: update width too large x: %d, w: %d\n", + __func__, x, w); + x = MIN(x, surface_width(surface)); + w = surface_width(surface) - x; + } + + if (y < 0) { + fprintf(stderr, "%s: update y was < 0 (%d)\n", __func__, y); + h += y; + y = 0; + } + if (h < 0) { + fprintf(stderr, "%s: update h was < 0 (%d)\n", __func__, h); + h = 0; + } + if (y + h > surface_height(surface)) { + fprintf(stderr, "%s: update height too large y: %d, h: %d\n", + __func__, y, h); + y = MIN(y, surface_height(surface)); + h = surface_height(surface) - y; + } + + bypl = surface_stride(surface); + width = surface_bytes_per_pixel(surface) * w; + start = surface_bytes_per_pixel(surface) * x + bypl * y; + src = s->vga.vram_ptr + start; + dst = surface_data(surface) + start; + + for (line = h; line > 0; line--, src += bypl, dst += bypl) { + memcpy(dst, src, width); + } + dpy_gfx_update(s->vga.con, x, y, w, h); +} + +static inline void vmsvga_update_rect_delayed(struct vmsvga_state_s *s, + int x, int y, int w, int h) +{ + struct vmsvga_rect_s *rect = &s->redraw_fifo[s->redraw_fifo_last++]; + + s->redraw_fifo_last &= REDRAW_FIFO_LEN - 1; + rect->x = x; + rect->y = y; + rect->w = w; + rect->h = h; +} + +static inline void vmsvga_update_rect_flush(struct vmsvga_state_s *s) +{ + struct vmsvga_rect_s *rect; + + if (s->invalidated) { + s->redraw_fifo_first = s->redraw_fifo_last; + return; + } + /* Overlapping region updates can be optimised out here - if someone + * knows a smart algorithm to do that, please share. */ + while (s->redraw_fifo_first != s->redraw_fifo_last) { + rect = &s->redraw_fifo[s->redraw_fifo_first++]; + s->redraw_fifo_first &= REDRAW_FIFO_LEN - 1; + vmsvga_update_rect(s, rect->x, rect->y, rect->w, rect->h); + } +} + +#ifdef HW_RECT_ACCEL +static inline void vmsvga_copy_rect(struct vmsvga_state_s *s, + int x0, int y0, int x1, int y1, int w, int h) +{ + DisplaySurface *surface = qemu_console_surface(s->vga.con); + uint8_t *vram = s->vga.vram_ptr; + int bypl = surface_stride(surface); + int bypp = surface_bytes_per_pixel(surface); + int width = bypp * w; + int line = h; + uint8_t *ptr[2]; + + if (y1 > y0) { + ptr[0] = vram + bypp * x0 + bypl * (y0 + h - 1); + ptr[1] = vram + bypp * x1 + bypl * (y1 + h - 1); + for (; line > 0; line --, ptr[0] -= bypl, ptr[1] -= bypl) { + memmove(ptr[1], ptr[0], width); + } + } else { + ptr[0] = vram + bypp * x0 + bypl * y0; + ptr[1] = vram + bypp * x1 + bypl * y1; + for (; line > 0; line --, ptr[0] += bypl, ptr[1] += bypl) { + memmove(ptr[1], ptr[0], width); + } + } + + vmsvga_update_rect_delayed(s, x1, y1, w, h); +} +#endif + +#ifdef HW_FILL_ACCEL +static inline void vmsvga_fill_rect(struct vmsvga_state_s *s, + uint32_t c, int x, int y, int w, int h) +{ + DisplaySurface *surface = qemu_console_surface(s->vga.con); + int bypl = surface_stride(surface); + int width = surface_bytes_per_pixel(surface) * w; + int line = h; + int column; + uint8_t *fst; + uint8_t *dst; + uint8_t *src; + uint8_t col[4]; + + col[0] = c; + col[1] = c >> 8; + col[2] = c >> 16; + col[3] = c >> 24; + + fst = s->vga.vram_ptr + surface_bytes_per_pixel(surface) * x + bypl * y; + + if (line--) { + dst = fst; + src = col; + for (column = width; column > 0; column--) { + *(dst++) = *(src++); + if (src - col == surface_bytes_per_pixel(surface)) { + src = col; + } + } + dst = fst; + for (; line > 0; line--) { + dst += bypl; + memcpy(dst, fst, width); + } + } + + vmsvga_update_rect_delayed(s, x, y, w, h); +} +#endif + +struct vmsvga_cursor_definition_s { + int width; + int height; + int id; + int bpp; + int hot_x; + int hot_y; + uint32_t mask[1024]; + uint32_t image[4096]; +}; + +#define SVGA_BITMAP_SIZE(w, h) ((((w) + 31) >> 5) * (h)) +#define SVGA_PIXMAP_SIZE(w, h, bpp) (((((w) * (bpp)) + 31) >> 5) * (h)) + +#ifdef HW_MOUSE_ACCEL +static inline void vmsvga_cursor_define(struct vmsvga_state_s *s, + struct vmsvga_cursor_definition_s *c) +{ + QEMUCursor *qc; + int i, pixels; + + qc = cursor_alloc(c->width, c->height); + qc->hot_x = c->hot_x; + qc->hot_y = c->hot_y; + switch (c->bpp) { + case 1: + cursor_set_mono(qc, 0xffffff, 0x000000, (void *)c->image, + 1, (void *)c->mask); +#ifdef DEBUG + cursor_print_ascii_art(qc, "vmware/mono"); +#endif + break; + case 32: + /* fill alpha channel from mask, set color to zero */ + cursor_set_mono(qc, 0x000000, 0x000000, (void *)c->mask, + 1, (void *)c->mask); + /* add in rgb values */ + pixels = c->width * c->height; + for (i = 0; i < pixels; i++) { + qc->data[i] |= c->image[i] & 0xffffff; + } +#ifdef DEBUG + cursor_print_ascii_art(qc, "vmware/32bit"); +#endif + break; + default: + fprintf(stderr, "%s: unhandled bpp %d, using fallback cursor\n", + __func__, c->bpp); + cursor_put(qc); + qc = cursor_builtin_left_ptr(); + } + + dpy_cursor_define(s->vga.con, qc); + cursor_put(qc); +} +#endif + +#define CMD(f) le32_to_cpu(s->cmd->f) + +static inline int vmsvga_fifo_length(struct vmsvga_state_s *s) +{ + int num; + + if (!s->config || !s->enable) { + return 0; + } + num = CMD(next_cmd) - CMD(stop); + if (num < 0) { + num += CMD(max) - CMD(min); + } + return num >> 2; +} + +static inline uint32_t vmsvga_fifo_read_raw(struct vmsvga_state_s *s) +{ + uint32_t cmd = s->fifo[CMD(stop) >> 2]; + + s->cmd->stop = cpu_to_le32(CMD(stop) + 4); + if (CMD(stop) >= CMD(max)) { + s->cmd->stop = s->cmd->min; + } + return cmd; +} + +static inline uint32_t vmsvga_fifo_read(struct vmsvga_state_s *s) +{ + return le32_to_cpu(vmsvga_fifo_read_raw(s)); +} + +static void vmsvga_fifo_run(struct vmsvga_state_s *s) +{ + uint32_t cmd, colour; + int args, len; + int x, y, dx, dy, width, height; + struct vmsvga_cursor_definition_s cursor; + uint32_t cmd_start; + + len = vmsvga_fifo_length(s); + while (len > 0) { + /* May need to go back to the start of the command if incomplete */ + cmd_start = s->cmd->stop; + + switch (cmd = vmsvga_fifo_read(s)) { + case SVGA_CMD_UPDATE: + case SVGA_CMD_UPDATE_VERBOSE: + len -= 5; + if (len < 0) { + goto rewind; + } + + x = vmsvga_fifo_read(s); + y = vmsvga_fifo_read(s); + width = vmsvga_fifo_read(s); + height = vmsvga_fifo_read(s); + vmsvga_update_rect_delayed(s, x, y, width, height); + break; + + case SVGA_CMD_RECT_FILL: + len -= 6; + if (len < 0) { + goto rewind; + } + + colour = vmsvga_fifo_read(s); + x = vmsvga_fifo_read(s); + y = vmsvga_fifo_read(s); + width = vmsvga_fifo_read(s); + height = vmsvga_fifo_read(s); +#ifdef HW_FILL_ACCEL + vmsvga_fill_rect(s, colour, x, y, width, height); + break; +#else + args = 0; + goto badcmd; +#endif + + case SVGA_CMD_RECT_COPY: + len -= 7; + if (len < 0) { + goto rewind; + } + + x = vmsvga_fifo_read(s); + y = vmsvga_fifo_read(s); + dx = vmsvga_fifo_read(s); + dy = vmsvga_fifo_read(s); + width = vmsvga_fifo_read(s); + height = vmsvga_fifo_read(s); +#ifdef HW_RECT_ACCEL + vmsvga_copy_rect(s, x, y, dx, dy, width, height); + break; +#else + args = 0; + goto badcmd; +#endif + + case SVGA_CMD_DEFINE_CURSOR: + len -= 8; + if (len < 0) { + goto rewind; + } + + cursor.id = vmsvga_fifo_read(s); + cursor.hot_x = vmsvga_fifo_read(s); + cursor.hot_y = vmsvga_fifo_read(s); + cursor.width = x = vmsvga_fifo_read(s); + cursor.height = y = vmsvga_fifo_read(s); + vmsvga_fifo_read(s); + cursor.bpp = vmsvga_fifo_read(s); + + args = SVGA_BITMAP_SIZE(x, y) + SVGA_PIXMAP_SIZE(x, y, cursor.bpp); + if (SVGA_BITMAP_SIZE(x, y) > sizeof cursor.mask || + SVGA_PIXMAP_SIZE(x, y, cursor.bpp) > sizeof cursor.image) { + goto badcmd; + } + + len -= args; + if (len < 0) { + goto rewind; + } + + for (args = 0; args < SVGA_BITMAP_SIZE(x, y); args++) { + cursor.mask[args] = vmsvga_fifo_read_raw(s); + } + for (args = 0; args < SVGA_PIXMAP_SIZE(x, y, cursor.bpp); args++) { + cursor.image[args] = vmsvga_fifo_read_raw(s); + } +#ifdef HW_MOUSE_ACCEL + vmsvga_cursor_define(s, &cursor); + break; +#else + args = 0; + goto badcmd; +#endif + + /* + * Other commands that we at least know the number of arguments + * for so we can avoid FIFO desync if driver uses them illegally. + */ + case SVGA_CMD_DEFINE_ALPHA_CURSOR: + len -= 6; + if (len < 0) { + goto rewind; + } + vmsvga_fifo_read(s); + vmsvga_fifo_read(s); + vmsvga_fifo_read(s); + x = vmsvga_fifo_read(s); + y = vmsvga_fifo_read(s); + args = x * y; + goto badcmd; + case SVGA_CMD_RECT_ROP_FILL: + args = 6; + goto badcmd; + case SVGA_CMD_RECT_ROP_COPY: + args = 7; + goto badcmd; + case SVGA_CMD_DRAW_GLYPH_CLIPPED: + len -= 4; + if (len < 0) { + goto rewind; + } + vmsvga_fifo_read(s); + vmsvga_fifo_read(s); + args = 7 + (vmsvga_fifo_read(s) >> 2); + goto badcmd; + case SVGA_CMD_SURFACE_ALPHA_BLEND: + args = 12; + goto badcmd; + + /* + * Other commands that are not listed as depending on any + * CAPABILITIES bits, but are not described in the README either. + */ + case SVGA_CMD_SURFACE_FILL: + case SVGA_CMD_SURFACE_COPY: + case SVGA_CMD_FRONT_ROP_FILL: + case SVGA_CMD_FENCE: + case SVGA_CMD_INVALID_CMD: + break; /* Nop */ + + default: + args = 0; + badcmd: + len -= args; + if (len < 0) { + goto rewind; + } + while (args--) { + vmsvga_fifo_read(s); + } + printf("%s: Unknown command 0x%02x in SVGA command FIFO\n", + __func__, cmd); + break; + + rewind: + s->cmd->stop = cmd_start; + break; + } + } + + s->syncing = 0; +} + +static uint32_t vmsvga_index_read(void *opaque, uint32_t address) +{ + struct vmsvga_state_s *s = opaque; + + return s->index; +} + +static void vmsvga_index_write(void *opaque, uint32_t address, uint32_t index) +{ + struct vmsvga_state_s *s = opaque; + + s->index = index; +} + +static uint32_t vmsvga_value_read(void *opaque, uint32_t address) +{ + uint32_t caps; + struct vmsvga_state_s *s = opaque; + DisplaySurface *surface = qemu_console_surface(s->vga.con); + + switch (s->index) { + case SVGA_REG_ID: + return s->svgaid; + + case SVGA_REG_ENABLE: + return s->enable; + + case SVGA_REG_WIDTH: + return surface_width(surface); + + case SVGA_REG_HEIGHT: + return surface_height(surface); + + case SVGA_REG_MAX_WIDTH: + return SVGA_MAX_WIDTH; + + case SVGA_REG_MAX_HEIGHT: + return SVGA_MAX_HEIGHT; + + case SVGA_REG_DEPTH: + return s->depth; + + case SVGA_REG_BITS_PER_PIXEL: + return (s->depth + 7) & ~7; + + case SVGA_REG_PSEUDOCOLOR: + return 0x0; + + case SVGA_REG_RED_MASK: + return surface->pf.rmask; + + case SVGA_REG_GREEN_MASK: + return surface->pf.gmask; + + case SVGA_REG_BLUE_MASK: + return surface->pf.bmask; + + case SVGA_REG_BYTES_PER_LINE: + return s->bypp * s->new_width; + + case SVGA_REG_FB_START: { + struct pci_vmsvga_state_s *pci_vmsvga + = container_of(s, struct pci_vmsvga_state_s, chip); + return pci_get_bar_addr(&pci_vmsvga->card, 1); + } + + case SVGA_REG_FB_OFFSET: + return 0x0; + + case SVGA_REG_VRAM_SIZE: + return s->vga.vram_size; /* No physical VRAM besides the framebuffer */ + + case SVGA_REG_FB_SIZE: + return s->vga.vram_size; + + case SVGA_REG_CAPABILITIES: + caps = SVGA_CAP_NONE; +#ifdef HW_RECT_ACCEL + caps |= SVGA_CAP_RECT_COPY; +#endif +#ifdef HW_FILL_ACCEL + caps |= SVGA_CAP_RECT_FILL; +#endif +#ifdef HW_MOUSE_ACCEL + if (dpy_cursor_define_supported(s->vga.con)) { + caps |= SVGA_CAP_CURSOR | SVGA_CAP_CURSOR_BYPASS_2 | + SVGA_CAP_CURSOR_BYPASS; + } +#endif + return caps; + + case SVGA_REG_MEM_START: { + struct pci_vmsvga_state_s *pci_vmsvga + = container_of(s, struct pci_vmsvga_state_s, chip); + return pci_get_bar_addr(&pci_vmsvga->card, 2); + } + + case SVGA_REG_MEM_SIZE: + return s->fifo_size; + + case SVGA_REG_CONFIG_DONE: + return s->config; + + case SVGA_REG_SYNC: + case SVGA_REG_BUSY: + return s->syncing; + + case SVGA_REG_GUEST_ID: + return s->guest; + + case SVGA_REG_CURSOR_ID: + return s->cursor.id; + + case SVGA_REG_CURSOR_X: + return s->cursor.x; + + case SVGA_REG_CURSOR_Y: + return s->cursor.x; + + case SVGA_REG_CURSOR_ON: + return s->cursor.on; + + case SVGA_REG_HOST_BITS_PER_PIXEL: + return (s->depth + 7) & ~7; + + case SVGA_REG_SCRATCH_SIZE: + return s->scratch_size; + + case SVGA_REG_MEM_REGS: + case SVGA_REG_NUM_DISPLAYS: + case SVGA_REG_PITCHLOCK: + case SVGA_PALETTE_BASE ... SVGA_PALETTE_END: + return 0; + + default: + if (s->index >= SVGA_SCRATCH_BASE && + s->index < SVGA_SCRATCH_BASE + s->scratch_size) { + return s->scratch[s->index - SVGA_SCRATCH_BASE]; + } + printf("%s: Bad register %02x\n", __func__, s->index); + } + + return 0; +} + +static void vmsvga_value_write(void *opaque, uint32_t address, uint32_t value) +{ + struct vmsvga_state_s *s = opaque; + + switch (s->index) { + case SVGA_REG_ID: + if (value == SVGA_ID_2 || value == SVGA_ID_1 || value == SVGA_ID_0) { + s->svgaid = value; + } + break; + + case SVGA_REG_ENABLE: + s->enable = !!value; + s->invalidated = 1; + s->vga.invalidate(&s->vga); + if (s->enable && s->config) { + vga_dirty_log_stop(&s->vga); + } else { + vga_dirty_log_start(&s->vga); + } + break; + + case SVGA_REG_WIDTH: + if (value <= SVGA_MAX_WIDTH) { + s->new_width = value; + s->invalidated = 1; + } else { + printf("%s: Bad width: %i\n", __func__, value); + } + break; + + case SVGA_REG_HEIGHT: + if (value <= SVGA_MAX_HEIGHT) { + s->new_height = value; + s->invalidated = 1; + } else { + printf("%s: Bad height: %i\n", __func__, value); + } + break; + + case SVGA_REG_BITS_PER_PIXEL: + if (value != s->depth) { + printf("%s: Bad bits per pixel: %i bits\n", __func__, value); + s->config = 0; + } + break; + + case SVGA_REG_CONFIG_DONE: + if (value) { + s->fifo = (uint32_t *) s->fifo_ptr; + /* Check range and alignment. */ + if ((CMD(min) | CMD(max) | CMD(next_cmd) | CMD(stop)) & 3) { + break; + } + if (CMD(min) < (uint8_t *) s->cmd->fifo - (uint8_t *) s->fifo) { + break; + } + if (CMD(max) > SVGA_FIFO_SIZE) { + break; + } + if (CMD(max) < CMD(min) + 10 * 1024) { + break; + } + vga_dirty_log_stop(&s->vga); + } + s->config = !!value; + break; + + case SVGA_REG_SYNC: + s->syncing = 1; + vmsvga_fifo_run(s); /* Or should we just wait for update_display? */ + break; + + case SVGA_REG_GUEST_ID: + s->guest = value; +#ifdef VERBOSE + if (value >= GUEST_OS_BASE && value < GUEST_OS_BASE + + ARRAY_SIZE(vmsvga_guest_id)) { + printf("%s: guest runs %s.\n", __func__, + vmsvga_guest_id[value - GUEST_OS_BASE]); + } +#endif + break; + + case SVGA_REG_CURSOR_ID: + s->cursor.id = value; + break; + + case SVGA_REG_CURSOR_X: + s->cursor.x = value; + break; + + case SVGA_REG_CURSOR_Y: + s->cursor.y = value; + break; + + case SVGA_REG_CURSOR_ON: + s->cursor.on |= (value == SVGA_CURSOR_ON_SHOW); + s->cursor.on &= (value != SVGA_CURSOR_ON_HIDE); +#ifdef HW_MOUSE_ACCEL + if (value <= SVGA_CURSOR_ON_SHOW) { + dpy_mouse_set(s->vga.con, s->cursor.x, s->cursor.y, s->cursor.on); + } +#endif + break; + + case SVGA_REG_DEPTH: + case SVGA_REG_MEM_REGS: + case SVGA_REG_NUM_DISPLAYS: + case SVGA_REG_PITCHLOCK: + case SVGA_PALETTE_BASE ... SVGA_PALETTE_END: + break; + + default: + if (s->index >= SVGA_SCRATCH_BASE && + s->index < SVGA_SCRATCH_BASE + s->scratch_size) { + s->scratch[s->index - SVGA_SCRATCH_BASE] = value; + break; + } + printf("%s: Bad register %02x\n", __func__, s->index); + } +} + +static uint32_t vmsvga_bios_read(void *opaque, uint32_t address) +{ + printf("%s: what are we supposed to return?\n", __func__); + return 0xcafe; +} + +static void vmsvga_bios_write(void *opaque, uint32_t address, uint32_t data) +{ + printf("%s: what are we supposed to do with (%08x)?\n", __func__, data); +} + +static inline void vmsvga_check_size(struct vmsvga_state_s *s) +{ + DisplaySurface *surface = qemu_console_surface(s->vga.con); + + if (s->new_width != surface_width(surface) || + s->new_height != surface_height(surface)) { + qemu_console_resize(s->vga.con, s->new_width, s->new_height); + s->invalidated = 1; + } +} + +static void vmsvga_update_display(void *opaque) +{ + struct vmsvga_state_s *s = opaque; + DisplaySurface *surface = qemu_console_surface(s->vga.con); + bool dirty = false; + + if (!s->enable) { + s->vga.update(&s->vga); + return; + } + + vmsvga_check_size(s); + + vmsvga_fifo_run(s); + vmsvga_update_rect_flush(s); + + /* + * Is it more efficient to look at vram VGA-dirty bits or wait + * for the driver to issue SVGA_CMD_UPDATE? + */ + if (memory_region_is_logging(&s->vga.vram)) { + vga_sync_dirty_bitmap(&s->vga); + dirty = memory_region_get_dirty(&s->vga.vram, 0, + surface_stride(surface) * surface_height(surface), + DIRTY_MEMORY_VGA); + } + if (s->invalidated || dirty) { + s->invalidated = 0; + memcpy(surface_data(surface), s->vga.vram_ptr, + surface_stride(surface) * surface_height(surface)); + dpy_gfx_update(s->vga.con, 0, 0, + surface_width(surface), surface_height(surface)); + } + if (dirty) { + memory_region_reset_dirty(&s->vga.vram, 0, + surface_stride(surface) * surface_height(surface), + DIRTY_MEMORY_VGA); + } +} + +static void vmsvga_reset(DeviceState *dev) +{ + struct pci_vmsvga_state_s *pci = + DO_UPCAST(struct pci_vmsvga_state_s, card.qdev, dev); + struct vmsvga_state_s *s = &pci->chip; + + s->index = 0; + s->enable = 0; + s->config = 0; + s->svgaid = SVGA_ID; + s->cursor.on = 0; + s->redraw_fifo_first = 0; + s->redraw_fifo_last = 0; + s->syncing = 0; + + vga_dirty_log_start(&s->vga); +} + +static void vmsvga_invalidate_display(void *opaque) +{ + struct vmsvga_state_s *s = opaque; + if (!s->enable) { + s->vga.invalidate(&s->vga); + return; + } + + s->invalidated = 1; +} + +/* save the vga display in a PPM image even if no display is + available */ +static void vmsvga_screen_dump(void *opaque, const char *filename, bool cswitch, + Error **errp) +{ + struct vmsvga_state_s *s = opaque; + DisplaySurface *surface = qemu_console_surface(s->vga.con); + + if (!s->enable) { + s->vga.screen_dump(&s->vga, filename, cswitch, errp); + return; + } + + if (surface_bits_per_pixel(surface) == 32) { + DisplaySurface *ds = qemu_create_displaysurface_from( + surface_width(surface), + surface_height(surface), + 32, + surface_stride(surface), + s->vga.vram_ptr, false); + ppm_save(filename, ds, errp); + g_free(ds); + } +} + +static void vmsvga_text_update(void *opaque, console_ch_t *chardata) +{ + struct vmsvga_state_s *s = opaque; + + if (s->vga.text_update) { + s->vga.text_update(&s->vga, chardata); + } +} + +static int vmsvga_post_load(void *opaque, int version_id) +{ + struct vmsvga_state_s *s = opaque; + + s->invalidated = 1; + if (s->config) { + s->fifo = (uint32_t *) s->fifo_ptr; + } + return 0; +} + +static const VMStateDescription vmstate_vmware_vga_internal = { + .name = "vmware_vga_internal", + .version_id = 0, + .minimum_version_id = 0, + .minimum_version_id_old = 0, + .post_load = vmsvga_post_load, + .fields = (VMStateField[]) { + VMSTATE_INT32_EQUAL(depth, struct vmsvga_state_s), + VMSTATE_INT32(enable, struct vmsvga_state_s), + VMSTATE_INT32(config, struct vmsvga_state_s), + VMSTATE_INT32(cursor.id, struct vmsvga_state_s), + VMSTATE_INT32(cursor.x, struct vmsvga_state_s), + VMSTATE_INT32(cursor.y, struct vmsvga_state_s), + VMSTATE_INT32(cursor.on, struct vmsvga_state_s), + VMSTATE_INT32(index, struct vmsvga_state_s), + VMSTATE_VARRAY_INT32(scratch, struct vmsvga_state_s, + scratch_size, 0, vmstate_info_uint32, uint32_t), + VMSTATE_INT32(new_width, struct vmsvga_state_s), + VMSTATE_INT32(new_height, struct vmsvga_state_s), + VMSTATE_UINT32(guest, struct vmsvga_state_s), + VMSTATE_UINT32(svgaid, struct vmsvga_state_s), + VMSTATE_INT32(syncing, struct vmsvga_state_s), + VMSTATE_UNUSED(4), /* was fb_size */ + VMSTATE_END_OF_LIST() + } +}; + +static const VMStateDescription vmstate_vmware_vga = { + .name = "vmware_vga", + .version_id = 0, + .minimum_version_id = 0, + .minimum_version_id_old = 0, + .fields = (VMStateField[]) { + VMSTATE_PCI_DEVICE(card, struct pci_vmsvga_state_s), + VMSTATE_STRUCT(chip, struct pci_vmsvga_state_s, 0, + vmstate_vmware_vga_internal, struct vmsvga_state_s), + VMSTATE_END_OF_LIST() + } +}; + +static void vmsvga_init(struct vmsvga_state_s *s, + MemoryRegion *address_space, MemoryRegion *io) +{ + DisplaySurface *surface; + + s->scratch_size = SVGA_SCRATCH_SIZE; + s->scratch = g_malloc(s->scratch_size * 4); + + s->vga.con = graphic_console_init(vmsvga_update_display, + vmsvga_invalidate_display, + vmsvga_screen_dump, + vmsvga_text_update, s); + surface = qemu_console_surface(s->vga.con); + + s->fifo_size = SVGA_FIFO_SIZE; + memory_region_init_ram(&s->fifo_ram, "vmsvga.fifo", s->fifo_size); + vmstate_register_ram_global(&s->fifo_ram); + s->fifo_ptr = memory_region_get_ram_ptr(&s->fifo_ram); + + vga_common_init(&s->vga); + vga_init(&s->vga, address_space, io, true); + vmstate_register(NULL, 0, &vmstate_vga_common, &s->vga); + /* Save some values here in case they are changed later. + * This is suspicious and needs more though why it is needed. */ + s->depth = surface_bits_per_pixel(surface); + s->bypp = surface_bytes_per_pixel(surface); +} + +static uint64_t vmsvga_io_read(void *opaque, hwaddr addr, unsigned size) +{ + struct vmsvga_state_s *s = opaque; + + switch (addr) { + case SVGA_IO_MUL * SVGA_INDEX_PORT: return vmsvga_index_read(s, addr); + case SVGA_IO_MUL * SVGA_VALUE_PORT: return vmsvga_value_read(s, addr); + case SVGA_IO_MUL * SVGA_BIOS_PORT: return vmsvga_bios_read(s, addr); + default: return -1u; + } +} + +static void vmsvga_io_write(void *opaque, hwaddr addr, + uint64_t data, unsigned size) +{ + struct vmsvga_state_s *s = opaque; + + switch (addr) { + case SVGA_IO_MUL * SVGA_INDEX_PORT: + vmsvga_index_write(s, addr, data); + break; + case SVGA_IO_MUL * SVGA_VALUE_PORT: + vmsvga_value_write(s, addr, data); + break; + case SVGA_IO_MUL * SVGA_BIOS_PORT: + vmsvga_bios_write(s, addr, data); + break; + } +} + +static const MemoryRegionOps vmsvga_io_ops = { + .read = vmsvga_io_read, + .write = vmsvga_io_write, + .endianness = DEVICE_LITTLE_ENDIAN, + .valid = { + .min_access_size = 4, + .max_access_size = 4, + }, +}; + +static int pci_vmsvga_initfn(PCIDevice *dev) +{ + struct pci_vmsvga_state_s *s = + DO_UPCAST(struct pci_vmsvga_state_s, card, dev); + + s->card.config[PCI_CACHE_LINE_SIZE] = 0x08; /* Cache line size */ + s->card.config[PCI_LATENCY_TIMER] = 0x40; /* Latency timer */ + s->card.config[PCI_INTERRUPT_LINE] = 0xff; /* End */ + + memory_region_init_io(&s->io_bar, &vmsvga_io_ops, &s->chip, + "vmsvga-io", 0x10); + memory_region_set_flush_coalesced(&s->io_bar); + pci_register_bar(&s->card, 0, PCI_BASE_ADDRESS_SPACE_IO, &s->io_bar); + + vmsvga_init(&s->chip, pci_address_space(dev), pci_address_space_io(dev)); + + pci_register_bar(&s->card, 1, PCI_BASE_ADDRESS_MEM_PREFETCH, + &s->chip.vga.vram); + pci_register_bar(&s->card, 2, PCI_BASE_ADDRESS_MEM_PREFETCH, + &s->chip.fifo_ram); + + if (!dev->rom_bar) { + /* compatibility with pc-0.13 and older */ + vga_init_vbe(&s->chip.vga, pci_address_space(dev)); + } + + return 0; +} + +static Property vga_vmware_properties[] = { + DEFINE_PROP_UINT32("vgamem_mb", struct pci_vmsvga_state_s, + chip.vga.vram_size_mb, 16), + DEFINE_PROP_END_OF_LIST(), +}; + +static void vmsvga_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + PCIDeviceClass *k = PCI_DEVICE_CLASS(klass); + + k->no_hotplug = 1; + k->init = pci_vmsvga_initfn; + k->romfile = "vgabios-vmware.bin"; + k->vendor_id = PCI_VENDOR_ID_VMWARE; + k->device_id = SVGA_PCI_DEVICE_ID; + k->class_id = PCI_CLASS_DISPLAY_VGA; + k->subsystem_vendor_id = PCI_VENDOR_ID_VMWARE; + k->subsystem_id = SVGA_PCI_DEVICE_ID; + dc->reset = vmsvga_reset; + dc->vmsd = &vmstate_vmware_vga; + dc->props = vga_vmware_properties; +} + +static const TypeInfo vmsvga_info = { + .name = "vmware-svga", + .parent = TYPE_PCI_DEVICE, + .instance_size = sizeof(struct pci_vmsvga_state_s), + .class_init = vmsvga_class_init, +}; + +static void vmsvga_register_types(void) +{ + type_register_static(&vmsvga_info); +} + +type_init(vmsvga_register_types) diff --git a/hw/display/xenfb.c b/hw/display/xenfb.c new file mode 100644 index 0000000000..8e4266142d --- /dev/null +++ b/hw/display/xenfb.c @@ -0,0 +1,1021 @@ +/* + * xen paravirt framebuffer backend + * + * Copyright IBM, Corp. 2005-2006 + * Copyright Red Hat, Inc. 2006-2008 + * + * Authors: + * Anthony Liguori <aliguori@us.ibm.com>, + * Markus Armbruster <armbru@redhat.com>, + * Daniel P. Berrange <berrange@redhat.com>, + * Pat Campbell <plc@novell.com>, + * Gerd Hoffmann <kraxel@redhat.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; under version 2 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, see <http://www.gnu.org/licenses/>. + */ + +#include <stdarg.h> +#include <stdlib.h> +#include <sys/types.h> +#include <fcntl.h> +#include <unistd.h> +#include <sys/mman.h> +#include <errno.h> +#include <stdio.h> +#include <string.h> +#include <time.h> + +#include "hw/hw.h" +#include "ui/console.h" +#include "char/char.h" +#include "hw/xen/xen_backend.h" + +#include <xen/event_channel.h> +#include <xen/io/fbif.h> +#include <xen/io/kbdif.h> +#include <xen/io/protocols.h> + +#ifndef BTN_LEFT +#define BTN_LEFT 0x110 /* from <linux/input.h> */ +#endif + +/* -------------------------------------------------------------------- */ + +struct common { + struct XenDevice xendev; /* must be first */ + void *page; + QemuConsole *con; +}; + +struct XenInput { + struct common c; + int abs_pointer_wanted; /* Whether guest supports absolute pointer */ + int button_state; /* Last seen pointer button state */ + int extended; + QEMUPutMouseEntry *qmouse; +}; + +#define UP_QUEUE 8 + +struct XenFB { + struct common c; + size_t fb_len; + int row_stride; + int depth; + int width; + int height; + int offset; + void *pixels; + int fbpages; + int feature_update; + int refresh_period; + int bug_trigger; + int have_console; + int do_resize; + + struct { + int x,y,w,h; + } up_rects[UP_QUEUE]; + int up_count; + int up_fullscreen; +}; + +/* -------------------------------------------------------------------- */ + +static int common_bind(struct common *c) +{ + int mfn; + + if (xenstore_read_fe_int(&c->xendev, "page-ref", &mfn) == -1) + return -1; + if (xenstore_read_fe_int(&c->xendev, "event-channel", &c->xendev.remote_port) == -1) + return -1; + + c->page = xc_map_foreign_range(xen_xc, c->xendev.dom, + XC_PAGE_SIZE, + PROT_READ | PROT_WRITE, mfn); + if (c->page == NULL) + return -1; + + xen_be_bind_evtchn(&c->xendev); + xen_be_printf(&c->xendev, 1, "ring mfn %d, remote-port %d, local-port %d\n", + mfn, c->xendev.remote_port, c->xendev.local_port); + + return 0; +} + +static void common_unbind(struct common *c) +{ + xen_be_unbind_evtchn(&c->xendev); + if (c->page) { + munmap(c->page, XC_PAGE_SIZE); + c->page = NULL; + } +} + +/* -------------------------------------------------------------------- */ + +#if 0 +/* + * These two tables are not needed any more, but left in here + * intentionally as documentation, to show how scancode2linux[] + * was generated. + * + * Tables to map from scancode to Linux input layer keycode. + * Scancodes are hardware-specific. These maps assumes a + * standard AT or PS/2 keyboard which is what QEMU feeds us. + */ +const unsigned char atkbd_set2_keycode[512] = { + + 0, 67, 65, 63, 61, 59, 60, 88, 0, 68, 66, 64, 62, 15, 41,117, + 0, 56, 42, 93, 29, 16, 2, 0, 0, 0, 44, 31, 30, 17, 3, 0, + 0, 46, 45, 32, 18, 5, 4, 95, 0, 57, 47, 33, 20, 19, 6,183, + 0, 49, 48, 35, 34, 21, 7,184, 0, 0, 50, 36, 22, 8, 9,185, + 0, 51, 37, 23, 24, 11, 10, 0, 0, 52, 53, 38, 39, 25, 12, 0, + 0, 89, 40, 0, 26, 13, 0, 0, 58, 54, 28, 27, 0, 43, 0, 85, + 0, 86, 91, 90, 92, 0, 14, 94, 0, 79,124, 75, 71,121, 0, 0, + 82, 83, 80, 76, 77, 72, 1, 69, 87, 78, 81, 74, 55, 73, 70, 99, + + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 217,100,255, 0, 97,165, 0, 0,156, 0, 0, 0, 0, 0, 0,125, + 173,114, 0,113, 0, 0, 0,126,128, 0, 0,140, 0, 0, 0,127, + 159, 0,115, 0,164, 0, 0,116,158, 0,150,166, 0, 0, 0,142, + 157, 0, 0, 0, 0, 0, 0, 0,155, 0, 98, 0, 0,163, 0, 0, + 226, 0, 0, 0, 0, 0, 0, 0, 0,255, 96, 0, 0, 0,143, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0,107, 0,105,102, 0, 0,112, + 110,111,108,112,106,103, 0,119, 0,118,109, 0, 99,104,119, 0, + +}; + +const unsigned char atkbd_unxlate_table[128] = { + + 0,118, 22, 30, 38, 37, 46, 54, 61, 62, 70, 69, 78, 85,102, 13, + 21, 29, 36, 45, 44, 53, 60, 67, 68, 77, 84, 91, 90, 20, 28, 27, + 35, 43, 52, 51, 59, 66, 75, 76, 82, 14, 18, 93, 26, 34, 33, 42, + 50, 49, 58, 65, 73, 74, 89,124, 17, 41, 88, 5, 6, 4, 12, 3, + 11, 2, 10, 1, 9,119,126,108,117,125,123,107,115,116,121,105, + 114,122,112,113,127, 96, 97,120, 7, 15, 23, 31, 39, 47, 55, 63, + 71, 79, 86, 94, 8, 16, 24, 32, 40, 48, 56, 64, 72, 80, 87,111, + 19, 25, 57, 81, 83, 92, 95, 98, 99,100,101,103,104,106,109,110 + +}; +#endif + +/* + * for (i = 0; i < 128; i++) { + * scancode2linux[i] = atkbd_set2_keycode[atkbd_unxlate_table[i]]; + * scancode2linux[i | 0x80] = atkbd_set2_keycode[atkbd_unxlate_table[i] | 0x80]; + * } + */ +static const unsigned char scancode2linux[512] = { + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, + 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, + 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, + 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, + 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, + 80, 81, 82, 83, 99, 0, 86, 87, 88,117, 0, 0, 95,183,184,185, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 93, 0, 0, 89, 0, 0, 85, 91, 90, 92, 0, 94, 0,124,121, 0, + + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 165, 0, 0, 0, 0, 0, 0, 0, 0,163, 0, 0, 96, 97, 0, 0, + 113,140,164, 0,166, 0, 0, 0, 0, 0,255, 0, 0, 0,114, 0, + 115, 0,150, 0, 0, 98,255, 99,100, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0,119,119,102,103,104, 0,105,112,106,118,107, + 108,109,110,111, 0, 0, 0, 0, 0, 0, 0,125,126,127,116,142, + 0, 0, 0,143, 0,217,156,173,128,159,158,157,155,226, 0,112, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +}; + +/* Send an event to the keyboard frontend driver */ +static int xenfb_kbd_event(struct XenInput *xenfb, + union xenkbd_in_event *event) +{ + struct xenkbd_page *page = xenfb->c.page; + uint32_t prod; + + if (xenfb->c.xendev.be_state != XenbusStateConnected) + return 0; + if (!page) + return 0; + + prod = page->in_prod; + if (prod - page->in_cons == XENKBD_IN_RING_LEN) { + errno = EAGAIN; + return -1; + } + + xen_mb(); /* ensure ring space available */ + XENKBD_IN_RING_REF(page, prod) = *event; + xen_wmb(); /* ensure ring contents visible */ + page->in_prod = prod + 1; + return xen_be_send_notify(&xenfb->c.xendev); +} + +/* Send a keyboard (or mouse button) event */ +static int xenfb_send_key(struct XenInput *xenfb, bool down, int keycode) +{ + union xenkbd_in_event event; + + memset(&event, 0, XENKBD_IN_EVENT_SIZE); + event.type = XENKBD_TYPE_KEY; + event.key.pressed = down ? 1 : 0; + event.key.keycode = keycode; + + return xenfb_kbd_event(xenfb, &event); +} + +/* Send a relative mouse movement event */ +static int xenfb_send_motion(struct XenInput *xenfb, + int rel_x, int rel_y, int rel_z) +{ + union xenkbd_in_event event; + + memset(&event, 0, XENKBD_IN_EVENT_SIZE); + event.type = XENKBD_TYPE_MOTION; + event.motion.rel_x = rel_x; + event.motion.rel_y = rel_y; +#if __XEN_LATEST_INTERFACE_VERSION__ >= 0x00030207 + event.motion.rel_z = rel_z; +#endif + + return xenfb_kbd_event(xenfb, &event); +} + +/* Send an absolute mouse movement event */ +static int xenfb_send_position(struct XenInput *xenfb, + int abs_x, int abs_y, int z) +{ + union xenkbd_in_event event; + + memset(&event, 0, XENKBD_IN_EVENT_SIZE); + event.type = XENKBD_TYPE_POS; + event.pos.abs_x = abs_x; + event.pos.abs_y = abs_y; +#if __XEN_LATEST_INTERFACE_VERSION__ == 0x00030207 + event.pos.abs_z = z; +#endif +#if __XEN_LATEST_INTERFACE_VERSION__ >= 0x00030208 + event.pos.rel_z = z; +#endif + + return xenfb_kbd_event(xenfb, &event); +} + +/* + * Send a key event from the client to the guest OS + * QEMU gives us a raw scancode from an AT / PS/2 style keyboard. + * We have to turn this into a Linux Input layer keycode. + * + * Extra complexity from the fact that with extended scancodes + * (like those produced by arrow keys) this method gets called + * twice, but we only want to send a single event. So we have to + * track the '0xe0' scancode state & collapse the extended keys + * as needed. + * + * Wish we could just send scancodes straight to the guest which + * already has code for dealing with this... + */ +static void xenfb_key_event(void *opaque, int scancode) +{ + struct XenInput *xenfb = opaque; + int down = 1; + + if (scancode == 0xe0) { + xenfb->extended = 1; + return; + } else if (scancode & 0x80) { + scancode &= 0x7f; + down = 0; + } + if (xenfb->extended) { + scancode |= 0x80; + xenfb->extended = 0; + } + xenfb_send_key(xenfb, down, scancode2linux[scancode]); +} + +/* + * Send a mouse event from the client to the guest OS + * + * The QEMU mouse can be in either relative, or absolute mode. + * Movement is sent separately from button state, which has to + * be encoded as virtual key events. We also don't actually get + * given any button up/down events, so have to track changes in + * the button state. + */ +static void xenfb_mouse_event(void *opaque, + int dx, int dy, int dz, int button_state) +{ + struct XenInput *xenfb = opaque; + DisplaySurface *surface = qemu_console_surface(xenfb->c.con); + int dw = surface_width(surface); + int dh = surface_height(surface); + int i; + + if (xenfb->abs_pointer_wanted) + xenfb_send_position(xenfb, + dx * (dw - 1) / 0x7fff, + dy * (dh - 1) / 0x7fff, + dz); + else + xenfb_send_motion(xenfb, dx, dy, dz); + + for (i = 0 ; i < 8 ; i++) { + int lastDown = xenfb->button_state & (1 << i); + int down = button_state & (1 << i); + if (down == lastDown) + continue; + + if (xenfb_send_key(xenfb, down, BTN_LEFT+i) < 0) + return; + } + xenfb->button_state = button_state; +} + +static int input_init(struct XenDevice *xendev) +{ + xenstore_write_be_int(xendev, "feature-abs-pointer", 1); + return 0; +} + +static int input_initialise(struct XenDevice *xendev) +{ + struct XenInput *in = container_of(xendev, struct XenInput, c.xendev); + int rc; + + if (!in->c.con) { + xen_be_printf(xendev, 1, "ds not set (yet)\n"); + return -1; + } + + rc = common_bind(&in->c); + if (rc != 0) + return rc; + + qemu_add_kbd_event_handler(xenfb_key_event, in); + return 0; +} + +static void input_connected(struct XenDevice *xendev) +{ + struct XenInput *in = container_of(xendev, struct XenInput, c.xendev); + + if (xenstore_read_fe_int(xendev, "request-abs-pointer", + &in->abs_pointer_wanted) == -1) { + in->abs_pointer_wanted = 0; + } + + if (in->qmouse) { + qemu_remove_mouse_event_handler(in->qmouse); + } + in->qmouse = qemu_add_mouse_event_handler(xenfb_mouse_event, in, + in->abs_pointer_wanted, + "Xen PVFB Mouse"); +} + +static void input_disconnect(struct XenDevice *xendev) +{ + struct XenInput *in = container_of(xendev, struct XenInput, c.xendev); + + if (in->qmouse) { + qemu_remove_mouse_event_handler(in->qmouse); + in->qmouse = NULL; + } + qemu_add_kbd_event_handler(NULL, NULL); + common_unbind(&in->c); +} + +static void input_event(struct XenDevice *xendev) +{ + struct XenInput *xenfb = container_of(xendev, struct XenInput, c.xendev); + struct xenkbd_page *page = xenfb->c.page; + + /* We don't understand any keyboard events, so just ignore them. */ + if (page->out_prod == page->out_cons) + return; + page->out_cons = page->out_prod; + xen_be_send_notify(&xenfb->c.xendev); +} + +/* -------------------------------------------------------------------- */ + +static void xenfb_copy_mfns(int mode, int count, unsigned long *dst, void *src) +{ + uint32_t *src32 = src; + uint64_t *src64 = src; + int i; + + for (i = 0; i < count; i++) + dst[i] = (mode == 32) ? src32[i] : src64[i]; +} + +static int xenfb_map_fb(struct XenFB *xenfb) +{ + struct xenfb_page *page = xenfb->c.page; + char *protocol = xenfb->c.xendev.protocol; + int n_fbdirs; + unsigned long *pgmfns = NULL; + unsigned long *fbmfns = NULL; + void *map, *pd; + int mode, ret = -1; + + /* default to native */ + pd = page->pd; + mode = sizeof(unsigned long) * 8; + + if (!protocol) { + /* + * Undefined protocol, some guesswork needed. + * + * Old frontends which don't set the protocol use + * one page directory only, thus pd[1] must be zero. + * pd[1] of the 32bit struct layout and the lower + * 32 bits of pd[0] of the 64bit struct layout have + * the same location, so we can check that ... + */ + uint32_t *ptr32 = NULL; + uint32_t *ptr64 = NULL; +#if defined(__i386__) + ptr32 = (void*)page->pd; + ptr64 = ((void*)page->pd) + 4; +#elif defined(__x86_64__) + ptr32 = ((void*)page->pd) - 4; + ptr64 = (void*)page->pd; +#endif + if (ptr32) { + if (ptr32[1] == 0) { + mode = 32; + pd = ptr32; + } else { + mode = 64; + pd = ptr64; + } + } +#if defined(__x86_64__) + } else if (strcmp(protocol, XEN_IO_PROTO_ABI_X86_32) == 0) { + /* 64bit dom0, 32bit domU */ + mode = 32; + pd = ((void*)page->pd) - 4; +#elif defined(__i386__) + } else if (strcmp(protocol, XEN_IO_PROTO_ABI_X86_64) == 0) { + /* 32bit dom0, 64bit domU */ + mode = 64; + pd = ((void*)page->pd) + 4; +#endif + } + + if (xenfb->pixels) { + munmap(xenfb->pixels, xenfb->fbpages * XC_PAGE_SIZE); + xenfb->pixels = NULL; + } + + xenfb->fbpages = (xenfb->fb_len + (XC_PAGE_SIZE - 1)) / XC_PAGE_SIZE; + n_fbdirs = xenfb->fbpages * mode / 8; + n_fbdirs = (n_fbdirs + (XC_PAGE_SIZE - 1)) / XC_PAGE_SIZE; + + pgmfns = g_malloc0(sizeof(unsigned long) * n_fbdirs); + fbmfns = g_malloc0(sizeof(unsigned long) * xenfb->fbpages); + + xenfb_copy_mfns(mode, n_fbdirs, pgmfns, pd); + map = xc_map_foreign_pages(xen_xc, xenfb->c.xendev.dom, + PROT_READ, pgmfns, n_fbdirs); + if (map == NULL) + goto out; + xenfb_copy_mfns(mode, xenfb->fbpages, fbmfns, map); + munmap(map, n_fbdirs * XC_PAGE_SIZE); + + xenfb->pixels = xc_map_foreign_pages(xen_xc, xenfb->c.xendev.dom, + PROT_READ | PROT_WRITE, fbmfns, xenfb->fbpages); + if (xenfb->pixels == NULL) + goto out; + + ret = 0; /* all is fine */ + +out: + g_free(pgmfns); + g_free(fbmfns); + return ret; +} + +static int xenfb_configure_fb(struct XenFB *xenfb, size_t fb_len_lim, + int width, int height, int depth, + size_t fb_len, int offset, int row_stride) +{ + size_t mfn_sz = sizeof(*((struct xenfb_page *)0)->pd); + size_t pd_len = sizeof(((struct xenfb_page *)0)->pd) / mfn_sz; + size_t fb_pages = pd_len * XC_PAGE_SIZE / mfn_sz; + size_t fb_len_max = fb_pages * XC_PAGE_SIZE; + int max_width, max_height; + + if (fb_len_lim > fb_len_max) { + xen_be_printf(&xenfb->c.xendev, 0, "fb size limit %zu exceeds %zu, corrected\n", + fb_len_lim, fb_len_max); + fb_len_lim = fb_len_max; + } + if (fb_len_lim && fb_len > fb_len_lim) { + xen_be_printf(&xenfb->c.xendev, 0, "frontend fb size %zu limited to %zu\n", + fb_len, fb_len_lim); + fb_len = fb_len_lim; + } + if (depth != 8 && depth != 16 && depth != 24 && depth != 32) { + xen_be_printf(&xenfb->c.xendev, 0, "can't handle frontend fb depth %d\n", + depth); + return -1; + } + if (row_stride <= 0 || row_stride > fb_len) { + xen_be_printf(&xenfb->c.xendev, 0, "invalid frontend stride %d\n", row_stride); + return -1; + } + max_width = row_stride / (depth / 8); + if (width < 0 || width > max_width) { + xen_be_printf(&xenfb->c.xendev, 0, "invalid frontend width %d limited to %d\n", + width, max_width); + width = max_width; + } + if (offset < 0 || offset >= fb_len) { + xen_be_printf(&xenfb->c.xendev, 0, "invalid frontend offset %d (max %zu)\n", + offset, fb_len - 1); + return -1; + } + max_height = (fb_len - offset) / row_stride; + if (height < 0 || height > max_height) { + xen_be_printf(&xenfb->c.xendev, 0, "invalid frontend height %d limited to %d\n", + height, max_height); + height = max_height; + } + xenfb->fb_len = fb_len; + xenfb->row_stride = row_stride; + xenfb->depth = depth; + xenfb->width = width; + xenfb->height = height; + xenfb->offset = offset; + xenfb->up_fullscreen = 1; + xenfb->do_resize = 1; + xen_be_printf(&xenfb->c.xendev, 1, "framebuffer %dx%dx%d offset %d stride %d\n", + width, height, depth, offset, row_stride); + return 0; +} + +/* A convenient function for munging pixels between different depths */ +#define BLT(SRC_T,DST_T,RSB,GSB,BSB,RDB,GDB,BDB) \ + for (line = y ; line < (y+h) ; line++) { \ + SRC_T *src = (SRC_T *)(xenfb->pixels \ + + xenfb->offset \ + + (line * xenfb->row_stride) \ + + (x * xenfb->depth / 8)); \ + DST_T *dst = (DST_T *)(data \ + + (line * linesize) \ + + (x * bpp / 8)); \ + int col; \ + const int RSS = 32 - (RSB + GSB + BSB); \ + const int GSS = 32 - (GSB + BSB); \ + const int BSS = 32 - (BSB); \ + const uint32_t RSM = (~0U) << (32 - RSB); \ + const uint32_t GSM = (~0U) << (32 - GSB); \ + const uint32_t BSM = (~0U) << (32 - BSB); \ + const int RDS = 32 - (RDB + GDB + BDB); \ + const int GDS = 32 - (GDB + BDB); \ + const int BDS = 32 - (BDB); \ + const uint32_t RDM = (~0U) << (32 - RDB); \ + const uint32_t GDM = (~0U) << (32 - GDB); \ + const uint32_t BDM = (~0U) << (32 - BDB); \ + for (col = x ; col < (x+w) ; col++) { \ + uint32_t spix = *src; \ + *dst = (((spix << RSS) & RSM & RDM) >> RDS) | \ + (((spix << GSS) & GSM & GDM) >> GDS) | \ + (((spix << BSS) & BSM & BDM) >> BDS); \ + src = (SRC_T *) ((unsigned long) src + xenfb->depth / 8); \ + dst = (DST_T *) ((unsigned long) dst + bpp / 8); \ + } \ + } + + +/* + * This copies data from the guest framebuffer region, into QEMU's + * displaysurface. qemu uses 16 or 32 bpp. In case the pv framebuffer + * uses something else we must convert and copy, otherwise we can + * supply the buffer directly and no thing here. + */ +static void xenfb_guest_copy(struct XenFB *xenfb, int x, int y, int w, int h) +{ + DisplaySurface *surface = qemu_console_surface(xenfb->c.con); + int line, oops = 0; + int bpp = surface_bits_per_pixel(surface); + int linesize = surface_stride(surface); + uint8_t *data = surface_data(surface); + + if (!is_buffer_shared(surface)) { + switch (xenfb->depth) { + case 8: + if (bpp == 16) { + BLT(uint8_t, uint16_t, 3, 3, 2, 5, 6, 5); + } else if (bpp == 32) { + BLT(uint8_t, uint32_t, 3, 3, 2, 8, 8, 8); + } else { + oops = 1; + } + break; + case 24: + if (bpp == 16) { + BLT(uint32_t, uint16_t, 8, 8, 8, 5, 6, 5); + } else if (bpp == 32) { + BLT(uint32_t, uint32_t, 8, 8, 8, 8, 8, 8); + } else { + oops = 1; + } + break; + default: + oops = 1; + } + } + if (oops) /* should not happen */ + xen_be_printf(&xenfb->c.xendev, 0, "%s: oops: convert %d -> %d bpp?\n", + __FUNCTION__, xenfb->depth, bpp); + + dpy_gfx_update(xenfb->c.con, x, y, w, h); +} + +#if 0 /* def XENFB_TYPE_REFRESH_PERIOD */ +static int xenfb_queue_full(struct XenFB *xenfb) +{ + struct xenfb_page *page = xenfb->c.page; + uint32_t cons, prod; + + if (!page) + return 1; + + prod = page->in_prod; + cons = page->in_cons; + return prod - cons == XENFB_IN_RING_LEN; +} + +static void xenfb_send_event(struct XenFB *xenfb, union xenfb_in_event *event) +{ + uint32_t prod; + struct xenfb_page *page = xenfb->c.page; + + prod = page->in_prod; + /* caller ensures !xenfb_queue_full() */ + xen_mb(); /* ensure ring space available */ + XENFB_IN_RING_REF(page, prod) = *event; + xen_wmb(); /* ensure ring contents visible */ + page->in_prod = prod + 1; + + xen_be_send_notify(&xenfb->c.xendev); +} + +static void xenfb_send_refresh_period(struct XenFB *xenfb, int period) +{ + union xenfb_in_event event; + + memset(&event, 0, sizeof(event)); + event.type = XENFB_TYPE_REFRESH_PERIOD; + event.refresh_period.period = period; + xenfb_send_event(xenfb, &event); +} +#endif + +/* + * Periodic update of display. + * Also transmit the refresh interval to the frontend. + * + * Never ever do any qemu display operations + * (resize, screen update) outside this function. + * Our screen might be inactive. When asked for + * an update we know it is active. + */ +static void xenfb_update(void *opaque) +{ + struct XenFB *xenfb = opaque; + DisplaySurface *surface; + int i; + + if (xenfb->c.xendev.be_state != XenbusStateConnected) + return; + + if (xenfb->feature_update) { +#if 0 /* XENFB_TYPE_REFRESH_PERIOD */ + struct DisplayChangeListener *l; + int period = 99999999; + int idle = 1; + + if (xenfb_queue_full(xenfb)) + return; + + QLIST_FOREACH(l, &xenfb->c.ds->listeners, next) { + if (l->idle) + continue; + idle = 0; + if (!l->gui_timer_interval) { + if (period > GUI_REFRESH_INTERVAL) + period = GUI_REFRESH_INTERVAL; + } else { + if (period > l->gui_timer_interval) + period = l->gui_timer_interval; + } + } + if (idle) + period = XENFB_NO_REFRESH; + + if (xenfb->refresh_period != period) { + xenfb_send_refresh_period(xenfb, period); + xenfb->refresh_period = period; + xen_be_printf(&xenfb->c.xendev, 1, "refresh period: %d\n", period); + } +#else + ; /* nothing */ +#endif + } else { + /* we don't get update notifications, thus use the + * sledge hammer approach ... */ + xenfb->up_fullscreen = 1; + } + + /* resize if needed */ + if (xenfb->do_resize) { + xenfb->do_resize = 0; + switch (xenfb->depth) { + case 16: + case 32: + /* console.c supported depth -> buffer can be used directly */ + surface = qemu_create_displaysurface_from + (xenfb->width, xenfb->height, xenfb->depth, + xenfb->row_stride, xenfb->pixels + xenfb->offset, + false); + break; + default: + /* we must convert stuff */ + surface = qemu_create_displaysurface(xenfb->width, xenfb->height); + break; + } + dpy_gfx_replace_surface(xenfb->c.con, surface); + xen_be_printf(&xenfb->c.xendev, 1, "update: resizing: %dx%d @ %d bpp%s\n", + xenfb->width, xenfb->height, xenfb->depth, + is_buffer_shared(surface) ? " (shared)" : ""); + xenfb->up_fullscreen = 1; + } + + /* run queued updates */ + if (xenfb->up_fullscreen) { + xen_be_printf(&xenfb->c.xendev, 3, "update: fullscreen\n"); + xenfb_guest_copy(xenfb, 0, 0, xenfb->width, xenfb->height); + } else if (xenfb->up_count) { + xen_be_printf(&xenfb->c.xendev, 3, "update: %d rects\n", xenfb->up_count); + for (i = 0; i < xenfb->up_count; i++) + xenfb_guest_copy(xenfb, + xenfb->up_rects[i].x, + xenfb->up_rects[i].y, + xenfb->up_rects[i].w, + xenfb->up_rects[i].h); + } else { + xen_be_printf(&xenfb->c.xendev, 3, "update: nothing\n"); + } + xenfb->up_count = 0; + xenfb->up_fullscreen = 0; +} + +/* QEMU display state changed, so refresh the framebuffer copy */ +static void xenfb_invalidate(void *opaque) +{ + struct XenFB *xenfb = opaque; + xenfb->up_fullscreen = 1; +} + +static void xenfb_handle_events(struct XenFB *xenfb) +{ + uint32_t prod, cons; + struct xenfb_page *page = xenfb->c.page; + + prod = page->out_prod; + if (prod == page->out_cons) + return; + xen_rmb(); /* ensure we see ring contents up to prod */ + for (cons = page->out_cons; cons != prod; cons++) { + union xenfb_out_event *event = &XENFB_OUT_RING_REF(page, cons); + int x, y, w, h; + + switch (event->type) { + case XENFB_TYPE_UPDATE: + if (xenfb->up_count == UP_QUEUE) + xenfb->up_fullscreen = 1; + if (xenfb->up_fullscreen) + break; + x = MAX(event->update.x, 0); + y = MAX(event->update.y, 0); + w = MIN(event->update.width, xenfb->width - x); + h = MIN(event->update.height, xenfb->height - y); + if (w < 0 || h < 0) { + xen_be_printf(&xenfb->c.xendev, 1, "bogus update ignored\n"); + break; + } + if (x != event->update.x || + y != event->update.y || + w != event->update.width || + h != event->update.height) { + xen_be_printf(&xenfb->c.xendev, 1, "bogus update clipped\n"); + } + if (w == xenfb->width && h > xenfb->height / 2) { + /* scroll detector: updated more than 50% of the lines, + * don't bother keeping track of the rectangles then */ + xenfb->up_fullscreen = 1; + } else { + xenfb->up_rects[xenfb->up_count].x = x; + xenfb->up_rects[xenfb->up_count].y = y; + xenfb->up_rects[xenfb->up_count].w = w; + xenfb->up_rects[xenfb->up_count].h = h; + xenfb->up_count++; + } + break; +#ifdef XENFB_TYPE_RESIZE + case XENFB_TYPE_RESIZE: + if (xenfb_configure_fb(xenfb, xenfb->fb_len, + event->resize.width, + event->resize.height, + event->resize.depth, + xenfb->fb_len, + event->resize.offset, + event->resize.stride) < 0) + break; + xenfb_invalidate(xenfb); + break; +#endif + } + } + xen_mb(); /* ensure we're done with ring contents */ + page->out_cons = cons; +} + +static int fb_init(struct XenDevice *xendev) +{ + struct XenFB *fb = container_of(xendev, struct XenFB, c.xendev); + + fb->refresh_period = -1; + +#ifdef XENFB_TYPE_RESIZE + xenstore_write_be_int(xendev, "feature-resize", 1); +#endif + return 0; +} + +static int fb_initialise(struct XenDevice *xendev) +{ + struct XenFB *fb = container_of(xendev, struct XenFB, c.xendev); + struct xenfb_page *fb_page; + int videoram; + int rc; + + if (xenstore_read_fe_int(xendev, "videoram", &videoram) == -1) + videoram = 0; + + rc = common_bind(&fb->c); + if (rc != 0) + return rc; + + fb_page = fb->c.page; + rc = xenfb_configure_fb(fb, videoram * 1024 * 1024U, + fb_page->width, fb_page->height, fb_page->depth, + fb_page->mem_length, 0, fb_page->line_length); + if (rc != 0) + return rc; + + rc = xenfb_map_fb(fb); + if (rc != 0) + return rc; + +#if 0 /* handled in xen_init_display() for now */ + if (!fb->have_console) { + fb->c.ds = graphic_console_init(xenfb_update, + xenfb_invalidate, + NULL, + NULL, + fb); + fb->have_console = 1; + } +#endif + + if (xenstore_read_fe_int(xendev, "feature-update", &fb->feature_update) == -1) + fb->feature_update = 0; + if (fb->feature_update) + xenstore_write_be_int(xendev, "request-update", 1); + + xen_be_printf(xendev, 1, "feature-update=%d, videoram=%d\n", + fb->feature_update, videoram); + return 0; +} + +static void fb_disconnect(struct XenDevice *xendev) +{ + struct XenFB *fb = container_of(xendev, struct XenFB, c.xendev); + + /* + * FIXME: qemu can't un-init gfx display (yet?). + * Replacing the framebuffer with anonymous shared memory + * instead. This releases the guest pages and keeps qemu happy. + */ + fb->pixels = mmap(fb->pixels, fb->fbpages * XC_PAGE_SIZE, + PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANON, + -1, 0); + common_unbind(&fb->c); + fb->feature_update = 0; + fb->bug_trigger = 0; +} + +static void fb_frontend_changed(struct XenDevice *xendev, const char *node) +{ + struct XenFB *fb = container_of(xendev, struct XenFB, c.xendev); + + /* + * Set state to Connected *again* once the frontend switched + * to connected. We must trigger the watch a second time to + * workaround a frontend bug. + */ + if (fb->bug_trigger == 0 && strcmp(node, "state") == 0 && + xendev->fe_state == XenbusStateConnected && + xendev->be_state == XenbusStateConnected) { + xen_be_printf(xendev, 2, "re-trigger connected (frontend bug)\n"); + xen_be_set_state(xendev, XenbusStateConnected); + fb->bug_trigger = 1; /* only once */ + } +} + +static void fb_event(struct XenDevice *xendev) +{ + struct XenFB *xenfb = container_of(xendev, struct XenFB, c.xendev); + + xenfb_handle_events(xenfb); + xen_be_send_notify(&xenfb->c.xendev); +} + +/* -------------------------------------------------------------------- */ + +struct XenDevOps xen_kbdmouse_ops = { + .size = sizeof(struct XenInput), + .init = input_init, + .initialise = input_initialise, + .connected = input_connected, + .disconnect = input_disconnect, + .event = input_event, +}; + +struct XenDevOps xen_framebuffer_ops = { + .size = sizeof(struct XenFB), + .init = fb_init, + .initialise = fb_initialise, + .disconnect = fb_disconnect, + .event = fb_event, + .frontend_changed = fb_frontend_changed, +}; + +/* + * FIXME/TODO: Kill this. + * Temporary needed while DisplayState reorganization is in flight. + */ +void xen_init_display(int domid) +{ + struct XenDevice *xfb, *xin; + struct XenFB *fb; + struct XenInput *in; + int i = 0; + +wait_more: + i++; + main_loop_wait(true); + xfb = xen_be_find_xendev("vfb", domid, 0); + xin = xen_be_find_xendev("vkbd", domid, 0); + if (!xfb || !xin) { + if (i < 256) { + usleep(10000); + goto wait_more; + } + xen_be_printf(NULL, 1, "displaystate setup failed\n"); + return; + } + + /* vfb */ + fb = container_of(xfb, struct XenFB, c.xendev); + fb->c.con = graphic_console_init(xenfb_update, + xenfb_invalidate, + NULL, + NULL, + fb); + fb->have_console = 1; + + /* vkbd */ + in = container_of(xin, struct XenInput, c.xendev); + in->c.con = fb->c.con; + + /* retry ->init() */ + xen_be_check_state(xin); + xen_be_check_state(xfb); +} |