Writing to any of the HOST_DATA0-7 registers pushes the written data
into a 128-bit accumulator. When the accumulator is full a flush is
triggered to copy it to the framebuffer. A final write to HOST_DATA_LAST
will also initiate a flush. The flush itself is left for the next patch.
Unaligned HOST_DATA* writes result in, from what I can tell, undefined
behavior on real hardware. A well-behaved driver shouldn't be doing this
anyway. For that reason they are not handled here at all.
Signed-off-by: Chad Jablonski <chad@jablonski.xyz>
---
hw/display/ati.c | 32 ++++++++++++++++++++++++++++++++
hw/display/ati_dbg.c | 9 +++++++++
hw/display/ati_int.h | 8 ++++++++
hw/display/ati_regs.h | 9 +++++++++
4 files changed, 58 insertions(+)
diff --git a/hw/display/ati.c b/hw/display/ati.c
index 21339d7e26..e6868ddb8b 100644
--- a/hw/display/ati.c
+++ b/hw/display/ati.c
@@ -568,6 +568,13 @@ static inline void ati_reg_write_offs(uint32_t *reg, int offs,
}
}
+static void ati_host_data_reset(ATIHostDataState *hd)
+{
+ hd->next = 0;
+ hd->row = 0;
+ hd->col = 0;
+}
+
static void ati_mm_write(void *opaque, hwaddr addr,
uint64_t data, unsigned int size)
{
@@ -843,6 +850,7 @@ static void ati_mm_write(void *opaque, hwaddr addr,
break;
case DST_WIDTH:
s->regs.dst_width = data & 0x3fff;
+ ati_host_data_reset(&s->host_data);
ati_2d_blt(s);
break;
case DST_HEIGHT:
@@ -893,6 +901,7 @@ static void ati_mm_write(void *opaque, hwaddr addr,
case DST_HEIGHT_WIDTH:
s->regs.dst_width = data & 0x3fff;
s->regs.dst_height = (data >> 16) & 0x3fff;
+ ati_host_data_reset(&s->host_data);
ati_2d_blt(s);
break;
case DP_GUI_MASTER_CNTL:
@@ -923,6 +932,7 @@ static void ati_mm_write(void *opaque, hwaddr addr,
case DST_WIDTH_X:
s->regs.dst_x = data & 0x3fff;
s->regs.dst_width = (data >> 16) & 0x3fff;
+ ati_host_data_reset(&s->host_data);
ati_2d_blt(s);
break;
case SRC_X_Y:
@@ -936,6 +946,7 @@ static void ati_mm_write(void *opaque, hwaddr addr,
case DST_WIDTH_HEIGHT:
s->regs.dst_height = data & 0x3fff;
s->regs.dst_width = (data >> 16) & 0x3fff;
+ ati_host_data_reset(&s->host_data);
ati_2d_blt(s);
break;
case DST_HEIGHT_Y:
@@ -1031,6 +1042,25 @@ static void ati_mm_write(void *opaque, hwaddr addr,
case SRC_SC_BOTTOM:
s->regs.src_sc_bottom = data & 0x3fff;
break;
+ case HOST_DATA0:
+ case HOST_DATA1:
+ case HOST_DATA2:
+ case HOST_DATA3:
+ case HOST_DATA4:
+ case HOST_DATA5:
+ case HOST_DATA6:
+ case HOST_DATA7:
+ s->host_data.acc[s->host_data.next++] = data;
+ if (s->host_data.next >= 4) {
+ qemu_log_mask(LOG_UNIMP, "HOST_DATA flush not yet implemented\n");
+ s->host_data.next = 0;
+ }
+ break;
+ case HOST_DATA_LAST:
+ s->host_data.acc[s->host_data.next] = data;
+ qemu_log_mask(LOG_UNIMP, "HOST_DATA flush not yet implemented\n");
+ ati_host_data_reset(&s->host_data);
+ break;
default:
break;
}
@@ -1124,6 +1154,8 @@ static void ati_vga_reset(DeviceState *dev)
/* reset vga */
vga_common_reset(&s->vga);
s->mode = VGA_MODE;
+
+ ati_host_data_reset(&s->host_data);
}
static void ati_vga_exit(PCIDevice *dev)
diff --git a/hw/display/ati_dbg.c b/hw/display/ati_dbg.c
index 3ffa7f35df..5c799d540a 100644
--- a/hw/display/ati_dbg.c
+++ b/hw/display/ati_dbg.c
@@ -252,6 +252,15 @@ static struct ati_regdesc ati_reg_names[] = {
{"MC_SRC1_CNTL", 0x19D8},
{"TEX_CNTL", 0x1800},
{"RAGE128_MPP_TB_CONFIG", 0x01c0},
+ {"HOST_DATA0", 0x17c0},
+ {"HOST_DATA1", 0x17c4},
+ {"HOST_DATA2", 0x17c8},
+ {"HOST_DATA3", 0x17cc},
+ {"HOST_DATA4", 0x17d0},
+ {"HOST_DATA5", 0x17d4},
+ {"HOST_DATA6", 0x17d8},
+ {"HOST_DATA7", 0x17dc},
+ {"HOST_DATA_LAST", 0x17e0},
{NULL, -1}
};
diff --git a/hw/display/ati_int.h b/hw/display/ati_int.h
index 7cf0933fa1..38725c57fa 100644
--- a/hw/display/ati_int.h
+++ b/hw/display/ati_int.h
@@ -91,6 +91,13 @@ typedef struct ATIVGARegs {
uint16_t src_sc_right;
} ATIVGARegs;
+typedef struct ATIHostDataState {
+ uint32_t row;
+ uint32_t col;
+ uint32_t next;
+ uint32_t acc[4];
+} ATIHostDataState;
+
struct ATIVGAState {
PCIDevice dev;
VGACommonState vga;
@@ -107,6 +114,7 @@ struct ATIVGAState {
MemoryRegion io;
MemoryRegion mm;
ATIVGARegs regs;
+ ATIHostDataState host_data;
};
const char *ati_reg_name(int num);
diff --git a/hw/display/ati_regs.h b/hw/display/ati_regs.h
index 3999edb9b7..48f15e9b1d 100644
--- a/hw/display/ati_regs.h
+++ b/hw/display/ati_regs.h
@@ -252,6 +252,15 @@
#define DP_T12_CNTL 0x178c
#define DST_BRES_T1_LNTH 0x1790
#define DST_BRES_T2_LNTH 0x1794
+#define HOST_DATA0 0x17c0
+#define HOST_DATA1 0x17c4
+#define HOST_DATA2 0x17c8
+#define HOST_DATA3 0x17cc
+#define HOST_DATA4 0x17d0
+#define HOST_DATA5 0x17d4
+#define HOST_DATA6 0x17d8
+#define HOST_DATA7 0x17dc
+#define HOST_DATA_LAST 0x17e0
#define SCALE_SRC_HEIGHT_WIDTH 0x1994
#define SCALE_OFFSET_0 0x1998
#define SCALE_PITCH 0x199c
--
2.51.2
On Thu, 15 Jan 2026, Chad Jablonski wrote:
> Writing to any of the HOST_DATA0-7 registers pushes the written data
> into a 128-bit accumulator. When the accumulator is full a flush is
> triggered to copy it to the framebuffer. A final write to HOST_DATA_LAST
> will also initiate a flush. The flush itself is left for the next patch.
>
> Unaligned HOST_DATA* writes result in, from what I can tell, undefined
> behavior on real hardware. A well-behaved driver shouldn't be doing this
> anyway. For that reason they are not handled here at all.
>
> Signed-off-by: Chad Jablonski <chad@jablonski.xyz>
> ---
> hw/display/ati.c | 32 ++++++++++++++++++++++++++++++++
> hw/display/ati_dbg.c | 9 +++++++++
> hw/display/ati_int.h | 8 ++++++++
> hw/display/ati_regs.h | 9 +++++++++
> 4 files changed, 58 insertions(+)
>
> diff --git a/hw/display/ati.c b/hw/display/ati.c
> index 21339d7e26..e6868ddb8b 100644
> --- a/hw/display/ati.c
> +++ b/hw/display/ati.c
> @@ -568,6 +568,13 @@ static inline void ati_reg_write_offs(uint32_t *reg, int offs,
> }
> }
>
> +static void ati_host_data_reset(ATIHostDataState *hd)
> +{
> + hd->next = 0;
> + hd->row = 0;
> + hd->col = 0;
> +}
I wonder where the real chip keeps track of these. Isn't this where the
dst_x dst_y would be updated? Or it may have some internal state not
exposed as registers? Maybe if you can dump registers you could compare
their values during blits after each host data reg write and check how
they change to reveal if any of them are updated? (But only if you're
curious; it would do for emulation using this approach with a separate
state as well.)
Regards,
BALATON Zoltan
>>
>> +static void ati_host_data_reset(ATIHostDataState *hd)
>> +{
>> + hd->next = 0;
>> + hd->row = 0;
>> + hd->col = 0;
>> +}
>
> I wonder where the real chip keeps track of these. Isn't this where the
> dst_x dst_y would be updated? Or it may have some internal state not
> exposed as registers? Maybe if you can dump registers you could compare
> their values during blits after each host data reg write and check how
> they change to reveal if any of them are updated? (But only if you're
> curious; it would do for emulation using this approach with a separate
> state as well.)
>
So far it's looking to me like this is internal state not exposed
through the registers. I do see one register that increments after every
host_data* write but it's in the PM4/CCE range (0x07e8) and is completely
undocumented. It _is_ a bit surprising to see changes in that range given CCE
is disabled. But nothing really obvious or conclusive yet, unfortunately.
On Fri, 30 Jan 2026, Chad Jablonski wrote:
>>> +static void ati_host_data_reset(ATIHostDataState *hd)
>>> +{
>>> + hd->next = 0;
>>> + hd->row = 0;
>>> + hd->col = 0;
>>> +}
>>
>> I wonder where the real chip keeps track of these. Isn't this where the
>> dst_x dst_y would be updated? Or it may have some internal state not
>> exposed as registers? Maybe if you can dump registers you could compare
>> their values during blits after each host data reg write and check how
>> they change to reveal if any of them are updated? (But only if you're
>> curious; it would do for emulation using this approach with a separate
>> state as well.)
>
> So far it's looking to me like this is internal state not exposed
> through the registers. I do see one register that increments after every
> host_data* write but it's in the PM4/CCE range (0x07e8) and is completely
> undocumented. It _is_ a bit surprising to see changes in that range given CCE
> is disabled. But nothing really obvious or conclusive yet, unfortunately.
OK thanks for checking. The M6 Register reference says 0x7ec is reserved
for future debug so it could be the one before is also some kind of debug
register but it's not documented so no idea just guessing.
So you also verified that not only host data but normal rectangular blits
don't update dst and src position either? Then don't know why it comes up
in documentation.
Regards,
BALATON Zoltan
© 2016 - 2026 Red Hat, Inc.