mirror of
https://github.com/MCKero6423/uv-k5-v3-emulator.git
synced 2026-10-02 03:15:36 +00:00
Surface firmware serial output instead of dropping it
The firmware has been printing all along and nothing was listening. USART1 has no
real model here -- it is one of the logging catch-all stubs -- so every byte went
into qemu_log_mask(LOG_UNIMP) and vanished.
Two things were needed, and the second was not in the plan:
1. Print USART1 DR writes (+0x04, per the vendor CMSIS header) as SERIAL lines.
2. Report TXE|TC in USART1 SR. This is the part I had missed. UART_Send() in
App/driver/uart.c spins on LL_USART_IsActiveFlag_TXE() with a bounded timeout
and *skips the byte* when the flag never sets. A stub returning 0 for SR meant
the firmware discarded its own output before it ever reached DR -- the only
write arriving was UART_Init()'s priming zero. So step 1 alone produced
nothing, which is why the first attempt looked like "the build has no logging".
Then a bug of my own: the priming byte is 0x00, I buffered it, and fprintf("%s")
stopped at that NUL and printed an empty line while all 46 bytes sat behind it.
NULs are now dropped, and a line flushes on CR as well as LF.
Verified: SERIAL UV-K5 Firmware, EGZUMER-F4HWN+NR7Y c91cec95
keypad_test.py still passes, which matters because this file is where removing
three fprintfs once silently deleted the keypad.
This commit is contained in:
1 parent
5b53cbbc82
commit
c6d58f5601
2 files changed
+130
-1
No files matched your search
+62
-1
@@ -1273,17 +1273,75 @@ struct PY32StubState {
|
||||
uint32_t regs[0x100];
|
||||
};
|
||||
|
||||
/* USART_SR transmit flags, from the vendor header: TXE is bit 7, TC bit 6. */
|
||||
#define PY32_USART_SR_TC (1u << 6)
|
||||
#define PY32_USART_SR_TXE (1u << 7)
|
||||
|
||||
static uint64_t py32_stub_read(void *opaque, hwaddr addr, unsigned size)
|
||||
{
|
||||
PY32StubState *s = opaque;
|
||||
const unsigned idx = addr >> 2;
|
||||
const uint32_t value = idx < ARRAY_SIZE(s->regs) ? s->regs[idx] : 0;
|
||||
uint32_t value = idx < ARRAY_SIZE(s->regs) ? s->regs[idx] : 0;
|
||||
|
||||
/*
|
||||
* USART1 SR must report the transmitter as ready, or the firmware discards
|
||||
* everything it tries to print.
|
||||
*
|
||||
* UART_Send() in App/driver/uart.c spins on LL_USART_IsActiveFlag_TXE() with
|
||||
* a bounded timeout and *skips the byte* when the flag never sets. A stub
|
||||
* that returns 0 for SR therefore silently loses all serial output: the only
|
||||
* write reaching DR is UART_Init()'s priming zero. Reporting TXE|TC keeps the
|
||||
* transmitter permanently ready, which is exactly right for a model that
|
||||
* consumes bytes instantly.
|
||||
*/
|
||||
if (addr == 0x00 && s->stub_name && !strcmp(s->stub_name, "usart1")) {
|
||||
value |= PY32_USART_SR_TXE | PY32_USART_SR_TC;
|
||||
}
|
||||
|
||||
qemu_log_mask(LOG_UNIMP, "py32-%s: read 0x%03" HWADDR_PRIx " -> 0x%08x\n",
|
||||
s->stub_name ?: "stub", addr, value);
|
||||
return value;
|
||||
}
|
||||
|
||||
/*
|
||||
* USART1 DR is the firmware's log output, so print it rather than dropping it.
|
||||
*
|
||||
* App/driver/uart.c drives USART1 at 38400 baud through UART_Send(), Main() sends
|
||||
* UART_Version at boot, and _putchar() routes every printf_ there. USART1 has no
|
||||
* real model here -- it is one of the logging catch-alls below -- so without this
|
||||
* the bytes vanish and the firmware appears to print nothing at all.
|
||||
*
|
||||
* DR is at +0x04: the vendor CMSIS header (py32f071xB.h) lays USART_TypeDef out as
|
||||
* SR at +0x00 then DR at +0x04. Buffered into a line so the output is readable
|
||||
* instead of one message per character.
|
||||
*/
|
||||
static void py32_stub_serial_byte(char ch)
|
||||
{
|
||||
static char line[256];
|
||||
static unsigned len;
|
||||
|
||||
/*
|
||||
* Drop NULs rather than buffering them. UART_Init() primes the transmitter
|
||||
* with LL_USART_TransmitData8(USARTx, 0), so the very first byte of the
|
||||
* session is 0x00; storing it made fprintf("%s") stop right there and print
|
||||
* an empty line, even though the 46 bytes of UART_Version arrived fine.
|
||||
*/
|
||||
if (ch == '\0') {
|
||||
return;
|
||||
}
|
||||
/* Flush on either terminator: the firmware sends CRLF, and a lone CR should
|
||||
* not hold a finished line hostage. */
|
||||
if (ch == '\n' || ch == '\r' || len >= sizeof(line) - 1) {
|
||||
line[len] = '\0';
|
||||
if (len > 0) {
|
||||
fprintf(stderr, "SERIAL %s\n", line);
|
||||
}
|
||||
len = 0;
|
||||
return;
|
||||
}
|
||||
line[len++] = ch;
|
||||
}
|
||||
|
||||
static void py32_stub_write(void *opaque, hwaddr addr, uint64_t value, unsigned size)
|
||||
{
|
||||
PY32StubState *s = opaque;
|
||||
@@ -1292,6 +1350,9 @@ static void py32_stub_write(void *opaque, hwaddr addr, uint64_t value, unsigned
|
||||
if (idx < ARRAY_SIZE(s->regs)) {
|
||||
s->regs[idx] = value;
|
||||
}
|
||||
if (addr == 0x04 && s->stub_name && !strcmp(s->stub_name, "usart1")) {
|
||||
py32_stub_serial_byte((char)(value & 0xff));
|
||||
}
|
||||
qemu_log_mask(LOG_UNIMP, "py32-%s: write 0x%03" HWADDR_PRIx " = 0x%08" PRIx64 "\n",
|
||||
s->stub_name ?: "stub", addr, value);
|
||||
}
|
||||
|
||||
Executable
+68
@@ -0,0 +1,68 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Firmware serial output must reach the host.
|
||||
|
||||
App/main.c sends UART_Version over USART1 right after UART_Init(), and _putchar
|
||||
routes every printf_ there too. The machine has no USART1 model -- it is one of the
|
||||
logging catch-all stubs -- so without help those bytes vanish and the firmware
|
||||
appears to print nothing at all.
|
||||
|
||||
Boots a real emulator on a private socket and checks the bytes come out of QEMU's
|
||||
stderr as SERIAL lines.
|
||||
|
||||
Run: python3 tools/test_serial_capture.py
|
||||
"""
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
import time
|
||||
|
||||
HERE = os.path.dirname(os.path.abspath(__file__))
|
||||
SIM = os.path.dirname(HERE)
|
||||
QEMU = os.path.expanduser("~/qemu-build/qemu-7.2+dfsg/build/qemu-system-arm")
|
||||
ELF = os.path.expanduser("~/uvk5-port/uvk5-sat/build/CW/nr7y.cw.elf")
|
||||
FLASH = os.path.join(SIM, "assets", "flash.img")
|
||||
LOG = "/tmp/uvk5-serial-test.log"
|
||||
QMP = "/tmp/uvk5-serial-test.sock"
|
||||
|
||||
|
||||
def main():
|
||||
for path, what in ((QEMU, "QEMU binary"), (ELF, "firmware ELF"),
|
||||
(FLASH, "flash image")):
|
||||
if not os.path.exists(path):
|
||||
sys.exit(f"missing {what}: {path}")
|
||||
if os.path.exists(QMP):
|
||||
os.unlink(QMP)
|
||||
|
||||
with open(LOG, "wb") as fh:
|
||||
proc = subprocess.Popen(
|
||||
[QEMU, "-M", f"uv-k5-v3,flash-image={FLASH}", "-nographic",
|
||||
"-monitor", "none", "-qmp", f"unix:{QMP},server=on,wait=off",
|
||||
"-kernel", ELF],
|
||||
stdout=subprocess.DEVNULL, stderr=fh)
|
||||
try:
|
||||
time.sleep(14)
|
||||
finally:
|
||||
proc.terminate()
|
||||
try:
|
||||
proc.wait(timeout=10)
|
||||
except subprocess.TimeoutExpired:
|
||||
proc.kill()
|
||||
if os.path.exists(QMP):
|
||||
os.unlink(QMP)
|
||||
|
||||
text = open(LOG, errors="replace").read()
|
||||
lines = [l for l in text.splitlines() if l.startswith("SERIAL")]
|
||||
print(f"captured {len(lines)} SERIAL line(s)")
|
||||
for line in lines[:10]:
|
||||
print(" ", line)
|
||||
|
||||
if not lines:
|
||||
print("\nFAIL no SERIAL output; USART1 DR writes are still being dropped")
|
||||
print(" (App/main.c:98 sends UART_Version, so something should appear)")
|
||||
return 1
|
||||
print("\nPASS firmware serial output reaches the host")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
Reference in new issue
Block a user