hw/display: Implement a virtual RGB screen for Espressif targets
The framebuffer emulated by this new component is fake in the sense that the real targets don't have it, but this does make it possible to have a GUI for programs that will run on the ESP32* targets.
This commit is contained in:
committed by
Ivan Grokhotkov
parent
62cdcabd79
commit
def343667b
@@ -140,3 +140,6 @@ config XLNX_DISPLAYPORT
|
||||
|
||||
config DM163
|
||||
bool
|
||||
|
||||
config ESP_RGB
|
||||
bool
|
||||
|
||||
@@ -0,0 +1,346 @@
|
||||
/*
|
||||
* ESP Display RGB emulation
|
||||
*
|
||||
* Copyright (c) 2023 Espressif Systems (Shanghai) Co. Ltd.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 or
|
||||
* (at your option) any later version.
|
||||
*/
|
||||
|
||||
#include "qemu/osdep.h"
|
||||
#include "qemu/module.h"
|
||||
#include "qapi/error.h"
|
||||
#include "sysemu/sysemu.h"
|
||||
#include "hw/sysbus.h"
|
||||
#include "hw/irq.h"
|
||||
#include "hw/display/esp_rgb.h"
|
||||
#include "ui/console.h"
|
||||
#include "qemu/error-report.h"
|
||||
#include "sysemu/dma.h"
|
||||
|
||||
#define RGB_WARNING 1
|
||||
#define RGB_DEBUG 0
|
||||
|
||||
#define RGB_VERSION_MAJOR 0
|
||||
#define RGB_VERSION_MINOR 2
|
||||
|
||||
static void update_rgb_surface(ESPRgbState* s){
|
||||
DisplaySurface *surface;
|
||||
switch (s->bpp){
|
||||
case BPP_32:
|
||||
surface = qemu_create_displaysurface_from(
|
||||
s->width, s->height,
|
||||
PIXMAN_x8r8g8b8,
|
||||
s->width * 4, NULL
|
||||
);
|
||||
break;
|
||||
case BPP_16:
|
||||
surface= qemu_create_displaysurface_from(
|
||||
s->width, s->height,
|
||||
PIXMAN_r5g6b5,
|
||||
s->width * 2, NULL
|
||||
);
|
||||
break;
|
||||
default:
|
||||
warn_report("[ESP RGB] Invalid %d bpp value", s->bpp);
|
||||
return;
|
||||
}
|
||||
surface->flags = QEMU_ALLOCATED_FLAG;
|
||||
dpy_gfx_replace_surface(s->con, surface);
|
||||
};
|
||||
|
||||
static uint64_t esp_rgb_read(void *opaque, hwaddr addr, unsigned int size)
|
||||
{
|
||||
ESPRgbState *s = ESP_RGB(opaque);
|
||||
uint32_t r = 0;
|
||||
|
||||
/* Only accept 32-bit transactions */
|
||||
if (size != sizeof(uint32_t)) {
|
||||
return r;
|
||||
}
|
||||
|
||||
switch(addr) {
|
||||
case A_RGB_VERSION:
|
||||
r = FIELD_DP32(r, RGB_VERSION, MAJOR, RGB_VERSION_MAJOR);
|
||||
r = FIELD_DP32(r, RGB_VERSION, MINOR, RGB_VERSION_MINOR);
|
||||
break;
|
||||
|
||||
case A_RGB_WIN_SIZE:
|
||||
r = FIELD_DP32(r, RGB_WIN_SIZE, WIDTH, s->width);
|
||||
r = FIELD_DP32(r, RGB_WIN_SIZE, HEIGHT, s->height);
|
||||
break;
|
||||
|
||||
case A_RGB_UPDATE_FROM:
|
||||
r = FIELD_DP32(r, RGB_UPDATE_FROM, X, s->from_x);
|
||||
r = FIELD_DP32(r, RGB_UPDATE_FROM, Y, s->from_y);
|
||||
break;
|
||||
|
||||
case A_RGB_UPDATE_TO:
|
||||
r = FIELD_DP32(r, RGB_UPDATE_TO, X, s->to_x);
|
||||
r = FIELD_DP32(r, RGB_UPDATE_TO, Y, s->to_y);
|
||||
break;
|
||||
|
||||
case A_RGB_UPDATE_CONTENT:
|
||||
r = s->color_content;
|
||||
break;
|
||||
|
||||
case A_RGB_UPDATE_STATUS:
|
||||
r = s->update_area;
|
||||
break;
|
||||
|
||||
case A_RGB_BPP_VALUE:
|
||||
r = s->bpp;
|
||||
break;
|
||||
|
||||
default:
|
||||
#if RGB_WARNING
|
||||
warn_report("[ESP RGB] Unsupported read to 0x%lx", (unsigned long) addr);
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
|
||||
#if RGB_DEBUG
|
||||
info_report("[ESP RGB] Reading 0x%lx (0x%x)", addr, r);
|
||||
#endif
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
|
||||
static void esp_rgb_write(void *opaque, hwaddr addr,
|
||||
uint64_t value, unsigned int size)
|
||||
{
|
||||
ESPRgbState *s = ESP_RGB(opaque);
|
||||
uint32_t width = 0;
|
||||
uint32_t height = 0;
|
||||
|
||||
/* Only accept 32-bit transactions */
|
||||
if (size != sizeof(uint32_t)) {
|
||||
return;
|
||||
}
|
||||
|
||||
#if RGB_DEBUG
|
||||
info_report("[ESP RGB] Writing 0x%lx = %08lx", addr, value);
|
||||
#endif
|
||||
|
||||
switch(addr) {
|
||||
case A_RGB_WIN_SIZE:
|
||||
width = FIELD_EX32(value, RGB_WIN_SIZE, WIDTH);
|
||||
height = FIELD_EX32(value, RGB_WIN_SIZE, HEIGHT);
|
||||
|
||||
/* Check the bounds of the new window size */
|
||||
s->width = MIN(width, ESP_RGB_MAX_WIDTH);
|
||||
s->height = MIN(height, ESP_RGB_MAX_HEIGHT);
|
||||
/* Make sure it's bigger than 10x10 */
|
||||
s->width = MAX(s->width, 10);
|
||||
s->height = MAX(s->height, 10);
|
||||
|
||||
/* Update the window size */
|
||||
s->do_update_surface = true;
|
||||
break;
|
||||
|
||||
case A_RGB_UPDATE_STATUS:
|
||||
s->update_area = FIELD_EX32(value, RGB_UPDATE_STATUS, ENA) != 0;
|
||||
break;
|
||||
|
||||
case A_RGB_UPDATE_FROM:
|
||||
s->from_x = FIELD_EX32(value, RGB_UPDATE_FROM, X);
|
||||
s->from_y = FIELD_EX32(value, RGB_UPDATE_FROM, Y);
|
||||
break;
|
||||
|
||||
case A_RGB_UPDATE_TO:
|
||||
s->to_x = FIELD_EX32(value, RGB_UPDATE_TO, X);
|
||||
s->to_y = FIELD_EX32(value, RGB_UPDATE_TO, Y);
|
||||
break;
|
||||
|
||||
case A_RGB_UPDATE_CONTENT:
|
||||
s->color_content = (uint32_t) value;
|
||||
break;
|
||||
|
||||
case A_RGB_BPP_VALUE:
|
||||
s->bpp = (BppEnum) value;
|
||||
s->do_update_surface = true;
|
||||
break;
|
||||
|
||||
default:
|
||||
#if RGB_WARNING
|
||||
warn_report("[ESP RGB] Unsupported write to 0x%lx (%08lx)", (unsigned long) addr, (unsigned long) value);
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
static void rgb_update(void* opaque)
|
||||
{
|
||||
ESPRgbState* s = (ESPRgbState*) opaque;
|
||||
|
||||
if (s->con && s->do_update_surface) {
|
||||
update_rgb_surface(s);
|
||||
s->do_update_surface = false;
|
||||
}
|
||||
|
||||
if (s->con && s->update_area) {
|
||||
uint32_t src = s->color_content;
|
||||
AddressSpace* src_as = NULL;
|
||||
|
||||
/* Since we are in a 32bpp configuration, it's enough to cast the framebuffer
|
||||
* as a uint32_t pointer */
|
||||
const int bpp = s->bpp;
|
||||
uint8_t* data = surface_data(qemu_console_surface(s->con));
|
||||
|
||||
/* Width and height of the area to update */
|
||||
const int width = s->to_x - s->from_x;
|
||||
const int height = s->to_y - s->from_y;
|
||||
const int bytes_per_pixel = bpp/8;
|
||||
const int total_bytes = width * height * bytes_per_pixel;
|
||||
|
||||
if (address_space_access_valid(&s->vram_as, src, total_bytes, false, MEMTXATTRS_UNSPECIFIED)) {
|
||||
src_as = &s->vram_as;
|
||||
} else if (address_space_access_valid(&s->intram_as, src, total_bytes, false, MEMTXATTRS_UNSPECIFIED)) {
|
||||
src_as = &s->intram_as;
|
||||
} else {
|
||||
s->update_area = false;
|
||||
#if RGB_WARNING
|
||||
warn_report("[ESP RGB] Invalid color content address or length");
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
|
||||
/* Only perform the copy if the area is valid */
|
||||
if (width > 0 && height > 0 &&
|
||||
(s->from_x + width) <= s->width && (s->from_y + height) <= s->height) {
|
||||
|
||||
uint8_t* dest = data + (s->from_y * s->width + s->from_x) * bytes_per_pixel;
|
||||
|
||||
/* Copy the pixels to the framebuffer */
|
||||
for (int i = 0; i < height; i++) {
|
||||
dma_memory_read(src_as, src, dest, width * bytes_per_pixel, MEMTXATTRS_UNSPECIFIED);
|
||||
/* Go to the next line in the destination */
|
||||
dest += s->width * bytes_per_pixel;
|
||||
/* Same goes for the source */
|
||||
src += width * bytes_per_pixel;
|
||||
}
|
||||
|
||||
dpy_gfx_update(s->con, s->from_x, s->from_y, width, height);
|
||||
}
|
||||
#if RGB_WARNING
|
||||
else {
|
||||
warn_report("[ESP RGB] Invalid drawing area");
|
||||
}
|
||||
#endif
|
||||
|
||||
/* Automatically clear the update flag, the guest can re-use the given color_content buffer.
|
||||
* It must set it again to trigger another update. */
|
||||
s->update_area = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void rgb_invalidate(void *opaque)
|
||||
{
|
||||
ESPRgbState* s = (ESPRgbState*) opaque;
|
||||
|
||||
if (s->con) {
|
||||
uint32_t* data = surface_data(qemu_console_surface(s->con));
|
||||
|
||||
/* On invalidate, reset the display */
|
||||
memset(data, 0, s->width * s->height * 4);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static const GraphicHwOps fb_ops = {
|
||||
.invalidate = rgb_invalidate,
|
||||
.gfx_update = rgb_update
|
||||
};
|
||||
|
||||
|
||||
static void esp_rgb_realize(DeviceState *dev, Error **errp)
|
||||
{
|
||||
ESPRgbState* s = ESP_RGB(dev);
|
||||
|
||||
assert(s->intram != NULL);
|
||||
/* Create an address space for internal RAM so that we can read data from it on GUI update */
|
||||
address_space_init(&s->intram_as, s->intram, "esp.rgb.intram_as");
|
||||
}
|
||||
|
||||
|
||||
static const MemoryRegionOps esp_rgb_ops = {
|
||||
.read = esp_rgb_read,
|
||||
.write = esp_rgb_write,
|
||||
.endianness = DEVICE_LITTLE_ENDIAN,
|
||||
};
|
||||
|
||||
|
||||
static void esp_rgb_init(Object *obj)
|
||||
{
|
||||
ESPRgbState* s = ESP_RGB(obj);
|
||||
SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
|
||||
|
||||
memory_region_init_io(&s->iomem, obj, &esp_rgb_ops, s,
|
||||
TYPE_ESP_RGB, ESP_RGB_IO_SIZE);
|
||||
sysbus_init_mmio(sbd, &s->iomem);
|
||||
|
||||
/* Default window size */
|
||||
s->width = ESP_RGB_MAX_WIDTH;
|
||||
s->height = ESP_RGB_MAX_HEIGHT;
|
||||
s->update_area = false;
|
||||
s->bpp = DEFAULT_BPP;
|
||||
|
||||
if (s->con == NULL) {
|
||||
s->con = graphic_console_init(DEVICE(s), 0, &fb_ops, s);
|
||||
/* Resize and use corrent color bpp*/
|
||||
update_rgb_surface(s);
|
||||
void * data = surface_data(qemu_console_surface(s->con));
|
||||
/* Initialize the window to black */
|
||||
memset(data, 0, (s->width * s->height * s->bpp) / 8);
|
||||
}
|
||||
|
||||
/* Create a memory region that can be used as a framebuffer by the guest */
|
||||
memory_region_init_ram(&s->vram, OBJECT(s), "esp-rgb-vram", ESP_RGB_MAX_VRAM_SIZE, &error_abort);
|
||||
|
||||
/* Create an AddressSpace out of the MemoryRegion to be able to perform DMA */
|
||||
address_space_init(&s->vram_as, &s->vram, "esp.rgb.vram_as");
|
||||
}
|
||||
|
||||
|
||||
static void esp_rgb_reset(DeviceState *dev)
|
||||
{
|
||||
ESPRgbState* s = ESP_RGB(dev);
|
||||
|
||||
/* Default window size */
|
||||
s->width = ESP_RGB_MAX_WIDTH;
|
||||
s->height = ESP_RGB_MAX_HEIGHT;
|
||||
s->update_area = false;
|
||||
s->from_x = 0;
|
||||
s->from_y = 0;
|
||||
s->to_x = 0;
|
||||
s->to_y = 0;
|
||||
}
|
||||
|
||||
|
||||
static void esp_rgb_class_init(ObjectClass *klass, void *data)
|
||||
{
|
||||
DeviceClass *dc = DEVICE_CLASS(klass);
|
||||
|
||||
dc->legacy_reset = esp_rgb_reset;
|
||||
dc->realize = esp_rgb_realize;
|
||||
}
|
||||
|
||||
static const TypeInfo esp_rgb_info = {
|
||||
.name = TYPE_ESP_RGB,
|
||||
.parent = TYPE_SYS_BUS_DEVICE,
|
||||
.instance_size = sizeof(ESPRgbState),
|
||||
.instance_init = esp_rgb_init,
|
||||
.class_init = esp_rgb_class_init,
|
||||
};
|
||||
|
||||
static void esp_rgb_register_types(void)
|
||||
{
|
||||
type_register_static(&esp_rgb_info);
|
||||
}
|
||||
|
||||
type_init(esp_rgb_register_types)
|
||||
@@ -31,6 +31,7 @@ system_ss.add(when: 'CONFIG_TCX', if_true: files('tcx.c'))
|
||||
system_ss.add(when: 'CONFIG_CG3', if_true: files('cg3.c'))
|
||||
system_ss.add(when: 'CONFIG_MACFB', if_true: files('macfb.c'))
|
||||
system_ss.add(when: 'CONFIG_NEXTCUBE', if_true: files('next-fb.c'))
|
||||
system_ss.add(when: 'CONFIG_ESP_RGB', if_true: files('esp_rgb.c'))
|
||||
|
||||
system_ss.add(when: 'CONFIG_VGA', if_true: files('vga.c'))
|
||||
system_ss.add(when: 'CONFIG_VIRTIO', if_true: files('virtio-dmabuf.c'))
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
#pragma once
|
||||
|
||||
#include "hw/hw.h"
|
||||
#include "hw/registerfields.h"
|
||||
|
||||
|
||||
#define TYPE_ESP_RGB "display.esp.rgb"
|
||||
#define ESP_RGB(obj) OBJECT_CHECK(ESPRgbState, (obj), TYPE_ESP_RGB)
|
||||
#define ESP_RGB_GET_CLASS(obj) OBJECT_GET_CLASS(ESPRgbState, obj, TYPE_ESP_RGB)
|
||||
#define ESP_RGB_CLASS(klass) OBJECT_CLASS_CHECK(ESPRgbState, klass, TYPE_ESP_RGB)
|
||||
|
||||
|
||||
#define ESP_RGB_MAX_WIDTH (800)
|
||||
#define ESP_RGB_MAX_HEIGHT (600)
|
||||
#define ESP_RGB_MAX_VRAM_SIZE (ESP_RGB_MAX_WIDTH * ESP_RGB_MAX_HEIGHT * 4)
|
||||
|
||||
#define DEFAULT_BPP (BPP_32)
|
||||
|
||||
typedef enum {
|
||||
BPP_16 = 16,
|
||||
BPP_32 = 32
|
||||
} BppEnum;
|
||||
|
||||
typedef struct ESPRgbState {
|
||||
SysBusDevice parent_obj;
|
||||
|
||||
QemuConsole *con;
|
||||
MemoryRegion iomem;
|
||||
MemoryRegion vram;
|
||||
AddressSpace vram_as;
|
||||
/* Internal RAM memory region */
|
||||
MemoryRegion* intram;
|
||||
AddressSpace intram_as;
|
||||
|
||||
/* Window size */
|
||||
uint32_t width;
|
||||
uint32_t height;
|
||||
bool do_update_surface;
|
||||
|
||||
/* Update area */
|
||||
bool update_area;
|
||||
uint32_t from_x;
|
||||
uint32_t from_y;
|
||||
uint32_t to_x;
|
||||
uint32_t to_y;
|
||||
uint32_t color_content;
|
||||
|
||||
/* BPP */
|
||||
BppEnum bpp;
|
||||
} ESPRgbState;
|
||||
|
||||
#define ESP_RGB_IO_SIZE (A_RGB_BPP_VALUE + 4)
|
||||
|
||||
REG32(RGB_VERSION, 0x00)
|
||||
FIELD(RGB_VERSION, MAJOR, 16, 16)
|
||||
FIELD(RGB_VERSION, MINOR, 0, 16)
|
||||
|
||||
/* Used to resize the window */
|
||||
REG32(RGB_WIN_SIZE, 0x04)
|
||||
FIELD(RGB_WIN_SIZE, WIDTH, 16, 16)
|
||||
FIELD(RGB_WIN_SIZE, HEIGHT, 0, 16)
|
||||
|
||||
/* Used to define the area to update on next window update */
|
||||
REG32(RGB_UPDATE_FROM, 0x08)
|
||||
FIELD(RGB_UPDATE_FROM, X, 16, 16)
|
||||
FIELD(RGB_UPDATE_FROM, Y, 0, 16)
|
||||
|
||||
REG32(RGB_UPDATE_TO, 0x0c)
|
||||
FIELD(RGB_UPDATE_TO, X, 16, 16)
|
||||
FIELD(RGB_UPDATE_TO, Y, 0, 16)
|
||||
|
||||
REG32(RGB_UPDATE_CONTENT, 0x10)
|
||||
|
||||
REG32(RGB_UPDATE_STATUS, 0x14)
|
||||
/* When set, only the area described above will be rendered
|
||||
* on next window update. When not set, the window is not updated.
|
||||
* Automatically cleared by the hardware after window update. */
|
||||
FIELD(RGB_UPDATE_STATUS, ENA, 0, 1)
|
||||
|
||||
REG32(RGB_BPP_VALUE, 0x18)
|
||||
Reference in New Issue
Block a user