mirror of
https://github.com/qemu/qemu.git
synced 2025-08-10 12:46:22 +00:00

Previously these somewhat device like structures were tracked using a list in the CXLState in each machine. This is proving restrictive in a few cases where we need to iterate through these without being aware of the machine type. Just make them sysbus devices. Restrict them to not user created as they need to be visible to early stages of machine init given effects on the memory map. This change both simplifies state tracking and enables features needed for performance optimization and hotness tracking by making it possible to retrieve the fixed memory window on actions elsewhere in the topology. In some cases the ordering of the Fixed Memory Windows matters. For those utility functions provide a GSList sorted by the window index. This ensures that we get consistency across: - ordering in the command line - ordering of the host PA ranges - ordering of ACPI CEDT structures describing the CFMWS. Other aspects don't have this constraint. For those direct iteration of the underlying hash structures is fine. In the setup path for the memory map in pc_memory_init() split the operations into two calls. The first, cxl_fmws_set_mmemap(), loops over fixed memory windows in order and assigns their addresses. The second, cxl_fmws_update_mmio() actually sets up the mmio for each window. This is obviously less efficient than a single loop but this split design is needed to put the logic in two different places in the arm64 support and it is not a hot enough path to justify an x86 only implementation. Reviewed-by: Li Zhijian <lizhijian@fujitsu.com> Tested-by: Li Zhijian <lizhijian@fujitsu.com> Signed-off-by: Jonathan Cameron <Jonathan.Cameron@huawei.com> Tested-by: Itaru Kitayama <itaru.kitayama@fujitsu.com> Message-id: 20250703104110.992379-3-Jonathan.Cameron@huawei.com Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
487 lines
13 KiB
C
487 lines
13 KiB
C
/*
|
|
* CXL host parameter parsing routines
|
|
*
|
|
* Copyright (c) 2022 Huawei
|
|
* Modeled loosely on the NUMA options handling in hw/core/numa.c
|
|
*/
|
|
|
|
#include "qemu/osdep.h"
|
|
#include "qemu/units.h"
|
|
#include "qemu/bitmap.h"
|
|
#include "qemu/error-report.h"
|
|
#include "qapi/error.h"
|
|
#include "system/qtest.h"
|
|
#include "hw/boards.h"
|
|
|
|
#include "qapi/qapi-visit-machine.h"
|
|
#include "hw/cxl/cxl.h"
|
|
#include "hw/cxl/cxl_host.h"
|
|
#include "hw/pci/pci_bus.h"
|
|
#include "hw/pci/pci_bridge.h"
|
|
#include "hw/pci/pci_host.h"
|
|
#include "hw/pci/pcie_port.h"
|
|
#include "hw/pci-bridge/pci_expander_bridge.h"
|
|
|
|
static void cxl_fixed_memory_window_config(CXLFixedMemoryWindowOptions *object,
|
|
int index, Error **errp)
|
|
{
|
|
ERRP_GUARD();
|
|
DeviceState *dev = qdev_new(TYPE_CXL_FMW);
|
|
CXLFixedWindow *fw = CXL_FMW(dev);
|
|
strList *target;
|
|
int i;
|
|
|
|
fw->index = index;
|
|
|
|
for (target = object->targets; target; target = target->next) {
|
|
fw->num_targets++;
|
|
}
|
|
|
|
fw->enc_int_ways = cxl_interleave_ways_enc(fw->num_targets, errp);
|
|
if (*errp) {
|
|
return;
|
|
}
|
|
|
|
if (object->size % (256 * MiB)) {
|
|
error_setg(errp,
|
|
"Size of a CXL fixed memory window must be a multiple of 256MiB");
|
|
return;
|
|
}
|
|
fw->size = object->size;
|
|
|
|
if (object->has_interleave_granularity) {
|
|
fw->enc_int_gran =
|
|
cxl_interleave_granularity_enc(object->interleave_granularity,
|
|
errp);
|
|
if (*errp) {
|
|
return;
|
|
}
|
|
} else {
|
|
/* Default to 256 byte interleave */
|
|
fw->enc_int_gran = 0;
|
|
}
|
|
|
|
fw->targets = g_malloc0_n(fw->num_targets, sizeof(*fw->targets));
|
|
for (i = 0, target = object->targets; target; i++, target = target->next) {
|
|
/* This link cannot be resolved yet, so stash the name for now */
|
|
fw->targets[i] = g_strdup(target->value);
|
|
}
|
|
|
|
sysbus_realize_and_unref(SYS_BUS_DEVICE(dev), errp);
|
|
}
|
|
|
|
static int cxl_fmws_link(Object *obj, void *opaque)
|
|
{
|
|
struct CXLFixedWindow *fw;
|
|
int i;
|
|
|
|
if (!object_dynamic_cast(obj, TYPE_CXL_FMW)) {
|
|
return 0;
|
|
}
|
|
fw = CXL_FMW(obj);
|
|
|
|
for (i = 0; i < fw->num_targets; i++) {
|
|
Object *o;
|
|
bool ambig;
|
|
|
|
o = object_resolve_path_type(fw->targets[i], TYPE_PXB_CXL_DEV,
|
|
&ambig);
|
|
if (!o) {
|
|
error_setg(&error_fatal, "Could not resolve CXLFM target %s",
|
|
fw->targets[i]);
|
|
return 1;
|
|
}
|
|
fw->target_hbs[i] = PXB_CXL_DEV(o);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
void cxl_fmws_link_targets(Error **errp)
|
|
{
|
|
/* Order doesn't matter for this, so no need to build list */
|
|
object_child_foreach_recursive(object_get_root(), cxl_fmws_link, NULL);
|
|
}
|
|
|
|
static bool cxl_hdm_find_target(uint32_t *cache_mem, hwaddr addr,
|
|
uint8_t *target)
|
|
{
|
|
int hdm_inc = R_CXL_HDM_DECODER1_BASE_LO - R_CXL_HDM_DECODER0_BASE_LO;
|
|
unsigned int hdm_count;
|
|
bool found = false;
|
|
int i;
|
|
uint32_t cap;
|
|
|
|
cap = ldl_le_p(cache_mem + R_CXL_HDM_DECODER_CAPABILITY);
|
|
hdm_count = cxl_decoder_count_dec(FIELD_EX32(cap,
|
|
CXL_HDM_DECODER_CAPABILITY,
|
|
DECODER_COUNT));
|
|
for (i = 0; i < hdm_count; i++) {
|
|
uint32_t ctrl, ig_enc, iw_enc, target_idx;
|
|
uint32_t low, high;
|
|
uint64_t base, size;
|
|
|
|
low = ldl_le_p(cache_mem + R_CXL_HDM_DECODER0_BASE_LO + i * hdm_inc);
|
|
high = ldl_le_p(cache_mem + R_CXL_HDM_DECODER0_BASE_HI + i * hdm_inc);
|
|
base = (low & 0xf0000000) | ((uint64_t)high << 32);
|
|
low = ldl_le_p(cache_mem + R_CXL_HDM_DECODER0_SIZE_LO + i * hdm_inc);
|
|
high = ldl_le_p(cache_mem + R_CXL_HDM_DECODER0_SIZE_HI + i * hdm_inc);
|
|
size = (low & 0xf0000000) | ((uint64_t)high << 32);
|
|
if (addr < base || addr >= base + size) {
|
|
continue;
|
|
}
|
|
|
|
ctrl = ldl_le_p(cache_mem + R_CXL_HDM_DECODER0_CTRL + i * hdm_inc);
|
|
if (!FIELD_EX32(ctrl, CXL_HDM_DECODER0_CTRL, COMMITTED)) {
|
|
return false;
|
|
}
|
|
found = true;
|
|
ig_enc = FIELD_EX32(ctrl, CXL_HDM_DECODER0_CTRL, IG);
|
|
iw_enc = FIELD_EX32(ctrl, CXL_HDM_DECODER0_CTRL, IW);
|
|
target_idx = (addr / cxl_decode_ig(ig_enc)) % (1 << iw_enc);
|
|
|
|
if (target_idx < 4) {
|
|
uint32_t val = ldl_le_p(cache_mem +
|
|
R_CXL_HDM_DECODER0_TARGET_LIST_LO +
|
|
i * hdm_inc);
|
|
*target = extract32(val, target_idx * 8, 8);
|
|
} else {
|
|
uint32_t val = ldl_le_p(cache_mem +
|
|
R_CXL_HDM_DECODER0_TARGET_LIST_HI +
|
|
i * hdm_inc);
|
|
*target = extract32(val, (target_idx - 4) * 8, 8);
|
|
}
|
|
break;
|
|
}
|
|
|
|
return found;
|
|
}
|
|
|
|
static PCIDevice *cxl_cfmws_find_device(CXLFixedWindow *fw, hwaddr addr)
|
|
{
|
|
CXLComponentState *hb_cstate, *usp_cstate;
|
|
PCIHostState *hb;
|
|
CXLUpstreamPort *usp;
|
|
int rb_index;
|
|
uint32_t *cache_mem;
|
|
uint8_t target;
|
|
bool target_found;
|
|
PCIDevice *rp, *d;
|
|
|
|
/* Address is relative to memory region. Convert to HPA */
|
|
addr += fw->base;
|
|
|
|
rb_index = (addr / cxl_decode_ig(fw->enc_int_gran)) % fw->num_targets;
|
|
hb = PCI_HOST_BRIDGE(fw->target_hbs[rb_index]->cxl_host_bridge);
|
|
if (!hb || !hb->bus || !pci_bus_is_cxl(hb->bus)) {
|
|
return NULL;
|
|
}
|
|
|
|
if (cxl_get_hb_passthrough(hb)) {
|
|
rp = pcie_find_port_first(hb->bus);
|
|
if (!rp) {
|
|
return NULL;
|
|
}
|
|
} else {
|
|
hb_cstate = cxl_get_hb_cstate(hb);
|
|
if (!hb_cstate) {
|
|
return NULL;
|
|
}
|
|
|
|
cache_mem = hb_cstate->crb.cache_mem_registers;
|
|
|
|
target_found = cxl_hdm_find_target(cache_mem, addr, &target);
|
|
if (!target_found) {
|
|
return NULL;
|
|
}
|
|
|
|
rp = pcie_find_port_by_pn(hb->bus, target);
|
|
if (!rp) {
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
d = pci_bridge_get_sec_bus(PCI_BRIDGE(rp))->devices[0];
|
|
if (!d) {
|
|
return NULL;
|
|
}
|
|
|
|
if (object_dynamic_cast(OBJECT(d), TYPE_CXL_TYPE3)) {
|
|
return d;
|
|
}
|
|
|
|
/*
|
|
* Could also be a switch. Note only one level of switching currently
|
|
* supported.
|
|
*/
|
|
if (!object_dynamic_cast(OBJECT(d), TYPE_CXL_USP)) {
|
|
return NULL;
|
|
}
|
|
usp = CXL_USP(d);
|
|
|
|
usp_cstate = cxl_usp_to_cstate(usp);
|
|
if (!usp_cstate) {
|
|
return NULL;
|
|
}
|
|
|
|
cache_mem = usp_cstate->crb.cache_mem_registers;
|
|
|
|
target_found = cxl_hdm_find_target(cache_mem, addr, &target);
|
|
if (!target_found) {
|
|
return NULL;
|
|
}
|
|
|
|
d = pcie_find_port_by_pn(&PCI_BRIDGE(d)->sec_bus, target);
|
|
if (!d) {
|
|
return NULL;
|
|
}
|
|
|
|
d = pci_bridge_get_sec_bus(PCI_BRIDGE(d))->devices[0];
|
|
if (!d) {
|
|
return NULL;
|
|
}
|
|
|
|
if (!object_dynamic_cast(OBJECT(d), TYPE_CXL_TYPE3)) {
|
|
return NULL;
|
|
}
|
|
|
|
return d;
|
|
}
|
|
|
|
static MemTxResult cxl_read_cfmws(void *opaque, hwaddr addr, uint64_t *data,
|
|
unsigned size, MemTxAttrs attrs)
|
|
{
|
|
CXLFixedWindow *fw = opaque;
|
|
PCIDevice *d;
|
|
|
|
d = cxl_cfmws_find_device(fw, addr);
|
|
if (d == NULL) {
|
|
*data = 0;
|
|
/* Reads to invalid address return poison */
|
|
return MEMTX_ERROR;
|
|
}
|
|
|
|
return cxl_type3_read(d, addr + fw->base, data, size, attrs);
|
|
}
|
|
|
|
static MemTxResult cxl_write_cfmws(void *opaque, hwaddr addr,
|
|
uint64_t data, unsigned size,
|
|
MemTxAttrs attrs)
|
|
{
|
|
CXLFixedWindow *fw = opaque;
|
|
PCIDevice *d;
|
|
|
|
d = cxl_cfmws_find_device(fw, addr);
|
|
if (d == NULL) {
|
|
/* Writes to invalid address are silent */
|
|
return MEMTX_OK;
|
|
}
|
|
|
|
return cxl_type3_write(d, addr + fw->base, data, size, attrs);
|
|
}
|
|
|
|
const MemoryRegionOps cfmws_ops = {
|
|
.read_with_attrs = cxl_read_cfmws,
|
|
.write_with_attrs = cxl_write_cfmws,
|
|
.endianness = DEVICE_LITTLE_ENDIAN,
|
|
.valid = {
|
|
.min_access_size = 1,
|
|
.max_access_size = 8,
|
|
.unaligned = true,
|
|
},
|
|
.impl = {
|
|
.min_access_size = 1,
|
|
.max_access_size = 8,
|
|
.unaligned = true,
|
|
},
|
|
};
|
|
|
|
static void machine_get_cxl(Object *obj, Visitor *v, const char *name,
|
|
void *opaque, Error **errp)
|
|
{
|
|
CXLState *cxl_state = opaque;
|
|
bool value = cxl_state->is_enabled;
|
|
|
|
visit_type_bool(v, name, &value, errp);
|
|
}
|
|
|
|
static void machine_set_cxl(Object *obj, Visitor *v, const char *name,
|
|
void *opaque, Error **errp)
|
|
{
|
|
CXLState *cxl_state = opaque;
|
|
bool value;
|
|
|
|
if (!visit_type_bool(v, name, &value, errp)) {
|
|
return;
|
|
}
|
|
cxl_state->is_enabled = value;
|
|
}
|
|
|
|
static void machine_get_cfmw(Object *obj, Visitor *v, const char *name,
|
|
void *opaque, Error **errp)
|
|
{
|
|
CXLState *state = opaque;
|
|
CXLFixedMemoryWindowOptionsList **list = &state->cfmw_list;
|
|
|
|
visit_type_CXLFixedMemoryWindowOptionsList(v, name, list, errp);
|
|
}
|
|
|
|
static void machine_set_cfmw(Object *obj, Visitor *v, const char *name,
|
|
void *opaque, Error **errp)
|
|
{
|
|
CXLState *state = opaque;
|
|
CXLFixedMemoryWindowOptionsList *cfmw_list = NULL;
|
|
CXLFixedMemoryWindowOptionsList *it;
|
|
int index;
|
|
|
|
visit_type_CXLFixedMemoryWindowOptionsList(v, name, &cfmw_list, errp);
|
|
if (!cfmw_list) {
|
|
return;
|
|
}
|
|
|
|
for (it = cfmw_list, index = 0; it; it = it->next, index++) {
|
|
cxl_fixed_memory_window_config(it->value, index, errp);
|
|
}
|
|
state->cfmw_list = cfmw_list;
|
|
}
|
|
|
|
void cxl_machine_init(Object *obj, CXLState *state)
|
|
{
|
|
object_property_add(obj, "cxl", "bool", machine_get_cxl,
|
|
machine_set_cxl, NULL, state);
|
|
object_property_set_description(obj, "cxl",
|
|
"Set on/off to enable/disable "
|
|
"CXL instantiation");
|
|
|
|
object_property_add(obj, "cxl-fmw", "CXLFixedMemoryWindow",
|
|
machine_get_cfmw, machine_set_cfmw,
|
|
NULL, state);
|
|
object_property_set_description(obj, "cxl-fmw",
|
|
"CXL Fixed Memory Windows (array)");
|
|
}
|
|
|
|
void cxl_hook_up_pxb_registers(PCIBus *bus, CXLState *state, Error **errp)
|
|
{
|
|
/* Walk the pci busses looking for pxb busses to hook up */
|
|
if (bus) {
|
|
QLIST_FOREACH(bus, &bus->child, sibling) {
|
|
if (!pci_bus_is_root(bus)) {
|
|
continue;
|
|
}
|
|
if (pci_bus_is_cxl(bus)) {
|
|
if (!state->is_enabled) {
|
|
error_setg(errp, "CXL host bridges present, but cxl=off");
|
|
return;
|
|
}
|
|
pxb_cxl_hook_up_registers(state, bus, errp);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static int cxl_fmws_find(Object *obj, void *opaque)
|
|
{
|
|
GSList **list = opaque;
|
|
|
|
if (!object_dynamic_cast(obj, TYPE_CXL_FMW)) {
|
|
return 0;
|
|
}
|
|
*list = g_slist_prepend(*list, obj);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static GSList *cxl_fmws_get_all(void)
|
|
{
|
|
GSList *list = NULL;
|
|
|
|
object_child_foreach_recursive(object_get_root(), cxl_fmws_find, &list);
|
|
|
|
return list;
|
|
}
|
|
|
|
static gint cfmws_cmp(gconstpointer a, gconstpointer b, gpointer d)
|
|
{
|
|
const struct CXLFixedWindow *ap = a;
|
|
const struct CXLFixedWindow *bp = b;
|
|
|
|
return ap->index > bp->index;
|
|
}
|
|
|
|
GSList *cxl_fmws_get_all_sorted(void)
|
|
{
|
|
return g_slist_sort_with_data(cxl_fmws_get_all(), cfmws_cmp, NULL);
|
|
}
|
|
|
|
static int cxl_fmws_mmio_map(Object *obj, void *opaque)
|
|
{
|
|
struct CXLFixedWindow *fw;
|
|
|
|
if (!object_dynamic_cast(obj, TYPE_CXL_FMW)) {
|
|
return 0;
|
|
}
|
|
fw = CXL_FMW(obj);
|
|
sysbus_mmio_map(SYS_BUS_DEVICE(fw), 0, fw->base);
|
|
|
|
return 0;
|
|
}
|
|
|
|
void cxl_fmws_update_mmio(void)
|
|
{
|
|
/* Ordering is not required for this */
|
|
object_child_foreach_recursive(object_get_root(), cxl_fmws_mmio_map, NULL);
|
|
}
|
|
|
|
hwaddr cxl_fmws_set_memmap(hwaddr base, hwaddr max_addr)
|
|
{
|
|
GSList *cfmws_list, *iter;
|
|
CXLFixedWindow *fw;
|
|
|
|
cfmws_list = cxl_fmws_get_all_sorted();
|
|
for (iter = cfmws_list; iter; iter = iter->next) {
|
|
fw = CXL_FMW(iter->data);
|
|
if (base + fw->size <= max_addr) {
|
|
fw->base = base;
|
|
base += fw->size;
|
|
}
|
|
}
|
|
g_slist_free(cfmws_list);
|
|
|
|
return base;
|
|
}
|
|
|
|
static void cxl_fmw_realize(DeviceState *dev, Error **errp)
|
|
{
|
|
CXLFixedWindow *fw = CXL_FMW(dev);
|
|
|
|
memory_region_init_io(&fw->mr, OBJECT(dev), &cfmws_ops, fw,
|
|
"cxl-fixed-memory-region", fw->size);
|
|
sysbus_init_mmio(SYS_BUS_DEVICE(dev), &fw->mr);
|
|
}
|
|
|
|
/*
|
|
* Note: Fixed memory windows represent fixed address decoders on the host and
|
|
* as such have no dynamic state to reset or migrate
|
|
*/
|
|
static void cxl_fmw_class_init(ObjectClass *klass, const void *data)
|
|
{
|
|
DeviceClass *dc = DEVICE_CLASS(klass);
|
|
|
|
dc->desc = "CXL Fixed Memory Window";
|
|
dc->realize = cxl_fmw_realize;
|
|
/* Reason - created by machines as tightly coupled to machine memory map */
|
|
dc->user_creatable = false;
|
|
}
|
|
|
|
static const TypeInfo cxl_fmw_info = {
|
|
.name = TYPE_CXL_FMW,
|
|
.parent = TYPE_SYS_BUS_DEVICE,
|
|
.instance_size = sizeof(CXLFixedWindow),
|
|
.class_init = cxl_fmw_class_init,
|
|
};
|
|
|
|
static void cxl_host_register_types(void)
|
|
{
|
|
type_register_static(&cxl_fmw_info);
|
|
}
|
|
type_init(cxl_host_register_types)
|