Skip to content

Commit 04b3574

Browse files
o-marshmallowigrr
authored andcommitted
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 or ESP32-C3 targets.
1 parent 4512cda commit 04b3574

File tree

8 files changed

+433
-4
lines changed

8 files changed

+433
-4
lines changed

hw/display/esp_rgb.c

Lines changed: 308 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,308 @@
1+
/*
2+
* ESP Display RGB emulation
3+
*
4+
* Copyright (c) 2023 Espressif Systems (Shanghai) Co. Ltd.
5+
*
6+
* This implementation overrides the ESP32 UARt one, check it out first.
7+
*
8+
* This program is free software; you can redistribute it and/or modify
9+
* it under the terms of the GNU General Public License version 2 or
10+
* (at your option) any later version.
11+
*/
12+
13+
#include "qemu/osdep.h"
14+
#include "qemu/module.h"
15+
#include "qapi/error.h"
16+
#include "sysemu/sysemu.h"
17+
#include "hw/sysbus.h"
18+
#include "hw/irq.h"
19+
#include "hw/display/esp_rgb.h"
20+
#include "ui/console.h"
21+
#include "qemu/error-report.h"
22+
#include "sysemu/dma.h"
23+
24+
#define RGB_WARNING 1
25+
#define RGB_DEBUG 0
26+
27+
#define RGB_VERSION_MAJOR 0
28+
#define RGB_VERSION_MINOR 1
29+
30+
static uint64_t esp_rgb_read(void *opaque, hwaddr addr, unsigned int size)
31+
{
32+
ESPRgbState *s = ESP_RGB(opaque);
33+
uint32_t r = 0;
34+
35+
/* Only accept 32-bit transactions */
36+
if (size != sizeof(uint32_t)) {
37+
return r;
38+
}
39+
40+
switch(addr) {
41+
case A_RGB_VERSION:
42+
r = FIELD_DP32(r, RGB_VERSION, MAJOR, RGB_VERSION_MAJOR);
43+
r = FIELD_DP32(r, RGB_VERSION, MINOR, RGB_VERSION_MINOR);
44+
break;
45+
46+
case A_RGB_WIN_SIZE:
47+
r = FIELD_DP32(r, RGB_WIN_SIZE, WIDTH, s->width);
48+
r = FIELD_DP32(r, RGB_WIN_SIZE, HEIGHT, s->height);
49+
break;
50+
51+
case A_RGB_UPDATE_FROM:
52+
r = FIELD_DP32(r, RGB_UPDATE_FROM, X, s->from_x);
53+
r = FIELD_DP32(r, RGB_UPDATE_FROM, Y, s->from_y);
54+
break;
55+
56+
case A_RGB_UPDATE_TO:
57+
r = FIELD_DP32(r, RGB_UPDATE_TO, X, s->to_x);
58+
r = FIELD_DP32(r, RGB_UPDATE_TO, Y, s->to_y);
59+
break;
60+
61+
case A_RGB_UPDATE_CONTENT:
62+
r = s->color_content;
63+
break;
64+
65+
case A_RGB_UPDATE_STATUS:
66+
r = s->update_area;
67+
break;
68+
69+
default:
70+
#if RGB_WARNING
71+
warn_report("[ESP RGB] Unsupported read to 0x%lx", addr);
72+
#endif
73+
break;
74+
}
75+
76+
#if RGB_DEBUG
77+
info_report("[ESP RGB] Reading 0x%lx (0x%x)", addr, r);
78+
#endif
79+
80+
return r;
81+
}
82+
83+
84+
static void esp_rgb_write(void *opaque, hwaddr addr,
85+
uint64_t value, unsigned int size)
86+
{
87+
ESPRgbState *s = ESP_RGB(opaque);
88+
uint32_t width = 0;
89+
uint32_t height = 0;
90+
91+
/* Only accept 32-bit transactions */
92+
if (size != sizeof(uint32_t)) {
93+
return;
94+
}
95+
96+
#if RGB_DEBUG
97+
info_report("[ESP RGB] Writing 0x%lx = %08lx", addr, value);
98+
#endif
99+
100+
switch(addr) {
101+
case A_RGB_WIN_SIZE:
102+
width = FIELD_EX32(value, RGB_WIN_SIZE, WIDTH);
103+
height = FIELD_EX32(value, RGB_WIN_SIZE, HEIGHT);
104+
105+
/* Check the bounds of the new window size */
106+
s->width = MIN(width, ESP_RGB_MAX_WIDTH);
107+
s->height = MIN(height, ESP_RGB_MAX_HEIGHT);
108+
/* Make sure it's bigger than 10x10 */
109+
s->width = MAX(s->width, 10);
110+
s->height = MAX(s->height, 10);
111+
112+
/* Update the window size */
113+
if (s->con) {
114+
qemu_console_resize(s->con, s->width, s->height);
115+
}
116+
break;
117+
118+
case A_RGB_UPDATE_STATUS:
119+
s->update_area = FIELD_EX32(value, RGB_UPDATE_STATUS, ENA) != 0;
120+
break;
121+
122+
case A_RGB_UPDATE_FROM:
123+
s->from_x = FIELD_EX32(value, RGB_UPDATE_FROM, X);
124+
s->from_y = FIELD_EX32(value, RGB_UPDATE_FROM, Y);
125+
break;
126+
127+
case A_RGB_UPDATE_TO:
128+
s->to_x = FIELD_EX32(value, RGB_UPDATE_TO, X);
129+
s->to_y = FIELD_EX32(value, RGB_UPDATE_TO, Y);
130+
break;
131+
132+
case A_RGB_UPDATE_CONTENT:
133+
s->color_content = (uint32_t) value;
134+
break;
135+
136+
default:
137+
#if RGB_WARNING
138+
warn_report("[ESP RGB] Unsupported write to 0x%lx (%08lx)", addr, value);
139+
#endif
140+
break;
141+
}
142+
143+
}
144+
145+
146+
static void rgb_update(void* opaque)
147+
{
148+
ESPRgbState* s = (ESPRgbState*) opaque;
149+
150+
if (s->con && s->update_area) {
151+
uint32_t src = s->color_content;
152+
AddressSpace* src_as = NULL;
153+
154+
/* Since we are in a 32bpp configuration, it's enough to cast the framebuffer
155+
* as a uint32_t pointer */
156+
uint32_t* data = surface_data(qemu_console_surface(s->con));
157+
158+
/* Width and height of the area to update */
159+
const int width = s->to_x - s->from_x;
160+
const int height = s->to_y - s->from_y;
161+
const int bytes_per_pixel = sizeof(uint32_t);
162+
const int total_bytes = width * height * bytes_per_pixel;
163+
164+
if (address_space_access_valid(&s->vram_as, src, total_bytes, false, MEMTXATTRS_UNSPECIFIED)) {
165+
src_as = &s->vram_as;
166+
} else if (address_space_access_valid(&s->intram_as, src, total_bytes, false, MEMTXATTRS_UNSPECIFIED)) {
167+
src_as = &s->intram_as;
168+
} else {
169+
s->update_area = false;
170+
#if RGB_WARNING
171+
warn_report("[ESP RGB] Invalid color content address or length");
172+
#endif
173+
return;
174+
}
175+
176+
/* Only perform the copy if the area is valid */
177+
if (width > 0 && height > 0 &&
178+
(s->from_x + width) <= s->width && (s->from_y + height) <= s->height) {
179+
180+
uint32_t* dest = &data[s->from_y * s->width + s->from_x];
181+
182+
/* Copy the pixels to the framebuffer */
183+
for (int i = 0; i < height; i++) {
184+
dma_memory_read(src_as, src, dest, width * bytes_per_pixel, MEMTXATTRS_UNSPECIFIED);
185+
/* Go to the next line in the destination */
186+
dest += s->width;
187+
/* Same goes for the source */
188+
src += width * bytes_per_pixel;
189+
}
190+
191+
dpy_gfx_update(s->con, s->from_x, s->from_y, width, height);
192+
}
193+
#if RGB_WARNING
194+
else {
195+
warn_report("[ESP RGB] Invalid drawing area");
196+
}
197+
#endif
198+
199+
/* Automatically clear the update flag, the guest can re-use the given color_content buffer.
200+
* It must set it again to trigger another update. */
201+
s->update_area = false;
202+
}
203+
}
204+
205+
206+
static void rgb_invalidate(void *opaque)
207+
{
208+
ESPRgbState* s = (ESPRgbState*) opaque;
209+
210+
if (s->con) {
211+
uint32_t* data = surface_data(qemu_console_surface(s->con));
212+
213+
/* On invalidate, reset the display */
214+
memset(data, 0, s->width * s->height * 4);
215+
}
216+
}
217+
218+
219+
static const GraphicHwOps fb_ops = {
220+
.invalidate = rgb_invalidate,
221+
.gfx_update = rgb_update
222+
};
223+
224+
225+
static void esp_rgb_realize(DeviceState *dev, Error **errp)
226+
{
227+
ESPRgbState* s = ESP_RGB(dev);
228+
229+
assert(s->intram != NULL);
230+
/* Create an address space for internal RAM so that we can read data from it on GUI update */
231+
address_space_init(&s->intram_as, s->intram, "esp32c3.rgb.intram_as");
232+
}
233+
234+
235+
static const MemoryRegionOps esp_rgb_ops = {
236+
.read = esp_rgb_read,
237+
.write = esp_rgb_write,
238+
.endianness = DEVICE_LITTLE_ENDIAN,
239+
};
240+
241+
242+
static void esp_rgb_init(Object *obj)
243+
{
244+
ESPRgbState* s = ESP_RGB(obj);
245+
SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
246+
247+
memory_region_init_io(&s->iomem, obj, &esp_rgb_ops, s,
248+
TYPE_ESP_RGB, ESP_RGB_IO_SIZE);
249+
sysbus_init_mmio(sbd, &s->iomem);
250+
251+
/* Default window size */
252+
s->width = ESP_RGB_MAX_WIDTH;
253+
s->height = ESP_RGB_MAX_HEIGHT;
254+
s->update_area = false;
255+
256+
if (s->con == NULL) {
257+
s->con = graphic_console_init(DEVICE(s), 0, &fb_ops, s);
258+
qemu_console_resize(s->con, s->width, s->height);
259+
uint32_t* data = surface_data(qemu_console_surface(s->con));
260+
/* Initialize the window to black */
261+
memset(data, 0, ESP_RGB_MAX_VRAM_SIZE);
262+
}
263+
264+
/* Create a memory region that can be used as a framebuffer by the guest */
265+
memory_region_init_ram(&s->vram, OBJECT(s), "esp32c3-rgb-vram", ESP_RGB_MAX_VRAM_SIZE, &error_abort);
266+
267+
/* Create an AddressSpace out of the MemoryRegion to be able to perform DMA */
268+
address_space_init(&s->vram_as, &s->vram, "esp32c3.rgb.vram_as");
269+
}
270+
271+
272+
static void esp_rgb_reset(DeviceState *dev)
273+
{
274+
ESPRgbState* s = ESP_RGB(dev);
275+
276+
/* Default window size */
277+
s->width = ESP_RGB_MAX_WIDTH;
278+
s->height = ESP_RGB_MAX_HEIGHT;
279+
s->update_area = false;
280+
s->from_x = 0;
281+
s->from_y = 0;
282+
s->to_x = 0;
283+
s->to_y = 0;
284+
}
285+
286+
287+
static void esp_rgb_class_init(ObjectClass *klass, void *data)
288+
{
289+
DeviceClass *dc = DEVICE_CLASS(klass);
290+
291+
dc->reset = esp_rgb_reset;
292+
dc->realize = esp_rgb_realize;
293+
}
294+
295+
static const TypeInfo esp_rgb_info = {
296+
.name = TYPE_ESP_RGB,
297+
.parent = TYPE_SYS_BUS_DEVICE,
298+
.instance_size = sizeof(ESPRgbState),
299+
.instance_init = esp_rgb_init,
300+
.class_init = esp_rgb_class_init,
301+
};
302+
303+
static void esp_rgb_register_types(void)
304+
{
305+
type_register_static(&esp_rgb_info);
306+
}
307+
308+
type_init(esp_rgb_register_types)

hw/display/meson.build

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ system_ss.add(when: 'CONFIG_TCX', if_true: files('tcx.c'))
3535
system_ss.add(when: 'CONFIG_CG3', if_true: files('cg3.c'))
3636
system_ss.add(when: 'CONFIG_MACFB', if_true: files('macfb.c'))
3737
system_ss.add(when: 'CONFIG_NEXTCUBE', if_true: files('next-fb.c'))
38+
system_ss.add(when: 'CONFIG_RISCV_ESP32C3', if_true: files('esp_rgb.c'))
39+
system_ss.add(when: 'CONFIG_XTENSA_ESP32', if_true: files('esp_rgb.c'))
3840

3941
system_ss.add(when: 'CONFIG_VGA', if_true: files('vga.c'))
4042

hw/riscv/esp32c3.c

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646
#include "hw/misc/esp32c3_ds.h"
4747
#include "hw/misc/esp32c3_jtag.h"
4848
#include "hw/dma/esp32c3_gdma.h"
49+
#include "hw/display/esp_rgb.h"
4950

5051
#define ESP32C3_IO_WARNING 0
5152

@@ -83,10 +84,13 @@ struct Esp32C3MachineState {
8384
ESP32C3SpiState spi1;
8485
ESP32C3RtcCntlState rtccntl;
8586
ESP32C3UsbJtagState jtag;
87+
ESPRgbState rgb;
8688
};
8789

90+
/* Fake register used by ESP-IDF application to determine whether the code is running on real hardware or on QEMU */
91+
#define A_SYSCON_ORIGIN_REG 0x3F8
8892
/* Temporary macro for generating a random value from register SYSCON_RND_DATA_REG */
89-
#define A_SYSCON_RND_DATA_REG 0x0B0
93+
#define A_SYSCON_RND_DATA_REG 0x0B0
9094

9195
/* Temporary macro to mark the CPU as in non-debugging mode */
9296
#define A_ASSIST_DEBUG_CORE_0_DEBUG_MODE_REG 0x098
@@ -107,6 +111,7 @@ enum MemoryRegions {
107111
ESP32C3_MEMREGION_RTCFAST,
108112
ESP32C3_MEMREGION_DCACHE,
109113
ESP32C3_MEMREGION_ICACHE,
114+
ESP32C3_MEMREGION_FRAMEBUF,
110115
};
111116

112117
#define ESP32C3_INTERNAL_SRAM0_SIZE (16*1024)
@@ -123,6 +128,8 @@ static const struct MemmapEntry {
123128
[ESP32C3_MEMREGION_RTCFAST] = { 0x50000000, 0x2000 },
124129
[ESP32C3_MEMREGION_DCACHE] = { 0x3c000000, 0x800000 },
125130
[ESP32C3_MEMREGION_ICACHE] = { 0x42000000, 0x800000 },
131+
/* Virtual Framebuffer, used for the graphical interface */
132+
[ESP32C3_MEMREGION_FRAMEBUF] = { 0x20000000, ESP_RGB_MAX_VRAM_SIZE }
126133
};
127134

128135

@@ -135,6 +142,9 @@ static uint64_t esp32c3_io_read(void *opaque, hwaddr addr, unsigned int size)
135142
{
136143
if (addr_in_range(addr + ESP32C3_IO_START_ADDR, DR_REG_RTC_I2C_BASE, DR_REG_RTC_I2C_BASE + 0x100)) {
137144
return (uint32_t) 0xffffff;
145+
} else if (addr + ESP32C3_IO_START_ADDR == DR_REG_SYSCON_BASE + A_SYSCON_ORIGIN_REG) {
146+
/* Return "QEMU" as a 32-bit value */
147+
return 0x51454d55;
138148
} else if (addr + ESP32C3_IO_START_ADDR == DR_REG_SYSCON_BASE + A_SYSCON_RND_DATA_REG) {
139149
/* Return a random 32-bit value */
140150
static bool init = false;
@@ -343,6 +353,7 @@ static void esp32c3_machine_init(MachineState *machine)
343353
object_initialize_child(OBJECT(machine), "spi1", &ms->spi1, TYPE_ESP32C3_SPI);
344354
object_initialize_child(OBJECT(machine), "rtccntl", &ms->rtccntl, TYPE_ESP32C3_RTC_CNTL);
345355
object_initialize_child(OBJECT(machine), "jtag", &ms->jtag, TYPE_ESP32C3_JTAG);
356+
object_initialize_child(OBJECT(machine), "rgb", &ms->rgb, TYPE_ESP_RGB);
346357

347358
/* Realize all the I/O peripherals we depend on */
348359

@@ -546,6 +557,16 @@ static void esp32c3_machine_init(MachineState *machine)
546557
memory_region_add_subregion_overlap(sys_mem, DR_REG_DIGITAL_SIGNATURE_BASE, mr, 0);
547558
}
548559

560+
/* RGB display realization */
561+
{
562+
/* Give the internal RAM memory region to the display */
563+
ms->rgb.intram = dram;
564+
sysbus_realize(SYS_BUS_DEVICE(&ms->rgb), &error_fatal);
565+
MemoryRegion *mr = sysbus_mmio_get_region(SYS_BUS_DEVICE(&ms->rgb), 0);
566+
memory_region_add_subregion_overlap(sys_mem, DR_REG_FRAMEBUF_BASE, mr, 0);
567+
memory_region_add_subregion_overlap(sys_mem, esp32c3_memmap[ESP32C3_MEMREGION_FRAMEBUF].base, &ms->rgb.vram, 0);
568+
}
569+
549570
/* Open and load the "bios", which is the ROM binary, also named "first stage bootloader" */
550571
char *rom_binary = qemu_find_file(QEMU_FILE_TYPE_BIOS, "esp32c3-rom.bin");
551572
if (rom_binary == NULL) {

0 commit comments

Comments
 (0)