add sample program of lvgl over PEG for 5gen brain

This commit is contained in:
2026-09-21 17:19:29 +09:00
parent d48949419e
commit 5f97124e9d
13 changed files with 2666 additions and 0 deletions
+3
View File
@@ -0,0 +1,3 @@
[submodule "x1/event_dumper/lvgl"]
path = x1/event_dumper/lvgl
url = https://github.com/lvgl/lvgl
+5
View File
@@ -46,6 +46,11 @@ injector/uboot.bin:
@./extract.py -p injector/disable_mmu.elf injector/disable_mmu.bin
@./extract.py -p injector/disable_mmu.elf injector/disable_mmu.bin
@./injector/inject.py 0xed0000 0x700000 injector/disable_mmu.bin u-boot.bin injector/uboot.bin
event_dumper/AppMain.bin: event_dumper/main.c
@$(CC) -nostdlib -static -fPIC -marm -march=armv7-a+fp -O0 event_dumper/main.c -o event_dumper/AppMain.elf -T event_dumper/linker.ld
@./extract.py -p event_dumper/AppMain.elf event_dumper/AppMain.bin
samplefb/AppMain.bin:
@$(CC) -nostdlib -static -fPIC -marm -march=armv7-a+fp -O0 samplefb/main.c -o samplefb/AppMain.elf -T samplefb/linker.ld
@./extract.py -p samplefb/AppMain.elf samplefb/AppMain.bin
+36
View File
@@ -0,0 +1,36 @@
include lvgl/lvgl.mk
CROSS_COMPILE:=arm-none-eabi-
AS:=$(CROSS_COMPILE)as
CC:=$(CROSS_COMPILE)gcc
STRIP:=$(CROSS_COMPILE)strip
OBJCOPY:=$(CROSS_COMPILE)objcopy
CFLAGS = -static -fno-pic -fno-pie -no-pie -marm -mcpu=cortex-a7 -O0 -I. -U__linux__ -fno-builtin-free
LDFLAGS = -nostdlib -marm -Llibbrain -lbrain -lgcc
LDFLAGS += -T linker.ld
SRCS = main.c
SRCS += $(CSRCS)
OBJS = $(SRCS:.c=.o)
OBJS := $(OBJS:.S=.o)
TARGET = AppMain.bin
LIBBRAIN = libbrain/libbrain.a
all: $(TARGET)
%.o: %.S
$(CC) $(CFLAGS) -c $< -o $@
$(LIBBRAIN):
$(MAKE) -C libbrain
$(TARGET): $(OBJS) $(LIBBRAIN)
@$(CC) $(OBJS) $(CFLAGS) $(LDFLAGS) -o AppMain.elf
@$(OBJCOPY) -O binary AppMain.elf $(TARGET)
clean:
rm -f $(OBJS) $(TARGET) AppMain.elf
$(MAKE) -C libbrain clean
+23
View File
@@ -0,0 +1,23 @@
CROSS_COMPILE := arm-linux-gnueabihf-
AS := $(CROSS_COMPILE)as
AR := $(CROSS_COMPILE)ar
TARGET := libbrain.a
OBJS := sys.o
ASFLAGS := -W
ARFLAGS := -crs
.PHONY:
all: $(TARGET)
.PHONY:
clean:
-rm -f $(TARGET) *.o
%.o: %.s
$(AS) $(ASFLAGS) $< -o $@
$(TARGET): $(OBJS)
$(AR) $(ARFLAGS) $@ $^
+57
View File
@@ -0,0 +1,57 @@
#ifndef H_LIB_BRAIN_H
#define H_LIB_BRAIN_H
typedef unsigned long size_t;
typedef void FILE;
typedef void* DIR;
struct stat {
unsigned int flags;
unsigned int st_atim;
unsigned int st_mtim;
unsigned int st_ctim;
size_t st_size;
};
struct dirent {
unsigned int type;
size_t size;
size_t filename_length;
char filename[260];
};
enum {
SEEK_SET = 0,
SEEK_CUR,
SEEK_END,
};
void *malloc(size_t size);
void *realloc(void* ptr, size_t size);
int free(void *ptr);
void *memcpy(void *dst, const void *src, size_t n);
void *memset(void *ptr, int ch, size_t n);
FILE *fopen(const char *filename, const char *mode);
int fclose(FILE *stream);
int ftell(FILE *stream);
size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);
int fseek(FILE *stream, int offset, int whence);
int feof(FILE *stream);
int fflush(FILE *stream);
int fgetc(FILE *stream);
char *fgets(char *s, int n, FILE *stream);
int fputc(int ch, FILE *stream);
int fputs(const char *str, FILE *stream);
int ferror(FILE *stream);
int remove(const char *filepath);
int rename(const char *oldpath, const char *newpath);
int mkdir(const char *pathname);
int stat(const char *filepath, struct stat *buf);
int closedir(DIR *dirp);
struct dirent *readdir(DIR *dirp);
DIR *opendir(const char *pathname);
int printf(const char *fmt,...);
#endif
+39
View File
@@ -0,0 +1,39 @@
.text
.align 4
.macro thunk name, addr
.global \name
.type \name, %function
\name:
ldr pc, [pc, #-4]
.word \addr
.endm
thunk malloc, 0x600063c4
thunk free, 0x600063c8
thunk realloc, 0x600063d0
thunk fopen, 0x60006400
thunk fclose, 0x60006404
thunk ftell, 0x60006408
thunk fread, 0x6000640c
thunk fwrite, 0x60006410
thunk feof, 0x60006418
thunk fflush, 0x6000641c
thunk fgetc, 0x60006420
thunk fgets, 0x60006424
thunk fputc, 0x60006428
thunk fputs, 0x6000642c
thunk ferror, 0x60006430
thunk remove, 0x60006434
thunk rename, 0x60006438
thunk mkdir, 0x6000643c
thunk stat, 0x60006444
thunk closedir, 0x6000644c
thunk readdir, 0x60006450
thunk opendir, 0x60006454
thunk printf, 0x60092004
thunk memset, 0x60092108
thunk memcpy, 0x60092118
+243
View File
@@ -0,0 +1,243 @@
OUTPUT_FORMAT("elf32-littlearm", "elf32-bigarm",
"elf32-littlearm")
OUTPUT_ARCH(arm)
ENTRY(main)
SECTIONS
{
/* Read-only sections, merged into text segment: */
PROVIDE (__executable_start = SEGMENT_START("text-segment", 0x00010000)); . = SEGMENT_START("text-segment", 0x00010000) + SIZEOF_HEADERS;
.interp : { *(.interp) }
.note.gnu.build-id : { *(.note.gnu.build-id) }
.hash : { *(.hash) }
.gnu.hash : { *(.gnu.hash) }
.dynsym : { *(.dynsym) }
.dynstr : { *(.dynstr) }
.gnu.version : { *(.gnu.version) }
.gnu.version_d : { *(.gnu.version_d) }
.gnu.version_r : { *(.gnu.version_r) }
.rel.dyn :
{
*(.rel.init)
*(.rel.text .rel.text.* .rel.gnu.linkonce.t.*)
*(.rel.fini)
*(.rel.rodata .rel.rodata.* .rel.gnu.linkonce.r.*)
*(.rel.data.rel.ro .rel.data.rel.ro.* .rel.gnu.linkonce.d.rel.ro.*)
*(.rel.data .rel.data.* .rel.gnu.linkonce.d.*)
*(.rel.tdata .rel.tdata.* .rel.gnu.linkonce.td.*)
*(.rel.tbss .rel.tbss.* .rel.gnu.linkonce.tb.*)
*(.rel.ctors)
*(.rel.dtors)
*(.rel.got)
*(.rel.bss .rel.bss.* .rel.gnu.linkonce.b.*)
PROVIDE_HIDDEN (__rel_iplt_start = .);
*(.rel.iplt)
PROVIDE_HIDDEN (__rel_iplt_end = .);
}
.rela.dyn :
{
*(.rela.init)
*(.rela.text .rela.text.* .rela.gnu.linkonce.t.*)
*(.rela.fini)
*(.rela.rodata .rela.rodata.* .rela.gnu.linkonce.r.*)
*(.rela.data .rela.data.* .rela.gnu.linkonce.d.*)
*(.rela.tdata .rela.tdata.* .rela.gnu.linkonce.td.*)
*(.rela.tbss .rela.tbss.* .rela.gnu.linkonce.tb.*)
*(.rela.ctors)
*(.rela.dtors)
*(.rela.got)
*(.rela.bss .rela.bss.* .rela.gnu.linkonce.b.*)
PROVIDE_HIDDEN (__rela_iplt_start = .);
*(.rela.iplt)
PROVIDE_HIDDEN (__rela_iplt_end = .);
}
.rel.plt :
{
*(.rel.plt)
}
.rela.plt :
{
*(.rela.plt)
}
.init :
{
KEEP (*(SORT_NONE(.init)))
}
.plt : { *(.plt) }
.iplt : { *(.iplt) }
.text 0x90000000:
{
*(.text.init)
*(.text.unlikely .text.*_unlikely .text.unlikely.*)
*(.text.exit .text.exit.*)
*(.text.startup .text.startup.*)
*(.text.hot .text.hot.*)
*(SORT(.text.sorted.*))
*(.text .stub .text.* .gnu.linkonce.t.*)
/* .gnu.warning sections are handled specially by elf.em. */
*(.gnu.warning)
*(.glue_7t) *(.glue_7) *(.vfp11_veneer) *(.v4_bx)
}
.fini :
{
KEEP (*(SORT_NONE(.fini)))
}
PROVIDE (__etext = .);
PROVIDE (_etext = .);
PROVIDE (etext = .);
.rodata : { *(.rodata .rodata.* .gnu.linkonce.r.*) }
.rodata1 : { *(.rodata1) }
.ARM.extab : { *(.ARM.extab* .gnu.linkonce.armextab.*) }
.ARM.exidx :
{
PROVIDE_HIDDEN (__exidx_start = .);
*(.ARM.exidx* .gnu.linkonce.armexidx.*)
PROVIDE_HIDDEN (__exidx_end = .);
}
.eh_frame_hdr : { *(.eh_frame_hdr) *(.eh_frame_entry .eh_frame_entry.*) }
.eh_frame : ONLY_IF_RO { KEEP (*(.eh_frame)) *(.eh_frame.*) }
.gcc_except_table : ONLY_IF_RO { *(.gcc_except_table .gcc_except_table.*) }
.gnu_extab : ONLY_IF_RO { *(.gnu_extab*) }
/* These sections are generated by the Sun/Oracle C++ compiler. */
.exception_ranges : ONLY_IF_RO { *(.exception_ranges*) }
/* Adjust the address for the data segment. We want to adjust up to
the same address within the page on the next page up. */
. = DATA_SEGMENT_ALIGN (CONSTANT (MAXPAGESIZE), CONSTANT (COMMONPAGESIZE));
/* Exception handling */
.eh_frame : ONLY_IF_RW { KEEP (*(.eh_frame)) *(.eh_frame.*) }
.gnu_extab : ONLY_IF_RW { *(.gnu_extab) }
.gcc_except_table : ONLY_IF_RW { *(.gcc_except_table .gcc_except_table.*) }
.exception_ranges : ONLY_IF_RW { *(.exception_ranges*) }
/* Thread Local Storage sections */
.tdata :
{
PROVIDE_HIDDEN (__tdata_start = .);
*(.tdata .tdata.* .gnu.linkonce.td.*)
}
.tbss : { *(.tbss .tbss.* .gnu.linkonce.tb.*) *(.tcommon) }
.preinit_array :
{
PROVIDE_HIDDEN (__preinit_array_start = .);
KEEP (*(.preinit_array))
PROVIDE_HIDDEN (__preinit_array_end = .);
}
.init_array :
{
PROVIDE_HIDDEN (__init_array_start = .);
KEEP (*(SORT_BY_INIT_PRIORITY(.init_array.*) SORT_BY_INIT_PRIORITY(.ctors.*)))
KEEP (*(.init_array EXCLUDE_FILE (*crtbegin.o *crtbegin?.o *crtend.o *crtend?.o ) .ctors))
PROVIDE_HIDDEN (__init_array_end = .);
}
.fini_array :
{
PROVIDE_HIDDEN (__fini_array_start = .);
KEEP (*(SORT_BY_INIT_PRIORITY(.fini_array.*) SORT_BY_INIT_PRIORITY(.dtors.*)))
KEEP (*(.fini_array EXCLUDE_FILE (*crtbegin.o *crtbegin?.o *crtend.o *crtend?.o ) .dtors))
PROVIDE_HIDDEN (__fini_array_end = .);
}
.ctors :
{
/* gcc uses crtbegin.o to find the start of
the constructors, so we make sure it is
first. Because this is a wildcard, it
doesn't matter if the user does not
actually link against crtbegin.o; the
linker won't look for a file to match a
wildcard. The wildcard also means that it
doesn't matter which directory crtbegin.o
is in. */
KEEP (*crtbegin.o(.ctors))
KEEP (*crtbegin?.o(.ctors))
/* We don't want to include the .ctor section from
the crtend.o file until after the sorted ctors.
The .ctor section from the crtend file contains the
end of ctors marker and it must be last */
KEEP (*(EXCLUDE_FILE (*crtend.o *crtend?.o ) .ctors))
KEEP (*(SORT(.ctors.*)))
KEEP (*(.ctors))
}
.dtors :
{
KEEP (*crtbegin.o(.dtors))
KEEP (*crtbegin?.o(.dtors))
KEEP (*(EXCLUDE_FILE (*crtend.o *crtend?.o ) .dtors))
KEEP (*(SORT(.dtors.*)))
KEEP (*(.dtors))
}
.jcr : { KEEP (*(.jcr)) }
.data.rel.ro : { *(.data.rel.ro.local* .gnu.linkonce.d.rel.ro.local.*) *(.data.rel.ro .data.rel.ro.* .gnu.linkonce.d.rel.ro.*) }
.dynamic : { *(.dynamic) }
. = DATA_SEGMENT_RELRO_END (0, .);
.got : { *(.got.plt) *(.igot.plt) *(.got) *(.igot) }
.data :
{
PROVIDE (__data_start = .);
*(.data .data.* .gnu.linkonce.d.*)
SORT(CONSTRUCTORS)
*(.data1)
*(.dynbss)
*(.bss .bss.* .gnu.linkonce.b.*)
*(COMMON)
/* Align here to ensure that the .bss section occupies space up to
_end. Align after .bss to ensure correct alignment even if the
.bss section disappears because there are no input sections.
FIXME: Why do we need it? When there is no .bss section, we do not
pad the .data section. */
. = ALIGN(. != 0 ? 32 / 8 : 1);
}
. = ALIGN(32 / 8);
. = SEGMENT_START("ldata-segment", .);
. = ALIGN(32 / 8);
__end__ = .;
_end = .; PROVIDE (end = .);
. = DATA_SEGMENT_END (.);
/* Stabs debugging sections. */
.stab 0 : { *(.stab) }
.stabstr 0 : { *(.stabstr) }
.stab.excl 0 : { *(.stab.excl) }
.stab.exclstr 0 : { *(.stab.exclstr) }
.stab.index 0 : { *(.stab.index) }
.stab.indexstr 0 : { *(.stab.indexstr) }
.comment 0 : { *(.comment) }
.gnu.build.attributes : { *(.gnu.build.attributes .gnu.build.attributes.*) }
/* DWARF debug sections.
Symbols in the DWARF debugging sections are relative to the beginning
of the section so we begin them at 0. */
/* DWARF 1. */
.debug 0 : { *(.debug) }
.line 0 : { *(.line) }
/* GNU DWARF 1 extensions. */
.debug_srcinfo 0 : { *(.debug_srcinfo) }
.debug_sfnames 0 : { *(.debug_sfnames) }
/* DWARF 1.1 and DWARF 2. */
.debug_aranges 0 : { *(.debug_aranges) }
.debug_pubnames 0 : { *(.debug_pubnames) }
/* DWARF 2. */
.debug_info 0 : { *(.debug_info .gnu.linkonce.wi.*) }
.debug_abbrev 0 : { *(.debug_abbrev) }
.debug_line 0 : { *(.debug_line .debug_line.* .debug_line_end) }
.debug_frame 0 : { *(.debug_frame) }
.debug_str 0 : { *(.debug_str) }
.debug_loc 0 : { *(.debug_loc) }
.debug_macinfo 0 : { *(.debug_macinfo) }
/* SGI/MIPS DWARF 2 extensions. */
.debug_weaknames 0 : { *(.debug_weaknames) }
.debug_funcnames 0 : { *(.debug_funcnames) }
.debug_typenames 0 : { *(.debug_typenames) }
.debug_varnames 0 : { *(.debug_varnames) }
/* DWARF 3. */
.debug_pubtypes 0 : { *(.debug_pubtypes) }
.debug_ranges 0 : { *(.debug_ranges) }
/* DWARF 5. */
.debug_addr 0 : { *(.debug_addr) }
.debug_line_str 0 : { *(.debug_line_str) }
.debug_loclists 0 : { *(.debug_loclists) }
.debug_macro 0 : { *(.debug_macro) }
.debug_names 0 : { *(.debug_names) }
.debug_rnglists 0 : { *(.debug_rnglists) }
.debug_str_offsets 0 : { *(.debug_str_offsets) }
.debug_sup 0 : { *(.debug_sup) }
.gnu.attributes 0 : { KEEP (*(.gnu.attributes)) }
.note.gnu.arm.ident 0 : { KEEP (*(.note.gnu.arm.ident)) }
/DISCARD/ : { *(.note.GNU-stack) *(.gnu_debuglink) *(.gnu.lto_*) }
}
File diff suppressed because it is too large Load Diff
+590
View File
@@ -0,0 +1,590 @@
/* ===========================================================================
* event_dumper — LVGL running as an app on top of the Brain's PEG kernel.
*
* Architecture (iter 26, confirmed on real hardware):
* - We register as a PegDecoratedWindow with Message override (slot 19).
* - Draw dispatch (slot 12) is never invoked for us by PEG's dirty-region
* walker, so we push our own shadow buffer to the screen ourselves
* via push_shadow_to_screen(), driven from Message and a 200ms LVGL
* timer. That guarantees updates without depending on PEG's Invalidate.
* ========================================================================= */
#include "lvgl/lvgl.h"
#include "pseudo_peg_lib.h"
/* libc stubs supplied by libbrain (see libbrain/sys.s). Extern-declared
* instead of `#include "libbrain/libbrain.h"` because that header defines
* `typedef unsigned long size_t` which conflicts with LVGL's `unsigned int`. */
extern void* malloc(unsigned int size);
extern void* realloc(void* ptr, unsigned int size);
extern int free(void* ptr);
extern void* memset(void* ptr, int ch, unsigned int n);
static PegBitmap g_lvgl_pegbmp;
typedef struct {
void* reserved;
char* exec_path;
void* pPresent;
void* reserved2;
} app_ctx;
/* ===== State ===== */
static void* g_window;
static int32_t g_fb_w = 480;
static int32_t g_fb_h = 640;
/* Saved from app_ctx so app_exit() can hand it back to sh_ExitApp */
static char* g_exec_path;
/* Key code (PegMessage.Param) that triggers a clean exit. Adjust after
* seeing the `key param=0x??` value in the debug label for the physical
* button you want to use. */
#define EXIT_KEY_SCAN 0x1B
/* LVGL renders directly into g_shadow (we own it, freed in app_exit).
* push_shadow_to_screen() blits it onto the PEG screen. */
static uint8_t* g_shadow;
static uint32_t g_shadow_bytes;
static volatile uint32_t g_draw_calls;
static volatile uint32_t g_msg_calls;
static volatile uint16_t g_last_msg_type;
static volatile uint16_t g_last_msg_subtype;
static volatile int32_t g_last_msg_data;
/* Ring buffer of recent wTypes so we can see the sequence of dispatches
* regardless of frequency. */
#define WTYPE_HISTORY_LEN 12
static volatile uint16_t g_wtype_history[WTYPE_HISTORY_LEN];
static volatile uint8_t g_wtype_head;
/* Count of each wType we've received (indexed by type; only first 64 kept
* to keep the array small). Higher types get bucketed into slot 63. */
static volatile uint32_t g_wtype_counts[64];
/* ---- PEG tick-rate probe (see PEG_SetTimer call in main) ---- */
#define PROBE_TIMER_ID 0x50EC
#define PROBE_TIMER_TICKS 50 /* per docs, ONE_SECOND is typically 50 */
static volatile uint32_t g_probe_fires;
static volatile uint32_t g_probe_first_ms;
static volatile uint32_t g_probe_last_ms;
static volatile uint32_t g_probe_last_delta_ms;
/* Captured base Message handler for delegation */
static msg_fn_t g_base_msg;
/* Forward decls (definitions live at the bottom of the file) */
uint32_t tstmr_get_millis(void);
void my_sleep(uint32_t millis);
/* LVGL indev shared state (produced by our_Message, consumed by read_cb) */
static lv_indev_t* g_indev_pointer;
static lv_indev_t* g_indev_keypad;
static volatile int16_t g_touch_x;
static volatile int16_t g_touch_y;
static volatile lv_indev_state_t g_touch_state = LV_INDEV_STATE_REL;
static volatile uint32_t g_key_pending;
/* Last-input snapshots for the debug label */
static volatile int16_t g_last_touch_x = -1;
static volatile int16_t g_last_touch_y = -1;
static volatile uint8_t g_last_touch_state; /* 0=up, 1=down */
/* Snapshot of the last PM_KEY* PegMessage for the debug label. Param is
* the actual key code; ExtParams[0] holds Brain's state/modifier bits. */
static volatile int32_t g_last_key_seen = 0; /* 0 = never seen */
static volatile uint16_t g_last_key_type; /* PM_KEY / PM_KEY_DOWN */
static volatile uint16_t g_last_key_param;
static volatile int32_t g_last_key_ext0;
static volatile int32_t g_last_key_ext1;
static volatile int16_t g_last_key_ptx;
static volatile int16_t g_last_key_pty;
static volatile lv_indev_state_t g_key_state = LV_INDEV_STATE_REL;
/* Brain key code (in PegMessage.Param) → LVGL key constant. */
static uint32_t peg_key_to_lvgl(int32_t k) {
switch (k) {
case 0x0D: return LV_KEY_ENTER;
case 0x1B: return LV_KEY_ESC;
case 0x08: return LV_KEY_BACKSPACE;
case 0x20: return ' ';
case 0x25E: return LV_KEY_LEFT;
case 0x25F: return LV_KEY_DOWN;
case 0x260: return LV_KEY_UP;
case 0x261: return LV_KEY_RIGHT;
case 0x265: return 0; /* SHIFT (sticky — sets modifier bit 0x40 in
* ExtParams[0] on the next real keypress) */
default: return (uint32_t)k;
}
}
static void indev_pointer_read(lv_indev_t* d, lv_indev_data_t* out) {
(void)d;
out->point.x = g_touch_x;
out->point.y = g_touch_y;
out->state = g_touch_state;
}
static void indev_keypad_read(lv_indev_t* d, lv_indev_data_t* out) {
(void)d;
out->key = g_key_pending;
out->state = g_key_state;
if (g_key_state == LV_INDEV_STATE_PR)
g_key_state = LV_INDEV_STATE_REL;
}
/* Tear down PEG + LVGL, then jump back to the launcher.
* sh_ExitApp never returns on success.
*
* Order matters:
* 1. KillTimer — stop any future PM_TIMER dispatch to a soon-to-be-freed
* window.
* 2. PEG_Destroy — posts PM_DESTROY; PresMgr unlinks us from its window
* tree and runs the dtor (which free()s the window). Async, but PEG
* typically processes the queue before yielding to the shell.
* 3. lv_deinit — releases every LVGL-owned allocation (display, indevs,
* timers, screen widgets). Uses our free() → sys_free trampoline.
* 4. free(shadow) — LVGL v9's lv_display_set_buffers doesn't take
* ownership, so the caller (us) owns it.
* 5. sh_ExitApp — launcher takes over.
*/
static void app_exit(void) {
if (g_window) {
PEG_KillTimer(g_window, PROBE_TIMER_ID);
PEG_Destroy(g_window, g_window);
g_window = NULL;
}
lv_deinit();
if (g_shadow) {
free(g_shadow);
g_shadow = NULL;
}
if (g_exec_path) sh_ExitApp(g_exec_path); // without this, screen goes mysterious green'ish window
/* Should not reach here. Fall through returns to caller; PEG scheduler
* takes over again which is at least survivable. */
}
/* Push our LVGL shadow through PEG's proper draw pipeline into PEG's
* internal shadow, then EndDraw's MemoryToScreen flushes to physical FB.
* Doing this synchronously means we don't depend on PEG's Draw dispatch
* (which never fires on us — the dirty walker skips windows that aren't
* registered as top-level focus targets via STATUS bit 0x40). */
static void push_shadow_to_screen(void) {
if (!g_shadow || !g_window)
return;
/* mReal is embedded in every PegThing at +0x0C */
const struct PegRect* pRect = (const struct PegRect*)((char*)g_window + 0x0C);
PEG_WinBeginDraw(g_window, pRect);
g_lvgl_pegbmp.bpp = 0x10;
g_lvgl_pegbmp.flags = 0x00;
g_lvgl_pegbmp.wWidth = (uint16_t)g_fb_w;
g_lvgl_pegbmp.wHeight = (uint16_t)g_fb_h;
g_lvgl_pegbmp.reserved = 0;
g_lvgl_pegbmp.refcount = 1;
g_lvgl_pegbmp.pData = g_shadow;
void* scr = PEG_GetScreen();
if (scr) {
void** vt = *(void***)scr;
((scr_bitmap_fn)vt[SCR_VF_BITMAP])(scr, 0, &g_lvgl_pegbmp);
((scr_enddraw_fn)vt[SCR_VF_ENDDRAW])(scr);
}
}
/* Debug label refreshed by a periodic LVGL timer */
static lv_obj_t* g_dbg_label;
static void refresh_cb(lv_timer_t* t) {
(void)t;
if (g_dbg_label) {
char buf[256];
int n = 0;
n += lv_snprintf(buf + n, sizeof(buf) - n,
"drw=%u msg=%u\n",
(unsigned)g_draw_calls, (unsigned)g_msg_calls);
n += lv_snprintf(buf + n, sizeof(buf) - n,
"hist:");
for (int i = 0; i < WTYPE_HISTORY_LEN && n < (int)sizeof(buf) - 8; i++) {
int idx = (g_wtype_head + i) % WTYPE_HISTORY_LEN;
n += lv_snprintf(buf + n, sizeof(buf) - n, " %u",
(unsigned)g_wtype_history[idx]);
}
n += lv_snprintf(buf + n, sizeof(buf) - n, "\n");
static const uint8_t interesting[] = {4, 6, 8, 9, 10, 11, 17, 24, 25, 28, 30, 32, 33};
for (unsigned i = 0; i < sizeof(interesting) && n < (int)sizeof(buf) - 16; i++) {
uint8_t k = interesting[i];
if (g_wtype_counts[k]) {
n += lv_snprintf(buf + n, sizeof(buf) - n, " t%u=%u",
k, (unsigned)g_wtype_counts[k]);
}
}
/* Last touch — coordinates + state (DN/UP) */
if (g_last_touch_x >= 0) {
n += lv_snprintf(buf + n, sizeof(buf) - n,
"\ntouch: (%d,%d) %s",
(int)g_last_touch_x, (int)g_last_touch_y,
g_last_touch_state ? "DN" : "UP");
}
/* Last key event: param = key code, ext0 flags (0x800 press,
* 0xC00 held, 0x40 SHIFT-sticky active). */
if (g_last_key_seen) {
n += lv_snprintf(buf + n, sizeof(buf) - n,
"\nkey t=%u param=0x%X ext0=0x%X ext1=0x%X"
"\n pt=(%d,%d)",
(unsigned)g_last_key_type,
(unsigned)g_last_key_param,
(unsigned)g_last_key_ext0,
(unsigned)g_last_key_ext1,
(int)g_last_key_ptx, (int)g_last_key_pty);
}
/* Tick-rate probe: show fires, last delta, and cumulative average.
* SetTimer requested PROBE_TIMER_TICKS ticks per fire; delta_ms
* lets us back-calculate ms per tick. */
if (g_probe_fires > 0) {
uint32_t avg = (g_probe_last_ms - g_probe_first_ms) /
(g_probe_fires > 1 ? g_probe_fires - 1 : 1);
n += lv_snprintf(buf + n, sizeof(buf) - n,
"\ntimer fires=%u last=%ums avg=%ums (tick~%u.%02ums)",
(unsigned)g_probe_fires,
(unsigned)g_probe_last_delta_ms,
(unsigned)avg,
(unsigned)(avg / PROBE_TIMER_TICKS),
(unsigned)((avg * 100u / PROBE_TIMER_TICKS) % 100u));
}
lv_label_set_text(g_dbg_label, buf);
}
push_shadow_to_screen();
}
/* Draw override (vtable slot 12). Not currently dispatched by PEG in our
* setup — kept as a reference implementation of the proper PEG draw
* pipeline in case a future subclass configuration triggers it. */
static void our_Draw(void* self, const struct PegRect* invalid) {
g_draw_calls++;
/* Probe (60x60 top-left, colour cycles) */
if (g_shadow) {
uint16_t* px = (uint16_t*)g_shadow;
uint16_t c = (uint16_t)((g_draw_calls * 0x1111) | 0x0800);
if (g_draw_calls & 1)
c ^= 0xF800;
for (int y = 0; y < 60; y++) {
uint16_t* row = px + (uint32_t)y * g_fb_w;
for (int x = 0; x < 60; x++)
row[x] = c;
}
}
lv_timer_handler();
/* Proper Draw pattern per PEG docs:
* BeginDraw(invalidRect) → base Draw / custom → EndDraw() */
PEG_WinBeginDraw(self, invalid);
PEG_WinBaseDraw(self, invalid);
/* Custom overlay: blit our LVGL shadow */
if (g_shadow) {
g_lvgl_pegbmp.bpp = 0x10;
g_lvgl_pegbmp.flags = 0x00;
g_lvgl_pegbmp.wWidth = (uint16_t)g_fb_w;
g_lvgl_pegbmp.wHeight = (uint16_t)g_fb_h;
g_lvgl_pegbmp.reserved = 0;
g_lvgl_pegbmp.refcount = 1;
g_lvgl_pegbmp.pData = g_shadow;
void* scr = PEG_GetScreen();
if (scr) {
void** scr_vt = *(void***)scr;
((scr_bitmap_fn)scr_vt[SCR_VF_BITMAP])(scr, 0, &g_lvgl_pegbmp);
}
}
/* Matching EndDraw — Screen()->EndDraw() per PegThing::EndDraw inline */
{
void* scr = PEG_GetScreen();
if (scr) {
void** scr_vt = *(void***)scr;
((scr_enddraw_fn)scr_vt[SCR_VF_ENDDRAW])(scr);
}
}
/* NO self-Invalidate — nesting BeginDraw/EndDraw inside our_Draw is
* balanced. Continuous updates need a separate Invalidate path. */
}
/* ===== Message override — dispatch input events into LVGL indev ========= */
static int32_t our_Message(void* self, const struct PegMessage* msg) {
unsigned short t = msg->Type;
unsigned short param = msg->Param;
g_msg_calls++;
g_last_msg_type = t;
g_last_msg_subtype = param;
g_last_msg_data = msg->ExtParams[0];
/* Ring buffer of wTypes we've seen */
g_wtype_history[g_wtype_head] = t;
g_wtype_head = (uint8_t)((g_wtype_head + 1) % WTYPE_HISTORY_LEN);
g_wtype_counts[t < 64 ? t : 63]++;
/* Small Message probe (top-right corner) — just for visual confirmation
* that Message dispatch is reaching us. Written into shadow only;
* push_shadow_to_screen below flushes it. */
if (g_shadow) {
uint16_t* sh = (uint16_t*)g_shadow;
uint16_t c = (uint16_t)((g_msg_calls * 0x2108) | 0x0400);
int32_t x0 = g_fb_w - 60;
for (int y = 0; y < 60; y++) {
uint16_t* row = sh + (uint32_t)y * g_fb_w + x0;
for (int x = 0; x < 60; x++)
row[x] = c;
}
int32_t bar_len = (t > 63 ? 63 : t) * 4;
for (int y = 60; y < 70; y++) {
uint16_t* row = sh + (uint32_t)y * g_fb_w + x0;
for (int x = 0; x < bar_len; x++)
row[x] = 0xF800;
for (int x = bar_len; x < 60; x++)
row[x] = 0x0000;
}
}
switch (t) {
/* Pointer / touch — position lives in the union's Point field */
case PM_LBUTTONDOWN:
g_touch_x = (int16_t)msg->Point.x;
g_touch_y = (int16_t)msg->Point.y;
g_touch_state = LV_INDEV_STATE_PR;
g_last_touch_x = g_touch_x;
g_last_touch_y = g_touch_y;
g_last_touch_state = 1;
break;
case PM_LBUTTONUP:
g_touch_x = (int16_t)msg->Point.x;
g_touch_y = (int16_t)msg->Point.y;
g_touch_state = LV_INDEV_STATE_REL;
g_last_touch_x = g_touch_x;
g_last_touch_y = g_touch_y;
g_last_touch_state = 0;
break;
/* Key input — Brain layout:
* Param = raw key code (e.g. 0x265 = SHIFT, 0x1B = ESC, ASCII)
* ExtParams[0]= state/modifier flags (0x800 = press, 0xC00 = held,
* bit 0x40 = SHIFT-sticky is active)
* ExtParams[1]= (unused) */
case PM_KEY:
case PM_KEY_DOWN: {
int32_t code = (int32_t)param;
g_last_key_seen = 1;
g_last_key_type = t;
g_last_key_param = param;
g_last_key_ext0 = msg->ExtParams[0];
g_last_key_ext1 = msg->ExtParams[1];
g_last_key_ptx = (int16_t)msg->Point.x;
g_last_key_pty = (int16_t)msg->Point.y;
if (code == EXIT_KEY_SCAN) {
app_exit(); /* never returns on success */
return 0;
}
uint32_t k = peg_key_to_lvgl(code);
if (k) {
g_key_pending = k;
g_key_state = LV_INDEV_STATE_PR;
}
break;
}
case PM_KEY_UP:
g_key_state = LV_INDEV_STATE_REL;
break;
case PM_TIMER: {
uint32_t now = tstmr_get_millis();
if (param == PROBE_TIMER_ID) {
if (g_probe_fires == 0) {
g_probe_first_ms = now;
g_probe_last_delta_ms = 0;
} else {
g_probe_last_delta_ms = now - g_probe_last_ms;
}
g_probe_last_ms = now;
g_probe_fires++;
}
break;
}
}
/* Tick LVGL on every message (input drives widget state changes),
* then push the result to the physical screen through PEG's pipeline. */
lv_timer_handler();
push_shadow_to_screen();
/* Delegate to base so PEG's own bookkeeping runs. */
return g_base_msg(self, msg);
}
/* flush_cb: g_shadow IS the LVGL frame buffer, so nothing to copy.
* push_shadow_to_screen() takes care of moving it to the panel. */
static void shadow_flush(lv_display_t* disp, const lv_area_t* area, uint8_t* px_buf) {
(void)area;
(void)px_buf;
lv_display_flush_ready(disp);
}
/* Private vtable — what a C++ compiler would auto-generate for a subclass.
* PEG's PegDecoratedWindow uses a shared static vtable in ROM (0x600DAF88),
* so we copy it here, patch our overridden slots, and swap our window's
* vptr to this array. Heap allocation would work equally well; static is
* chosen for simpler debugging (fixed address). */
static void* g_static_vt[80] __attribute__((aligned(16)));
__attribute__((section(".text.init"))) void main(app_ctx* ctx) {
/* Save exec_path so app_exit() can pass it to sh_ExitApp later */
if (ctx) g_exec_path = ctx->exec_path;
/* --- Panel geometry --- */
if (ctx && ctx->pPresent) {
short rect[4] = {0};
PEG_ScreenRect(ctx->pPresent, rect);
int32_t w = rect[2] - rect[0] + 1;
int32_t h = rect[3] - rect[1] + 1;
if (w >= 64 && w <= 4096 && h >= 64 && h <= 4096) {
g_fb_w = w;
g_fb_h = h;
}
}
/* --- Shadow buffer LVGL renders into --- */
g_shadow_bytes = (uint32_t)g_fb_w * (uint32_t)g_fb_h * 2u;
g_shadow = (uint8_t*)lv_malloc(g_shadow_bytes);
/* --- LVGL bring-up (rendering into shadow buffer) --- */
lv_init();
lv_tick_set_cb(tstmr_get_millis);
lv_display_t* display = lv_display_create(g_fb_w, g_fb_h);
lv_display_set_color_format(display, LV_COLOR_FORMAT_RGB565);
lv_display_set_buffers(display, g_shadow, NULL, g_shadow_bytes,
LV_DISPLAY_RENDER_MODE_FULL);
lv_display_set_flush_cb(display, shadow_flush);
/* ---- LVGL widget setup ---- */
lv_obj_t* scr = lv_screen_active();
lv_obj_set_style_bg_color(scr, lv_color_hex(0x102040), 0); /* dark blue-grey */
lv_obj_set_style_bg_opa(scr, LV_OPA_COVER, 0);
lv_obj_t* label = lv_label_create(scr);
lv_label_set_text_fmt(label,
"LVGL via PEG\n%ldx%ld RGB565", (long)g_fb_w, (long)g_fb_h);
lv_obj_set_style_text_color(label, lv_color_hex(0xFFFFFF), 0);
lv_obj_align(label, LV_ALIGN_CENTER, 0, 0);
/* A colourful rectangle so we see a solid block regardless of font */
lv_obj_t* box = lv_obj_create(scr);
lv_obj_set_size(box, 200, 100);
lv_obj_align(box, LV_ALIGN_TOP_MID, 0, 20);
lv_obj_set_style_bg_color(box, lv_color_hex(0x00C000), 0);
lv_obj_set_style_border_width(box, 4, 0);
lv_obj_set_style_border_color(box, lv_color_hex(0xFF8000), 0);
/* Debug label at bottom: msg counts, wType histogram, last touch/key,
* PEG-timer probe stats. */
lv_obj_t* dbg = lv_label_create(scr);
lv_obj_set_style_text_color(dbg, lv_color_hex(0xFFFF00), 0);
lv_obj_set_style_bg_color(dbg, lv_color_hex(0x000000), 0);
lv_obj_set_style_bg_opa(dbg, LV_OPA_COVER, 0);
lv_obj_set_style_pad_all(dbg, 4, 0);
lv_obj_align(dbg, LV_ALIGN_BOTTOM_LEFT, 0, 0);
lv_label_set_text(dbg, "waiting...");
g_dbg_label = dbg;
/* Setup indev */
g_indev_pointer = lv_indev_create();
lv_indev_set_type(g_indev_pointer, LV_INDEV_TYPE_POINTER);
lv_indev_set_read_cb(g_indev_pointer, indev_pointer_read);
g_indev_keypad = lv_indev_create();
lv_indev_set_type(g_indev_keypad, LV_INDEV_TYPE_KEYPAD);
lv_indev_set_read_cb(g_indev_keypad, indev_keypad_read);
/* Render into shadow a few times to complete initial paint */
for (int i = 0; i < 5; i++)
lv_timer_handler();
/* Repeating LVGL timer: refresh debug label and push shadow → panel
* so widgets animate even when no input arrives. */
lv_timer_create(refresh_cb, 200, NULL);
/* --- Register as a real PEG window --- */
if (ctx && ctx->pPresent) {
g_window = lv_malloc(0x108);
if (g_window) {
memset(g_window, 0, 0x108);
/* Construct + place. AdjustClient recomputes mClient from
* mReal + frame thickness; harmless even though we bypass
* PEG's client-area drawing. */
PEG_DecWindow_ctor(g_window, AF_ENABLED | FF_THIN);
PEG_RectSet((short*)((char*)g_window + 0x0C),
0, 0, (short)(g_fb_w - 1), (short)(g_fb_h - 1));
PEG_AdjustClient(g_window);
/* Manual subclassing: copy the shared PegDecoratedWindow vtable,
* patch our overridden slots, and point our instance at it.
* (What a C++ compiler would do automatically for us.) */
void** base_vt = *(void***)g_window;
for (int i = 0; i < 80; i++)
g_static_vt[i] = base_vt[i];
g_base_msg = (msg_fn_t)base_vt[VFUNC_MESSAGE];
g_static_vt[VFUNC_DRAW] = (void*)our_Draw;
g_static_vt[VFUNC_MESSAGE] = (void*)our_Message;
*(void**)g_window = g_static_vt;
/* PegPresentationManager::Add(window, show=TRUE) */
typedef void (*add_fn)(void*, void*, int);
void** pm_vt = *(void***)ctx->pPresent;
((add_fn)pm_vt[3])(ctx->pPresent, g_window, 1);
/* Route keyboard input to us without triggering the focus-event
* chain (bit 0x40 pathway) which needs additional setup. */
PEG_SetCurrent(ctx->pPresent, g_window);
/* Kick off the PEG-tick probe: one PM_TIMER per PROBE_TIMER_TICKS
* ticks, repeated. Handled in our_Message case PM_TIMER. */
PEG_SetTimer(g_window, PROBE_TIMER_ID,
PROBE_TIMER_TICKS, PROBE_TIMER_TICKS);
}
}
/* Return — cooperative scheduler, main-loop starves everything. */
return;
}
/* ===== LVGL alloc hooks + libc stubs ================================== */
void* lv_malloc_core(size_t size) {
return malloc(size);
}
void* lv_realloc_core(void* p, size_t size) {
return realloc(p, size);
}
void lv_free_core(void* p) {
free(p);
}
void lv_mem_init(void) {}
void lv_mem_deinit(void) {}
lv_result_t lv_mem_test_core(void) {
return LV_RESULT_OK;
}
void lv_mem_monitor_core(lv_mem_monitor_t* m) {
(void)m;
}
uint32_t tstmr_get_millis(void) {
uint32_t lo = *(volatile uint32_t*)0x410A3C00;
uint32_t hi = *(volatile uint32_t*)0x410A3C04;
return (uint32_t)(((((uint64_t)hi) << 32) | lo) / 1000);
}
void my_sleep(uint32_t millis) {
uint32_t start = tstmr_get_millis();
while (tstmr_get_millis() - start < millis) {
}
}
+65
View File
@@ -0,0 +1,65 @@
#ifndef PEG_CONST_H
#define PEG_CONST_H
/* PegThing frame-style values (from PEG API docs) */
#define FF_NONE 0
#define FF_THIN 1
#define FF_RAISED 2
#define FF_RECESSED 3
#define FF_THICK 4
#define AF_ENABLED 0x10
/* ===== PegMessage.Type constants (Brain-specific numbering) =============
* Values come from watching wTypes arrive in our_Message on real hardware.
* They do NOT match the generic PEG SDK numbering. Field usage per type:
* PM_KEY / PM_KEY_DOWN — Param = key code (0x265=SHIFT, 0x1B=ESC, ASCII,
* arrows 0x25E..0x261). ExtParams[0] = KFLAG_* /
* KMOD_* bits below. ExtParams[1] unused.
* PM_LBUTTONDOWN/UP — Point.x/y = touch coordinates.
* PM_TIMER — Param = timer Id passed to SetTimer. */
#define PM_KEY 9
#define PM_LBUTTONDOWN 10
#define PM_LBUTTONUP 11
#define PM_TIMER 32
#define PM_KEY_DOWN 33
#define PM_KEY_UP 34 /* tentative — needs confirmation */
/* Bits found in PegMessage.ExtParams[0] on key messages */
#define KMOD_SHIFT 0x40 /* SHIFT-sticky is engaged for this event */
#define KFLAG_PRESS 0x800 /* set on any key press */
#define KFLAG_HELD 0xC00 /* set while a key is held down (repeat) */
struct PegPoint {
unsigned short x;
unsigned short y;
};
struct PegRect {
unsigned short Left;
unsigned short Top;
unsigned short Right;
unsigned short Bottom;
};
struct PegMessage {
void* pSource;
void* pTarget;
unsigned short Type;
unsigned short Param;
union {
struct PegRect Rect;
struct PegPoint Point;
int ExtParams[2];
void* pData;
int UserLong[2];
unsigned int UserULONG[2];
short UserShoft[4];
unsigned short UserUShoft[4];
unsigned char UserUByte[8];
};
void* pNext;
};
#endif // PEG_CONST_H
+71
View File
@@ -0,0 +1,71 @@
#ifndef PEG_FUNC_H
#define PEG_FUNC_H
#include "peg_const.h"
/* ===== Function-pointer typedefs =========================================
* Since we can't run C++ static init (no ELF loader), method prototypes
* from the PEG SDK are re-declared here as function pointers, addressed
* directly at the fixed kernel export slots. */
typedef void* (*decwin_ctor_proc) (void* self, unsigned short style);
typedef void (*pegrect_set_proc) (short* rect, short x1, short y1,
short x2, short y2);
typedef void (*screen_rect_proc) (void* pm, short out_rect[4]);
typedef void (*adjust_client_proc) (void* self);
typedef void (*win_begin_draw_proc)(void* self, const struct PegRect* rect);
typedef void (*win_base_draw_proc) (void* self, const struct PegRect* rect);
typedef void* (*get_screen_proc) (void);
typedef void (*set_current_proc) (void* pm, void* target);
typedef void (*pt_settimer_proc) (void* pWho, unsigned int Id,
unsigned int Count, unsigned int Reset);
typedef void (*pt_killtimer_proc) (void* pWho, unsigned int Id);
typedef void (*pt_destroy_proc) (void* pSource, void* pTarget);
typedef void (*sh_exitapp_proc) (char* path);
/* PegScreen vtable slots */
typedef struct __attribute__((packed)) {
unsigned char bpp;
unsigned char flags;
unsigned short wWidth;
unsigned short wHeight;
unsigned short reserved;
unsigned int refcount;
unsigned char* pData;
} PegBitmap;
typedef void (*scr_bitmap_fn) (void* scr, int packed_xy, PegBitmap* bmp);
typedef void (*scr_enddraw_fn)(void* scr);
/* Signature of every PegThing::Message override (vfunc[19]) */
typedef int (*msg_fn_t)(void* self, const struct PegMessage* msg);
/* ===== Kernel export addresses (x1ram, verified) ========================
* Most PEG_* addresses in the 0x60004000-0x60006FFF range are jump-table
* trampolines (`ldr pc,[pc,#-4]; .word ADDR`); a few point straight at
* the implementation. sh_ExitApp lives in Brain's shell area, not PEG. */
#define PEG_DecWindow_ctor ((decwin_ctor_proc) 0x6000486c) /* ord 0x09 */
#define PEG_RectSet ((pegrect_set_proc) 0x60004130) /* ord 0x23 */
#define PEG_ScreenRect ((screen_rect_proc) 0x60005358) /* ord 0x0B */
#define PEG_AdjustClient ((adjust_client_proc) 0x60004850) /* ord 0x07 */
#define PEG_WinBeginDraw ((win_begin_draw_proc)0x60004590) /* → 6010D050 → screen.vfunc[3] */
#define PEG_WinBaseDraw ((win_base_draw_proc) 0x6000602C) /* → 600478EC = PegWindow::Draw base */
#define PEG_GetScreen ((get_screen_proc) 0x60004144) /* → 600B5520 */
#define PEG_SetCurrent ((set_current_proc) 0x600313C8) /* PresMgr, routes key input */
#define PEG_SetTimer ((pt_settimer_proc) 0x600470A0) /* PegThing wrapper — Count/Reset in PEG ticks */
#define PEG_KillTimer ((pt_killtimer_proc) 0x600470D0) /* PegThing wrapper */
#define PEG_Destroy ((pt_destroy_proc) 0x6002A3E4) /* posts PM_DESTROY to pTarget */
/* Brain shell function — hands control back to the launcher.
* NEVER RETURNS; call after cleaning up owned resources. */
#define sh_ExitApp ((sh_exitapp_proc) 0x840E7A48)
/* PegScreen vtable slots we call directly */
#define SCR_VF_BITMAP 4 /* Bitmap blit — screen->vfunc[4](packed_xy, bmp) */
#define SCR_VF_ENDDRAW 16 /* EndDraw — flushes MemoryToScreen when nesting=0 */
/* PegThing vtable slots we override */
#define VFUNC_DRAW 12
#define VFUNC_MESSAGE 19
#endif /* PEG_FUNC_H */
+7
View File
@@ -0,0 +1,7 @@
#ifndef PSEUDO_PEG_LIB_H
#define PSEUDO_PEG_LIB_H
#include "peg_const.h"
#include "peg_func.h"
#endif // PSEUDO_PEG_LIB_H