All of lore.kernel.org
 help / color / mirror / Atom feed
From: Oleksii Kurochko <oleksii.kurochko@gmail.com>
To: Jan Beulich <jbeulich@suse.com>
Cc: "Romain Caritey" <Romain.Caritey@microchip.com>,
	"Baptiste Le Duc" <baptiste.le-duc@vates.tech>,
	"Alistair Francis" <alistair.francis@wdc.com>,
	"Connor Davis" <connojdavis@gmail.com>,
	"Andrew Cooper" <andrew.cooper3@citrix.com>,
	"Anthony PERARD" <anthony.perard@vates.tech>,
	"Michal Orzel" <michal.orzel@amd.com>,
	"Julien Grall" <julien@xen.org>,
	"Roger Pau Monné" <roger@xenproject.org>,
	"Stefano Stabellini" <sstabellini@kernel.org>,
	xen-devel@lists.xenproject.org
Subject: Re: [PATCH v1 04/17] xen/riscv: introduce device-agnostic MMIO emulation dispatch
Date: Thu, 30 Jul 2026 18:03:43 +0200	[thread overview]
Message-ID: <d5867843-802d-493f-a535-1f40d9337b63@gmail.com> (raw)
In-Reply-To: <704870c1-18ec-4c7b-873c-e07e77ae0d39@suse.com>



On 7/28/26 2:23 PM, Jan Beulich wrote:
> On 20.07.2026 18:02, Oleksii Kurochko wrote:
>> RISC-V guests can expose several virtual interrupt controllers at
>> distinct GPA ranges: vPLIC (hasn't been introduced yet) for legacy machines,
>> vAPLIC and vIMSIC for AIA-compliant ones (is being introduced in the follow
>> up patches). Routing MMIO faults via a per-device is_access() check in the
>> trap handler would couple it to every device it must serve, requiring a
>> new conditional branch in the fault path each time a new emulated device is
>> added.
>>
>> Introduce a per-domain MMIO handler registration table, modeled
>> after the equivalent ARM framework, so that virtual devices
>> self-register their GPA ranges and read/write callbacks at domain
>> creation time. The MMIO fault path delegates to a single
>> try_handle_mmio() entry point and remains agnostic of which device
>> owns a particular address.
>>
>> Subsequent patches wire this into arch_domain_create() and the MMIO fault
>> path in traps.c.
>>
>> Signed-off-by: Oleksii Kurochko <oleksii.kurochko@gmail.com>
>> Reviewed-by: Baptiste Le Duc <baptiste.le-duc@vates.tech>
>> ---
>> Note that find_mmio_handler() and try_handle_mmio() is handling found
>> handler differently for now in comparison to Arm. But this behaviour will
>> be aligned at the end. Look at discussion:
>>    https://lore.kernel.org/xen-devel/cd78972e-88d5-471d-a201-5f9cd1392c73@gmail.com/T/#t
>> ---
>> ---
>>   xen/arch/riscv/Makefile             |   1 +
>>   xen/arch/riscv/domain.c             |   4 +
>>   xen/arch/riscv/include/asm/domain.h |   3 +
>>   xen/arch/riscv/include/asm/mmio.h   |  63 ++++++++++++
>>   xen/arch/riscv/mmio.c               | 145 ++++++++++++++++++++++++++++
>>   5 files changed, 216 insertions(+)
>>   create mode 100644 xen/arch/riscv/include/asm/mmio.h
>>   create mode 100644 xen/arch/riscv/mmio.c
>>
>> diff --git a/xen/arch/riscv/Makefile b/xen/arch/riscv/Makefile
>> index 046f73f4d87c..c452ebc3cf61 100644
>> --- a/xen/arch/riscv/Makefile
>> +++ b/xen/arch/riscv/Makefile
>> @@ -14,6 +14,7 @@ obj-y += intc.o
>>   obj-y += irq.o
>>   obj-y += kernel.init.o
>>   obj-y += mm.o
>> +obj-y += mmio.o
>>   obj-y += p2m.o
>>   obj-y += paging.o
>>   obj-y += pt.o
>> diff --git a/xen/arch/riscv/domain.c b/xen/arch/riscv/domain.c
>> index 4db9c28662c7..1e6f0ef66c2f 100644
>> --- a/xen/arch/riscv/domain.c
>> +++ b/xen/arch/riscv/domain.c
>> @@ -12,6 +12,7 @@
>>   #include <asm/cpufeature.h>
>>   #include <asm/csr.h>
>>   #include <asm/intc.h>
>> +#include <asm/mmio.h>
>>   #include <asm/riscv_encoding.h>
>>   #include <asm/vtimer.h>
>>   
>> @@ -308,6 +309,9 @@ int arch_domain_create(struct domain *d,
>>       if ( (rc = p2m_init(d, config)) != 0)
>>           goto fail;
>>   
>> +    if ( (rc = domain_io_init(d, MAX_IO_HANDLER)) != 0 )
>> +        goto fail;
> 
> Why does MAX_IO_HANDLER need passing into the function? Isn't that a global
> boundary?

Good question. Considering that all domains are initialized with 
MAX_IO_HANDLER I think we could drop an argument for domain_io_init() 
and just use MAX_IO_HANDLER inside it for init. of handlers array.

> 
>> --- /dev/null
>> +++ b/xen/arch/riscv/include/asm/mmio.h
>> @@ -0,0 +1,63 @@
>> +/* SPDX-License-Identifier: GPL-2.0-or-later */
>> +#ifndef RISCV_MMIO_H
>> +#define RISCV_MMIO_H
>> +
>> +#include <xen/lib.h>
>> +#include <xen/rwlock.h>
>> +
>> +#define MAX_IO_HANDLER  16
>> +
>> +typedef struct {
>> +    paddr_t gpa;
>> +    unsigned int len;  /* access width in bytes (1, 2, 4, 8) */
>> +    bool is_write;
>> +    register_t data;   /* store: value to write; load: value read (set by handler) */
>> +} mmio_info_t;
>> +
>> +enum io_state
>> +{
>> +    IO_ABORT,       /* The IO was handled and led to an abort. */
>> +    IO_HANDLED,     /* The IO was successfully handled. */
>> +    IO_UNHANDLED,   /* No handler found for the IO. */
>> +};
>> +
>> +typedef enum io_state (*mmio_read_t)(struct vcpu *v, mmio_info_t *info,
>> +                                     register_t *r);
>> +typedef enum io_state (*mmio_write_t)(struct vcpu *v, mmio_info_t *info,
>> +                                      register_t r);
> 
> Can't info be pointer-to-const in the write case?

With the current implementaion it could be done for both mmio_read_t and 
mmio_write_t as value is return through r argument.

In both cases, why is there
> both "r" passed into the function as well as the info->data field, supposedly
> (as per the comment) serving the same purpose?

Agree, we don't need both "r" and info->data as they are serving the 
same purpose.

But I don't know which one option is actually better to drop "r" 
argument or drop ->data member in mmio_info_t.


> 
> Furthermore I think it helps if ...
> 
>> +struct mmio_handler_ops {
>> +    mmio_read_t read;
>> +    mmio_write_t write;
> 
> ... pointer-ness is easily seen at use sites. I.e.
> 
> typedef enum io_state mmio_read_t(struct vcpu *v, mmio_info_t *info,
>                                    register_t *r);
> typedef enum io_state mmio_write_t(struct vcpu *v, const mmio_info_t *info,
>                                     register_t r);
> 
> struct mmio_handler_ops {
>      mmio_read_t *read;
>      mmio_write_t *write;
> };
> 

I will apply that.

>> +};
>> +
>> +struct mmio_handler {
>> +    paddr_t addr;
>> +    paddr_t size;
>> +    const struct mmio_handler_ops *ops;
>> +};
>> +
>> +struct vmmio {
>> +    unsigned int num_entries;
>> +    unsigned int max_num_entries;
>> +    rwlock_t lock;
>> +    struct mmio_handler *handlers;
> 
> There shouldn't be any writes through this pointer, should there? In which
> case it (once again) wants to be pointer-to-const.

Agree, it should be const.

> 
>> --- /dev/null
>> +++ b/xen/arch/riscv/mmio.c
>> @@ -0,0 +1,145 @@
>> +/* SPDX-License-Identifier: GPL-2.0-or-later */
>> +/*
>> + * Copyright (C) Vates
>> + */
>> +
>> +#include <xen/bsearch.h>
>> +#include <xen/lib.h>
>> +#include <xen/rwlock.h>
>> +#include <xen/sched.h>
>> +#include <xen/sort.h>
>> +#include <xen/xvmalloc.h>
>> +
>> +#include <asm/current.h>
>> +#include <asm/mmio.h>
>> +
>> +static enum io_state handle_read(const struct mmio_handler *handler,
>> +                                 struct vcpu *v,
>> +                                 mmio_info_t *info)
>> +{
>> +    register_t r = 0;
>> +    enum io_state rc;
>> +
>> +    rc = handler->ops->read(v, info, &r);
>> +    if ( rc == IO_HANDLED )
>> +        info->data = r;
> 
> Extending my earlier comment: Why could ->read() not put the value directly
> into info->data? And why ...
> 
>> +static enum io_state handle_write(const struct mmio_handler *handler,
>> +                                  struct vcpu *v,
>> +                                  mmio_info_t *info)
>> +{
>> +    return handler->ops->write(v, info, info->data);
> 
> ... can't write take the value directly from info->data?

I totally agree, it can. Do you think it is better to keep ->data and 
drop an argument 'r' or vice versa?

> 
>> +}
>> +
>> +/* Assumes mmio regions are not overlapping. */
> 
> Are you guaranteeing this anywhere?

There is no such guarantee. register_mmio_handler() simply adds the 
handler to the handlers array without performing any checks. I can add 
such a check. The only question is whether it should be enabled only in 
debug builds or in all builds.

I assume this is a rare case, and overlapping regions would indicate 
that something is wrong with the guest's memory layout configuration so 
it seems like it would be enough to add only for debug builds.

> 
>> +static int cmp_mmio_handler(const void *key, const void *elem)
>> +{
>> +    const struct mmio_handler *handler0 = key;
>> +    const struct mmio_handler *handler1 = elem;
>> +
>> +    if ( handler0->addr < handler1->addr )
>> +        return -1;
>> +
>> +    if ( handler0->addr >= (handler1->addr + handler1->size) )
>> +        return 1;
>> +
>> +    return 0;
>> +}
>> +
>> +static void swap_mmio_handler(void *a, void *b)
>> +{
>> +    struct mmio_handler *t1 = a, *t2 = b;
>> +
>> +    SWAP(*t1, *t2);
>> +}
>> +
>> +/*
>> + * Return a copy of the matching handler rather than a pointer into
>> + * vmmio->handlers: a concurrent register_mmio_handler() re-sorts the
>> + * array, so an escaped pointer could refer to a different (or torn)
>> + * entry once the lock is dropped.  The copy stays valid as the ops
>> + * structures are never freed.
>> + */
>> +static bool find_mmio_handler(struct domain *d, paddr_t gpa,
>> +                              struct mmio_handler *out)
>> +{
>> +    struct vmmio *vmmio = &d->arch.vmmio;
>> +    struct mmio_handler key = { .addr = gpa };
>> +    const struct mmio_handler *handler;
>> +
>> +    read_lock(&vmmio->lock);
>> +    handler = bsearch(&key, vmmio->handlers, vmmio->num_entries,
>> +                      sizeof(*handler), cmp_mmio_handler);
> 
> So beyond the assumption stated further up you also assume the array to
> be sorted. Which you ...
> 
>> +void register_mmio_handler(struct domain *d,
>> +                           const struct mmio_handler_ops *ops,
>> +                           paddr_t addr, paddr_t size)
>> +{
>> +    struct vmmio *vmmio = &d->arch.vmmio;
>> +    struct mmio_handler *handler;
>> +
>> +    write_lock(&vmmio->lock);
>> +
>> +    BUG_ON(vmmio->num_entries >= vmmio->max_num_entries);
> 
> (Do we really need to crash in such a case? Can't we just fail domain
> creation?)

Generally, no. However, the approach used by Arm's dom0less solution is 
to crash as soon as any issue occurs instead of trying to continue 
running other domains, so I follow the same approach for RISC-V.

Even if I return an error here, the common dom0less code will panic anyway.

> 
>> +    handler = &vmmio->handlers[vmmio->num_entries];
>> +    handler->ops = ops;
>> +    handler->addr = addr;
>> +    handler->size = size;
>> +    vmmio->num_entries++;
>> +
>> +    /* Sort mmio handlers in ascending order based on base address */
>> +    sort(vmmio->handlers, vmmio->num_entries, sizeof(struct mmio_handler),
>> +         cmp_mmio_handler, swap_mmio_handler);
> 
> ... arrange for here, yet in a pretty inefficient way: Inserting in an
> already sorted list can be had without recurring calls to sort().

Good point. I will rework that.
> 
>> +int domain_io_init(struct domain *d, unsigned int max_count)
>> +{
>> +    rwlock_init(&d->arch.vmmio.lock);
>> +    d->arch.vmmio.num_entries = 0;
>> +    d->arch.vmmio.max_num_entries = max_count;
>> +    d->arch.vmmio.handlers = xvzalloc_array(struct mmio_handler, max_count);
> 
> If already an allocation is needed in all cases, why not allocate struct
> vmmio, defined like this:
> 
> struct vmmio {
>      unsigned int num_entries;
>      unsigned int max_num_entries;
>      rwlock_t lock;
>      struct mmio_handler handlers[];
> };
> 
> and then using xvzalloc_flex_struct(). Or yet simpler if (as mentioned
> elsewhere) max_count doesn't need passing into here:
> 
> struct vmmio {
>      unsigned int num_entries;
>      unsigned int max_num_entries;
>      rwlock_t lock;
>      struct mmio_handler handlers[MAX_IO_HANDLER];
> };
> 
Agree, both option are good to me. Considering that we are going to use 
MAX_IO_HANDLER then second option is really better for now.

Thanks!

~ Oleksii


  reply	other threads:[~2026-07-30 16:04 UTC|newest]

Thread overview: 41+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-07-20 16:01 [PATCH v1 00/17] [RISC-V] virtual interrupt controller (vAPLIC/vIMSIC) support Oleksii Kurochko
2026-07-20 16:01 ` [PATCH v1 01/17] xen/riscv: manage IRQ_DISABLED flag in APLIC irq enable/disable callbacks Oleksii Kurochko
2026-07-27 15:19   ` Jan Beulich
2026-07-20 16:02 ` [PATCH v1 02/17] xen/riscv: add basic VGEIN management for AIA guests Oleksii Kurochko
2026-07-27 15:41   ` Jan Beulich
2026-07-29 14:55     ` Oleksii Kurochko
2026-07-30  7:42       ` Jan Beulich
2026-07-30 15:46         ` Oleksii Kurochko
2026-07-30 16:03           ` Jan Beulich
2026-07-31 14:59             ` Oleksii Kurochko
2026-08-03 10:37               ` Jan Beulich
2026-07-20 16:02 ` [PATCH v1 03/17] xen/riscv: add missing APLIC register offsets, masks to asm/aplic.h Oleksii Kurochko
2026-07-28 12:02   ` Jan Beulich
2026-07-29 15:26     ` Oleksii Kurochko
2026-07-30  7:53       ` Jan Beulich
2026-07-20 16:02 ` [PATCH v1 04/17] xen/riscv: introduce device-agnostic MMIO emulation dispatch Oleksii Kurochko
2026-07-28 12:23   ` Jan Beulich
2026-07-30 16:03     ` Oleksii Kurochko [this message]
2026-07-30 16:09       ` Jan Beulich
2026-07-31 15:24         ` Oleksii Kurochko
2026-08-03 10:41           ` Jan Beulich
2026-08-04 10:26             ` Oleksii Kurochko
2026-07-20 16:02 ` [PATCH v1 05/17] xen/riscv: implement virtual APLIC MMIO emulation Oleksii Kurochko
2026-08-06 14:28   ` Jan Beulich
2026-08-07 16:08     ` Oleksii Kurochko
2026-07-20 16:02 ` [PATCH v1 06/17] xen/riscv: map IMSIC interrupt file for vCPUs Oleksii Kurochko
2026-08-06 14:48   ` Jan Beulich
2026-07-20 16:02 ` [PATCH v1 07/17] xen/riscv: introduce vCPU AIA initialization Oleksii Kurochko
2026-08-06 14:56   ` Jan Beulich
2026-07-20 16:02 ` [PATCH v1 08/17] xen/riscv: add IMSIC state save/restore Oleksii Kurochko
2026-07-20 16:02 ` [PATCH v1 09/17] xen/riscv: add helper to check APLIC MSI mode Oleksii Kurochko
2026-07-20 16:02 ` [PATCH v1 10/17] xen/riscv: introduce vintc_state_{save,restore}() Oleksii Kurochko
2026-07-20 16:02 ` [PATCH v1 11/17] xen/riscv: add vAPLIC state save/restore hooks Oleksii Kurochko
2026-07-20 16:02 ` [PATCH v1 12/17] xen/riscv: extend exception tables with type and data fields Oleksii Kurochko
2026-07-20 16:02 ` [PATCH v1 13/17] xen/riscv: add unprivileged guest memory read helper Oleksii Kurochko
2026-07-20 16:02 ` [PATCH v1 14/17] xen/riscv: add guest page fault handling stub Oleksii Kurochko
2026-07-20 16:02 ` [PATCH v1 15/17] xen/riscv: implement trap redirection to a guest Oleksii Kurochko
2026-07-27 15:21 ` [PATCH v1 00/17] [RISC-V] virtual interrupt controller (vAPLIC/vIMSIC) support Jan Beulich
2026-07-29 13:41   ` Oleksii Kurochko
2026-07-29 13:40 ` [PATCH v1 16/17] xen/riscv: add guest load emulation for trapped MMIO accesses Oleksii Kurochko
2026-07-29 13:40 ` [PATCH v1 17/17] xen/riscv: add guest store " Oleksii Kurochko

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=d5867843-802d-493f-a535-1f40d9337b63@gmail.com \
    --to=oleksii.kurochko@gmail.com \
    --cc=Romain.Caritey@microchip.com \
    --cc=alistair.francis@wdc.com \
    --cc=andrew.cooper3@citrix.com \
    --cc=anthony.perard@vates.tech \
    --cc=baptiste.le-duc@vates.tech \
    --cc=connojdavis@gmail.com \
    --cc=jbeulich@suse.com \
    --cc=julien@xen.org \
    --cc=michal.orzel@amd.com \
    --cc=roger@xenproject.org \
    --cc=sstabellini@kernel.org \
    --cc=xen-devel@lists.xenproject.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
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.