All of lore.kernel.org
 help / color / mirror / Atom feed
* [RFC PATCH v2 QEMU 0/4] cxl/plugins: Hotness Monitoring Unit with 'real' data.
@ 2025-06-12 15:57 Jonathan Cameron via
  2025-06-12 15:57 ` [RFC PATCH v2 QEMU 1/4] hw/cxl: Switch to using an array for CXLRegisterLocator base addresses Jonathan Cameron via
                   ` (3 more replies)
  0 siblings, 4 replies; 6+ messages in thread
From: Jonathan Cameron via @ 2025-06-12 15:57 UTC (permalink / raw)
  To: Pierrick Bouvier, fan.ni, qemu-devel, Alex Bennée,
	Alexandre Iooss, Mahmoud Mandour, Bowman Terry
  Cc: linuxarm, rientjes, dave, joshua.hahnjy, rkodsara, sj,
	xuezhengchu, yiannis, ziy, weixugc, Bharata B Rao

RFC v2: Think of this as a WIP posting. It is useful for OS driver and
use case work but not ready for upstream QEMU.
- Integrated the previously separate CXL server with the plugin to
  avoid one hop.
- Many more control features emulated - still a few to go such as
  transaction type filtering and filtering which part of device memory
  is being tracked. It 'works' but my test set is fairly small today.
- Interleave handling.  This was fiddly given the interleave set position
  can only be discovered by walking the heirarchical decode path once
  all the decoders are committed.

+CC some of the folk in the hotness tracking Linux kernel discussions as an FYI.

Depends on: https://lore.kernel.org/qemu-devel/20250612134338.1871023-1-Jonathan.Cameron@huawei.com/
[PATCH v15 0/4] arm/virt: CXL support via pxb_cxl
as the interleave set position discovery requires the fixed memory
windows to be devices we can search for.

I'll push out a refreshed staging tree at gitlab.com/jic23/qemu cxl-<date>
shortly but changes from tree already there for these patches are superficial only.

Performance without the patch:
https://gitlab.com/jic23/qemu/-/commit/8aa244db9490f5326f6ec07bceac27f12eea2fd2
or with interleave is slow so I'd recommend playing around with it
with that patch in place and no interleave. It will still be fairly slow
but cup of tea level rather than overnight.

Thanks to Pierrick Bouvier for suggesting squashing plugin and server.
All comments welcome!

Updated cover letter:

The main aim of this work is provide a 'real' test platform for
development of kernel support for this type of hardware prior to ready
availability of devices + later to provide a test platform everyone
can get even when devices are available. The main use case is driving
which pages to promote from CXL memory into faster local DRAM. It is
real in the sense of approximating some aspects such as the influence
of the cache hierarchy and a very simplistic model the tracking hardware.
The fidelity of those aspects can be improved but that may not be
necessary to drive design decisions in the kernel.

This is an RFC mainly to seek feedback on the approach used, particularly
the aspect of how to get data from a TCG plugin into a device model. The
approach used here is an adaption of that used in RFC v1 with a socket
between the plugin and the device emulation. That is used both for
control signals that set parameters of the tracking and indicate the
end of epoch (when to grab data).

The code here is at best a PoC to illustrate what we have in mind
It's not nice code at all, feature gaps, bugs and all!  So whilst
detailed review is always welcome I'm not requesting it for now.

Kernel support was posted a while back but was done against fake data
(still supported here if you don't provide the port parameter to the type3 device)
https://lore.kernel.org/linux-cxl/20241121101845.1815660-1-Jonathan.Cameron@huawei.com/

This should apply more or less to upstream.

Thanks to Niyas Sait for his suggestions on how to make all this work!

Background
----------

What is the Compute eXpress Link Hotness Monitoring unit and what is it for?
- In a tiered memory equipped server with the slow tier being attached via
  CXL the expectation is a given workload will benefit from putting data
  that is frequently fetched from memory in lower latency directly attached
  DRAM.  Less frequently used data can be served from the CXL attached memory
  with no significant loss of performance.  Any data that is hot enough to
  almost always be in cache doesn't matter as it is only fetch from memory
  occasionally.
- Working out which memory is best places where is hard to do and in some
  workloads a dynamic problem. As such we need something we can measure
  to provide some indication of what data is in the wrong place.
  There are existing techniques to do this (page faulting, various
  CPU tracing systems, access bit scanning etc) but they all have significant
  overheads.
- Monitoring accesses on the CXL device provides a path to getting good
  data without those overheads.  These units are known as CXL Hotness
  Monitoring Units or CHMUs.  Loosely speaking they count accesses to
  granuals of data (e.g. 4KiB pages).  Exactly how they do that and
  where they sacrifice data accuracy is an implementation trade off.

Why do we need a model that gives real data?
- In general there is a need to develop software on top of these units
  to move data to the right place. Hard to evaluate that if we are making
  up the info on what is 'hot'.
- Need to allow for a bunch of 'impdef' solutions. Note that CHMU
  in this patch set is an oracle - it has enough counters to count
  every access.  That's not realistic but it doesn't get me shouted
  at by our architecture teams for giving away any secrets.
  If we move forward with this, I'll probably implement a limited
  counter + full CAM solution (also unrealistic, but closer to real)
  I'd be very interested in contributions of other approaches (there
  are lots in the literature, under the term top-k)
- Resources will be constrained, so whilst a CHMU might in theory
  allow monitoring everything at once, that will come with a big
  accuracy cost.  We need to design the algorithms that give us
  good data given those constraints.

So we need a solution to explore the design space and develop the software
to take advantage of this hardware. Post LSF/MM the discussion continues
on list and there is a biweekly sync call - see linux-mm for details

QEMU plugins give us a way to do this.  In particular the existing
Cache plugin can be easily modified to tell use what memory addresses
missed at the last level of emulated cache.  We can then filter those
for the memory address range that maps to CXL and feed them to our
counter implementation. On the other side, each instance of CXL type 3
device can connect to this server (in the plugin) and request hotness
monitoring services + provide parameters etc.  Elements such as
list threshold management and overflow detection etc are in the CXL
HMU QEMU device mode.

How to use it!
--------------

It runs a little slow but it runs and generates somewhat plausible outputs.
I'd definitely suggest running it with the pass through optimization
patch on the CXL staging tree (and a single direct connected device).
I'll be posting a new version of that tree with this series on it
shortly.  Look for acxl 2025-06-xx branch

Your millage will vary if you try to use other parameters, or
hotness units beyond the first one (implementation far from complete!)

To run start the server in contrib/hmu/ providing a port number to listen
on.

./chmu 4443

Then launch QEMU with something like the following.

qemu-system-aarch64 -icount shift=1 \
 -plugin ../qemu/bin/native/contrib/plugins/libcache.so,hotness=1,missfilterbase=1099511627776,missfiltersize=1099511627776,dcachesize=8192,dassoc=4,dblksize=64,icachesize=8192,iassoc=4,iblksize=64,l2cachesize=32768,l2assoc=16,l2blksize=64 \
 -M virt,ras=on,nvdimm=on,gic-version=3,cxl=on,hmat=on -m 4g,maxmem=8g,slots=4 -cpu max -smp 4 \
 -kernel Image \
 -drive if=none,file=full.qcow2,format=qcow2,id=hd \
 -device pcie-root-port,id=root_port1 \
 -device virtio-blk-pci,drive=hd,x-max-bounce-buffer-size=512k \
 -nographic -no-reboot -append 'earlycon memblock=debug root=/dev/vda2 fsck.mode=skip maxcpus=4 tp_printk' \
 -monitor telnet:127.0.0.1:1234,server,nowait -bios QEMU_EFI.fd \
 -object memory-backend-ram,size=4G,id=mem0 \
 -object memory-backend-file,id=cxl-mem1,share=on,mem-path=/tmp/t3_cxl1.raw,size=1G,align=256M \
 -object memory-backend-file,id=cxl-mem2,share=on,mem-path=/tmp/t3_cxl2.raw,size=1G,align=256M \
 -object memory-backend-file,id=cxl-lsa1,share=on,mem-path=/tmp/t3_lsa1.raw,size=1M,align=1M \
  -object memory-backend-file,id=cxl-mem3,share=on,mem-path=/tmp/t3_cxl3.raw,size=1G,align=256M \
 -object memory-backend-file,id=cxl-mem4,share=on,mem-path=/tmp/t3_cxl4.raw,size=1G,align=256M \
 -object memory-backend-file,id=cxl-lsa2,share=on,mem-path=/tmp/t3_lsa2.raw,size=1M,align=1M \
 -device pxb-cxl,bus_nr=12,bus=pcie.0,id=cxl.1,hdm_for_passthrough=true,numa_node=0\
 -device cxl-rp,port=0,bus=cxl.1,id=cxl_rp_port0,chassis=0,slot=2 \
 -device cxl-type3,bus=cxl_rp_port0,volatile-memdev=cxl-mem1,id=cxl-pmem1,lsa=cxl-lsa1,sn=3,x-speed=32,x-width=16,chmu-port=4443 \
 -machine cxl-fmw.0.targets.0=cxl.1,cxl-fmw.0.size=8G,cxl-fmw.0.interleave-granularity=1k \
 -numa node,nodeid=0,cpus=0-3,memdev=mem0 \
 -numa node,nodeid=1 \
 -object acpi-generic-initiator,id=bob2,pci-dev=bob,node=1 \
 -numa node,nodeid=2 \
 -object acpi-generic-port,id=bob11,pci-bus=cxl.1,node=2 \

In the guest, create and bind the region - this brings up the CXL memory
device so accesses go to the memory.

  cd /sys/bus/cxl/devices/decoder0.0/
  cat create_ram_region
  echo region0 > create_ram_region
  echo ram > /sys/bus/cxl/devices/decoder2.0/mode
  echo ram > /sys/bus/cxl/devices/decoder3.0/mode
  echo $((256 << 21)) > /sys/bus/cxl/devices/decoder2.0/dpa_size
  cd /sys/bus/cxl/devices/region0/
  echo 256 > interleave_granularity
  echo 1 > interleave_ways
  echo $((256 << 21)) > size
  echo decoder2.0 > target0
  echo 1 > commit
  echo region0 > /sys/bus/cxl/drivers/cxl_region/bind

Finally start perf with something like:

./perf record -a  -e cxl_hmu_mem0.0.0/epoch_type=0,access_type=6,\
hotness_threshold=635,epoch_multiplier=4,epoch_scale=4,\
range_base=0,range_size=4096/  ./stress.sh

where stress.sh is

  sleep 2
  numactl --membind 3 stress-ng --vm 1 --vm-bytes 1M --vm-keep -t 5s
  sleep 2

See the results with
./perf report --dump-raw-trace | grep -A 200 HMU


Jonathan Cameron (4):
  hw/cxl: Switch to using an array for CXLRegisterLocator base
    addresses.
  hw/cxl: Add emulation of a CXL Hotness Monitoring Unit (CHMU)
  hw/cxl: Provide a means to get the interleave set position for an EP
  plugins: cache: Add a hotness tracker for cache misses with socket
    connection to device emulation

 include/hw/cxl/cxl.h           |   2 +
 include/hw/cxl/cxl_chmu.h      | 187 ++++++++++++
 include/hw/cxl/cxl_device.h    |  24 +-
 include/hw/cxl/cxl_pci.h       |  20 +-
 contrib/plugins/cache.c        | 434 ++++++++++++++++++++++++++-
 hw/cxl/cxl-chmu.c              | 516 +++++++++++++++++++++++++++++++++
 hw/cxl/cxl-component-utils.c   |   4 +
 hw/cxl/cxl-host.c              |  72 +++++
 hw/cxl/switch-mailbox-cci.c    |   4 +-
 hw/mem/cxl_type3.c             | 117 +++++++-
 hw/pci-bridge/cxl_downstream.c |   4 +-
 hw/pci-bridge/cxl_root_port.c  |   4 +-
 hw/pci-bridge/cxl_upstream.c   |   4 +-
 hw/cxl/meson.build             |   1 +
 14 files changed, 1363 insertions(+), 30 deletions(-)
 create mode 100644 include/hw/cxl/cxl_chmu.h
 create mode 100644 hw/cxl/cxl-chmu.c

-- 
2.48.1



^ permalink raw reply	[flat|nested] 6+ messages in thread

* [RFC PATCH v2 QEMU 1/4] hw/cxl: Switch to using an array for CXLRegisterLocator base addresses.
  2025-06-12 15:57 [RFC PATCH v2 QEMU 0/4] cxl/plugins: Hotness Monitoring Unit with 'real' data Jonathan Cameron via
@ 2025-06-12 15:57 ` Jonathan Cameron via
  2025-06-12 15:57 ` [RFC PATCH v2 QEMU 2/4] hw/cxl: Add emulation of a CXL Hotness Monitoring Unit (CHMU) Jonathan Cameron via
                   ` (2 subsequent siblings)
  3 siblings, 0 replies; 6+ messages in thread
From: Jonathan Cameron via @ 2025-06-12 15:57 UTC (permalink / raw)
  To: Pierrick Bouvier, fan.ni, qemu-devel, Alex Bennée,
	Alexandre Iooss, Mahmoud Mandour, Bowman Terry
  Cc: linuxarm, rientjes, dave, joshua.hahnjy, rkodsara, sj,
	xuezhengchu, yiannis, ziy, weixugc, Bharata B Rao

Allows for easier looping over entries when adding CHMU and CPMU instances.

Signed-off-by: Jonathan Cameron <Jonathan.Cameron@huawei.com>
---
CHMU RFC v2: New patch to simplify a few code paths.
---
 include/hw/cxl/cxl_pci.h       | 17 ++++++++++-------
 hw/cxl/switch-mailbox-cci.c    |  4 ++--
 hw/mem/cxl_type3.c             | 12 ++++++++----
 hw/pci-bridge/cxl_downstream.c |  4 ++--
 hw/pci-bridge/cxl_root_port.c  |  4 ++--
 hw/pci-bridge/cxl_upstream.c   |  4 ++--
 6 files changed, 26 insertions(+), 19 deletions(-)

diff --git a/include/hw/cxl/cxl_pci.h b/include/hw/cxl/cxl_pci.h
index d0855ed78b..00a0335d55 100644
--- a/include/hw/cxl/cxl_pci.h
+++ b/include/hw/cxl/cxl_pci.h
@@ -161,6 +161,12 @@ typedef struct CXLDVSECPortFlexBus {
 } CXLDVSECPortFlexBus;
 QEMU_BUILD_BUG_ON(sizeof(CXLDVSECPortFlexBus) != 0x20);
 
+/* Only applies to the type 3 device emulation */
+enum register_locator_indicies {
+    REG_LOC_IDX_COMPONENT,
+    REG_LOC_IDX_DEVICE,
+    NR_REG_LOC_IDX
+};
 /*
  * CXL r3.1 Section 8.1.9: Register Locator DVSEC
  * DVSEC ID: 8, Revision 0
@@ -168,14 +174,11 @@ QEMU_BUILD_BUG_ON(sizeof(CXLDVSECPortFlexBus) != 0x20);
 typedef struct CXLDVSECRegisterLocator {
     DVSECHeader hdr;
     uint16_t rsvd;
-    uint32_t reg0_base_lo;
-    uint32_t reg0_base_hi;
-    uint32_t reg1_base_lo;
-    uint32_t reg1_base_hi;
-    uint32_t reg2_base_lo;
-    uint32_t reg2_base_hi;
+    struct {
+            uint32_t lo;
+            uint32_t hi;
+    } reg_base[NR_REG_LOC_IDX];
 } CXLDVSECRegisterLocator;
-QEMU_BUILD_BUG_ON(sizeof(CXLDVSECRegisterLocator) != 0x24);
 
 /* BAR Equivalence Indicator */
 #define BEI_BAR_10H 0
diff --git a/hw/cxl/switch-mailbox-cci.c b/hw/cxl/switch-mailbox-cci.c
index 223f220433..af91525445 100644
--- a/hw/cxl/switch-mailbox-cci.c
+++ b/hw/cxl/switch-mailbox-cci.c
@@ -50,8 +50,8 @@ static void cswbcci_realize(PCIDevice *pci_dev, Error **errp)
                      &cxl_dstate->device_registers);
     regloc_dvsec = &(CXLDVSECRegisterLocator) {
         .rsvd         = 0,
-        .reg0_base_lo = RBI_CXL_DEVICE_REG | 0,
-        .reg0_base_hi = 0,
+        .reg_base[0].lo = RBI_CXL_DEVICE_REG | 0,
+        .reg_base[0].hi = 0,
     };
     cxl_component_create_dvsec(cxl_cstate, CXL3_SWITCH_MAILBOX_CCI,
                                REG_LOC_DVSEC_LENGTH, REG_LOC_DVSEC,
diff --git a/hw/mem/cxl_type3.c b/hw/mem/cxl_type3.c
index ca9fe89e4f..dcefd41088 100644
--- a/hw/mem/cxl_type3.c
+++ b/hw/mem/cxl_type3.c
@@ -386,10 +386,14 @@ static void build_dvsecs(CXLType3Dev *ct3d)
 
     dvsec = (uint8_t *)&(CXLDVSECRegisterLocator){
         .rsvd         = 0,
-        .reg0_base_lo = RBI_COMPONENT_REG | CXL_COMPONENT_REG_BAR_IDX,
-        .reg0_base_hi = 0,
-        .reg1_base_lo = RBI_CXL_DEVICE_REG | CXL_DEVICE_REG_BAR_IDX,
-        .reg1_base_hi = 0,
+        .reg_base[REG_LOC_IDX_COMPONENT] = {
+            .lo = RBI_COMPONENT_REG | CXL_COMPONENT_REG_BAR_IDX,
+            .hi = 0,
+        },
+        .reg_base[REG_LOC_IDX_DEVICE] = {
+            .lo = RBI_CXL_DEVICE_REG | CXL_DEVICE_REG_BAR_IDX,
+            .hi = 0,
+        },
     };
     cxl_component_create_dvsec(cxl_cstate, CXL2_TYPE3_DEVICE,
                                REG_LOC_DVSEC_LENGTH, REG_LOC_DVSEC,
diff --git a/hw/pci-bridge/cxl_downstream.c b/hw/pci-bridge/cxl_downstream.c
index 1065245a8b..387cebbb98 100644
--- a/hw/pci-bridge/cxl_downstream.c
+++ b/hw/pci-bridge/cxl_downstream.c
@@ -126,8 +126,8 @@ static void build_dvsecs(CXLComponentState *cxl)
 
     dvsec = (uint8_t *)&(CXLDVSECRegisterLocator){
         .rsvd         = 0,
-        .reg0_base_lo = RBI_COMPONENT_REG | CXL_COMPONENT_REG_BAR_IDX,
-        .reg0_base_hi = 0,
+        .reg_base[0].lo = RBI_COMPONENT_REG | CXL_COMPONENT_REG_BAR_IDX,
+        .reg_base[0].hi = 0,
     };
     cxl_component_create_dvsec(cxl, CXL2_DOWNSTREAM_PORT,
                                REG_LOC_DVSEC_LENGTH, REG_LOC_DVSEC,
diff --git a/hw/pci-bridge/cxl_root_port.c b/hw/pci-bridge/cxl_root_port.c
index e6a4035d26..d955f3bcc5 100644
--- a/hw/pci-bridge/cxl_root_port.c
+++ b/hw/pci-bridge/cxl_root_port.c
@@ -136,8 +136,8 @@ static void build_dvsecs(CXLComponentState *cxl)
 
     dvsec = (uint8_t *)&(CXLDVSECRegisterLocator){
         .rsvd         = 0,
-        .reg0_base_lo = RBI_COMPONENT_REG | CXL_COMPONENT_REG_BAR_IDX,
-        .reg0_base_hi = 0,
+        .reg_base[0].lo = RBI_COMPONENT_REG | CXL_COMPONENT_REG_BAR_IDX,
+        .reg_base[0].hi = 0,
     };
     cxl_component_create_dvsec(cxl, CXL2_ROOT_PORT,
                                REG_LOC_DVSEC_LENGTH, REG_LOC_DVSEC,
diff --git a/hw/pci-bridge/cxl_upstream.c b/hw/pci-bridge/cxl_upstream.c
index 208e0c6172..28f7542814 100644
--- a/hw/pci-bridge/cxl_upstream.c
+++ b/hw/pci-bridge/cxl_upstream.c
@@ -129,8 +129,8 @@ static void build_dvsecs(CXLComponentState *cxl)
 
     dvsec = (uint8_t *)&(CXLDVSECRegisterLocator){
         .rsvd         = 0,
-        .reg0_base_lo = RBI_COMPONENT_REG | CXL_COMPONENT_REG_BAR_IDX,
-        .reg0_base_hi = 0,
+        .reg_base[0].lo = RBI_COMPONENT_REG | CXL_COMPONENT_REG_BAR_IDX,
+        .reg_base[0].hi = 0,
     };
     cxl_component_create_dvsec(cxl, CXL2_UPSTREAM_PORT,
                                REG_LOC_DVSEC_LENGTH, REG_LOC_DVSEC,
-- 
2.48.1



^ permalink raw reply related	[flat|nested] 6+ messages in thread

* [RFC PATCH v2 QEMU 2/4] hw/cxl: Add emulation of a CXL Hotness Monitoring Unit (CHMU)
  2025-06-12 15:57 [RFC PATCH v2 QEMU 0/4] cxl/plugins: Hotness Monitoring Unit with 'real' data Jonathan Cameron via
  2025-06-12 15:57 ` [RFC PATCH v2 QEMU 1/4] hw/cxl: Switch to using an array for CXLRegisterLocator base addresses Jonathan Cameron via
@ 2025-06-12 15:57 ` Jonathan Cameron via
  2025-06-19 10:55   ` Jonathan Cameron via
  2025-06-12 15:57 ` [RFC PATCH v2 QEMU 3/4] hw/cxl: Provide a means to get the interleave set position for an EP Jonathan Cameron via
  2025-06-12 15:57 ` [RFC PATCH v2 QEMU 4/4] plugins: cache: Add a hotness tracker for cache misses with socket connection to device emulation Jonathan Cameron via
  3 siblings, 1 reply; 6+ messages in thread
From: Jonathan Cameron via @ 2025-06-12 15:57 UTC (permalink / raw)
  To: Pierrick Bouvier, fan.ni, qemu-devel, Alex Bennée,
	Alexandre Iooss, Mahmoud Mandour, Bowman Terry
  Cc: linuxarm, rientjes, dave, joshua.hahnjy, rkodsara, sj,
	xuezhengchu, yiannis, ziy, weixugc, Bharata B Rao

CXL r3.2 defines a CXL Hotness Monitoring Unit. This allows for a CXL
device to do on device estimation of which 'granuals' of data are 'hot'
- that is accessed a lot. For a typical application hot data on a CXL
device both wastes potentially limited bandwidth and many have latency
impacts. Access counts are therefore a measurable proxy on which to base
memory placement decisions.

Typical use cases include:
1 - Establishing which data to move to faster RAM in a tiered memory
    system. Discussions on how to do this in Linux are ongoing so likely
    use case 2 will happen first.
2 - Provide detailed data (at low overhead) on what memory in an
    application is hot, allowing for optimization of initial data
    placement on future runs fo the application.

The focus of this emulation is providing a way to capture 'real' data
in order to help us develop and tune the kernel stack.

This emulated device will be fed with data from a QEMU plugin. That
plugin is responsible for the actual tracking and counting part of
hotness tracking. This device simply provides a timebase (epoch end
point) along with configuration and data retrieval.

The connection to the QEMU plugin providing the data is via a sockets.
Supply the cxl-type3 device parameter chmu-port=4443 to specify the
network port as 4443 and ensure the plugin is loaded (see later patch).

Signed-off-by: Jonathan Cameron <Jonathan.Cameron@huawei.com>
---
 include/hw/cxl/cxl.h        |   1 +
 include/hw/cxl/cxl_chmu.h   | 187 +++++++++++++
 include/hw/cxl/cxl_device.h |  24 +-
 include/hw/cxl/cxl_pci.h    |   3 +
 hw/cxl/cxl-chmu.c           | 516 ++++++++++++++++++++++++++++++++++++
 hw/mem/cxl_type3.c          | 103 ++++++-
 hw/cxl/meson.build          |   1 +
 7 files changed, 831 insertions(+), 4 deletions(-)

diff --git a/include/hw/cxl/cxl.h b/include/hw/cxl/cxl.h
index de66ab8c35..12844d3418 100644
--- a/include/hw/cxl/cxl.h
+++ b/include/hw/cxl/cxl.h
@@ -16,6 +16,7 @@
 #include "hw/pci/pci_host.h"
 #include "cxl_pci.h"
 #include "cxl_component.h"
+#include "cxl_chmu.h"
 #include "cxl_device.h"
 
 #define CXL_CACHE_LINE_SIZE 64
diff --git a/include/hw/cxl/cxl_chmu.h b/include/hw/cxl/cxl_chmu.h
new file mode 100644
index 0000000000..2186e11a31
--- /dev/null
+++ b/include/hw/cxl/cxl_chmu.h
@@ -0,0 +1,187 @@
+/*
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * QEMU CXL Hotness Monitoring Unit
+ *
+ * Copyright (c) 2025 Huawei
+ */
+
+#include "hw/register.h"
+
+#ifndef _CXL_CHMU_H_
+#define _CXL_CHMU_H_
+
+/* Emulated parameters - arbitrary choices */
+#define CXL_CHMU_INSTANCES_PER_BLOCK 1
+#define CXL_HOTLIST_ENTRIES 1024
+
+/* 1TB - should be enough for anyone, right? */
+#define CXL_MAX_DRAM_CAPACITY 0x10000000000UL
+
+/* Relative to per instance base address */
+#define CXL_CHMU_HL_START (0x70 + (CXL_MAX_DRAM_CAPACITY / (0x10000000UL * 8)))
+#define CXL_CHMU_INSTANCE_SIZE (CXL_CHMU_HL_START + CXL_HOTLIST_ENTRIES * 8)
+#define CXL_CHMU_SIZE \
+    (0x10 + CXL_CHMU_INSTANCE_SIZE * CXL_CHMU_INSTANCES_PER_BLOCK)
+
+/*
+ * Many of these registers are documented as being a multiple of 64 bits long.
+ * Reading then can only be done in 64 bit chunks though so specify them here
+ * as multiple registers.
+ */
+REG64(CXL_CHMU_COMMON_CAP0, 0x0)
+    FIELD(CXL_CHMU_COMMON_CAP0, VERSION, 0, 4)
+    FIELD(CXL_CHMU_COMMON_CAP0, NUM_INSTANCES, 8, 8)
+REG64(CXL_CHMU_COMMON_CAP1, 0x8)
+    FIELD(CXL_CHMU_COMMON_CAP1, INSTANCE_LENGTH, 0, 16)
+
+/* Per instance registers for instance 0 in CHMU main address space */
+REG64(CXL_CHMU0_CAP0, 0x10)
+    FIELD(CXL_CHMU0_CAP0, MSI_N, 0, 4)
+    FIELD(CXL_CHMU0_CAP0, OVERFLOW_INT, 4, 1)
+    FIELD(CXL_CHMU0_CAP0, LEVEL_INT, 5, 1)
+    FIELD(CXL_CHMU0_CAP0, EPOCH_TYPE, 6, 2)
+#define CXL_CHMU0_CAP0_EPOCH_TYPE_GLOBAL 0
+#define CXL_CHMU0_CAP0_EPOCH_TYPE_PERCNT 1
+    /* Break up the Tracked M2S Request field into flags */
+    FIELD(CXL_CHMU0_CAP0, TRACKED_M2S_REQ_NONTEE_R, 8, 1)
+    FIELD(CXL_CHMU0_CAP0, TRACKED_M2S_REQ_NONTEE_W, 9, 1)
+    FIELD(CXL_CHMU0_CAP0, TRACKED_M2S_REQ_NONTEE_RW, 10, 1)
+    FIELD(CXL_CHMU0_CAP0, TRACKED_M2S_REQ_ALL_R, 11, 1)
+    FIELD(CXL_CHMU0_CAP0, TRACKED_M2S_REQ_ALL_W, 12, 1)
+    FIELD(CXL_CHMU0_CAP0, TRACKED_M2S_REQ_ALL_RW, 13, 1)
+
+    FIELD(CXL_CHMU0_CAP0, MAX_EPOCH_LENGTH_SCALE, 16, 4)
+#define CXL_CHMU_EPOCH_LENGTH_SCALE_100USEC 1
+#define CXL_CHMU_EPOCH_LENGTH_SCALE_1MSEC 2
+#define CXL_CHMU_EPOCH_LENGTH_SCALE_10MSEC 3
+#define CXL_CHMU_EPOCH_LENGTH_SCALE_100MSEC 4
+#define CXL_CHMU_EPOCH_LENGTH_SCALE_1SEC 5
+    FIELD(CXL_CHMU0_CAP0, MAX_EPOCH_LENGTH_VAL, 20, 12)
+    FIELD(CXL_CHMU0_CAP0, MIN_EPOCH_LENGTH_SCALE, 32, 4)
+    FIELD(CXL_CHMU0_CAP0, MIN_EPOCH_LENGTH_VAL, 36, 12)
+    FIELD(CXL_CHMU0_CAP0, HOTLIST_SIZE, 48, 16)
+REG64(CXL_CHMU0_CAP1, 0x18)
+    FIELD(CXL_CHMU0_CAP1, UNIT_SIZES, 0, 32)
+    FIELD(CXL_CHMU0_CAP1, DOWN_SAMPLING_FACTORS, 32, 16)
+    /* Split up Flags */
+    FIELD(CXL_CHMU0_CAP1, FLAGS_EPOCH_BASED, 48, 1)
+    FIELD(CXL_CHMU0_CAP1, FLAGS_ALWAYS_ON, 49, 1)
+    FIELD(CXL_CHMU0_CAP1, FLAGS_RANDOMIZED_DOWN_SAMPLING, 50, 1)
+    FIELD(CXL_CHMU0_CAP1, FLAGS_OVERLAPPING_ADDRESS_RANGES, 51, 1)
+    FIELD(CXL_CHMU0_CAP1, FLAGS_INSERT_AFTER_CLEAR, 52, 1)
+REG64(CXL_CHMU0_CAP2, 0x20)
+    FIELD(CXL_CHMU0_CAP2, BITMAP_REG_OFFSET, 0, 64)
+REG64(CXL_CHMU0_CAP3, 0x28)
+    FIELD(CXL_CHMU0_CAP3, HOTLIST_REG_OFFSET, 0, 64)
+
+REG64(CXL_CHMU0_CONF0, 0x50)
+    FIELD(CXL_CHMU0_CONF0, M2S_REQ_TO_TRACK, 0, 8)
+    FIELD(CXL_CHMU0_CONF0, FLAGS_RANDOMIZE_DOWNSAMPLING, 8, 1)
+    FIELD(CXL_CHMU0_CONF0, FLAGS_INT_ON_OVERFLOW, 9, 1)
+    FIELD(CXL_CHMU0_CONF0, FLAGS_INT_ON_FILL_THRESH, 10, 1)
+    FIELD(CXL_CHMU0_CONF0, CONTROL_ENABLE, 16, 1)
+    FIELD(CXL_CHMU0_CONF0, CONTROL_RESET, 17, 1)
+    FIELD(CXL_CHMU0_CONF0, HOTNESS_THRESHOLD, 32, 32)
+REG64(CXL_CHMU0_CONF1, 0x58)
+    FIELD(CXL_CHMU0_CONF1, UNIT_SIZE, 0, 32)
+    FIELD(CXL_CHMU0_CONF1, DOWN_SAMPLING_FACTOR, 32, 8)
+    FIELD(CXL_CHMU0_CONF1, REPORTING_MODE, 40, 8)
+    FIELD(CXL_CHMU0_CONF1, EPOCH_LENGTH_SCALE, 48, 4)
+    FIELD(CXL_CHMU0_CONF1, EPOCH_LENGTH_VAL, 52, 12)
+REG64(CXL_CHMU0_CONF2, 0x60)
+    FIELD(CXL_CHMU0_CONF2, NOTIFICATION_THRESHOLD, 0, 16)
+
+REG64(CXL_CHMU0_STATUS, 0x70)
+    /* Break up status field into separate flags */
+    FIELD(CXL_CHMU0_STATUS, STATUS_ENABLED, 0, 1)
+    FIELD(CXL_CHMU0_STATUS, OPERATION_IN_PROG, 16, 16)
+    FIELD(CXL_CHMU0_STATUS, COUNTER_WIDTH, 32, 8)
+    /* Break up oddly named overflow interrupt stats */
+    FIELD(CXL_CHMU0_STATUS, OVERFLOW_INT, 40, 1)
+    FIELD(CXL_CHMU0_STATUS, LEVEL_INT, 41, 1)
+
+REG16(CXL_CHMU0_HEAD, 0x78)
+REG16(CXL_CHMU0_TAIL, 0x7A)
+
+/* Provide first few of these so we can calculate the size */
+REG64(CXL_CHMU0_RANGE_CONFIG_BITMAP0, 0x80)
+REG64(CXL_CHMU0_RANGE_CONFIG_BITMAP1, 0x88)
+
+REG64(CXL_CHMU0_HOTLIST0, CXL_CHMU_HL_START + 0x10)
+REG64(CXL_CHMU0_HOTLIST1, CXL_CHMU_HL_START + 0x10)
+
+REG64(CXL_CHMU1_CAP0, 0x10 + CXL_CHMU_INSTANCE_SIZE)
+
+typedef struct CHMUState CHMUState;
+
+/*
+ * Each device may have multiple CHMUs (CHMUState) with each CHMU having
+ * multiple hotness tracker instances (CHMUInstance).
+ */
+typedef struct CHMUInstance {
+    /* The reference to the PCIDevice is needed for MSI */
+    Object *private;
+    /* Number of counts in an epoch to be considered hot */
+    uint32_t hotness_thresh;
+    /* Tracking unit in bytes of DPA space as power of 2 */
+    uint32_t unit_size;
+    /*
+     * Ring buffer pointers
+     * - head is the offset in the ring of the oldest hot unit
+     * - tail is the offset in the ring of where the next hot unit will be
+     *   saved.
+     *
+     * Ring empty if head == tail.
+     * Ring full if (tail + 1) % length == head
+     */
+    uint16_t head, tail;
+    /* Ring buffer event threshold. Interrupt of first exceeding */
+    uint16_t fill_thresh;
+    /* Down sampling factor */
+    uint8_t ds_factor;
+    /* Type of request to track */
+    uint8_t what;
+
+    /* Interrupt controls and status */
+    bool int_on_overflow;
+    bool int_on_fill_thresh;
+    bool overflow_set;
+    bool fill_thresh_set;
+    uint8_t msi_n;
+
+    bool enabled;
+    uint64_t hotlist[CXL_HOTLIST_ENTRIES];
+    QEMUTimer *timer;
+    uint32_t epoch_ms;
+    uint8_t epoch_scale;
+    uint16_t epoch_val;
+    /* Reference needed for timer */
+    CHMUState *parent;
+} CHMUInstance;
+
+typedef struct CHMUState {
+    CHMUInstance inst[CXL_CHMU_INSTANCES_PER_BLOCK];
+    int socket;
+    /* Hack updated on first HDM decoder only */
+    uint16_t port;
+
+    /*
+     * Routing of accesses depends on interleave settings of the
+     * relevant memory range. That must be passed to the cache plugin.
+     */
+    struct {
+        uint64_t base;
+        uint64_t size;
+        uint64_t dpa_base;
+        uint16_t interleave_gran;
+        uint8_t ways;
+        uint8_t way;
+    } decoder[CXL_HDM_DECODER_COUNT];
+} CHMUState;
+
+typedef struct cxl_device_state CXLDeviceState;
+int cxl_chmu_register_block_init(Object *obj, CXLDeviceState *cxl_dstte,
+                                 int id, uint8_t msi_n, Error **errp);
+
+#endif /* _CXL_CHMU_H_ */
diff --git a/include/hw/cxl/cxl_device.h b/include/hw/cxl/cxl_device.h
index 9cc08da4cf..c4c092d77e 100644
--- a/include/hw/cxl/cxl_device.h
+++ b/include/hw/cxl/cxl_device.h
@@ -13,6 +13,7 @@
 #include "hw/cxl/cxl_component.h"
 #include "hw/pci/pci_device.h"
 #include "hw/register.h"
+#include "hw/cxl/cxl_chmu.h"
 #include "hw/cxl/cxl_events.h"
 
 /*
@@ -91,9 +92,21 @@
     (CXL_MAILBOX_REGISTERS_OFFSET + CXL_MAILBOX_REGISTERS_LENGTH)
 #define CXL_MEMORY_DEVICE_REGISTERS_LENGTH 0x8
 
+#define CXL_NUM_CHMU_INSTANCES 1
+#define CXL_CHMU_OFFSET(x)                                               \
+    QEMU_ALIGN_UP(CXL_MEMORY_DEVICE_REGISTERS_OFFSET +                  \
+                  CXL_MEMORY_DEVICE_REGISTERS_LENGTH +                  \
+                  (x) * QEMU_ALIGN_UP(CXL_CHMU_SIZE, 1 << 16),          \
+                  1 << 16)
+
 #define CXL_MMIO_SIZE                                                   \
-    (CXL_DEVICE_CAP_REG_SIZE + CXL_DEVICE_STATUS_REGISTERS_LENGTH +     \
-     CXL_MAILBOX_REGISTERS_LENGTH + CXL_MEMORY_DEVICE_REGISTERS_LENGTH)
+    QEMU_ALIGN_UP(CXL_DEVICE_CAP_REG_SIZE +                             \
+                  CXL_DEVICE_STATUS_REGISTERS_LENGTH +                  \
+                  CXL_MAILBOX_REGISTERS_LENGTH +                        \
+                  CXL_MEMORY_DEVICE_REGISTERS_LENGTH +                  \
+                  CXL_NUM_CHMU_INSTANCES *                              \
+                  QEMU_ALIGN_UP(CXL_CHMU_SIZE, 1 << 16),                \
+                  (1 << 16))
 
 /* CXL r3.1 Table 8-34: Command Return Codes */
 typedef enum {
@@ -236,6 +249,7 @@ typedef struct CXLCCI {
 
 typedef struct cxl_device_state {
     MemoryRegion device_registers;
+    MemoryRegion chmu_registers[1];
 
     /* CXL r3.1 Section 8.2.8.3: Device Status Registers */
     struct {
@@ -285,6 +299,7 @@ typedef struct cxl_device_state {
     uint64_t vmem_size;
 
     const struct cxl_cmd (*cxl_cmd_set)[256];
+    CHMUState chmu[1];
     CXLEventLog event_logs[CXL_EVENT_TYPE_MAX];
 } CXLDeviceState;
 
@@ -698,6 +713,11 @@ MemTxResult cxl_type3_read(PCIDevice *d, hwaddr host_addr, uint64_t *data,
 MemTxResult cxl_type3_write(PCIDevice *d, hwaddr host_addr, uint64_t data,
                             unsigned size, MemTxAttrs attrs);
 
+bool cxl_type3_get_hdm_interleave_props(CXLType3Dev *ct3d, int which,
+                                        uint64_t *hpa_base, uint16_t *granual,
+                                        uint8_t *ways);
+void cxl_type3_set_hdm_isp(CXLType3Dev *ctrd, int which, uint8_t isp);
+
 uint64_t cxl_device_get_timestamp(CXLDeviceState *cxlds);
 
 void cxl_event_init(CXLDeviceState *cxlds, int start_msg_num);
diff --git a/include/hw/cxl/cxl_pci.h b/include/hw/cxl/cxl_pci.h
index 00a0335d55..5af10e8ce0 100644
--- a/include/hw/cxl/cxl_pci.h
+++ b/include/hw/cxl/cxl_pci.h
@@ -165,6 +165,7 @@ QEMU_BUILD_BUG_ON(sizeof(CXLDVSECPortFlexBus) != 0x20);
 enum register_locator_indicies {
     REG_LOC_IDX_COMPONENT,
     REG_LOC_IDX_DEVICE,
+    REG_LOC_IDX_CHMU0,
     NR_REG_LOC_IDX
 };
 /*
@@ -193,5 +194,7 @@ typedef struct CXLDVSECRegisterLocator {
 #define RBI_COMPONENT_REG  (1 << 8)
 #define RBI_BAR_VIRT_ACL   (2 << 8)
 #define RBI_CXL_DEVICE_REG (3 << 8)
+#define RBI_CXL_CPMU_REG   (4 << 8)
+#define RBI_CXL_CHMU_REG   (5 << 8)
 
 #endif
diff --git a/hw/cxl/cxl-chmu.c b/hw/cxl/cxl-chmu.c
new file mode 100644
index 0000000000..2e50eff5f8
--- /dev/null
+++ b/hw/cxl/cxl-chmu.c
@@ -0,0 +1,516 @@
+/*
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * CXL Hotness Monitoring Unit
+ *
+ * Copyright(C) 2025 Huawei
+ *
+ * TODO:
+ * - Support bitmap of 256MiB ranges to track.
+ * - Downsampling
+ * - Multiple instances per block (CXL_CHMU_INSTANCES_PER_BLOCK > 1)
+ * - Read / Write only filtering
+ * - Cleanup error logging.
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/log.h"
+#include "qemu/guest-random.h"
+#include "hw/cxl/cxl.h"
+#include "hw/cxl/cxl_chmu.h"
+
+#include "hw/pci/msi.h"
+#include "hw/pci/msix.h"
+
+#define CHMU_HOTLIST_LENGTH 1024
+
+/* Must match enum in plugin */
+enum chmu_consumer_request {
+    QUERY_TAIL,
+    QUERY_HEAD,
+    SET_THRESHOLD,
+    SET_HEAD,
+    SET_HOTLIST_SIZE,
+    QUERY_HOTLIST_ENTRY,
+    SIGNAL_EPOCH_END,
+    SET_ENABLED,
+    SET_GRANUAL_SIZE,
+    SET_HPA_BASE,
+    SET_HPA_SIZE,
+    SET_DPA_BASE,
+    SET_INTERLEAVE_WAYS,
+    SET_INTERLEAVE_WAY,
+    SET_INTERLEAVE_GRAN,
+};
+
+static int chmu_send(CHMUState *chmu, uint64_t instance,
+                     enum chmu_consumer_request command,
+                     uint64_t param, uint64_t param2, uint64_t *response)
+{
+    uint64_t request[4] = { instance, command, param, param2 };
+    uint64_t temp;
+    uint64_t *reply = response ?: &temp;
+
+    send(chmu->socket, request, sizeof(request), 0);
+    if (recv(chmu->socket, reply, sizeof(*reply), 0) < sizeof(reply)) {
+        return -1;
+    }
+    return 0;
+}
+
+static uint64_t chmu_read(void *opaque, hwaddr offset, unsigned size)
+{
+    const hwaddr chmu_stride = A_CXL_CHMU1_CAP0 - A_CXL_CHMU0_CAP0;
+    CHMUState *chmu = opaque;
+    CHMUInstance *chmui;
+    uint64_t val = 0;
+    int instance = 0;
+    int rc;
+
+    if (offset >= A_CXL_CHMU0_CAP0) {
+        instance = (offset - A_CXL_CHMU0_CAP0) / chmu_stride;
+        /*
+         * Offset allows register defs for CHMU instance 0 to be used
+         * for all instances. Includes COMMON_CAP.
+         */
+        offset -= chmu_stride * instance;
+    }
+
+    if (instance >= CXL_CHMU_INSTANCES_PER_BLOCK) {
+        return 0;
+    }
+
+    chmui = &chmu->inst[instance];
+    switch (offset) {
+    case A_CXL_CHMU_COMMON_CAP0:
+        val = FIELD_DP64(val, CXL_CHMU_COMMON_CAP0, VERSION, 1);
+        val = FIELD_DP64(val, CXL_CHMU_COMMON_CAP0, NUM_INSTANCES,
+                         CXL_CHMU_INSTANCES_PER_BLOCK);
+        break;
+    case A_CXL_CHMU_COMMON_CAP1:
+        val = FIELD_DP64(val, CXL_CHMU_COMMON_CAP1, INSTANCE_LENGTH,
+                         A_CXL_CHMU1_CAP0 - A_CXL_CHMU0_CAP0);
+        break;
+    case A_CXL_CHMU0_CAP0:
+        val = FIELD_DP64(val, CXL_CHMU0_CAP0, MSI_N, chmui->msi_n);
+        val = FIELD_DP64(val, CXL_CHMU0_CAP0, OVERFLOW_INT, 1);
+        val = FIELD_DP64(val, CXL_CHMU0_CAP0, LEVEL_INT, 1);
+        val = FIELD_DP64(val, CXL_CHMU0_CAP0, EPOCH_TYPE,
+                         CXL_CHMU0_CAP0_EPOCH_TYPE_GLOBAL);
+        val = FIELD_DP64(val, CXL_CHMU0_CAP0, TRACKED_M2S_REQ_NONTEE_R, 1);
+        val = FIELD_DP64(val, CXL_CHMU0_CAP0, TRACKED_M2S_REQ_NONTEE_W, 1);
+        val = FIELD_DP64(val, CXL_CHMU0_CAP0, TRACKED_M2S_REQ_NONTEE_RW, 1);
+        /* No emulation of TEE modes yet so don't pretend to support them */
+
+        /* Epoch length from 100 milliseconds to 100 second */
+        val = FIELD_DP64(val, CXL_CHMU0_CAP0, MAX_EPOCH_LENGTH_SCALE,
+                         CXL_CHMU_EPOCH_LENGTH_SCALE_1SEC);
+        val = FIELD_DP64(val, CXL_CHMU0_CAP0, MAX_EPOCH_LENGTH_VAL, 100);
+        val = FIELD_DP64(val, CXL_CHMU0_CAP0, MIN_EPOCH_LENGTH_SCALE,
+                         CXL_CHMU_EPOCH_LENGTH_SCALE_100MSEC);
+        val = FIELD_DP64(val, CXL_CHMU0_CAP0, MIN_EPOCH_LENGTH_VAL, 1);
+        val = FIELD_DP64(val, CXL_CHMU0_CAP0, HOTLIST_SIZE,
+                         CXL_HOTLIST_ENTRIES);
+        break;
+    case A_CXL_CHMU0_CAP1:
+        /* 4KiB and 8KiB only - 2^N * 256 for each bit set */
+        val = FIELD_DP64(val, CXL_CHMU0_CAP1, UNIT_SIZES, BIT(4) | BIT(5));
+        /* No downsampling  - 2^(N - 1) for each bit set */
+        val = FIELD_DP64(val, CXL_CHMU0_CAP1, DOWN_SAMPLING_FACTORS, BIT(1));
+        val = FIELD_DP64(val, CXL_CHMU0_CAP1, FLAGS_EPOCH_BASED, 1);
+        val = FIELD_DP64(val, CXL_CHMU0_CAP1, FLAGS_ALWAYS_ON, 0);
+        val = FIELD_DP64(val, CXL_CHMU0_CAP1, FLAGS_RANDOMIZED_DOWN_SAMPLING,
+                         1);
+        val = FIELD_DP64(val, CXL_CHMU0_CAP1, FLAGS_OVERLAPPING_ADDRESS_RANGES,
+                         1);
+        /*
+         * Feature to enable a backlog of entries that immediately fill the list
+         * once space is available. Only relevant if reading list infrequently
+         * and concerned about stale data. (Not implemented)
+         */
+        val = FIELD_DP64(val, CXL_CHMU0_CAP1, FLAGS_INSERT_AFTER_CLEAR, 0);
+        break;
+    case A_CXL_CHMU0_CAP2:
+        val = FIELD_DP64(val, CXL_CHMU0_CAP2, BITMAP_REG_OFFSET,
+                         A_CXL_CHMU0_RANGE_CONFIG_BITMAP0 - A_CXL_CHMU0_CAP0);
+        break;
+    case A_CXL_CHMU0_CAP3:
+        val = FIELD_DP64(val, CXL_CHMU0_CAP3, HOTLIST_REG_OFFSET,
+                         A_CXL_CHMU0_HOTLIST0 - A_CXL_CHMU0_CAP0);
+        break;
+    case A_CXL_CHMU0_STATUS:
+        val = FIELD_DP64(val, CXL_CHMU0_STATUS, STATUS_ENABLED,
+                         chmui->enabled ? 1 : 0);
+        val = FIELD_DP64(val, CXL_CHMU0_STATUS, OPERATION_IN_PROG,
+                         0); /* All operations effectively instantaneous */
+        val = FIELD_DP64(val, CXL_CHMU0_STATUS, COUNTER_WIDTH, 16);
+        val = FIELD_DP64(val, CXL_CHMU0_STATUS, OVERFLOW_INT,
+                         chmui->overflow_set ? 1 : 0);
+        val = FIELD_DP64(val, CXL_CHMU0_STATUS, LEVEL_INT,
+                         chmui->fill_thresh_set ? 1 : 0);
+        break;
+    case A_CXL_CHMU0_CONF0:
+        val = FIELD_DP64(val, CXL_CHMU0_CONF0, M2S_REQ_TO_TRACK, chmui->what);
+        val = FIELD_DP64(val, CXL_CHMU0_CONF0, FLAGS_RANDOMIZE_DOWNSAMPLING, 0);
+        val = FIELD_DP64(val, CXL_CHMU0_CONF0, FLAGS_INT_ON_OVERFLOW,
+                         chmui->int_on_overflow);
+        val = FIELD_DP64(val, CXL_CHMU0_CONF0, FLAGS_INT_ON_FILL_THRESH,
+                         chmui->int_on_fill_thresh);
+        val = FIELD_DP64(val, CXL_CHMU0_CONF0, CONTROL_ENABLE,
+                         chmui->enabled);
+        val = FIELD_DP64(val, CXL_CHMU0_CONF0, CONTROL_RESET, 0);
+        val = FIELD_DP64(val, CXL_CHMU0_CONF0, HOTNESS_THRESHOLD,
+                         chmui->hotness_thresh);
+        break;
+    case A_CXL_CHMU0_CONF1:
+        val = FIELD_DP64(val, CXL_CHMU0_CONF1, UNIT_SIZE,
+                         chmui->unit_size);
+        val = FIELD_DP64(val, CXL_CHMU0_CONF1, DOWN_SAMPLING_FACTOR, 0);
+        val = FIELD_DP64(val, CXL_CHMU0_CONF1, REPORTING_MODE, 0);
+        val = FIELD_DP64(val, CXL_CHMU0_CONF1, EPOCH_LENGTH_SCALE,
+                         chmui->epoch_scale);
+        val = FIELD_DP64(val, CXL_CHMU0_CONF1, EPOCH_LENGTH_VAL,
+                         chmui->epoch_val);
+        break;
+    case A_CXL_CHMU0_CONF2:
+        val = FIELD_DP64(val, CXL_CHMU0_CONF2, NOTIFICATION_THRESHOLD,
+                         chmui->fill_thresh);
+        break;
+    case A_CXL_CHMU0_TAIL:
+        if (chmu->socket) {
+            rc = chmu_send(chmu, instance, QUERY_TAIL, 0, 0, &val);
+            if (rc < 0) {
+                printf("Failed to read tail\n");
+                return 0;
+            }
+        } else {
+            val = chmui->tail;
+        }
+        break;
+    case A_CXL_CHMU0_HEAD:
+        if (chmu->socket) {
+            rc = chmu_send(chmu, instance, QUERY_HEAD, 0, 0, &val);
+            if (rc < 0) {
+                printf("Failed to read head\n");
+                return 0;
+            }
+        } else {
+            val = chmui->head;
+        }
+        break;
+    case A_CXL_CHMU0_HOTLIST0...(8 * (A_CXL_CHMU0_HOTLIST0 +
+                                      CHMU_HOTLIST_LENGTH)):
+        if (chmu->socket) {
+            rc = chmu_send(chmu, instance, QUERY_HOTLIST_ENTRY,
+                           (offset - A_CXL_CHMU0_HOTLIST0) / 8, 0, &val);
+            if (rc < 0) {
+                printf("Failed to read a hotlist entry\n");
+                return 0;
+            }
+        } else {
+            val = chmui->hotlist[(offset - A_CXL_CHMU0_HOTLIST0) / 8];
+        }
+        break;
+    }
+    return val;
+}
+
+static void chmu_write(void *opaque, hwaddr offset, uint64_t value,
+                       unsigned size)
+{
+    CHMUState *chmu = opaque;
+    CHMUInstance *chmui;
+    hwaddr chmu_stride = A_CXL_CHMU1_CAP0 - A_CXL_CHMU0_CAP0;
+    int instance = 0;
+    int i, rc;
+
+    if (offset >= A_CXL_CHMU0_CAP0) {
+        instance = (offset - A_CXL_CHMU0_CAP0) / chmu_stride;
+        /* offset as if in chmu0 so includes the common caps */
+        offset -= chmu_stride * instance;
+    }
+    if (instance >= CXL_CHMU_INSTANCES_PER_BLOCK) {
+        return;
+    }
+
+    chmui = &chmu->inst[instance];
+
+    switch (offset) {
+    case A_CXL_CHMU0_STATUS:
+        /* The interrupt fields are RW12C */
+        if (FIELD_EX64(value, CXL_CHMU0_STATUS, OVERFLOW_INT)) {
+            chmui->overflow_set = false;
+        }
+        if (FIELD_EX64(value, CXL_CHMU0_STATUS, LEVEL_INT)) {
+            chmui->fill_thresh_set = false;
+        }
+        break;
+    case A_CXL_CHMU0_RANGE_CONFIG_BITMAP0...(A_CXL_CHMU0_HOTLIST0 - 8):
+        /* TODO - wire this up */
+        printf("Bitmap write %lx %lx\n",
+               offset - A_CXL_CHMU0_RANGE_CONFIG_BITMAP0, value);
+        break;
+    case A_CXL_CHMU0_CONF0:
+        if (FIELD_EX64(value, CXL_CHMU0_CONF0, CONTROL_ENABLE)) {
+            chmui->enabled = true;
+            timer_mod(chmui->timer,
+                      qemu_clock_get_ms(QEMU_CLOCK_VIRTUAL) + chmui->epoch_ms);
+        } else {
+            timer_del(chmui->timer);
+            chmui->enabled = false;
+        }
+        if (chmu->socket) {
+            bool enabled = FIELD_EX64(value, CXL_CHMU0_CONF0, CONTROL_ENABLE);
+
+            if (enabled) {
+                int d;
+                for (d = 0; d < CXL_HDM_DECODER_COUNT; d++) {
+                    /* Should loop over ranges + the base addresses */
+
+                    rc = chmu_send(chmu, instance, SET_HPA_BASE,
+                                   chmu->decoder[d].base, d, NULL);
+                    if (rc < 0) {
+                        printf("Failed to set base\n");
+                    }
+                    rc = chmu_send(chmu, instance, SET_HPA_SIZE,
+                                   chmu->decoder[d].size, d, NULL);
+                    if (rc < 0) {
+                        printf("Failed to set size\n");
+                    }
+                    rc = chmu_send(chmu, instance, SET_DPA_BASE,
+                                   chmu->decoder[d].dpa_base, d, NULL);
+                    if (rc < 0) {
+                        printf("Failed to set base\n");
+                    }
+
+                    rc = chmu_send(chmu, instance, SET_INTERLEAVE_WAYS,
+                                   chmu->decoder[d].ways, d, NULL);
+                    if (rc < 0) {
+                        printf("Failed to set ways\n");
+                    }
+                    rc = chmu_send(chmu, instance, SET_INTERLEAVE_WAY,
+                                   chmu->decoder[d].way, d, NULL);
+                    if (rc < 0) {
+                        printf("Failed to set ways\n");
+                    }
+                    rc = chmu_send(chmu, instance, SET_INTERLEAVE_GRAN,
+                                   chmu->decoder[d].interleave_gran, d, NULL);
+                    if (rc < 0) {
+                        printf("Failed to set ways\n");
+                    }
+                }
+            }
+            rc = chmu_send(chmu, instance, SET_THRESHOLD,
+                           FIELD_EX64(value, CXL_CHMU0_CONF0,
+                                      HOTNESS_THRESHOLD),
+                           0, NULL);
+            if (rc < 0) {
+                printf("failed to set threshold\n");
+            }
+            rc = chmu_send(chmu, instance, SET_ENABLED, enabled ? 1 : 0, 0,
+                           NULL);
+            if (rc < 0) {
+                printf("Failed to set enabled\n");
+            }
+        }
+
+        if (FIELD_EX64(value, CXL_CHMU0_CONF0, CONTROL_RESET)) {
+            chmui->head = 0;
+            chmui->tail = 0;
+            for (i = 0; i < CXL_HOTLIST_ENTRIES; i++) {
+                chmui->hotlist[i] = 0;
+            }
+        }
+        chmui->what = FIELD_EX64(value, CXL_CHMU0_CONF0, M2S_REQ_TO_TRACK);
+        chmui->int_on_overflow =
+            FIELD_EX64(value, CXL_CHMU0_CONF0, FLAGS_INT_ON_OVERFLOW);
+        chmui->int_on_fill_thresh =
+            FIELD_EX64(value, CXL_CHMU0_CONF0, FLAGS_INT_ON_FILL_THRESH);
+        chmui->hotness_thresh =
+            FIELD_EX64(value, CXL_CHMU0_CONF0, HOTNESS_THRESHOLD);
+        break;
+    case A_CXL_CHMU0_CONF1: {
+        chmui->unit_size = FIELD_EX64(value, CXL_CHMU0_CONF1, UNIT_SIZE);
+        chmui->ds_factor =
+            FIELD_EX64(value, CXL_CHMU0_CONF1, DOWN_SAMPLING_FACTOR);
+
+        /* TODO: Sanity check value in supported range */
+        chmui->epoch_scale =
+            FIELD_EX64(value, CXL_CHMU0_CONF1, EPOCH_LENGTH_SCALE);
+        chmui->epoch_val = FIELD_EX64(value, CXL_CHMU0_CONF1, EPOCH_LENGTH_VAL);
+        switch (chmui->epoch_scale) {
+            /* TODO: Implement maths, not lookup */
+        case 1: /* 100usec */
+            chmui->epoch_ms = chmui->epoch_val / 10;
+            break;
+        case 2:
+            chmui->epoch_ms = chmui->epoch_val;
+            break;
+        case 3:
+            chmui->epoch_ms = chmui->epoch_val * 10;
+            break;
+        case 4:
+            chmui->epoch_ms = chmui->epoch_val * 100;
+            break;
+        case 5:
+            chmui->epoch_ms = chmui->epoch_val * 1000;
+            break;
+        default:
+            /* Unknown value so ignore */
+            break;
+        }
+        break;
+    }
+    case A_CXL_CHMU0_CONF2:
+        chmui->fill_thresh = FIELD_EX64(value, CXL_CHMU0_CONF2,
+                                        NOTIFICATION_THRESHOLD);
+        break;
+    case A_CXL_CHMU0_HEAD:
+        chmui->head = value;
+        if (chmu->socket) {
+            rc = chmu_send(chmu, instance, SET_HEAD, value, 0, NULL);
+            if (rc < 0) {
+                printf("Failed to set head pointer\n");
+            }
+        }
+        break;
+    case A_CXL_CHMU0_TAIL: /* Not sure why this is writeable! */
+        chmui->tail = value;
+        break;
+    }
+}
+
+static const MemoryRegionOps chmu_ops = {
+    .read = chmu_read,
+    .write = chmu_write,
+    .endianness = DEVICE_LITTLE_ENDIAN,
+    .valid = {
+        .min_access_size = 1,
+        .max_access_size = 8,
+        .unaligned = false,
+    },
+    .impl = {
+        .min_access_size = 4,
+        .max_access_size = 8,
+    },
+};
+
+static void chmu_timer_update(void *opaque)
+{
+    CHMUInstance *chmui = opaque;
+    PCIDevice *pdev = PCI_DEVICE(chmui->private);
+    bool interrupt_needed = false;
+    uint64_t reply;
+    int rc;
+
+    timer_del(chmui->timer);
+
+    /* FIXME: instance always 0! */
+    rc = chmu_send(chmui->parent, 0, SIGNAL_EPOCH_END, 0, 0, &reply);
+    if (rc < 0) {
+        error_setg(&error_fatal, "Epoch signalling failed");
+        return;
+    }
+
+    rc = chmu_send(chmui->parent, 0, QUERY_TAIL, 0, 0, &reply);
+    if (rc < 0) {
+        error_setg(&error_fatal, "Tail read failed");
+        return;
+    }
+    chmui->tail = reply;
+    printf("After epoch tail is %x\n", chmui->tail);
+
+    /* All interrupt code is kept in here whatever the data source */
+    if (chmui->int_on_fill_thresh && !chmui->fill_thresh_set) {
+        if (((chmui->tail > chmui->head) &&
+             (chmui->tail - chmui->head > chmui->fill_thresh)) |
+            ((chmui->tail < chmui->head) &&
+             (CXL_HOTLIST_ENTRIES - chmui->head + chmui->tail >
+              chmui->fill_thresh))) {
+            chmui->fill_thresh_set = true;
+            interrupt_needed = true;
+        }
+    }
+    if (chmui->int_on_overflow && !chmui->overflow_set) {
+        if ((chmui->tail + 1) % CXL_HOTLIST_ENTRIES == chmui->head) {
+            chmui->overflow_set = true;
+            interrupt_needed = true;
+        }
+    }
+
+    if (interrupt_needed) {
+        if (msix_enabled(pdev)) {
+            msix_notify(pdev, chmui->msi_n);
+        } else if (msi_enabled(pdev)) {
+            msi_notify(pdev, chmui->msi_n);
+        }
+    }
+
+    timer_mod(chmui->timer,
+              qemu_clock_get_ms(QEMU_CLOCK_VIRTUAL) + chmui->epoch_ms);
+}
+
+int cxl_chmu_register_block_init(Object *obj, CXLDeviceState *cxl_dstate,
+                                 int id, uint8_t msi_n, Error **errp)
+{
+    CHMUState *chmu = &cxl_dstate->chmu[id];
+    MemoryRegion *registers = &cxl_dstate->chmu_registers[id];
+    g_autofree gchar *name = g_strdup_printf("chmu%d-registers", id);
+    int i;
+
+    memory_region_init_io(registers, obj, &chmu_ops, chmu, name,
+                          pow2ceil(CXL_CHMU_SIZE));
+    memory_region_add_subregion(&cxl_dstate->device_registers,
+                                CXL_CHMU_OFFSET(id), registers);
+
+    for (i = 0; i < CXL_CHMU_INSTANCES_PER_BLOCK; i++) {
+        CHMUInstance *chmui = &chmu->inst[i];
+
+        chmui->parent = chmu; /* Back reference needed for timer */
+        chmui->private = obj; /* Reference to PCIDevice needed for MSI/MSI-x */
+        chmui->msi_n = msi_n + i;
+        chmui->timer = timer_new_ms(QEMU_CLOCK_VIRTUAL, chmu_timer_update,
+                                    chmui);
+    }
+
+    /* No port means fake non funtional hardware only */
+    if (chmu->port) {
+        struct sockaddr_in server_addr = {};
+
+        chmu->socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
+        if (chmu->socket < 0) {
+            error_setg(errp, "Failed to create a socket");
+            return -1;
+        }
+
+        server_addr.sin_family = AF_INET;
+        server_addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
+        server_addr.sin_port = htons(chmu->port);
+        if (connect(chmu->socket, (struct sockaddr *)&server_addr,
+                    sizeof(server_addr)) < 0) {
+            close(chmu->socket);
+            error_setg(errp, "Socket connect failed");
+            return -1;
+        }
+
+        for (i = 0; i < CXL_CHMU_INSTANCES_PER_BLOCK; i++) {
+            uint64_t granual_size = (1 << chmu->inst[i].unit_size);
+            int rc;
+
+            rc = chmu_send(chmu, i, SET_HOTLIST_SIZE, CHMU_HOTLIST_LENGTH, 0,
+                           NULL);
+            if (rc) {
+                error_setg(errp, "Failed to set hotlist size");
+                return rc;
+            }
+
+            rc = chmu_send(chmu, i, SET_GRANUAL_SIZE, granual_size, 0, NULL);
+            if (rc) {
+                error_setg(errp, "Failed to set granual size");
+                return rc;
+            }
+        }
+    }
+
+    return 0;
+}
diff --git a/hw/mem/cxl_type3.c b/hw/mem/cxl_type3.c
index dcefd41088..43f4cd8023 100644
--- a/hw/mem/cxl_type3.c
+++ b/hw/mem/cxl_type3.c
@@ -36,7 +36,10 @@ enum CXL_T3_MSIX_VECTOR {
     CXL_T3_MSIX_PCIE_DOE_TABLE_ACCESS = 0,
     CXL_T3_MSIX_EVENT_START = 2,
     CXL_T3_MSIX_MBOX = CXL_T3_MSIX_EVENT_START + CXL_EVENT_TYPE_MAX,
-    CXL_T3_MSIX_VECTOR_NR
+    CXL_T3_MSIX_CHMU0_BASE,
+    /* One interrupt per CMUH instance in the block */
+    CXL_T3_MSIX_VECTOR_NR =
+        CXL_T3_MSIX_CHMU0_BASE + CXL_CHMU_INSTANCES_PER_BLOCK,
 };
 
 #define DWORD_BYTE 4
@@ -394,7 +397,13 @@ static void build_dvsecs(CXLType3Dev *ct3d)
             .lo = RBI_CXL_DEVICE_REG | CXL_DEVICE_REG_BAR_IDX,
             .hi = 0,
         },
+        .reg_base[REG_LOC_IDX_CHMU0] = {
+            .lo = CXL_CHMU_OFFSET(0) | RBI_CXL_CHMU_REG |
+            CXL_DEVICE_REG_BAR_IDX,
+            .hi = 0,
+        },
     };
+
     cxl_component_create_dvsec(cxl_cstate, CXL2_TYPE3_DEVICE,
                                REG_LOC_DVSEC_LENGTH, REG_LOC_DVSEC,
                                REG_LOC_DVSEC_REVID, dvsec);
@@ -418,19 +427,101 @@ static void build_dvsecs(CXLType3Dev *ct3d)
                                PCIE_CXL3_FLEXBUS_PORT_DVSEC_REVID, dvsec);
 }
 
+bool cxl_type3_get_hdm_interleave_props(CXLType3Dev *ct3d, int which,
+                                        uint64_t *hpa_base, uint16_t *granual,
+                                        uint8_t *ways)
+{
+    int hdm_inc = R_CXL_HDM_DECODER1_BASE_LO - R_CXL_HDM_DECODER0_BASE_LO;
+    ComponentRegisters *cregs = &ct3d->cxl_cstate.crb;
+    uint32_t *cache_mem = cregs->cache_mem_registers;
+    uint32_t ctrl, low, high;
+
+    ctrl = ldl_le_p(cache_mem + R_CXL_HDM_DECODER0_CTRL + which * hdm_inc);
+    /* TODO: Sanity checks that the decoder is possible */
+    if (!FIELD_EX32(ctrl, CXL_HDM_DECODER0_CTRL, COMMITTED)) {
+        return false;
+    }
+
+    *granual = cxl_decode_ig(FIELD_EX32(ctrl, CXL_HDM_DECODER0_CTRL, IG));
+    *ways = cxl_interleave_ways_dec(FIELD_EX32(ctrl, CXL_HDM_DECODER0_CTRL, IW),
+                                    NULL);
+    low = ldl_le_p(cache_mem + R_CXL_HDM_DECODER0_BASE_LO + which * hdm_inc);
+    high = ldl_le_p(cache_mem + R_CXL_HDM_DECODER0_BASE_HI + which * hdm_inc);
+    *hpa_base = ((uint64_t)high << 32) | (low & 0xf0000000);
+
+    return true;
+}
+
+/* Only the CHMU needs to know the way */
+void cxl_type3_set_hdm_isp(CXLType3Dev *ct3d, int which, uint8_t isp)
+{
+    ct3d->cxl_dstate.chmu[0].decoder[which].way = isp;
+}
+
 static void hdm_decoder_commit(CXLType3Dev *ct3d, int which)
 {
     int hdm_inc = R_CXL_HDM_DECODER1_BASE_LO - R_CXL_HDM_DECODER0_BASE_LO;
     ComponentRegisters *cregs = &ct3d->cxl_cstate.crb;
     uint32_t *cache_mem = cregs->cache_mem_registers;
-    uint32_t ctrl;
+    uint32_t ctrl, low, high;
+    uint64_t dpa_base = 0;
+    uint8_t iws;
+    uint16_t ig;
+    int d;
 
     ctrl = ldl_le_p(cache_mem + R_CXL_HDM_DECODER0_CTRL + which * hdm_inc);
     /* TODO: Sanity checks that the decoder is possible */
     ctrl = FIELD_DP32(ctrl, CXL_HDM_DECODER0_CTRL, ERR, 0);
     ctrl = FIELD_DP32(ctrl, CXL_HDM_DECODER0_CTRL, COMMITTED, 1);
 
+    /* Get interleave details for chmu */
+    ig = FIELD_EX32(ctrl, CXL_HDM_DECODER0_CTRL, IG);
+    ct3d->cxl_dstate.chmu[0].decoder[which].interleave_gran = cxl_decode_ig(ig);
+
+    iws = FIELD_EX32(ctrl, CXL_HDM_DECODER0_CTRL, IW);
+    ct3d->cxl_dstate.chmu[0].decoder[which].ways =
+        cxl_interleave_ways_dec(iws, NULL);
+
     stl_le_p(cache_mem + R_CXL_HDM_DECODER0_CTRL + which * hdm_inc, ctrl);
+
+    low = ldl_le_p(cache_mem + R_CXL_HDM_DECODER0_BASE_LO + which * hdm_inc);
+    high = ldl_le_p(cache_mem + R_CXL_HDM_DECODER0_BASE_HI + which * hdm_inc);
+    ct3d->cxl_dstate.chmu[0].decoder[which].base =
+        ((uint64_t)high << 32) | (low & 0xf0000000);
+
+    low = ldl_le_p(cache_mem + R_CXL_HDM_DECODER0_SIZE_LO + which * hdm_inc);
+    high = ldl_le_p(cache_mem + R_CXL_HDM_DECODER0_SIZE_HI + which * hdm_inc);
+    ct3d->cxl_dstate.chmu[0].decoder[which].size =
+        ((uint64_t)high << 32) | (low & 0xf0000000);
+
+    /*
+     * To figure out the DPA start, Add size / ways + skip for all earlier
+     * decoders + skip for the current one.
+     */
+    for (d = 0; d < which; d++) {
+        ctrl = ldl_le_p(cache_mem + R_CXL_HDM_DECODER0_CTRL + d * hdm_inc);
+
+        low = ldl_le_p(cache_mem + R_CXL_HDM_DECODER0_DPA_SKIP_LO
+                       + d * hdm_inc);
+        high = ldl_le_p(cache_mem + R_CXL_HDM_DECODER0_DPA_SKIP_HI
+                        + d * hdm_inc);
+        dpa_base += ((uint64_t)high << 32) | (low & 0xf0000000);
+
+        iws = FIELD_EX32(ctrl, CXL_HDM_DECODER0_CTRL, IW);
+        low = ldl_le_p(cache_mem + R_CXL_HDM_DECODER0_SIZE_LO + d * hdm_inc);
+        high = ldl_le_p(cache_mem + R_CXL_HDM_DECODER0_SIZE_HI + d * hdm_inc);
+        /* DPA space used is size / ways */
+        dpa_base += (((uint64_t)high << 32) | (low & 0xf0000000)) /
+            cxl_interleave_ways_dec(iws, NULL);
+    }
+    low = ldl_le_p(cache_mem + R_CXL_HDM_DECODER0_DPA_SKIP_LO +
+                   which * hdm_inc);
+    high = ldl_le_p(cache_mem + R_CXL_HDM_DECODER0_DPA_SKIP_HI +
+                    which * hdm_inc);
+    dpa_base += ((uint64_t)high << 32) | (low & 0xf0000000);
+
+
+    ct3d->cxl_dstate.chmu[0].decoder[which].dpa_base = dpa_base;
 }
 
 static void hdm_decoder_uncommit(CXLType3Dev *ct3d, int which)
@@ -913,6 +1004,13 @@ static void ct3_realize(PCIDevice *pci_dev, Error **errp)
 
     cxl_device_register_block_init(OBJECT(pci_dev), &ct3d->cxl_dstate,
                                    &ct3d->cci);
+
+    rc = cxl_chmu_register_block_init(OBJECT(pci_dev), &ct3d->cxl_dstate,
+                                      0, CXL_T3_MSIX_CHMU0_BASE, errp);
+    if (rc) {
+        goto err_free_special_ops;
+    }
+
     pci_register_bar(pci_dev, CXL_DEVICE_REG_BAR_IDX,
                      PCI_BASE_ADDRESS_SPACE_MEMORY |
                          PCI_BASE_ADDRESS_MEM_TYPE_64,
@@ -1288,6 +1386,7 @@ static const Property ct3_props[] = {
                                 speed, PCIE_LINK_SPEED_32),
     DEFINE_PROP_PCIE_LINK_WIDTH("x-width", CXLType3Dev,
                                 width, PCIE_LINK_WIDTH_16),
+    DEFINE_PROP_UINT16("chmu-port", CXLType3Dev, cxl_dstate.chmu[0].port, 0),
 };
 
 static uint64_t get_lsa_size(CXLType3Dev *ct3d)
diff --git a/hw/cxl/meson.build b/hw/cxl/meson.build
index 3e375f61a9..e3abb49d27 100644
--- a/hw/cxl/meson.build
+++ b/hw/cxl/meson.build
@@ -6,6 +6,7 @@ system_ss.add(when: 'CONFIG_CXL',
                    'cxl-host.c',
                    'cxl-cdat.c',
                    'cxl-events.c',
+                   'cxl-chmu.c',
                    'switch-mailbox-cci.c',
                ),
                if_false: files(
-- 
2.48.1



^ permalink raw reply related	[flat|nested] 6+ messages in thread

* [RFC PATCH v2 QEMU 3/4] hw/cxl: Provide a means to get the interleave set position for an EP
  2025-06-12 15:57 [RFC PATCH v2 QEMU 0/4] cxl/plugins: Hotness Monitoring Unit with 'real' data Jonathan Cameron via
  2025-06-12 15:57 ` [RFC PATCH v2 QEMU 1/4] hw/cxl: Switch to using an array for CXLRegisterLocator base addresses Jonathan Cameron via
  2025-06-12 15:57 ` [RFC PATCH v2 QEMU 2/4] hw/cxl: Add emulation of a CXL Hotness Monitoring Unit (CHMU) Jonathan Cameron via
@ 2025-06-12 15:57 ` Jonathan Cameron via
  2025-06-12 15:57 ` [RFC PATCH v2 QEMU 4/4] plugins: cache: Add a hotness tracker for cache misses with socket connection to device emulation Jonathan Cameron via
  3 siblings, 0 replies; 6+ messages in thread
From: Jonathan Cameron via @ 2025-06-12 15:57 UTC (permalink / raw)
  To: Pierrick Bouvier, fan.ni, qemu-devel, Alex Bennée,
	Alexandre Iooss, Mahmoud Mandour, Bowman Terry
  Cc: linuxarm, rientjes, dave, joshua.hahnjy, rkodsara, sj,
	xuezhengchu, yiannis, ziy, weixugc, Bharata B Rao

CXL interleave decoding is hieriarchical in a fashion that means the CXL
memory devices only need to know the interleave granularity and interleave
ways to figure out which address bits to drop from incoming translactions.
Unfortunately to provide the right information to the hotness monitoring
plugin, which filters transactions in Host Physical Address space, it is
necessary to know which interleave set position a given device is in.

I tried various more sophisticated solutions to provide this information
but they were all rather complex. The solution used here is the brute force
one.  Every time an HDM Decoder is committed (these are the address routing
elements) it checks every Type 3 Device HDM Decoder to find a HPA range. It
then uses the address routing HPA to Device matching heirarchical routing
at a series of addresses corresponding to the first byte of each interleave
set. When the same device is reached then we know we have the correct
Interleave Set Position and pass that to the CHMU.

Signed-off-by: Jonathan Cameron <Jonathan.Cameron@huawei.com>

---
RFC v2: New patch. Note lightly tested only so far.
---
 include/hw/cxl/cxl.h         |  1 +
 hw/cxl/cxl-component-utils.c |  4 ++
 hw/cxl/cxl-host.c            | 72 ++++++++++++++++++++++++++++++++++++
 hw/mem/cxl_type3.c           |  2 +
 4 files changed, 79 insertions(+)

diff --git a/include/hw/cxl/cxl.h b/include/hw/cxl/cxl.h
index 12844d3418..b4b83c0b63 100644
--- a/include/hw/cxl/cxl.h
+++ b/include/hw/cxl/cxl.h
@@ -71,4 +71,5 @@ CXLComponentState *cxl_usp_to_cstate(CXLUpstreamPort *usp);
 typedef struct CXLDownstreamPort CXLDownstreamPort;
 DECLARE_INSTANCE_CHECKER(CXLDownstreamPort, CXL_DSP, TYPE_CXL_DSP)
 
+void cxl_update_isp(void);
 #endif
diff --git a/hw/cxl/cxl-component-utils.c b/hw/cxl/cxl-component-utils.c
index 473895948b..f53ce1ebaa 100644
--- a/hw/cxl/cxl-component-utils.c
+++ b/hw/cxl/cxl-component-utils.c
@@ -116,6 +116,10 @@ static void dumb_hdm_handler(CXLComponentState *cxl_cstate, hwaddr offset,
         value = FIELD_DP32(value, CXL_HDM_DECODER0_CTRL, COMMITTED, 0);
     }
     stl_le_p((uint8_t *)cache_mem + offset, value);
+
+    if (should_commit) {
+        cxl_update_isp();
+    }
 }
 
 static void cxl_cache_mem_write_reg(void *opaque, hwaddr offset, uint64_t value,
diff --git a/hw/cxl/cxl-host.c b/hw/cxl/cxl-host.c
index 5239555f6c..893ef7f7fa 100644
--- a/hw/cxl/cxl-host.c
+++ b/hw/cxl/cxl-host.c
@@ -279,6 +279,78 @@ static MemTxResult cxl_write_cfmws(void *opaque, hwaddr addr,
     return cxl_type3_write(d, addr + fw->base, data, size, attrs);
 }
 
+/*
+ * Updating the end point decoder stashed Interleave Set Positions (ISP)
+ * that is needed for the CHMU to pass to the cache plugin + hotness tracker
+ * is tricky as the decoders can be committed in any order.
+ *
+ * Brute force the problem by finding any endpoints below a cfmws and for
+ * each enabled decoder, probing until we get a match - if any upstream
+ * decoders are not commited this will fail but that is fine as we try again
+ * later when the situation is resolved by commiting upstream decoders.
+ *
+ * This is a rare operation, so not worth complexity of walking down from
+ * the Fixed memory windows. Just compare all with all.
+ */
+
+/* Update ISP for a given Type 3 memory device */
+static int cxl_type3_update_isp(Object *obj, void *opaque)
+{
+    CXLType3Dev *ct3d;
+    CXLFixedWindow *fw = opaque;
+    int i;
+
+    /*
+     * From the CXL Type 3 HDM decoders we need interleave info.
+     * That will let us then find out for each decoder what hits it....
+     */
+    if (!object_dynamic_cast(obj, TYPE_CXL_TYPE3)) {
+        return 0;
+    }
+
+    ct3d = CXL_TYPE3(obj);
+
+    for (i = 0; i < CXL_HDM_DECODER_COUNT; i++) {
+        uint64_t hpa_base;
+        uint16_t granual;
+        uint8_t ways, w;
+        PCIDevice *d;
+
+        if (!cxl_type3_get_hdm_interleave_props(ct3d, i, &hpa_base, &granual,
+                                                &ways)) {
+            continue; /* commit in order, but teardown can be messy */
+        }
+
+        for (w = 0; w < ways; w++) {
+            d = cxl_cfmws_find_device(fw, hpa_base + w * granual - fw->base);
+            if (d == PCI_DEVICE(ct3d)) {
+                cxl_type3_set_hdm_isp(ct3d, i, w);
+            }
+        }
+    }
+    return 0;
+}
+
+static int cxl_fmw_update_isp(Object *obj, void *priv)
+{
+    struct CXLFixedWindow *fw;
+
+    if (!object_dynamic_cast(obj, TYPE_CXL_FMW)) {
+        return 0;
+    }
+    fw = CXL_FMW(obj);
+    object_child_foreach_recursive(object_get_root(),
+                                   cxl_type3_update_isp, fw);
+    return 0;
+}
+
+/* Update all Interleave Set Positions on all EP HDM decoders */
+void cxl_update_isp(void)
+{
+    object_child_foreach_recursive(object_get_root(),
+                                   cxl_fmw_update_isp, NULL);
+}
+
 const MemoryRegionOps cfmws_ops = {
     .read_with_attrs = cxl_read_cfmws,
     .write_with_attrs = cxl_write_cfmws,
diff --git a/hw/mem/cxl_type3.c b/hw/mem/cxl_type3.c
index 43f4cd8023..8e9f76a07a 100644
--- a/hw/mem/cxl_type3.c
+++ b/hw/mem/cxl_type3.c
@@ -484,6 +484,8 @@ static void hdm_decoder_commit(CXLType3Dev *ct3d, int which)
 
     stl_le_p(cache_mem + R_CXL_HDM_DECODER0_CTRL + which * hdm_inc, ctrl);
 
+    cxl_update_isp();
+
     low = ldl_le_p(cache_mem + R_CXL_HDM_DECODER0_BASE_LO + which * hdm_inc);
     high = ldl_le_p(cache_mem + R_CXL_HDM_DECODER0_BASE_HI + which * hdm_inc);
     ct3d->cxl_dstate.chmu[0].decoder[which].base =
-- 
2.48.1



^ permalink raw reply related	[flat|nested] 6+ messages in thread

* [RFC PATCH v2 QEMU 4/4] plugins: cache: Add a hotness tracker for cache misses with socket connection to device emulation
  2025-06-12 15:57 [RFC PATCH v2 QEMU 0/4] cxl/plugins: Hotness Monitoring Unit with 'real' data Jonathan Cameron via
                   ` (2 preceding siblings ...)
  2025-06-12 15:57 ` [RFC PATCH v2 QEMU 3/4] hw/cxl: Provide a means to get the interleave set position for an EP Jonathan Cameron via
@ 2025-06-12 15:57 ` Jonathan Cameron via
  3 siblings, 0 replies; 6+ messages in thread
From: Jonathan Cameron via @ 2025-06-12 15:57 UTC (permalink / raw)
  To: Pierrick Bouvier, fan.ni, qemu-devel, Alex Bennée,
	Alexandre Iooss, Mahmoud Mandour, Bowman Terry
  Cc: linuxarm, rientjes, dave, joshua.hahnjy, rkodsara, sj,
	xuezhengchu, yiannis, ziy, weixugc, Bharata B Rao

This adds simple hotness tracker instances suitable for pairing with
the CXL HMU emulation with control and data transfer via a socket
(port 4443)

A typical command line is:
-plugin ../qemu/bin/native/contrib/plugins/libcache.so,hotness=1,\
dcachesize=8192,dassoc=4,dblksize=64,icachesize=8192,iassoc=4,\
iblksize=64,l2cachesize=32768,l2assoc=16,l2blksize=64

Most of the parameters are concerned with configuring the cache topology so
that the accesses that reach the hotness monitor (which is pretending to
be on the CXL device) reflect those that did not hit in cache. The only
hotness specific parameter is hotness=1 to turn on hotness tracking
and allow connections from consuming device emulation.

There are many approximations in this cache model but it is closer than
not modelling the caches at all. More sophisticated modeling is easy
to add but will come with a performance cost.

The hotness tracker is based on an oracle counter array (1 counter per
granual) + hotness threshold (supplied from the emulated device).
Real devices will be resource constrained and are likely to implement
either a limited number of precise counters, or an imprecise counting
method. Emulating any of these should be easy to add.

The device emulation sends an end of epoch system based on the emulated
machine idea of time. At that point entries are added to the reported
hotlist for any counters that are over the threshold set via hotness=X.
That hotlist is queried by the device side.  All configuration is
provided over the socket from the emulated CXL Hotness Monitoring unit.

RFC question: Should I split this off as a separate plugin that duplicates
all of the cache plugin logic as well as providing the hostness monitor.

Signed-off-by: Jonathan Cameron <Jonathan.Cameron@huawei.com>
---

RFCv2: Bring the hotness server element into the plugin.

Still an RFC because there are more features to implement. Looking
for feedback on the overall approach.
---
 contrib/plugins/cache.c | 434 +++++++++++++++++++++++++++++++++++++++-
 1 file changed, 427 insertions(+), 7 deletions(-)

diff --git a/contrib/plugins/cache.c b/contrib/plugins/cache.c
index 56508587d3..26185c52b0 100644
--- a/contrib/plugins/cache.c
+++ b/contrib/plugins/cache.c
@@ -7,10 +7,64 @@
 
 #include <inttypes.h>
 #include <stdio.h>
+#include <unistd.h>
 #include <glib.h>
+#include <sys/socket.h>
+#include <pthread.h>
+#include <arpa/inet.h>
 
 #include <qemu-plugin.h>
 
+/* ? Where to put a header with this stuff that the CHMU and plugin need? */
+#define HOTNESS_SERVER_PORT 4443
+enum consumer_request {
+    QUERY_TAIL,
+    QUERY_HEAD,
+    SET_THRESHOLD,
+    SET_HEAD,
+    SET_HOTLIST_SIZE,
+    QUERY_HOTLIST_ENTRY,
+    SIGNAL_EPOCH_END,
+    SET_ENABLED,
+    SET_GRANUAL_SIZE, /* Granualrity of DPA blocks to track (1 << unit size) */
+    SET_HPA_BASE,
+    SET_HPA_SIZE,
+    SET_DPA_BASE,
+    SET_INTERLEAVE_WAYS,
+    SET_INTERLEAVE_WAY,
+    SET_INTERLEAVE_GRAN,
+};
+
+#define HOTNESS_NUM_RANGES 8
+struct tracking_instance {
+    /*
+     * Some checks are done first without lock and then repeated with
+     * lock to avoid contention. (TODO show that matters)
+     */
+    pthread_mutex_t lock;
+    struct tracking_range {
+        uint64_t base;
+        uint64_t size;
+        uint64_t dpa_offset;
+        uint8_t ways;
+        uint8_t way;
+        uint64_t interleave_granual;
+    } ranges[HOTNESS_NUM_RANGES];
+    uint16_t head, tail;
+    uint32_t granual_size;
+    uint16_t hotlist_length;
+    uint64_t threshold;
+    uint64_t *hotlist;
+    uint32_t *counters;
+    size_t num_counters;
+    bool enabled;
+};
+
+#define MAX_INSTANCES 16
+pthread_mutex_t instances_lock;
+static int num_tracking_instances;
+static struct tracking_instance *instances[MAX_INSTANCES] = {};
+
 #define STRTOLL(x) g_ascii_strtoll(x, NULL, 10)
 
 QEMU_PLUGIN_EXPORT int qemu_plugin_version = QEMU_PLUGIN_VERSION;
@@ -104,6 +158,7 @@ static Cache **l2_ucaches;
 static GMutex *l1_dcache_locks;
 static GMutex *l1_icache_locks;
 static GMutex *l2_ucache_locks;
+static GMutex *socket_lock;
 
 static uint64_t l1_dmem_accesses;
 static uint64_t l1_imem_accesses;
@@ -385,6 +440,80 @@ static bool access_cache(Cache *cache, uint64_t addr)
     return false;
 }
 
+static bool match_range(struct tracking_range *range, uint64_t paddr)
+{
+    uint64_t offset;
+
+    if (!range->size ||
+        paddr < range->base ||
+        paddr >= range->base + range->size / range->ways) {
+        return false;
+    }
+    if (range->ways == 0 || range->ways == 1) { /* no interleave */
+        return true;
+    }
+
+    /* Offset in granuals */
+    offset = (paddr - range->base) / range->interleave_granual;
+    if (offset % range->ways != range->way) {
+        return false;
+    }
+    return true;
+}
+
+/* Under instance lock */
+static void notify_tracker(struct tracking_instance *inst, uint64_t paddr)
+{
+    uint64_t offset;
+    int i;
+
+    /*
+     * This check may be wrong if racing with enabled, but
+     * we don't use the data until we have the lock and recheck.
+     * If we drop an access due to a race on an enable/disable/enable
+     * then meh.
+     */
+    for (i = 0; i < HOTNESS_NUM_RANGES; i++) {
+        if (!match_range(&inst->ranges[i], paddr)) {
+            continue;
+        }
+        break;
+    }
+    if (i == HOTNESS_NUM_RANGES) {
+        return;
+    }
+
+    pthread_mutex_lock(&inst->lock);
+    /* recheck under the lock */
+    if (!inst->enabled || !inst->counters ||
+        !match_range(&inst->ranges[i], paddr)) {
+        goto err;
+    }
+
+    offset = (paddr - inst->ranges[i].base + inst->ranges[i].dpa_offset) /
+        (inst->granual_size * inst->ranges[i].ways);
+
+    /*  TODO - check masking */
+    if (offset >= inst->num_counters) {
+        fprintf(stderr, "Out of range? %lx %lx\n", offset, inst->num_counters);
+        goto err;
+    }
+    inst->counters[offset]++;
+ err:
+    pthread_mutex_unlock(&inst->lock);
+}
+
+static void miss(uint64_t paddr)
+{
+    int i;
+
+    for (i = 0; i < num_tracking_instances; i++) {
+        if (instances[i]->enabled) {
+            notify_tracker(instances[i], paddr);
+        }
+    }
+}
+
 static void vcpu_mem_access(unsigned int vcpu_index, qemu_plugin_meminfo_t info,
                             uint64_t vaddr, void *userdata)
 {
@@ -395,9 +524,6 @@ static void vcpu_mem_access(unsigned int vcpu_index, qemu_plugin_meminfo_t info,
     bool hit_in_l1;
 
     hwaddr = qemu_plugin_get_hwaddr(info, vaddr);
-    if (hwaddr && qemu_plugin_hwaddr_is_io(hwaddr)) {
-        return;
-    }
 
     effective_addr = hwaddr ? qemu_plugin_hwaddr_phys_addr(hwaddr) : vaddr;
     cache_idx = vcpu_index % cores;
@@ -412,7 +538,11 @@ static void vcpu_mem_access(unsigned int vcpu_index, qemu_plugin_meminfo_t info,
     l1_dcaches[cache_idx]->accesses++;
     g_mutex_unlock(&l1_dcache_locks[cache_idx]);
 
-    if (hit_in_l1 || !use_l2) {
+    if (hit_in_l1) {
+        return;
+    }
+    if (!use_l2) {
+        miss(effective_addr);
         /* No need to access L2 */
         return;
     }
@@ -422,6 +552,7 @@ static void vcpu_mem_access(unsigned int vcpu_index, qemu_plugin_meminfo_t info,
         insn = userdata;
         __atomic_fetch_add(&insn->l2_misses, 1, __ATOMIC_SEQ_CST);
         l2_ucaches[cache_idx]->misses++;
+        miss(effective_addr);
     }
     l2_ucaches[cache_idx]->accesses++;
     g_mutex_unlock(&l2_ucache_locks[cache_idx]);
@@ -447,8 +578,12 @@ static void vcpu_insn_exec(unsigned int vcpu_index, void *userdata)
     l1_icaches[cache_idx]->accesses++;
     g_mutex_unlock(&l1_icache_locks[cache_idx]);
 
-    if (hit_in_l1 || !use_l2) {
-        /* No need to access L2 */
+    if (hit_in_l1) {
+        return;
+    }
+
+    if (!use_l2) {
+        miss(insn_addr);
         return;
     }
 
@@ -735,15 +870,286 @@ static void policy_init(void)
     }
 }
 
+static int register_tracker(struct tracking_instance *inst)
+{
+    pthread_mutex_lock(&instances_lock);
+    if (num_tracking_instances >= MAX_INSTANCES) {
+        pthread_mutex_unlock(&instances_lock);
+        return -1;
+    }
+    instances[num_tracking_instances++] = inst;
+    pthread_mutex_unlock(&instances_lock);
+
+    return 0;
+}
+
+/* Per hotness monitoring unit thread */
+static void *consumer_innerloop(void *_socket)
+{
+    int socket = *(int *)_socket;
+    struct tracking_instance inst = {};
+    /* Instance, command, parameter, parameter2 */
+    uint64_t paddr[4];
+    int rc;
+
+    pthread_mutex_init(&inst.lock, NULL);
+
+    /* For now only handle a single instance per block */
+    rc = register_tracker(&inst);
+    if (rc) {
+        fprintf(stderr, "Failed to register tracker\n");
+        return NULL;
+    }
+
+    while (1) {
+        uint64_t reply, param, param2;
+        enum consumer_request request;
+
+        rc = read(socket, paddr, sizeof(paddr));
+        if (rc < sizeof(paddr)) {
+            fprintf(stderr, "short message %x\n", rc);
+            continue;
+        }
+        if (paddr[0] > 0) {
+            fprintf(stderr, "Instance out of range\n");
+            continue;
+        }
+        request = paddr[1];
+        param = paddr[2];
+        param2 = paddr[3];
+
+        pthread_mutex_lock(&inst.lock);
+        switch (request) {
+        case QUERY_TAIL:
+            reply = inst.tail;
+            break;
+        case QUERY_HEAD:
+            reply = inst.head;
+            break;
+        case SET_HEAD:
+            reply = param;
+            inst.head = param;
+            break;
+        case SET_HOTLIST_SIZE: {
+            uint64_t *newlist;
+
+            reply = param;
+            inst.hotlist_length = param;
+            newlist = realloc(inst.hotlist, sizeof(*inst.hotlist) * param);
+            if (!newlist) {
+                fprintf(stderr, "failed to allocate hotlist\n");
+                break;
+            }
+            inst.hotlist = newlist;
+            break;
+        }
+        case QUERY_HOTLIST_ENTRY:
+            if (param >= inst.hotlist_length) {
+                fprintf(stderr, "out of range hotlist read?\n");
+                break;
+            }
+            reply = inst.hotlist[param];
+            break;
+        case SIGNAL_EPOCH_END: {
+            int space;
+            int added = 0;
+            int max = 0;
+
+            reply = param;
+
+            /* Head is read location, tail write */
+            /* If the rdad location is after the tail then gap */
+            if (inst.head > inst.tail) {
+                space = inst.head - inst.tail - 1;
+            } else {
+                space = inst.hotlist_length - inst.tail + inst.head - 1;
+            }
+            printf("Epoch end, space %d given %d %d %d\n",
+                   space, inst.hotlist_length, inst.head, inst.tail);
+            if (!inst.counters) {
+                fprintf(stderr,
+                        "How did we reach end of an epoque without counters?\n");
+                break;
+            }
+            for (int i = 0; i < inst.num_counters; i++) {
+                /*
+                 * This helps tune tests - unfortunately no such thing in the
+                 * CXL spec
+                 */
+                if (inst.counters[i] > max) {
+                    max = inst.counters[i];
+                }
+                if (!(inst.counters[i] > inst.threshold)) {
+                    continue;
+                }
+                inst.hotlist[inst.tail] = (uint64_t)inst.counters[i] |
+                    ((uint64_t)i << 32);
+                inst.tail = (inst.tail + 1) % inst.hotlist_length;
+                added++;
+                if (added == space) {
+                    break;
+                }
+            }
+            memset(inst.counters, 0,
+                   inst.num_counters * sizeof(*inst.counters));
+
+            printf("End of epoch %u %u %d\n", inst.head, inst.tail, max);
+            break;
+        }
+        case SET_ENABLED:
+            reply = param;
+            if (param && !inst.enabled) {
+                uint32_t *new_counters;
+                uint32_t num_counters;
+                uint64_t full_range = 0;
+                int i;
+
+                for (i = 0; i < HOTNESS_NUM_RANGES; i++) {
+                    uint64_t end;
+
+                    /* Skip disabled ranges */
+                    if (inst.ranges[i].size == 0 || inst.ranges[i].ways == 0) {
+                        continue;
+                    }
+                    end = inst.ranges[i].dpa_offset +
+                        inst.ranges[i].size / inst.ranges[i].ways;
+                    if (end > full_range) {
+                        full_range = end;
+                    }
+                }
+                num_counters = full_range / inst.granual_size;
+                new_counters = realloc(inst.counters,
+                                      sizeof(*inst.counters) * num_counters);
+                if (!new_counters) {
+                    fprintf(stderr, "Failed to allocate counter storage\n");
+                    break;
+                }
+                inst.counters = new_counters;
+                inst.num_counters = num_counters;
+            }
+            inst.enabled = !!param;
+            break;
+        case SET_THRESHOLD:
+            reply = param;
+            if (!inst.enabled) {
+                inst.threshold = param;
+            }
+            break;
+        case SET_GRANUAL_SIZE:
+            reply = param;
+            if (!inst.enabled) {
+                inst.granual_size = param;
+            }
+            break;
+        case SET_HPA_BASE:
+            reply = param;
+            if (!inst.enabled) {
+                inst.ranges[param2].base = param;
+            }
+            break;
+        case SET_HPA_SIZE:
+            reply = param;
+            if (!inst.enabled) {
+                inst.ranges[param2].size = param;
+            }
+            break;
+        case SET_DPA_BASE:
+            reply = param;
+            if (!inst.enabled) {
+                inst.ranges[param2].dpa_offset = param;
+            }
+            break;
+        case SET_INTERLEAVE_WAYS:
+            reply = param;
+            if (!inst.enabled) {
+                inst.ranges[param2].ways = param;
+            }
+            break;
+        case SET_INTERLEAVE_WAY:
+            reply = param;
+            if (!inst.enabled) {
+                inst.ranges[param2].way = param;
+            }
+            break;
+        case SET_INTERLEAVE_GRAN:
+            reply = param;
+            if (!inst.enabled) {
+                inst.ranges[param2].interleave_granual = param;
+            }
+            break;
+        default:
+            fprintf(stderr, "Unexpected command to hotness monitor\n");
+            break;
+        }
+        rc = write(socket, &reply, sizeof(reply));
+        if (rc < 0) {
+            fprintf(stderr, "write failed - muddle on\n");
+        } else if (rc != sizeof(reply)) {
+            fprintf(stderr, "partial write? %d\n", rc);
+        }
+        pthread_mutex_unlock(&inst.lock);
+    }
+}
+
+/* Outer thread that is responsible for spinning off individual server thread */
+static void *hotness_serverloop(void *private)
+{
+    int server_fd, new_socket;
+    int opt = 1;
+    struct sockaddr_in address;
+    socklen_t addrlen = sizeof(address);
+    int rc;
+
+    server_fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
+    if (server_fd == 0) {
+        return NULL;
+    }
+
+    if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT,
+                   &opt, sizeof(opt))) {
+        return NULL;
+    }
+
+    address.sin_family = AF_INET;
+    address.sin_addr.s_addr = INADDR_ANY;
+    address.sin_port = htons(HOTNESS_SERVER_PORT);
+
+    rc = bind(server_fd, (struct sockaddr *)&address, sizeof(address));
+    if (rc < 0) {
+        return NULL;
+    }
+
+    if (listen(server_fd, 3) < 0) {
+        return NULL;
+    }
+
+    while (1) {
+        pthread_t thread;
+
+        new_socket = accept(server_fd, (struct sockaddr *)&address, &addrlen);
+        if (new_socket < 0) {
+            return NULL;
+        }
+
+        if (pthread_create(&thread, NULL, consumer_innerloop, &new_socket)) {
+            fprintf(stderr, "thread create fail\n");
+            return NULL;
+        }
+    }
+
+    return NULL;
+}
+
 QEMU_PLUGIN_EXPORT
 int qemu_plugin_install(qemu_plugin_id_t id, const qemu_info_t *info,
                         int argc, char **argv)
 {
-    int i;
+    int i, hotness;
     int l1_iassoc, l1_iblksize, l1_icachesize;
     int l1_dassoc, l1_dblksize, l1_dcachesize;
     int l2_assoc, l2_blksize, l2_cachesize;
 
+    hotness = 0; /* No hotness server */
     limit = 32;
     sys = info->system_emulation;
 
@@ -808,6 +1214,8 @@ int qemu_plugin_install(qemu_plugin_id_t id, const qemu_info_t *info,
                 fprintf(stderr, "invalid eviction policy: %s\n", opt);
                 return -1;
             }
+        } else if (g_strcmp0(tokens[0], "hotness") == 0) {
+            hotness = STRTOLL(tokens[1]);
         } else {
             fprintf(stderr, "option parsing failed: %s\n", opt);
             return -1;
@@ -840,6 +1248,8 @@ int qemu_plugin_install(qemu_plugin_id_t id, const qemu_info_t *info,
         return -1;
     }
 
+    socket_lock = g_new0(GMutex, 1);
+
     l1_dcache_locks = g_new0(GMutex, cores);
     l1_icache_locks = g_new0(GMutex, cores);
     l2_ucache_locks = use_l2 ? g_new0(GMutex, cores) : NULL;
@@ -849,5 +1259,15 @@ int qemu_plugin_install(qemu_plugin_id_t id, const qemu_info_t *info,
 
     miss_ht = g_hash_table_new_full(g_int64_hash, g_int64_equal, NULL, insn_free);
 
+    if (hotness) {
+        pthread_t server_thread;
+
+        pthread_mutex_init(&instances_lock, NULL);
+        if (pthread_create(&server_thread, NULL, hotness_serverloop, NULL)) {
+            fprintf(stderr, "Hotness server failed\n");
+            return -1;
+        }
+    }
+
     return 0;
 }
-- 
2.48.1



^ permalink raw reply related	[flat|nested] 6+ messages in thread

* Re: [RFC PATCH v2 QEMU 2/4] hw/cxl: Add emulation of a CXL Hotness Monitoring Unit (CHMU)
  2025-06-12 15:57 ` [RFC PATCH v2 QEMU 2/4] hw/cxl: Add emulation of a CXL Hotness Monitoring Unit (CHMU) Jonathan Cameron via
@ 2025-06-19 10:55   ` Jonathan Cameron via
  0 siblings, 0 replies; 6+ messages in thread
From: Jonathan Cameron via @ 2025-06-19 10:55 UTC (permalink / raw)
  To: Pierrick Bouvier, fan.ni, qemu-devel, Alex Bennée,
	Alexandre Iooss, Mahmoud Mandour, Bowman Terry
  Cc: linuxarm, rientjes, dave, joshua.hahnjy, rkodsara, sj,
	xuezhengchu, yiannis, ziy, weixugc, Bharata B Rao

On Thu, 12 Jun 2025 16:57:22 +0100
Jonathan Cameron <Jonathan.Cameron@huawei.com> wrote:

> CXL r3.2 defines a CXL Hotness Monitoring Unit. This allows for a CXL
> device to do on device estimation of which 'granuals' of data are 'hot'
> - that is accessed a lot. For a typical application hot data on a CXL
> device both wastes potentially limited bandwidth and many have latency
> impacts. Access counts are therefore a measurable proxy on which to base
> memory placement decisions.
> 
> Typical use cases include:
> 1 - Establishing which data to move to faster RAM in a tiered memory
>     system. Discussions on how to do this in Linux are ongoing so likely
>     use case 2 will happen first.
> 2 - Provide detailed data (at low overhead) on what memory in an
>     application is hot, allowing for optimization of initial data
>     placement on future runs fo the application.
> 
> The focus of this emulation is providing a way to capture 'real' data
> in order to help us develop and tune the kernel stack.
> 
> This emulated device will be fed with data from a QEMU plugin. That
> plugin is responsible for the actual tracking and counting part of
> hotness tracking. This device simply provides a timebase (epoch end
> point) along with configuration and data retrieval.
> 
> The connection to the QEMU plugin providing the data is via a sockets.
> Supply the cxl-type3 device parameter chmu-port=4443 to specify the
> network port as 4443 and ensure the plugin is loaded (see later patch).
> 
> Signed-off-by: Jonathan Cameron <Jonathan.Cameron@huawei.com>

Although we don't support downsampling the device is currently reporting that
it does by a factor of 2.  Fix below.

Yuquan Wang ran into some related problems.
Note that with current posted rfc1 kernel driver best bet is just
don't specify the downsampling parameter at all. It will default to none
and doesn't run into the bug here (as we never check the value if not specified).

Whilst testing this I did manage to trigger a double free in qemu, but
I haven't replicated that yet. 

Jonathan


> ---

> +static uint64_t chmu_read(void *opaque, hwaddr offset, unsigned size)
> +{
> +    const hwaddr chmu_stride = A_CXL_CHMU1_CAP0 - A_CXL_CHMU0_CAP0;
> +    CHMUState *chmu = opaque;
> +    CHMUInstance *chmui;
> +    uint64_t val = 0;
> +    int instance = 0;
> +    int rc;
> +
> +    if (offset >= A_CXL_CHMU0_CAP0) {
> +        instance = (offset - A_CXL_CHMU0_CAP0) / chmu_stride;
> +        /*
> +         * Offset allows register defs for CHMU instance 0 to be used
> +         * for all instances. Includes COMMON_CAP.
> +         */
> +        offset -= chmu_stride * instance;
> +    }
> +
> +    if (instance >= CXL_CHMU_INSTANCES_PER_BLOCK) {
> +        return 0;
> +    }
> +
> +    chmui = &chmu->inst[instance];
> +    switch (offset) {
> +    case A_CXL_CHMU_COMMON_CAP0:
> +        val = FIELD_DP64(val, CXL_CHMU_COMMON_CAP0, VERSION, 1);
> +        val = FIELD_DP64(val, CXL_CHMU_COMMON_CAP0, NUM_INSTANCES,
> +                         CXL_CHMU_INSTANCES_PER_BLOCK);
> +        break;
> +    case A_CXL_CHMU_COMMON_CAP1:
> +        val = FIELD_DP64(val, CXL_CHMU_COMMON_CAP1, INSTANCE_LENGTH,
> +                         A_CXL_CHMU1_CAP0 - A_CXL_CHMU0_CAP0);
> +        break;
> +    case A_CXL_CHMU0_CAP0:
> +        val = FIELD_DP64(val, CXL_CHMU0_CAP0, MSI_N, chmui->msi_n);
> +        val = FIELD_DP64(val, CXL_CHMU0_CAP0, OVERFLOW_INT, 1);
> +        val = FIELD_DP64(val, CXL_CHMU0_CAP0, LEVEL_INT, 1);
> +        val = FIELD_DP64(val, CXL_CHMU0_CAP0, EPOCH_TYPE,
> +                         CXL_CHMU0_CAP0_EPOCH_TYPE_GLOBAL);
> +        val = FIELD_DP64(val, CXL_CHMU0_CAP0, TRACKED_M2S_REQ_NONTEE_R, 1);
> +        val = FIELD_DP64(val, CXL_CHMU0_CAP0, TRACKED_M2S_REQ_NONTEE_W, 1);
> +        val = FIELD_DP64(val, CXL_CHMU0_CAP0, TRACKED_M2S_REQ_NONTEE_RW, 1);
> +        /* No emulation of TEE modes yet so don't pretend to support them */
> +
> +        /* Epoch length from 100 milliseconds to 100 second */
> +        val = FIELD_DP64(val, CXL_CHMU0_CAP0, MAX_EPOCH_LENGTH_SCALE,
> +                         CXL_CHMU_EPOCH_LENGTH_SCALE_1SEC);
> +        val = FIELD_DP64(val, CXL_CHMU0_CAP0, MAX_EPOCH_LENGTH_VAL, 100);
> +        val = FIELD_DP64(val, CXL_CHMU0_CAP0, MIN_EPOCH_LENGTH_SCALE,
> +                         CXL_CHMU_EPOCH_LENGTH_SCALE_100MSEC);
> +        val = FIELD_DP64(val, CXL_CHMU0_CAP0, MIN_EPOCH_LENGTH_VAL, 1);
> +        val = FIELD_DP64(val, CXL_CHMU0_CAP0, HOTLIST_SIZE,
> +                         CXL_HOTLIST_ENTRIES);
> +        break;
> +    case A_CXL_CHMU0_CAP1:
> +        /* 4KiB and 8KiB only - 2^N * 256 for each bit set */
> +        val = FIELD_DP64(val, CXL_CHMU0_CAP1, UNIT_SIZES, BIT(4) | BIT(5));
> +        /* No downsampling  - 2^(N - 1) for each bit set */

This spec is a bit confusing around this, but I think it is 2^N

> +        val = FIELD_DP64(val, CXL_CHMU0_CAP1, DOWN_SAMPLING_FACTORS, BIT(1));
Hence BIT(0) is appropriate here for no support (full rate only)

> +        val = FIELD_DP64(val, CXL_CHMU0_CAP1, FLAGS_EPOCH_BASED, 1);
> +        val = FIELD_DP64(val, CXL_CHMU0_CAP1, FLAGS_ALWAYS_ON, 0);
> +        val = FIELD_DP64(val, CXL_CHMU0_CAP1, FLAGS_RANDOMIZED_DOWN_SAMPLING,
> +                         1);
> +        val = FIELD_DP64(val, CXL_CHMU0_CAP1, FLAGS_OVERLAPPING_ADDRESS_RANGES,
> +                         1);
> +        /*
> +         * Feature to enable a backlog of entries that immediately fill the list
> +         * once space is available. Only relevant if reading list infrequently
> +         * and concerned about stale data. (Not implemented)
> +         */
> +        val = FIELD_DP64(val, CXL_CHMU0_CAP1, FLAGS_INSERT_AFTER_CLEAR, 0);
> +        break;
> +    case A_CXL_CHMU0_CAP2:
> +        val = FIELD_DP64(val, CXL_CHMU0_CAP2, BITMAP_REG_OFFSET,
> +                         A_CXL_CHMU0_RANGE_CONFIG_BITMAP0 - A_CXL_CHMU0_CAP0);
> +        break;
> +    case A_CXL_CHMU0_CAP3:
> +        val = FIELD_DP64(val, CXL_CHMU0_CAP3, HOTLIST_REG_OFFSET,
> +                         A_CXL_CHMU0_HOTLIST0 - A_CXL_CHMU0_CAP0);
> +        break;
> +    case A_CXL_CHMU0_STATUS:
> +        val = FIELD_DP64(val, CXL_CHMU0_STATUS, STATUS_ENABLED,
> +                         chmui->enabled ? 1 : 0);
> +        val = FIELD_DP64(val, CXL_CHMU0_STATUS, OPERATION_IN_PROG,
> +                         0); /* All operations effectively instantaneous */
> +        val = FIELD_DP64(val, CXL_CHMU0_STATUS, COUNTER_WIDTH, 16);
> +        val = FIELD_DP64(val, CXL_CHMU0_STATUS, OVERFLOW_INT,
> +                         chmui->overflow_set ? 1 : 0);
> +        val = FIELD_DP64(val, CXL_CHMU0_STATUS, LEVEL_INT,
> +                         chmui->fill_thresh_set ? 1 : 0);
> +        break;
> +    case A_CXL_CHMU0_CONF0:
> +        val = FIELD_DP64(val, CXL_CHMU0_CONF0, M2S_REQ_TO_TRACK, chmui->what);
> +        val = FIELD_DP64(val, CXL_CHMU0_CONF0, FLAGS_RANDOMIZE_DOWNSAMPLING, 0);

We hard code the value 0 here which corresponds to BIT(0) above.

> +        val = FIELD_DP64(val, CXL_CHMU0_CONF0, FLAGS_INT_ON_OVERFLOW,
> +                         chmui->int_on_overflow);
> +        val = FIELD_DP64(val, CXL_CHMU0_CONF0, FLAGS_INT_ON_FILL_THRESH,
> +                         chmui->int_on_fill_thresh);
> +        val = FIELD_DP64(val, CXL_CHMU0_CONF0, CONTROL_ENABLE,
> +                         chmui->enabled);
> +        val = FIELD_DP64(val, CXL_CHMU0_CONF0, CONTROL_RESET, 0);
> +        val = FIELD_DP64(val, CXL_CHMU0_CONF0, HOTNESS_THRESHOLD,
> +                         chmui->hotness_thresh);
> +        break;



^ permalink raw reply	[flat|nested] 6+ messages in thread

end of thread, other threads:[~2025-06-19 10:56 UTC | newest]

Thread overview: 6+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2025-06-12 15:57 [RFC PATCH v2 QEMU 0/4] cxl/plugins: Hotness Monitoring Unit with 'real' data Jonathan Cameron via
2025-06-12 15:57 ` [RFC PATCH v2 QEMU 1/4] hw/cxl: Switch to using an array for CXLRegisterLocator base addresses Jonathan Cameron via
2025-06-12 15:57 ` [RFC PATCH v2 QEMU 2/4] hw/cxl: Add emulation of a CXL Hotness Monitoring Unit (CHMU) Jonathan Cameron via
2025-06-19 10:55   ` Jonathan Cameron via
2025-06-12 15:57 ` [RFC PATCH v2 QEMU 3/4] hw/cxl: Provide a means to get the interleave set position for an EP Jonathan Cameron via
2025-06-12 15:57 ` [RFC PATCH v2 QEMU 4/4] plugins: cache: Add a hotness tracker for cache misses with socket connection to device emulation Jonathan Cameron via

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.