diff mbox series

[04/14] hw/core: Introduce TYPE_SHARED_IRQ

Message ID 20250108092538.11474-5-shentey@gmail.com (mailing list archive)
State New
Headers show
Series i.MX and SDHCI improvements | expand

Commit Message

Bernhard Beschow Jan. 8, 2025, 9:25 a.m. UTC
Signed-off-by: Bernhard Beschow <shentey@gmail.com>
---
 include/hw/core/shared-irq.h | 39 ++++++++++++++++
 hw/core/shared-irq.c         | 88 ++++++++++++++++++++++++++++++++++++
 hw/core/Kconfig              |  3 ++
 hw/core/meson.build          |  1 +
 4 files changed, 131 insertions(+)
 create mode 100644 include/hw/core/shared-irq.h
 create mode 100644 hw/core/shared-irq.c

Comments

BALATON Zoltan Jan. 8, 2025, 1:53 p.m. UTC | #1
On Wed, 8 Jan 2025, Bernhard Beschow wrote:
> Signed-off-by: Bernhard Beschow <shentey@gmail.com>
> ---
> include/hw/core/shared-irq.h | 39 ++++++++++++++++
> hw/core/shared-irq.c         | 88 ++++++++++++++++++++++++++++++++++++
> hw/core/Kconfig              |  3 ++
> hw/core/meson.build          |  1 +
> 4 files changed, 131 insertions(+)
> create mode 100644 include/hw/core/shared-irq.h
> create mode 100644 hw/core/shared-irq.c
>
> diff --git a/include/hw/core/shared-irq.h b/include/hw/core/shared-irq.h
> new file mode 100644
> index 0000000000..803c303dd0
> --- /dev/null
> +++ b/include/hw/core/shared-irq.h
> @@ -0,0 +1,39 @@
> +/*
> + * IRQ sharing device.
> + *
> + * Copyright (c) 2025 Bernhard Beschow <shentey@gmail.com>
> + *
> + * SPDX-License-Identifier: GPL-2.0-or-later
> + */
> +
> +/*
> + * This is a simple device which has one GPIO output line and multiple GPIO
> + * input lines. The output line is active if at least one of the input lines is.

How is this different from TYPE_OR_IRQ. Also or-irq.h is in 
include/hw/or-irq.h not in include/hw/core/ where split-irq.h is so maybe 
these could all be moved to one place for consistency? Or-irq also has a 
reset method, do you need one in this device?

Regards,
BALATON Zoltan

> + *
> + * QEMU interface:
> + *  + N unnamed GPIO inputs: the input lines
> + *  + one unnamed GPIO output: the output line
> + *  + QOM property "num-lines": sets the number of input lines
> + */
> +#ifndef HW_SHARED_IRQ_H
> +#define HW_SHARED_IRQ_H
> +
> +#include "hw/sysbus.h"
> +#include "qom/object.h"
> +
> +#define TYPE_SHARED_IRQ "shared-irq"
> +
> +#define MAX_SHARED_LINES 16
> +
> +
> +OBJECT_DECLARE_SIMPLE_TYPE(SharedIRQ, SHARED_IRQ)
> +
> +struct SharedIRQ {
> +    DeviceState parent_obj;
> +
> +    qemu_irq out_irq;
> +    uint16_t irq_states;
> +    uint8_t num_lines;
> +};
> +
> +#endif
> diff --git a/hw/core/shared-irq.c b/hw/core/shared-irq.c
> new file mode 100644
> index 0000000000..b2a4ea4a66
> --- /dev/null
> +++ b/hw/core/shared-irq.c
> @@ -0,0 +1,88 @@
> +/*
> + * IRQ sharing device.
> + *
> + * Copyright (c) 2025 Bernhard Beschow <shentey@gmail.com>
> + *
> + * SPDX-License-Identifier: GPL-2.0-or-later
> + */
> +
> +#include "qemu/osdep.h"
> +#include "hw/core/shared-irq.h"
> +#include "hw/irq.h"
> +#include "hw/qdev-properties.h"
> +#include "qapi/error.h"
> +#include "migration/vmstate.h"
> +
> +static void shared_irq_handler(void *opaque, int n, int level)
> +{
> +    SharedIRQ *s = opaque;
> +    uint16_t mask = BIT(n);
> +
> +    if (level) {
> +        s->irq_states |= mask;
> +    } else {
> +        s->irq_states &= ~mask;
> +    }
> +
> +    qemu_set_irq(s->out_irq, !!s->irq_states);
> +}
> +
> +static void shared_irq_init(Object *obj)
> +{
> +    SharedIRQ *s = SHARED_IRQ(obj);
> +
> +    qdev_init_gpio_out(DEVICE(s), &s->out_irq, 1);
> +}
> +
> +static void shared_irq_realize(DeviceState *dev, Error **errp)
> +{
> +    SharedIRQ *s = SHARED_IRQ(dev);
> +
> +    if (s->num_lines < 1 || s->num_lines >= MAX_SHARED_LINES) {
> +        error_setg(errp,
> +                   "IRQ shared number of lines %d must be between 1 and %d",
> +                   s->num_lines, MAX_SHARED_LINES);
> +        return;
> +    }
> +
> +    qdev_init_gpio_in(dev, shared_irq_handler, s->num_lines);
> +}
> +
> +static const Property shared_irq_properties[] = {
> +    DEFINE_PROP_UINT8("num-lines", SharedIRQ, num_lines, 1),
> +};
> +
> +static const VMStateDescription shared_irq_vmstate = {
> +    .name = TYPE_SHARED_IRQ,
> +    .version_id = 1,
> +    .minimum_version_id = 1,
> +    .fields = (const VMStateField[]) {
> +        VMSTATE_UINT16(irq_states, SharedIRQ),
> +        VMSTATE_END_OF_LIST()
> +    },
> +};
> +
> +static void shared_irq_class_init(ObjectClass *klass, void *data)
> +{
> +    DeviceClass *dc = DEVICE_CLASS(klass);
> +
> +    /* No state to reset */
> +    device_class_set_props(dc, shared_irq_properties);
> +    dc->vmsd = &shared_irq_vmstate;
> +    dc->realize = shared_irq_realize;
> +
> +    /* Reason: Needs to be wired up to work */
> +    dc->user_creatable = false;
> +}
> +
> +static const TypeInfo shared_irq_types[] = {
> +    {
> +       .name = TYPE_SHARED_IRQ,
> +       .parent = TYPE_DEVICE,
> +       .instance_size = sizeof(SharedIRQ),
> +       .instance_init = shared_irq_init,
> +       .class_init = shared_irq_class_init,
> +    },
> +};
> +
> +DEFINE_TYPES(shared_irq_types)
> diff --git a/hw/core/Kconfig b/hw/core/Kconfig
> index d1bdf765ee..ddff977963 100644
> --- a/hw/core/Kconfig
> +++ b/hw/core/Kconfig
> @@ -32,6 +32,9 @@ config PLATFORM_BUS
> config REGISTER
>     bool
>
> +config SHARED_IRQ
> +    bool
> +
> config SPLIT_IRQ
>     bool
>
> diff --git a/hw/core/meson.build b/hw/core/meson.build
> index ce9dfa3f4b..6b5bdc8ec7 100644
> --- a/hw/core/meson.build
> +++ b/hw/core/meson.build
> @@ -21,6 +21,7 @@ system_ss.add(when: 'CONFIG_OR_IRQ', if_true: files('or-irq.c'))
> system_ss.add(when: 'CONFIG_PLATFORM_BUS', if_true: files('platform-bus.c'))
> system_ss.add(when: 'CONFIG_PTIMER', if_true: files('ptimer.c'))
> system_ss.add(when: 'CONFIG_REGISTER', if_true: files('register.c'))
> +system_ss.add(when: 'CONFIG_SHARED_IRQ', if_true: files('shared-irq.c'))
> system_ss.add(when: 'CONFIG_SPLIT_IRQ', if_true: files('split-irq.c'))
> system_ss.add(when: 'CONFIG_XILINX_AXI', if_true: files('stream.c'))
> system_ss.add(when: 'CONFIG_PLATFORM_BUS', if_true: files('sysbus-fdt.c'))
>
Bernhard Beschow Jan. 8, 2025, 2:26 p.m. UTC | #2
Am 8. Januar 2025 09:25:28 UTC schrieb Bernhard Beschow <shentey@gmail.com>:
>Signed-off-by: Bernhard Beschow <shentey@gmail.com>
>---
> include/hw/core/shared-irq.h | 39 ++++++++++++++++
> hw/core/shared-irq.c         | 88 ++++++++++++++++++++++++++++++++++++
> hw/core/Kconfig              |  3 ++
> hw/core/meson.build          |  1 +
> 4 files changed, 131 insertions(+)
> create mode 100644 include/hw/core/shared-irq.h
> create mode 100644 hw/core/shared-irq.c
>
>diff --git a/include/hw/core/shared-irq.h b/include/hw/core/shared-irq.h
>new file mode 100644

As pointed out by David, this device is redundant to TYPE_OR_IRQ. I'll drop it in v2.

Best regards,
Bernhard

>index 0000000000..803c303dd0
>--- /dev/null
>+++ b/include/hw/core/shared-irq.h
>@@ -0,0 +1,39 @@
>+/*
>+ * IRQ sharing device.
>+ *
>+ * Copyright (c) 2025 Bernhard Beschow <shentey@gmail.com>
>+ *
>+ * SPDX-License-Identifier: GPL-2.0-or-later
>+ */
>+
>+/*
>+ * This is a simple device which has one GPIO output line and multiple GPIO
>+ * input lines. The output line is active if at least one of the input lines is.
>+ *
>+ * QEMU interface:
>+ *  + N unnamed GPIO inputs: the input lines
>+ *  + one unnamed GPIO output: the output line
>+ *  + QOM property "num-lines": sets the number of input lines
>+ */
>+#ifndef HW_SHARED_IRQ_H
>+#define HW_SHARED_IRQ_H
>+
>+#include "hw/sysbus.h"
>+#include "qom/object.h"
>+
>+#define TYPE_SHARED_IRQ "shared-irq"
>+
>+#define MAX_SHARED_LINES 16
>+
>+
>+OBJECT_DECLARE_SIMPLE_TYPE(SharedIRQ, SHARED_IRQ)
>+
>+struct SharedIRQ {
>+    DeviceState parent_obj;
>+
>+    qemu_irq out_irq;
>+    uint16_t irq_states;
>+    uint8_t num_lines;
>+};
>+
>+#endif
>diff --git a/hw/core/shared-irq.c b/hw/core/shared-irq.c
>new file mode 100644
>index 0000000000..b2a4ea4a66
>--- /dev/null
>+++ b/hw/core/shared-irq.c
>@@ -0,0 +1,88 @@
>+/*
>+ * IRQ sharing device.
>+ *
>+ * Copyright (c) 2025 Bernhard Beschow <shentey@gmail.com>
>+ *
>+ * SPDX-License-Identifier: GPL-2.0-or-later
>+ */
>+
>+#include "qemu/osdep.h"
>+#include "hw/core/shared-irq.h"
>+#include "hw/irq.h"
>+#include "hw/qdev-properties.h"
>+#include "qapi/error.h"
>+#include "migration/vmstate.h"
>+
>+static void shared_irq_handler(void *opaque, int n, int level)
>+{
>+    SharedIRQ *s = opaque;
>+    uint16_t mask = BIT(n);
>+
>+    if (level) {
>+        s->irq_states |= mask;
>+    } else {
>+        s->irq_states &= ~mask;
>+    }
>+
>+    qemu_set_irq(s->out_irq, !!s->irq_states);
>+}
>+
>+static void shared_irq_init(Object *obj)
>+{
>+    SharedIRQ *s = SHARED_IRQ(obj);
>+
>+    qdev_init_gpio_out(DEVICE(s), &s->out_irq, 1);
>+}
>+
>+static void shared_irq_realize(DeviceState *dev, Error **errp)
>+{
>+    SharedIRQ *s = SHARED_IRQ(dev);
>+
>+    if (s->num_lines < 1 || s->num_lines >= MAX_SHARED_LINES) {
>+        error_setg(errp,
>+                   "IRQ shared number of lines %d must be between 1 and %d",
>+                   s->num_lines, MAX_SHARED_LINES);
>+        return;
>+    }
>+
>+    qdev_init_gpio_in(dev, shared_irq_handler, s->num_lines);
>+}
>+
>+static const Property shared_irq_properties[] = {
>+    DEFINE_PROP_UINT8("num-lines", SharedIRQ, num_lines, 1),
>+};
>+
>+static const VMStateDescription shared_irq_vmstate = {
>+    .name = TYPE_SHARED_IRQ,
>+    .version_id = 1,
>+    .minimum_version_id = 1,
>+    .fields = (const VMStateField[]) {
>+        VMSTATE_UINT16(irq_states, SharedIRQ),
>+        VMSTATE_END_OF_LIST()
>+    },
>+};
>+
>+static void shared_irq_class_init(ObjectClass *klass, void *data)
>+{
>+    DeviceClass *dc = DEVICE_CLASS(klass);
>+
>+    /* No state to reset */
>+    device_class_set_props(dc, shared_irq_properties);
>+    dc->vmsd = &shared_irq_vmstate;
>+    dc->realize = shared_irq_realize;
>+
>+    /* Reason: Needs to be wired up to work */
>+    dc->user_creatable = false;
>+}
>+
>+static const TypeInfo shared_irq_types[] = {
>+    {
>+       .name = TYPE_SHARED_IRQ,
>+       .parent = TYPE_DEVICE,
>+       .instance_size = sizeof(SharedIRQ),
>+       .instance_init = shared_irq_init,
>+       .class_init = shared_irq_class_init,
>+    },
>+};
>+
>+DEFINE_TYPES(shared_irq_types)
>diff --git a/hw/core/Kconfig b/hw/core/Kconfig
>index d1bdf765ee..ddff977963 100644
>--- a/hw/core/Kconfig
>+++ b/hw/core/Kconfig
>@@ -32,6 +32,9 @@ config PLATFORM_BUS
> config REGISTER
>     bool
> 
>+config SHARED_IRQ
>+    bool
>+
> config SPLIT_IRQ
>     bool
> 
>diff --git a/hw/core/meson.build b/hw/core/meson.build
>index ce9dfa3f4b..6b5bdc8ec7 100644
>--- a/hw/core/meson.build
>+++ b/hw/core/meson.build
>@@ -21,6 +21,7 @@ system_ss.add(when: 'CONFIG_OR_IRQ', if_true: files('or-irq.c'))
> system_ss.add(when: 'CONFIG_PLATFORM_BUS', if_true: files('platform-bus.c'))
> system_ss.add(when: 'CONFIG_PTIMER', if_true: files('ptimer.c'))
> system_ss.add(when: 'CONFIG_REGISTER', if_true: files('register.c'))
>+system_ss.add(when: 'CONFIG_SHARED_IRQ', if_true: files('shared-irq.c'))
> system_ss.add(when: 'CONFIG_SPLIT_IRQ', if_true: files('split-irq.c'))
> system_ss.add(when: 'CONFIG_XILINX_AXI', if_true: files('stream.c'))
> system_ss.add(when: 'CONFIG_PLATFORM_BUS', if_true: files('sysbus-fdt.c'))
Bernhard Beschow Jan. 9, 2025, 9:14 a.m. UTC | #3
Am 8. Januar 2025 13:53:02 UTC schrieb BALATON Zoltan <balaton@eik.bme.hu>:
>On Wed, 8 Jan 2025, Bernhard Beschow wrote:
>> Signed-off-by: Bernhard Beschow <shentey@gmail.com>
>> ---
>> include/hw/core/shared-irq.h | 39 ++++++++++++++++
>> hw/core/shared-irq.c         | 88 ++++++++++++++++++++++++++++++++++++
>> hw/core/Kconfig              |  3 ++
>> hw/core/meson.build          |  1 +
>> 4 files changed, 131 insertions(+)
>> create mode 100644 include/hw/core/shared-irq.h
>> create mode 100644 hw/core/shared-irq.c
>> 
>> diff --git a/include/hw/core/shared-irq.h b/include/hw/core/shared-irq.h
>> new file mode 100644
>> index 0000000000..803c303dd0
>> --- /dev/null
>> +++ b/include/hw/core/shared-irq.h
>> @@ -0,0 +1,39 @@
>> +/*
>> + * IRQ sharing device.
>> + *
>> + * Copyright (c) 2025 Bernhard Beschow <shentey@gmail.com>
>> + *
>> + * SPDX-License-Identifier: GPL-2.0-or-later
>> + */
>> +
>> +/*
>> + * This is a simple device which has one GPIO output line and multiple GPIO
>> + * input lines. The output line is active if at least one of the input lines is.
>
>How is this different from TYPE_OR_IRQ. Also or-irq.h is in include/hw/or-irq.h not in include/hw/core/ where split-irq.h is

I missed exactly that.

> so maybe these could all be moved to one place for consistency? Or-irq also has a reset method, do you need one in this device?

TYPE_OR_IRQ should be used instead. The next iteration will use it and this patch will be dropped.

Best regards,
Bernhard

>
>Regards,
>BALATON Zoltan
>
>> + *
>> + * QEMU interface:
>> + *  + N unnamed GPIO inputs: the input lines
>> + *  + one unnamed GPIO output: the output line
>> + *  + QOM property "num-lines": sets the number of input lines
>> + */
>> +#ifndef HW_SHARED_IRQ_H
>> +#define HW_SHARED_IRQ_H
>> +
>> +#include "hw/sysbus.h"
>> +#include "qom/object.h"
>> +
>> +#define TYPE_SHARED_IRQ "shared-irq"
>> +
>> +#define MAX_SHARED_LINES 16
>> +
>> +
>> +OBJECT_DECLARE_SIMPLE_TYPE(SharedIRQ, SHARED_IRQ)
>> +
>> +struct SharedIRQ {
>> +    DeviceState parent_obj;
>> +
>> +    qemu_irq out_irq;
>> +    uint16_t irq_states;
>> +    uint8_t num_lines;
>> +};
>> +
>> +#endif
>> diff --git a/hw/core/shared-irq.c b/hw/core/shared-irq.c
>> new file mode 100644
>> index 0000000000..b2a4ea4a66
>> --- /dev/null
>> +++ b/hw/core/shared-irq.c
>> @@ -0,0 +1,88 @@
>> +/*
>> + * IRQ sharing device.
>> + *
>> + * Copyright (c) 2025 Bernhard Beschow <shentey@gmail.com>
>> + *
>> + * SPDX-License-Identifier: GPL-2.0-or-later
>> + */
>> +
>> +#include "qemu/osdep.h"
>> +#include "hw/core/shared-irq.h"
>> +#include "hw/irq.h"
>> +#include "hw/qdev-properties.h"
>> +#include "qapi/error.h"
>> +#include "migration/vmstate.h"
>> +
>> +static void shared_irq_handler(void *opaque, int n, int level)
>> +{
>> +    SharedIRQ *s = opaque;
>> +    uint16_t mask = BIT(n);
>> +
>> +    if (level) {
>> +        s->irq_states |= mask;
>> +    } else {
>> +        s->irq_states &= ~mask;
>> +    }
>> +
>> +    qemu_set_irq(s->out_irq, !!s->irq_states);
>> +}
>> +
>> +static void shared_irq_init(Object *obj)
>> +{
>> +    SharedIRQ *s = SHARED_IRQ(obj);
>> +
>> +    qdev_init_gpio_out(DEVICE(s), &s->out_irq, 1);
>> +}
>> +
>> +static void shared_irq_realize(DeviceState *dev, Error **errp)
>> +{
>> +    SharedIRQ *s = SHARED_IRQ(dev);
>> +
>> +    if (s->num_lines < 1 || s->num_lines >= MAX_SHARED_LINES) {
>> +        error_setg(errp,
>> +                   "IRQ shared number of lines %d must be between 1 and %d",
>> +                   s->num_lines, MAX_SHARED_LINES);
>> +        return;
>> +    }
>> +
>> +    qdev_init_gpio_in(dev, shared_irq_handler, s->num_lines);
>> +}
>> +
>> +static const Property shared_irq_properties[] = {
>> +    DEFINE_PROP_UINT8("num-lines", SharedIRQ, num_lines, 1),
>> +};
>> +
>> +static const VMStateDescription shared_irq_vmstate = {
>> +    .name = TYPE_SHARED_IRQ,
>> +    .version_id = 1,
>> +    .minimum_version_id = 1,
>> +    .fields = (const VMStateField[]) {
>> +        VMSTATE_UINT16(irq_states, SharedIRQ),
>> +        VMSTATE_END_OF_LIST()
>> +    },
>> +};
>> +
>> +static void shared_irq_class_init(ObjectClass *klass, void *data)
>> +{
>> +    DeviceClass *dc = DEVICE_CLASS(klass);
>> +
>> +    /* No state to reset */
>> +    device_class_set_props(dc, shared_irq_properties);
>> +    dc->vmsd = &shared_irq_vmstate;
>> +    dc->realize = shared_irq_realize;
>> +
>> +    /* Reason: Needs to be wired up to work */
>> +    dc->user_creatable = false;
>> +}
>> +
>> +static const TypeInfo shared_irq_types[] = {
>> +    {
>> +       .name = TYPE_SHARED_IRQ,
>> +       .parent = TYPE_DEVICE,
>> +       .instance_size = sizeof(SharedIRQ),
>> +       .instance_init = shared_irq_init,
>> +       .class_init = shared_irq_class_init,
>> +    },
>> +};
>> +
>> +DEFINE_TYPES(shared_irq_types)
>> diff --git a/hw/core/Kconfig b/hw/core/Kconfig
>> index d1bdf765ee..ddff977963 100644
>> --- a/hw/core/Kconfig
>> +++ b/hw/core/Kconfig
>> @@ -32,6 +32,9 @@ config PLATFORM_BUS
>> config REGISTER
>>     bool
>> 
>> +config SHARED_IRQ
>> +    bool
>> +
>> config SPLIT_IRQ
>>     bool
>> 
>> diff --git a/hw/core/meson.build b/hw/core/meson.build
>> index ce9dfa3f4b..6b5bdc8ec7 100644
>> --- a/hw/core/meson.build
>> +++ b/hw/core/meson.build
>> @@ -21,6 +21,7 @@ system_ss.add(when: 'CONFIG_OR_IRQ', if_true: files('or-irq.c'))
>> system_ss.add(when: 'CONFIG_PLATFORM_BUS', if_true: files('platform-bus.c'))
>> system_ss.add(when: 'CONFIG_PTIMER', if_true: files('ptimer.c'))
>> system_ss.add(when: 'CONFIG_REGISTER', if_true: files('register.c'))
>> +system_ss.add(when: 'CONFIG_SHARED_IRQ', if_true: files('shared-irq.c'))
>> system_ss.add(when: 'CONFIG_SPLIT_IRQ', if_true: files('split-irq.c'))
>> system_ss.add(when: 'CONFIG_XILINX_AXI', if_true: files('stream.c'))
>> system_ss.add(when: 'CONFIG_PLATFORM_BUS', if_true: files('sysbus-fdt.c'))
>>
David Woodhouse Jan. 9, 2025, 11:43 a.m. UTC | #4
On Wed, 2025-01-08 at 14:26 +0000, Bernhard Beschow wrote:
> 
> 
> Am 8. Januar 2025 09:25:28 UTC schrieb Bernhard Beschow <shentey@gmail.com>:
> > Signed-off-by: Bernhard Beschow <shentey@gmail.com>
> > ---
> > include/hw/core/shared-irq.h | 39 ++++++++++++++++
> > hw/core/shared-irq.c         | 88 ++++++++++++++++++++++++++++++++++++
> > hw/core/Kconfig              |  3 ++
> > hw/core/meson.build          |  1 +
> > 4 files changed, 131 insertions(+)
> > create mode 100644 include/hw/core/shared-irq.h
> > create mode 100644 hw/core/shared-irq.c
> > 
> > diff --git a/include/hw/core/shared-irq.h b/include/hw/core/shared-irq.h
> > new file mode 100644
> 
> As pointed out by David, this device is redundant to TYPE_OR_IRQ. I'll drop it in v2.

Of course, I'd much rather *fix* it to do a reverse callback up the
chain when the line is deasserted, to check whether any sources want it
to be asserted again. That's the only way we can do VFIO level
interrupts properly.
diff mbox series

Patch

diff --git a/include/hw/core/shared-irq.h b/include/hw/core/shared-irq.h
new file mode 100644
index 0000000000..803c303dd0
--- /dev/null
+++ b/include/hw/core/shared-irq.h
@@ -0,0 +1,39 @@ 
+/*
+ * IRQ sharing device.
+ *
+ * Copyright (c) 2025 Bernhard Beschow <shentey@gmail.com>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+/*
+ * This is a simple device which has one GPIO output line and multiple GPIO
+ * input lines. The output line is active if at least one of the input lines is.
+ *
+ * QEMU interface:
+ *  + N unnamed GPIO inputs: the input lines
+ *  + one unnamed GPIO output: the output line
+ *  + QOM property "num-lines": sets the number of input lines
+ */
+#ifndef HW_SHARED_IRQ_H
+#define HW_SHARED_IRQ_H
+
+#include "hw/sysbus.h"
+#include "qom/object.h"
+
+#define TYPE_SHARED_IRQ "shared-irq"
+
+#define MAX_SHARED_LINES 16
+
+
+OBJECT_DECLARE_SIMPLE_TYPE(SharedIRQ, SHARED_IRQ)
+
+struct SharedIRQ {
+    DeviceState parent_obj;
+
+    qemu_irq out_irq;
+    uint16_t irq_states;
+    uint8_t num_lines;
+};
+
+#endif
diff --git a/hw/core/shared-irq.c b/hw/core/shared-irq.c
new file mode 100644
index 0000000000..b2a4ea4a66
--- /dev/null
+++ b/hw/core/shared-irq.c
@@ -0,0 +1,88 @@ 
+/*
+ * IRQ sharing device.
+ *
+ * Copyright (c) 2025 Bernhard Beschow <shentey@gmail.com>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "hw/core/shared-irq.h"
+#include "hw/irq.h"
+#include "hw/qdev-properties.h"
+#include "qapi/error.h"
+#include "migration/vmstate.h"
+
+static void shared_irq_handler(void *opaque, int n, int level)
+{
+    SharedIRQ *s = opaque;
+    uint16_t mask = BIT(n);
+
+    if (level) {
+        s->irq_states |= mask;
+    } else {
+        s->irq_states &= ~mask;
+    }
+
+    qemu_set_irq(s->out_irq, !!s->irq_states);
+}
+
+static void shared_irq_init(Object *obj)
+{
+    SharedIRQ *s = SHARED_IRQ(obj);
+
+    qdev_init_gpio_out(DEVICE(s), &s->out_irq, 1);
+}
+
+static void shared_irq_realize(DeviceState *dev, Error **errp)
+{
+    SharedIRQ *s = SHARED_IRQ(dev);
+
+    if (s->num_lines < 1 || s->num_lines >= MAX_SHARED_LINES) {
+        error_setg(errp,
+                   "IRQ shared number of lines %d must be between 1 and %d",
+                   s->num_lines, MAX_SHARED_LINES);
+        return;
+    }
+
+    qdev_init_gpio_in(dev, shared_irq_handler, s->num_lines);
+}
+
+static const Property shared_irq_properties[] = {
+    DEFINE_PROP_UINT8("num-lines", SharedIRQ, num_lines, 1),
+};
+
+static const VMStateDescription shared_irq_vmstate = {
+    .name = TYPE_SHARED_IRQ,
+    .version_id = 1,
+    .minimum_version_id = 1,
+    .fields = (const VMStateField[]) {
+        VMSTATE_UINT16(irq_states, SharedIRQ),
+        VMSTATE_END_OF_LIST()
+    },
+};
+
+static void shared_irq_class_init(ObjectClass *klass, void *data)
+{
+    DeviceClass *dc = DEVICE_CLASS(klass);
+
+    /* No state to reset */
+    device_class_set_props(dc, shared_irq_properties);
+    dc->vmsd = &shared_irq_vmstate;
+    dc->realize = shared_irq_realize;
+
+    /* Reason: Needs to be wired up to work */
+    dc->user_creatable = false;
+}
+
+static const TypeInfo shared_irq_types[] = {
+    {
+       .name = TYPE_SHARED_IRQ,
+       .parent = TYPE_DEVICE,
+       .instance_size = sizeof(SharedIRQ),
+       .instance_init = shared_irq_init,
+       .class_init = shared_irq_class_init,
+    },
+};
+
+DEFINE_TYPES(shared_irq_types)
diff --git a/hw/core/Kconfig b/hw/core/Kconfig
index d1bdf765ee..ddff977963 100644
--- a/hw/core/Kconfig
+++ b/hw/core/Kconfig
@@ -32,6 +32,9 @@  config PLATFORM_BUS
 config REGISTER
     bool
 
+config SHARED_IRQ
+    bool
+
 config SPLIT_IRQ
     bool
 
diff --git a/hw/core/meson.build b/hw/core/meson.build
index ce9dfa3f4b..6b5bdc8ec7 100644
--- a/hw/core/meson.build
+++ b/hw/core/meson.build
@@ -21,6 +21,7 @@  system_ss.add(when: 'CONFIG_OR_IRQ', if_true: files('or-irq.c'))
 system_ss.add(when: 'CONFIG_PLATFORM_BUS', if_true: files('platform-bus.c'))
 system_ss.add(when: 'CONFIG_PTIMER', if_true: files('ptimer.c'))
 system_ss.add(when: 'CONFIG_REGISTER', if_true: files('register.c'))
+system_ss.add(when: 'CONFIG_SHARED_IRQ', if_true: files('shared-irq.c'))
 system_ss.add(when: 'CONFIG_SPLIT_IRQ', if_true: files('split-irq.c'))
 system_ss.add(when: 'CONFIG_XILINX_AXI', if_true: files('stream.c'))
 system_ss.add(when: 'CONFIG_PLATFORM_BUS', if_true: files('sysbus-fdt.c'))