[PATCH 2/7] hw/pci-host: add basic Nuvoton PCIe window support

Yubin Zou posted 7 patches 2 weeks, 4 days ago
Maintainers: Paolo Bonzini <pbonzini@redhat.com>, Peter Maydell <peter.maydell@linaro.org>, Tyrone Ting <kfting@nuvoton.com>, Hao Wu <wuhaotsh@google.com>
[PATCH 2/7] hw/pci-host: add basic Nuvoton PCIe window support
Posted by Yubin Zou 2 weeks, 4 days ago
From: Titus Rwantare <titusr@google.com>

Adds the windowing registers without address translation

Signed-off-by: Titus Rwantare <titusr@google.com>
---
 hw/pci-host/npcm_pcierc.c         | 223 +++++++++++++++++++++++++++++++++++++-
 include/hw/pci-host/npcm_pcierc.h |  77 ++++++++++++-
 2 files changed, 297 insertions(+), 3 deletions(-)

diff --git a/hw/pci-host/npcm_pcierc.c b/hw/pci-host/npcm_pcierc.c
index 3afe92e264f6ce4312e94f05b5e908840008df64..bffdec71acaba6562856b3bdd8aec07c3c153323 100644
--- a/hw/pci-host/npcm_pcierc.c
+++ b/hw/pci-host/npcm_pcierc.c
@@ -16,6 +16,193 @@
 #include "qom/object.h"
 #include "trace.h"
 
+/* Map enabled windows to a memory subregion */
+static void npcm_pcierc_map_enabled(NPCMPCIERCState *s, NPCMPCIEWindow *w)
+{
+    MemoryRegion *system = get_system_memory();
+    uint32_t size = NPCM_PCIERC_SAL_SIZE(w->sal);
+    hwaddr bar = ((uint64_t)w->sah) << 32 | (w->sal & 0xFFFFF000);
+    char name[26];
+
+    /* check if window is enabled */
+    if (!(w->sal & NPCM_PCIERC_SAL_EN)) {
+        return;
+    }
+
+    if (size > 2 * GiB || size < 4 * KiB) {
+        qemu_log_mask(LOG_GUEST_ERROR,
+                      "%s: Invalid PCI window size %d bytes\n",
+                      __func__, size);
+        return;
+    }
+
+    if (w->type == AXI2PCIE) {
+        snprintf(name, sizeof(name), "npcm-axi2pcie-window-%d", w->id);
+    } else if (w->type == PCIE2AXI) {
+        snprintf(name, sizeof(name), "npcm-pcie2axi-window-%d", w->id);
+    } else {
+        qemu_log_mask(LOG_GUEST_ERROR,
+                      "%s: unable to map uninitialized PCIe window",
+                      __func__);
+        return;
+    }
+
+    /* TODO: set subregion to target translation address */
+    /* add subregion starting at the window source address */
+    if (!memory_region_is_mapped(&w->mem)) {
+        memory_region_init(&w->mem, OBJECT(s), name, size);
+        memory_region_add_subregion(system, bar, &w->mem);
+    }
+}
+
+/* unmap windows marked as disabled */
+static void npcm_pcierc_unmap_disabled(NPCMPCIEWindow *w)
+{
+    MemoryRegion *system = get_system_memory();
+    /* Bit 0 in the Source address enables the window */
+    if (memory_region_is_mapped(&w->mem) && !(w->sal & NPCM_PCIERC_SAL_EN)) {
+        memory_region_del_subregion(system, &w->mem);
+    }
+}
+
+static void npcm_pcie_update_window_maps(NPCMPCIERCState *s)
+{
+    for (int i = 0; i < NPCM_PCIERC_NUM_PA_WINDOWS; i++) {
+        npcm_pcierc_unmap_disabled(&s->pcie2axi[i]);
+    }
+
+    for (int i = 0; i < NPCM_PCIERC_NUM_AP_WINDOWS; i++) {
+        npcm_pcierc_unmap_disabled(&s->axi2pcie[i]);
+    }
+
+    for (int i = 0; i < NPCM_PCIERC_NUM_AP_WINDOWS; i++) {
+        npcm_pcierc_map_enabled(s, &s->axi2pcie[i]);
+    }
+
+    for (int i = 0; i < NPCM_PCIERC_NUM_PA_WINDOWS; i++) {
+        npcm_pcierc_map_enabled(s, &s->pcie2axi[i]);
+    }
+}
+
+static NPCMPCIEWindow *npcm_pcierc_get_window(NPCMPCIERCState *s, hwaddr addr)
+{
+    NPCMPCIEWindow *window;
+
+    switch (addr) {
+    case NPCM_PCIERC_PAnSAL(0) ... NPCM_PCIERC_PAnTP(1):
+        window = &s->pcie2axi[NPCM_PCIERC_PA_WINDOW(addr)];
+        break;
+
+    case NPCM_PCIERC_APnSAL(0) ... NPCM_PCIERC_APnTP(4):
+        window = &s->axi2pcie[NPCM_PCIERC_AP_WINDOW(addr)];
+        break;
+
+    default:
+        qemu_log_mask(LOG_GUEST_ERROR,
+                      "%s: invalid window address 0x%lx\n",
+                      __func__, addr);
+        return 0;
+    }
+    return window;
+}
+
+static int npcm_pcierc_get_window_offset(NPCMPCIEWindow *w, hwaddr addr)
+{
+    if (w->type == AXI2PCIE) {
+        return addr & NPCM_PCIERC_AP_OFFSET_MASK;
+    } else if (w->type == PCIE2AXI) {
+        return addr & NPCM_PCIERC_PA_OFFSET_MASK;
+    } else {
+        qemu_log_mask(LOG_GUEST_ERROR,
+                      "%s: unable to access uninitialized PCIe window",
+                      __func__);
+        return -1;
+    }
+}
+
+static uint32_t npcm_pcierc_read_window(NPCMPCIERCState *s, hwaddr addr)
+{
+    NPCMPCIEWindow *window = npcm_pcierc_get_window(s, addr);
+    int offset;
+
+    if (!window) {
+        return 0;
+    }
+
+    offset = npcm_pcierc_get_window_offset(window, addr);
+    if (offset < 0) {
+        return 0;
+    }
+
+    switch (offset) {
+    case NPCM_PCIERC_SAL_OFFSET:
+        return window->sal;
+
+    case NPCM_PCIERC_SAH_OFFSET:
+        return window->sah;
+
+    case NPCM_PCIERC_TAL_OFFSET:
+        return window->tal;
+
+    case NPCM_PCIERC_TAH_OFFSET:
+        return window->tah;
+
+    case NPCM_PCIERC_PARAM_OFFSET:
+        return window->params;
+
+    default:
+        qemu_log_mask(LOG_GUEST_ERROR,
+                      "%s: invalid window offset 0x%x\n",
+                      __func__, offset);
+        return 0;
+    }
+}
+
+static void npcm_pcierc_write_window(NPCMPCIERCState *s, hwaddr addr,
+                                     uint64_t data)
+{
+    NPCMPCIEWindow *window = npcm_pcierc_get_window(s, addr);
+    int offset;
+
+    if (!window) {
+        return;
+    }
+
+    offset = npcm_pcierc_get_window_offset(window, addr);
+    if (offset < 0) {
+        return;
+    }
+
+    switch (offset) {
+    case NPCM_PCIERC_SAL_OFFSET:
+        window->sal = data;
+        break;
+
+    case NPCM_PCIERC_SAH_OFFSET:
+        window->sah = data;
+        break;
+
+    case NPCM_PCIERC_TAL_OFFSET:
+        window->tal = data;
+        break;
+
+    case NPCM_PCIERC_TAH_OFFSET:
+        window->tah = data;
+        break;
+
+    case NPCM_PCIERC_PARAM_OFFSET:
+        window->params = data;
+        break;
+
+    default:
+        qemu_log_mask(LOG_GUEST_ERROR,
+                      "%s: invalid window offset 0x%x\n",
+                      __func__, offset);
+    }
+
+    npcm_pcie_update_window_maps(s);
+}
+
 static uint64_t npcm_pcierc_cfg_read(void *opaque, hwaddr addr, unsigned size)
 {
     NPCMPCIERCState *s = NPCM_PCIERC(opaque);
@@ -46,6 +233,10 @@ static uint64_t npcm_pcierc_cfg_read(void *opaque, hwaddr addr, unsigned size)
         ret = s->axierr;
         break;
 
+    case NPCM_PCIERC_PAnSAL(0) ... NPCM_PCIERC_APnTP(4):
+        ret = npcm_pcierc_read_window(s, addr);
+        break;
+
     default:
         qemu_log_mask(LOG_UNIMP,
                       "%s: read from unimplemented register 0x%04lx\n",
@@ -88,6 +279,10 @@ static void npcm_pcierc_cfg_write(void *opaque, hwaddr addr, uint64_t data,
         s->axierr = data;
         break;
 
+    case NPCM_PCIERC_PAnSAL(0) ... NPCM_PCIERC_APnTP(4):
+        npcm_pcierc_write_window(s, addr, data);
+        break;
+
     default:
         qemu_log_mask(LOG_UNIMP,
                       "%s: write to unimplemented reg 0x%04lx data: 0x%lx\n",
@@ -96,6 +291,22 @@ static void npcm_pcierc_cfg_write(void *opaque, hwaddr addr, uint64_t data,
     }
 }
 
+static void npcm_pcierc_reset_pcie_windows(NPCMPCIERCState *s)
+{
+    memset(s->axi2pcie, 0, sizeof(s->axi2pcie));
+    memset(s->pcie2axi, 0, sizeof(s->pcie2axi));
+
+    for (int i = 0; i < NPCM_PCIERC_NUM_PA_WINDOWS; i++) {
+        s->pcie2axi[i].id = i;
+        s->pcie2axi[i].type = PCIE2AXI;
+    }
+
+    for (int i = 0; i < NPCM_PCIERC_NUM_AP_WINDOWS; i++) {
+        s->axi2pcie[i].id = i;
+        s->axi2pcie[i].type = AXI2PCIE;
+    }
+}
+
 static void npcm_pcierc_reset(Object *obj, ResetType type)
 {
     NPCMPCIERCState *s = NPCM_PCIERC(obj);
@@ -106,6 +317,8 @@ static void npcm_pcierc_reset(Object *obj, ResetType type)
     s->rcimsiaddr = 0;
     s->rcmsisstat = 0;
     s->axierr = 0;
+
+    npcm_pcierc_reset_pcie_windows(s);
 }
 
 static const char *npcm_pcierc_root_bus_path(PCIHostState *host_bridge,
@@ -136,6 +349,13 @@ static void npcm_pcierc_realize(DeviceState *dev, Error **errp)
     sysbus_init_irq(sbd, &s->irq);
 }
 
+static void npcm_pcierc_instance_init(Object *obj)
+{
+    NPCMPCIERCState *s = NPCM_PCIERC(obj);
+
+    npcm_pcierc_reset_pcie_windows(s);
+}
+
 static void npcm_pcierc_class_init(ObjectClass *klass, const void *data)
 {
     DeviceClass *dc = DEVICE_CLASS(klass);
@@ -144,7 +364,7 @@ static void npcm_pcierc_class_init(ObjectClass *klass, const void *data)
 
     hbc->root_bus_path = npcm_pcierc_root_bus_path;
     dc->realize = npcm_pcierc_realize;
-    rc->phases.enter = npcm_pcierc_reset;
+    rc->phases.exit = npcm_pcierc_reset;
     set_bit(DEVICE_CATEGORY_BRIDGE, dc->categories);
     dc->fw_name = "pci";
 }
@@ -153,6 +373,7 @@ static const TypeInfo npcm_pcierc_type_info = {
     .name = TYPE_NPCM_PCIERC,
     .parent = TYPE_PCIE_HOST_BRIDGE,
     .instance_size = sizeof(NPCMPCIERCState),
+    .instance_init = npcm_pcierc_instance_init,
     .class_init = npcm_pcierc_class_init,
 };
 
diff --git a/include/hw/pci-host/npcm_pcierc.h b/include/hw/pci-host/npcm_pcierc.h
index 2c817147d495fdc1d1fa4b389bad0469fd6a825e..410b34d1c1ced0e25f63fc7693d87bb625a80776 100644
--- a/include/hw/pci-host/npcm_pcierc.h
+++ b/include/hw/pci-host/npcm_pcierc.h
@@ -9,8 +9,8 @@
 /*
  * The PCIERC configuration registers must be initialised by the BMC kernel
  * during boot for PCIe to function
- * - A single window from the PCIe to the Memory controller
- * - 4 windows from the BMC to the PCIe.
+ * - A single window from PCIe to the Memory controller
+ * - 4 windows from the BMC to PCIe.
  *     1 of these five BMC-to-PCIe windows must be allocated for configuration
  *     transactions, the rest can be used for I/0 or memory transactions
  * - All BMC-to-PCIe windows are mapped to address range
@@ -34,9 +34,77 @@
 #define NPCM_PCIERC_MSISTAT             0x194 /* MSI Status Register */
 #define NPCM_PCIERC_AXI_ERROR_REPORT    0x3E0
 
+/* PCIe-to-AXI Window 0 and 1 Registers */
+/* Source address low */
+#define NPCM_PCIERC_PAnSAL(n)          (0x600 + (0x100 * (n)))
+/* Source address high */
+#define NPCM_PCIERC_PAnSAH(n)          (0x604 + (0x100 * (n)))
+/* Translation address low */
+#define NPCM_PCIERC_PAnTAL(n)          (0x608 + (0x100 * (n)))
+/* Translation address high */
+#define NPCM_PCIERC_PAnTAH(n)          (0x60C + (0x100 * (n)))
+/* Translation parameters */
+#define NPCM_PCIERC_PAnTP(n)           (0x610 + (0x100 * (n)))
+/* Get window number from address */
+#define NPCM_PCIERC_PA_WINDOW(addr)    (((addr) - 0x600) / 0x100)
+#define NPCM_PCIERC_PA_OFFSET_MASK      0xff
+
+/* AXI-to-PCIe Window 1 to 5 Registers, n in range [0,4] */
+/* Source address low */
+#define NPCM_PCIERC_APnSAL(n)          (0x820 + (0x20 * (n)))
+/* Source address high */
+#define NPCM_PCIERC_APnSAH(n)          (0x824 + (0x20 * (n)))
+/* Translation address low */
+#define NPCM_PCIERC_APnTAL(n)          (0x828 + (0x20 * (n)))
+/* Translation address high */
+#define NPCM_PCIERC_APnTAH(n)          (0x82C + (0x20 * (n)))
+/* Translation parameters */
+#define NPCM_PCIERC_APnTP(n)           (0x830 + (0x20 * (n)))
+/* Get window number from address */
+#define NPCM_PCIERC_AP_WINDOW(addr)    (((addr) - 0x820) / 0x20)
+#define NPCM_PCIERC_AP_OFFSET_MASK      0x1f
+
+/* Translation window parameters */
+#define NPCM_PCIERC_TRSL_ID(p)              ((p) & 0x7)
+#define     NPCM_PCIERC_TRSL_ID_TX_RX       0
+#define     NPCM_PCIERC_TRSL_ID_CONFIG      1
+#define NPCM_PCIERC_TRSF_PARAM(p)           (((p) >> 16) & 0xFFF)
+#define     NPCM_PCIERC_TRSF_PARAM_MEMORY   0
+#define     NPCM_PCIERC_TRSF_PARAM_CONFIG   1
+#define     NPCM_PCIERC_TRSF_PARAM_IO       2
+
+#define NPCM_PCIERC_SAL_OFFSET              0x0
+#define     NPCM_PCIERC_SAL_EN              1
+#define     NPCM_PCIERC_SAL_SIZE(addr)     (2ull << (((addr) >> 1) & 0x1F))
+#define NPCM_PCIERC_SAH_OFFSET              0x4
+#define NPCM_PCIERC_TAL_OFFSET              0x8
+#define NPCM_PCIERC_TAH_OFFSET              0xC
+#define NPCM_PCIERC_PARAM_OFFSET            0x10
+
+#define NPCM_PCIERC_NUM_PA_WINDOWS          2
+#define NPCM_PCIERC_NUM_AP_WINDOWS          5
+
 #define TYPE_NPCM_PCIERC "npcm-pcie-root-complex"
 OBJECT_DECLARE_SIMPLE_TYPE(NPCMPCIERCState, NPCM_PCIERC)
 
+typedef enum {
+    AXI2PCIE = 1,
+    PCIE2AXI
+} NPCMPCIEWindowType;
+
+/* Nuvoton PCIe translation Window */
+typedef struct NPCMPCIEWindow {
+    uint32_t sal;            /* source address low */
+    uint32_t sah;            /* source address high */
+    uint32_t tal;            /* translation address low */
+    uint32_t tah;            /* translation address high */
+    uint32_t params;         /* translation window parameters */
+
+    MemoryRegion mem;        /* QEMU memory subregion per window */
+    NPCMPCIEWindowType type; /* translation direction */
+    uint8_t id;
+} NPCMPCIEWindow;
+
 struct NPCMPCIERCState {
     PCIExpressHost parent;
 
@@ -50,6 +118,11 @@ struct NPCMPCIERCState {
     uint32_t rcimsiaddr;
     uint32_t rcmsisstat;
     uint32_t axierr;
+    /* PCIe to AXI Windows */
+    NPCMPCIEWindow pcie2axi[NPCM_PCIERC_NUM_PA_WINDOWS];
+
+    /* AXI to PCIe Windows */
+    NPCMPCIEWindow axi2pcie[NPCM_PCIERC_NUM_AP_WINDOWS];
 };
 
 #endif /* NPCM_PCIERC_H */

-- 
2.51.0.384.g4c02a37b29-goog
Re: [PATCH 2/7] hw/pci-host: add basic Nuvoton PCIe window support
Posted by Peter Maydell 3 days ago
On Tue, 9 Sept 2025 at 23:11, Yubin Zou <yubinz@google.com> wrote:
>
> From: Titus Rwantare <titusr@google.com>
>
> Adds the windowing registers without address translation
>
> Signed-off-by: Titus Rwantare <titusr@google.com>
> ---
>  hw/pci-host/npcm_pcierc.c         | 223 +++++++++++++++++++++++++++++++++++++-
>  include/hw/pci-host/npcm_pcierc.h |  77 ++++++++++++-
>  2 files changed, 297 insertions(+), 3 deletions(-)
>
> diff --git a/hw/pci-host/npcm_pcierc.c b/hw/pci-host/npcm_pcierc.c
> index 3afe92e264f6ce4312e94f05b5e908840008df64..bffdec71acaba6562856b3bdd8aec07c3c153323 100644
> --- a/hw/pci-host/npcm_pcierc.c
> +++ b/hw/pci-host/npcm_pcierc.c
> @@ -16,6 +16,193 @@
>  #include "qom/object.h"
>  #include "trace.h"
>
> +/* Map enabled windows to a memory subregion */
> +static void npcm_pcierc_map_enabled(NPCMPCIERCState *s, NPCMPCIEWindow *w)
> +{
> +    MemoryRegion *system = get_system_memory();

> +    /* TODO: set subregion to target translation address */
> +    /* add subregion starting at the window source address */
> +    if (!memory_region_is_mapped(&w->mem)) {
> +        memory_region_init(&w->mem, OBJECT(s), name, size);
> +        memory_region_add_subregion(system, bar, &w->mem);
> +    }

This looks weird. Generally devices should not map themselves
into the system address space, although some of our older
pci-host devices do for historical reasons. Should we
be modelling this some other way?

thanks
-- PMM
Re: [PATCH 2/7] hw/pci-host: add basic Nuvoton PCIe window support
Posted by Titus Rwantare 2 days, 21 hours ago
On Thu, 25 Sept 2025 at 09:38, Peter Maydell <peter.maydell@linaro.org> wrote:
>
> On Tue, 9 Sept 2025 at 23:11, Yubin Zou <yubinz@google.com> wrote:
> >
> > From: Titus Rwantare <titusr@google.com>
> >
> > Adds the windowing registers without address translation
> >
> > Signed-off-by: Titus Rwantare <titusr@google.com>
> > ---
> >  hw/pci-host/npcm_pcierc.c         | 223 +++++++++++++++++++++++++++++++++++++-
> >  include/hw/pci-host/npcm_pcierc.h |  77 ++++++++++++-
> >  2 files changed, 297 insertions(+), 3 deletions(-)
> >
> > diff --git a/hw/pci-host/npcm_pcierc.c b/hw/pci-host/npcm_pcierc.c
> > index 3afe92e264f6ce4312e94f05b5e908840008df64..bffdec71acaba6562856b3bdd8aec07c3c153323 100644
> > --- a/hw/pci-host/npcm_pcierc.c
> > +++ b/hw/pci-host/npcm_pcierc.c
> > @@ -16,6 +16,193 @@
> >  #include "qom/object.h"
> >  #include "trace.h"
> >
> > +/* Map enabled windows to a memory subregion */
> > +static void npcm_pcierc_map_enabled(NPCMPCIERCState *s, NPCMPCIEWindow *w)
> > +{
> > +    MemoryRegion *system = get_system_memory();
>
> > +    /* TODO: set subregion to target translation address */
> > +    /* add subregion starting at the window source address */
> > +    if (!memory_region_is_mapped(&w->mem)) {
> > +        memory_region_init(&w->mem, OBJECT(s), name, size);
> > +        memory_region_add_subregion(system, bar, &w->mem);
> > +    }
>
> This looks weird. Generally devices should not map themselves
> into the system address space, although some of our older
> pci-host devices do for historical reasons. Should we
> be modelling this some other way?
>
> thanks
> -- PMM

I can update this. What devices are doing it the new way?

-Titus
Re: [PATCH 2/7] hw/pci-host: add basic Nuvoton PCIe window support
Posted by Peter Maydell 2 days, 7 hours ago
On Thu, 25 Sept 2025 at 20:40, Titus Rwantare <titusr@google.com> wrote:
>
> On Thu, 25 Sept 2025 at 09:38, Peter Maydell <peter.maydell@linaro.org> wrote:
> >
> > On Tue, 9 Sept 2025 at 23:11, Yubin Zou <yubinz@google.com> wrote:
> > >
> > > From: Titus Rwantare <titusr@google.com>
> > >
> > > Adds the windowing registers without address translation
> > >
> > > Signed-off-by: Titus Rwantare <titusr@google.com>
> > > ---
> > >  hw/pci-host/npcm_pcierc.c         | 223 +++++++++++++++++++++++++++++++++++++-
> > >  include/hw/pci-host/npcm_pcierc.h |  77 ++++++++++++-
> > >  2 files changed, 297 insertions(+), 3 deletions(-)
> > >
> > > diff --git a/hw/pci-host/npcm_pcierc.c b/hw/pci-host/npcm_pcierc.c
> > > index 3afe92e264f6ce4312e94f05b5e908840008df64..bffdec71acaba6562856b3bdd8aec07c3c153323 100644
> > > --- a/hw/pci-host/npcm_pcierc.c
> > > +++ b/hw/pci-host/npcm_pcierc.c
> > > @@ -16,6 +16,193 @@
> > >  #include "qom/object.h"
> > >  #include "trace.h"
> > >
> > > +/* Map enabled windows to a memory subregion */
> > > +static void npcm_pcierc_map_enabled(NPCMPCIERCState *s, NPCMPCIEWindow *w)
> > > +{
> > > +    MemoryRegion *system = get_system_memory();
> >
> > > +    /* TODO: set subregion to target translation address */
> > > +    /* add subregion starting at the window source address */
> > > +    if (!memory_region_is_mapped(&w->mem)) {
> > > +        memory_region_init(&w->mem, OBJECT(s), name, size);
> > > +        memory_region_add_subregion(system, bar, &w->mem);
> > > +    }
> >
> > This looks weird. Generally devices should not map themselves
> > into the system address space, although some of our older
> > pci-host devices do for historical reasons. Should we
> > be modelling this some other way?

> I can update this. What devices are doing it the new way?

I'm not sure exactly what your device is doing here in
general, but typically the device exposes sysbus MRs
to the SoC and the SoC maps those where they ought to go.

Is there documentation on what the hardware behaviour is?

thanks
-- PMM