Commit d0ec544d6b for qemu.org

commit d0ec544d6b3c2ed01dc82f53e5898e12b6cafa5f
Author: Gilles Grimaud <gilles.grimaud@univ-lille.fr>
Date:   Mon Sep 14 21:23:17 2026 +0200

    target/arm: enforce Armv6-M NVIC limits

    Armv6-M supports no more than 32 external interrupts. Its ICTR and Active
    Bit Register locations are reserved, and Cortex-M0 implements two priority
    bits.

    Reject Armv6-M NVIC configurations with more than 32 external interrupts.
    Add a check-tcg test that verifies the reserved register behavior and
    priority masking on the microbit machine.

    Signed-off-by: Gilles Grimaud <gilles.grimaud@univ-lille.fr>
    Reviewed-by: Peter Maydell <peter.maydell@linaro.org>
    Signed-off-by: Peter Maydell <peter.maydell@linaro.org>

diff --git a/hw/intc/armv7m_nvic.c b/hw/intc/armv7m_nvic.c
index a7651f831e..b1d27dfb32 100644
--- a/hw/intc/armv7m_nvic.c
+++ b/hw/intc/armv7m_nvic.c
@@ -2728,6 +2728,11 @@ static void armv7m_nvic_realize(DeviceState *dev, Error **errp)
         return;
     }

+    if (!arm_feature(&s->cpu->env, ARM_FEATURE_V7) && s->num_irq > 32) {
+        error_setg(errp, "Armv6-M NVIC cannot exceed 32 external IRQs");
+        return;
+    }
+
     qdev_init_gpio_in(dev, set_irq_level, s->num_irq);

     /* include space for internal exception vectors */
diff --git a/tests/tcg/arm/system/meson.build b/tests/tcg/arm/system/meson.build
index 4c77b9c3d6..5286997670 100644
--- a/tests/tcg/arm/system/meson.build
+++ b/tests/tcg/arm/system/meson.build
@@ -39,6 +39,12 @@ tests += {
                '-T', files('test-armv6m-undef.ld')],
     'qemu_args': ['-M', 'microbit', qemu_base_args],
   },
+  'test-armv6m-nvic.S': {
+    'cflags': ['-mcpu=cortex-m0', '-mfloat-abi=soft', '-nostdlib',
+               '-Wl,--build-id=none',
+               '-T', files('test-armv6m-undef.ld')],
+    'qemu_args': ['-M', 'microbit', qemu_base_args],
+  },
   'semiconsole.c': {
     'cflags': cflags,
     'qemu_args': ['-serial', 'none', '-chardev', 'stdio,mux=on,id=stdio0',
diff --git a/tests/tcg/arm/system/test-armv6m-nvic.S b/tests/tcg/arm/system/test-armv6m-nvic.S
new file mode 100644
index 0000000000..ecb29eaa3d
--- /dev/null
+++ b/tests/tcg/arm/system/test-armv6m-nvic.S
@@ -0,0 +1,70 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/* Test Armv6-M NVIC register behavior. */
+
+.syntax unified
+.cpu cortex-m0
+.thumb
+
+#define SRAM_BASE 0x20000000
+#define SRAM_SIZE (16 * 1024)
+
+#define NVIC_ICTR  0xe000e004
+#define NVIC_IABR0 0xe000e300
+#define NVIC_IPR0  0xe000e400
+
+#define semihosting_call bkpt 0xab
+#define SYS_EXIT 0x18
+
+vector_table:
+    .word SRAM_BASE + SRAM_SIZE
+    .word reset + 1
+    .word 0
+    .word failure + 1
+    .rept 44
+    .word 0
+    .endr
+
+.equ exc_reset_thumb, reset + 1
+.global exc_reset_thumb
+reset:
+    /* ICTR is reserved and reads as zero on Armv6-M. */
+    ldr r0, =NVIC_ICTR
+    ldr r1, [r0]
+    cmp r1, 0
+    bne failure
+
+    /* The Active Bit Register is reserved and behaves as RAZ/WI. */
+    ldr r0, =NVIC_IABR0
+    movs r1, 1
+    str r1, [r0]
+    ldr r1, [r0]
+    cmp r1, 0
+    bne failure
+
+    /* Cortex-M0 implements the two most significant priority bits. */
+    ldr r0, =NVIC_IPR0
+    movs r1, 0xff
+    strb r1, [r0]
+    ldrb r1, [r0]
+    cmp r1, 0xc0
+    bne failure
+
+success:
+    movs r0, 1
+    b exit
+
+failure:
+    movs r0, 0
+
+exit:
+    movs r1, 0
+    cmp r0, 1
+    bne 1f
+    ldr r1, ADP_Stopped_ApplicationExit
+1:
+    movs r0, SYS_EXIT
+    semihosting_call
+
+.align 2
+ADP_Stopped_ApplicationExit:
+    .word 0x20026