Implement registers used for loading and reading microcode for the CCE engine. Loading the microcode is the first step for any driver implementing CCE. Reading, while not used by drivers is very helpful for any reverse engineering and testing work. The microcode is currently stored but not used. This lays the groundwork for future RE work on the microcode.
There's some quirky behavior around microcode reads that isn't documented elsewhere. There appear to be two internal pointers, one for reading and one for writing that can get out of sync. Comments in the code expand on this. Tested and validated against a Rage 128 Pro Ultra (PCI 1002:5446). Signed-off-by: Chad Jablonski <[email protected]> --- hw/display/ati.c | 47 ++++++++++++++++++++++++++++++++++++++++++++ hw/display/ati_cce.h | 26 ++++++++++++++++++++++++ hw/display/ati_int.h | 2 ++ 3 files changed, 75 insertions(+) create mode 100644 hw/display/ati_cce.h diff --git a/hw/display/ati.c b/hw/display/ati.c index 33f8e211dc..e291926470 100644 --- a/hw/display/ati.c +++ b/hw/display/ati.c @@ -510,6 +510,31 @@ static uint64_t ati_mm_read(void *opaque, hwaddr addr, unsigned int size) case DEFAULT_SC_BOTTOM_RIGHT: val = s->regs.default_sc_bottom_right; break; + case PM4_MICROCODE_ADDR: + val = s->cce.microcode.addr; + break; + case PM4_MICROCODE_RADDR: + val = 0; + break; + case PM4_MICROCODE_DATAH: + val = (s->cce.microcode.microcode[s->cce.microcode.raddr] >> 32) & + 0xffffffff; + break; + case PM4_MICROCODE_DATAL: + val = s->cce.microcode.microcode[s->cce.microcode.raddr] & 0xffffffff; + s->cce.microcode.addr += 1; + /* + * The write address (addr) is always copied into the + * read address (raddr) after a DATAL read. This leads + * to surprising behavior when the PM4_MICROCODE_ADDR + * instead of the PM4_MICROCODE_RADDR register is set to + * a value just before a read. The first read after this + * will reflect the previous raddr before incrementing and + * re-syncing with addr. This is expected and observed on + * the hardware. + */ + s->cce.microcode.raddr = s->cce.microcode.addr; + break; default: break; } @@ -932,6 +957,28 @@ void ati_reg_write(ATIVGAState *s, hwaddr addr, case DEFAULT_SC_BOTTOM_RIGHT: s->regs.default_sc_bottom_right = data & 0x3fff3fff; break; + case PM4_MICROCODE_ADDR: + s->cce.microcode.addr = data; + break; + case PM4_MICROCODE_RADDR: + s->cce.microcode.raddr = data; + s->cce.microcode.addr = data; + break; + case PM4_MICROCODE_DATAH: { + uint64_t curr = s->cce.microcode.microcode[s->cce.microcode.addr]; + uint64_t low = curr & 0xffffffff; + uint64_t high = (data & 0x1f) << 32; + s->cce.microcode.microcode[s->cce.microcode.addr] = high | low; + break; + } + case PM4_MICROCODE_DATAL: { + uint64_t curr = s->cce.microcode.microcode[s->cce.microcode.addr]; + uint64_t low = data & 0xffffffff; + uint64_t high = curr & (0xffffffffull << 32); + s->cce.microcode.microcode[s->cce.microcode.addr] = high | low; + s->cce.microcode.addr += 1; + break; + } default: break; } diff --git a/hw/display/ati_cce.h b/hw/display/ati_cce.h new file mode 100644 index 0000000000..f2ef1345de --- /dev/null +++ b/hw/display/ati_cce.h @@ -0,0 +1,26 @@ +/* + * QEMU ATI SVGA emulation + * CCE engine functions + * + * Copyright (c) 2025 Chad Jablonski + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef ATI_CCE_H +#define ATI_CCE_H + +#include "qemu/osdep.h" +#include "qemu/log.h" + +typedef struct ATIPM4MicrocodeState { + uint8_t addr; + uint8_t raddr; + uint64_t microcode[256]; +} ATIPM4MicrocodeState; + +typedef struct ATICCEState { + ATIPM4MicrocodeState microcode; +} ATICCEState; + +#endif /* ATI_CCE_H */ diff --git a/hw/display/ati_int.h b/hw/display/ati_int.h index ea1a8bceab..ed6307151b 100644 --- a/hw/display/ati_int.h +++ b/hw/display/ati_int.h @@ -14,6 +14,7 @@ #include "hw/i2c/bitbang_i2c.h" #include "vga_int.h" #include "qom/object.h" +#include "ati_cce.h" /*#define DEBUG_ATI*/ @@ -100,6 +101,7 @@ struct ATIVGAState { MemoryRegion io; MemoryRegion mm; ATIVGARegs regs; + ATICCEState cce; }; const char *ati_reg_name(int num); -- 2.51.2
