diff mbox series

[v5,2/3] tests/qtest: Add STM32L4x5 EXTI QTest testcase

Message ID 20231228161944.303768-3-ines.varhol@telecom-paris.fr
State New
Headers show
Series Add device STM32L4x5 EXTI | expand

Commit Message

Inès Varhol Dec. 28, 2023, 4:19 p.m. UTC
Signed-off-by: Arnaud Minier <arnaud.minier@telecom-paris.fr>
Signed-off-by: Inès Varhol <ines.varhol@telecom-paris.fr>
---
 tests/qtest/meson.build           |   5 +
 tests/qtest/stm32l4x5_exti-test.c | 596 ++++++++++++++++++++++++++++++
 2 files changed, 601 insertions(+)
 create mode 100644 tests/qtest/stm32l4x5_exti-test.c

Comments

Alistair Francis Jan. 4, 2024, 3:39 a.m. UTC | #1
On Fri, Dec 29, 2023 at 3:34 AM Inès Varhol
<ines.varhol@telecom-paris.fr> wrote:
>
> Signed-off-by: Arnaud Minier <arnaud.minier@telecom-paris.fr>
> Signed-off-by: Inès Varhol <ines.varhol@telecom-paris.fr>

Acked-by: Alistair Francis <alistair.francis@wdc.com>

Alistair

> ---
>  tests/qtest/meson.build           |   5 +
>  tests/qtest/stm32l4x5_exti-test.c | 596 ++++++++++++++++++++++++++++++
>  2 files changed, 601 insertions(+)
>  create mode 100644 tests/qtest/stm32l4x5_exti-test.c
>
> diff --git a/tests/qtest/meson.build b/tests/qtest/meson.build
> index 47dabf91d0..d5126f4d86 100644
> --- a/tests/qtest/meson.build
> +++ b/tests/qtest/meson.build
> @@ -194,6 +194,10 @@ qtests_aspeed = \
>    ['aspeed_hace-test',
>     'aspeed_smc-test',
>     'aspeed_gpio-test']
> +
> +qtests_stm32l4x5 = \
> +  ['stm32l4x5_exti-test']
> +
>  qtests_arm = \
>    (config_all_devices.has_key('CONFIG_MPS2') ? ['sse-timer-test'] : []) + \
>    (config_all_devices.has_key('CONFIG_CMSDK_APB_DUALTIMER') ? ['cmsdk-apb-dualtimer-test'] : []) + \
> @@ -207,6 +211,7 @@ qtests_arm = \
>    (config_all_devices.has_key('CONFIG_TPM_TIS_I2C') ? ['tpm-tis-i2c-test'] : []) + \
>    (config_all_devices.has_key('CONFIG_VEXPRESS') ? ['test-arm-mptimer'] : []) + \
>    (config_all_devices.has_key('CONFIG_MICROBIT') ? ['microbit-test'] : []) + \
> +  (config_all_devices.has_key('CONFIG_STM32L4X5_SOC') ? qtests_stm32l4x5 : []) + \
>    ['arm-cpu-features',
>     'boot-serial-test']
>
> diff --git a/tests/qtest/stm32l4x5_exti-test.c b/tests/qtest/stm32l4x5_exti-test.c
> new file mode 100644
> index 0000000000..60c8297246
> --- /dev/null
> +++ b/tests/qtest/stm32l4x5_exti-test.c
> @@ -0,0 +1,596 @@
> +/*
> + * QTest testcase for STM32L4x5_EXTI
> + *
> + * Copyright (c) 2023 Arnaud Minier <arnaud.minier@telecom-paris.fr>
> + * Copyright (c) 2023 Inès Varhol <ines.varhol@telecom-paris.fr>
> + *
> + * This work is licensed under the terms of the GNU GPL, version 2 or later.
> + * See the COPYING file in the top-level directory.
> + */
> +
> +#include "qemu/osdep.h"
> +#include "libqtest-single.h"
> +
> +#define EXTI_BASE_ADDR 0x40010400
> +#define EXTI_IMR1 0x00
> +#define EXTI_EMR1 0x04
> +#define EXTI_RTSR1 0x08
> +#define EXTI_FTSR1 0x0C
> +#define EXTI_SWIER1 0x10
> +#define EXTI_PR1 0x14
> +#define EXTI_IMR2 0x20
> +#define EXTI_EMR2 0x24
> +#define EXTI_RTSR2 0x28
> +#define EXTI_FTSR2 0x2C
> +#define EXTI_SWIER2 0x30
> +#define EXTI_PR2 0x34
> +
> +#define NVIC_ISER 0xE000E100
> +#define NVIC_ISPR 0xE000E200
> +#define NVIC_ICPR 0xE000E280
> +
> +#define EXTI0_IRQ 6
> +#define EXTI1_IRQ 7
> +#define EXTI35_IRQ 1
> +
> +static void enable_nvic_irq(unsigned int n)
> +{
> +    writel(NVIC_ISER, 1 << n);
> +}
> +
> +static void unpend_nvic_irq(unsigned int n)
> +{
> +    writel(NVIC_ICPR, 1 << n);
> +}
> +
> +static bool check_nvic_pending(unsigned int n)
> +{
> +    return readl(NVIC_ISPR) & (1 << n);
> +}
> +
> +static void exti_writel(unsigned int offset, uint32_t value)
> +{
> +    writel(EXTI_BASE_ADDR + offset, value);
> +}
> +
> +static uint32_t exti_readl(unsigned int offset)
> +{
> +    return readl(EXTI_BASE_ADDR + offset);
> +}
> +
> +static void test_reg_write_read(void)
> +{
> +    /* Test that non-reserved bits in xMR and xTSR can be set and cleared */
> +
> +    exti_writel(EXTI_IMR1, 0xFFFFFFFF);
> +    uint32_t imr1 = exti_readl(EXTI_IMR1);
> +    g_assert_cmpuint(imr1, ==, 0xFFFFFFFF);
> +    exti_writel(EXTI_IMR1, 0x00000000);
> +    imr1 = exti_readl(EXTI_IMR1);
> +    g_assert_cmpuint(imr1, ==, 0x00000000);
> +
> +    exti_writel(EXTI_EMR1, 0xFFFFFFFF);
> +    uint32_t emr1 = exti_readl(EXTI_EMR1);
> +    g_assert_cmpuint(emr1, ==, 0xFFFFFFFF);
> +    exti_writel(EXTI_EMR1, 0x00000000);
> +    emr1 = exti_readl(EXTI_EMR1);
> +    g_assert_cmpuint(emr1, ==, 0x00000000);
> +
> +    exti_writel(EXTI_RTSR1, 0xFFFFFFFF);
> +    uint32_t rtsr1 = exti_readl(EXTI_RTSR1);
> +    g_assert_cmpuint(rtsr1, ==, 0x007DFFFF);
> +    exti_writel(EXTI_RTSR1, 0x00000000);
> +    rtsr1 = exti_readl(EXTI_RTSR1);
> +    g_assert_cmpuint(rtsr1, ==, 0x00000000);
> +
> +    exti_writel(EXTI_FTSR1, 0xFFFFFFFF);
> +    uint32_t ftsr1 = exti_readl(EXTI_FTSR1);
> +    g_assert_cmpuint(ftsr1, ==, 0x007DFFFF);
> +    exti_writel(EXTI_FTSR1, 0x00000000);
> +    ftsr1 = exti_readl(EXTI_FTSR1);
> +    g_assert_cmpuint(ftsr1, ==, 0x00000000);
> +
> +    exti_writel(EXTI_IMR2, 0xFFFFFFFF);
> +    uint32_t imr2 = exti_readl(EXTI_IMR2);
> +    g_assert_cmpuint(imr2, ==, 0x000000FF);
> +    exti_writel(EXTI_IMR2, 0x00000000);
> +    imr2 = exti_readl(EXTI_IMR2);
> +    g_assert_cmpuint(imr2, ==, 0x00000000);
> +
> +    exti_writel(EXTI_EMR2, 0xFFFFFFFF);
> +    uint32_t emr2 = exti_readl(EXTI_EMR2);
> +    g_assert_cmpuint(emr2, ==, 0x000000FF);
> +    exti_writel(EXTI_EMR2, 0x00000000);
> +    emr2 = exti_readl(EXTI_EMR2);
> +    g_assert_cmpuint(emr2, ==, 0x00000000);
> +
> +    exti_writel(EXTI_RTSR2, 0xFFFFFFFF);
> +    uint32_t rtsr2 = exti_readl(EXTI_RTSR2);
> +    g_assert_cmpuint(rtsr2, ==, 0x00000078);
> +    exti_writel(EXTI_RTSR2, 0x00000000);
> +    rtsr2 = exti_readl(EXTI_RTSR2);
> +    g_assert_cmpuint(rtsr2, ==, 0x00000000);
> +
> +    exti_writel(EXTI_FTSR2, 0xFFFFFFFF);
> +    uint32_t ftsr2 = exti_readl(EXTI_FTSR2);
> +    g_assert_cmpuint(ftsr2, ==, 0x00000078);
> +    exti_writel(EXTI_FTSR2, 0x00000000);
> +    ftsr2 = exti_readl(EXTI_FTSR2);
> +    g_assert_cmpuint(ftsr2, ==, 0x00000000);
> +}
> +
> +static void test_direct_lines_write(void)
> +{
> +    /* Test that direct lines reserved bits are not written to */
> +
> +    exti_writel(EXTI_RTSR1, 0xFF820000);
> +    uint32_t rtsr1 = exti_readl(EXTI_RTSR1);
> +    g_assert_cmpuint(rtsr1, ==, 0x00000000);
> +
> +    exti_writel(EXTI_FTSR1, 0xFF820000);
> +    uint32_t ftsr1 = exti_readl(EXTI_FTSR1);
> +    g_assert_cmpuint(ftsr1, ==, 0x00000000);
> +
> +    exti_writel(EXTI_SWIER1, 0xFF820000);
> +    uint32_t swier1 = exti_readl(EXTI_SWIER1);
> +    g_assert_cmpuint(swier1, ==, 0x00000000);
> +
> +    exti_writel(EXTI_PR1, 0xFF820000);
> +    uint32_t pr1 = exti_readl(EXTI_PR1);
> +    g_assert_cmpuint(pr1, ==, 0x00000000);
> +
> +    exti_writel(EXTI_RTSR2, 0x00000087);
> +    const uint32_t rtsr2 = exti_readl(EXTI_RTSR2);
> +    g_assert_cmpuint(rtsr2, ==, 0x00000000);
> +
> +    exti_writel(EXTI_FTSR2, 0x00000087);
> +    const uint32_t ftsr2 = exti_readl(EXTI_FTSR2);
> +    g_assert_cmpuint(ftsr2, ==, 0x00000000);
> +
> +    exti_writel(EXTI_SWIER2, 0x00000087);
> +    const uint32_t swier2 = exti_readl(EXTI_SWIER2);
> +    g_assert_cmpuint(swier2, ==, 0x00000000);
> +
> +    exti_writel(EXTI_PR2, 0x00000087);
> +    const uint32_t pr2 = exti_readl(EXTI_PR2);
> +    g_assert_cmpuint(pr2, ==, 0x00000000);
> +}
> +
> +static void test_reserved_bits_write(void)
> +{
> +    /* Test that reserved bits stay are not written to */
> +
> +    exti_writel(EXTI_IMR2, 0xFFFFFF00);
> +    uint32_t imr2 = exti_readl(EXTI_IMR2);
> +    g_assert_cmpuint(imr2, ==, 0x00000000);
> +
> +    exti_writel(EXTI_EMR2, 0xFFFFFF00);
> +    uint32_t emr2 = exti_readl(EXTI_EMR2);
> +    g_assert_cmpuint(emr2, ==, 0x00000000);
> +
> +    exti_writel(EXTI_RTSR2, 0xFFFFFF00);
> +    const uint32_t rtsr2 = exti_readl(EXTI_RTSR2);
> +    g_assert_cmpuint(rtsr2, ==, 0x00000000);
> +
> +    exti_writel(EXTI_FTSR2, 0xFFFFFF00);
> +    const uint32_t ftsr2 = exti_readl(EXTI_FTSR2);
> +    g_assert_cmpuint(ftsr2, ==, 0x00000000);
> +
> +    exti_writel(EXTI_SWIER2, 0xFFFFFF00);
> +    const uint32_t swier2 = exti_readl(EXTI_SWIER2);
> +    g_assert_cmpuint(swier2, ==, 0x00000000);
> +
> +    exti_writel(EXTI_PR2, 0xFFFFFF00);
> +    const uint32_t pr2 = exti_readl(EXTI_PR2);
> +    g_assert_cmpuint(pr2, ==, 0x00000000);
> +}
> +
> +static void test_software_interrupt(void)
> +{
> +    /*
> +     * Test that we can launch a software irq by :
> +     * - enabling its line in IMR
> +     * - and then setting a bit from '0' to '1' in SWIER
> +     *
> +     * And that the interruption stays pending in NVIC
> +     * even after clearing the pending bit in PR.
> +     */
> +
> +    /*
> +     * Testing interrupt line EXTI0
> +     * Bit 0 in EXTI_*1 registers (EXTI0) corresponds to GPIO Px_0
> +     */
> +
> +    enable_nvic_irq(EXTI0_IRQ);
> +    /* Check that there are no interrupts already pending in PR */
> +    uint32_t pr1 = exti_readl(EXTI_PR1);
> +    g_assert_cmpuint(pr1, ==, 0x00000000);
> +    /* Check that this specific interrupt isn't pending in NVIC */
> +    g_assert_false(check_nvic_pending(EXTI0_IRQ));
> +
> +    /* Enable interrupt line EXTI0 */
> +    exti_writel(EXTI_IMR1, 0x00000001);
> +    /* Set the right SWIER bit from '0' to '1' */
> +    exti_writel(EXTI_SWIER1, 0x00000000);
> +    exti_writel(EXTI_SWIER1, 0x00000001);
> +
> +    /* Check that the write in SWIER was effective */
> +    uint32_t swier1 = exti_readl(EXTI_SWIER1);
> +    g_assert_cmpuint(swier1, ==, 0x00000001);
> +    /* Check that the corresponding pending bit in PR is set */
> +    pr1 = exti_readl(EXTI_PR1);
> +    g_assert_cmpuint(pr1, ==, 0x00000001);
> +    /* Check that the corresponding interrupt is pending in the NVIC */
> +    g_assert_true(check_nvic_pending(EXTI0_IRQ));
> +
> +    /* Clear the pending bit in PR */
> +    exti_writel(EXTI_PR1, 0x00000001);
> +
> +    /* Check that the write in PR was effective */
> +    pr1 = exti_readl(EXTI_PR1);
> +    g_assert_cmpuint(pr1, ==, 0x00000000);
> +    /* Check that the corresponding bit in SWIER was cleared */
> +    swier1 = exti_readl(EXTI_SWIER1);
> +    g_assert_cmpuint(swier1, ==, 0x00000000);
> +    /* Check that the interrupt is still pending in the NVIC */
> +    g_assert_true(check_nvic_pending(EXTI0_IRQ));
> +
> +    /*
> +     * Testing interrupt line EXTI35
> +     * Bit 3 in EXTI_*2 registers (EXTI35) corresponds to PVM 1 Wakeup
> +     */
> +
> +    enable_nvic_irq(EXTI35_IRQ);
> +    /* Check that there are no interrupts already pending */
> +    uint32_t pr2 = exti_readl(EXTI_PR2);
> +    g_assert_cmpuint(pr2, ==, 0x00000000);
> +    g_assert_false(check_nvic_pending(EXTI35_IRQ));
> +
> +    /* Enable interrupt line EXTI0 */
> +    exti_writel(EXTI_IMR2, 0x00000008);
> +    /* Set the right SWIER bit from '0' to '1' */
> +    exti_writel(EXTI_SWIER2, 0x00000000);
> +    exti_writel(EXTI_SWIER2, 0x00000008);
> +
> +    /* Check that the write in SWIER was effective */
> +    uint32_t swier2 = exti_readl(EXTI_SWIER2);
> +    g_assert_cmpuint(swier2, ==, 0x00000008);
> +    /* Check that the corresponding pending bit in PR is set */
> +    pr2 = exti_readl(EXTI_PR2);
> +    g_assert_cmpuint(pr2, ==, 0x00000008);
> +    /* Check that the corresponding interrupt is pending in the NVIC */
> +    g_assert_true(check_nvic_pending(EXTI35_IRQ));
> +
> +    /* Clear the pending bit in PR */
> +    exti_writel(EXTI_PR2, 0x00000008);
> +
> +    /* Check that the write in PR was effective */
> +    pr2 = exti_readl(EXTI_PR2);
> +    g_assert_cmpuint(pr2, ==, 0x00000000);
> +    /* Check that the corresponding bit in SWIER was cleared */
> +    swier2 = exti_readl(EXTI_SWIER2);
> +    g_assert_cmpuint(swier2, ==, 0x00000000);
> +    /* Check that the interrupt is still pending in the NVIC */
> +    g_assert_true(check_nvic_pending(EXTI35_IRQ));
> +
> +    /* Clean NVIC */
> +    unpend_nvic_irq(EXTI0_IRQ);
> +    g_assert_false(check_nvic_pending(EXTI0_IRQ));
> +    unpend_nvic_irq(EXTI35_IRQ);
> +    g_assert_false(check_nvic_pending(EXTI35_IRQ));
> +}
> +
> +static void test_edge_selector(void)
> +{
> +    enable_nvic_irq(EXTI0_IRQ);
> +
> +    /* Configure EXTI line 0 irq on rising edge */
> +    qtest_set_irq_in(global_qtest, "/machine/unattached/device[0]/exti",
> +                     NULL, 0, 1);
> +    exti_writel(EXTI_IMR1, 0x00000001);
> +    exti_writel(EXTI_RTSR1, 0x00000001);
> +    exti_writel(EXTI_FTSR1, 0x00000000);
> +
> +    /* Test that an irq is raised on rising edge only */
> +    qtest_set_irq_in(global_qtest, "/machine/unattached/device[0]/exti",
> +                     NULL, 0, 0);
> +
> +    uint32_t pr1 = exti_readl(EXTI_PR1);
> +    g_assert_cmpuint(pr1, ==, 0x00000000);
> +    g_assert_false(check_nvic_pending(EXTI0_IRQ));
> +
> +    qtest_set_irq_in(global_qtest, "/machine/unattached/device[0]/exti",
> +                     NULL, 0, 1);
> +
> +    pr1 = exti_readl(EXTI_PR1);
> +    g_assert_cmpuint(pr1, ==, 0x00000001);
> +    g_assert_true(check_nvic_pending(EXTI0_IRQ));
> +
> +    /* Clean the test */
> +    exti_writel(EXTI_PR1, 0x00000001);
> +    pr1 = exti_readl(EXTI_PR1);
> +    g_assert_cmpuint(pr1, ==, 0x00000000);
> +    unpend_nvic_irq(EXTI0_IRQ);
> +    g_assert_false(check_nvic_pending(EXTI0_IRQ));
> +
> +    /* Configure EXTI line 0 irq on falling edge */
> +    qtest_set_irq_in(global_qtest, "/machine/unattached/device[0]/exti",
> +                     NULL, 0, 0);
> +    exti_writel(EXTI_IMR1, 0x00000001);
> +    exti_writel(EXTI_RTSR1, 0x00000000);
> +    exti_writel(EXTI_FTSR1, 0x00000001);
> +
> +    /* Test that an irq is raised on falling edge only */
> +    qtest_set_irq_in(global_qtest, "/machine/unattached/device[0]/exti",
> +                     NULL, 0, 1);
> +
> +    pr1 = exti_readl(EXTI_PR1);
> +    g_assert_cmpuint(pr1, ==, 0x00000000);
> +    g_assert_false(check_nvic_pending(EXTI0_IRQ));
> +
> +    qtest_set_irq_in(global_qtest, "/machine/unattached/device[0]/exti",
> +                     NULL, 0, 0);
> +
> +    pr1 = exti_readl(EXTI_PR1);
> +    g_assert_cmpuint(pr1, ==, 0x00000001);
> +    g_assert_true(check_nvic_pending(EXTI0_IRQ));
> +
> +    /* Clean the test */
> +    exti_writel(EXTI_PR1, 0x00000001);
> +    pr1 = exti_readl(EXTI_PR1);
> +    g_assert_cmpuint(pr1, ==, 0x00000000);
> +    unpend_nvic_irq(EXTI0_IRQ);
> +    g_assert_false(check_nvic_pending(EXTI0_IRQ));
> +
> +    /* Configure EXTI line 0 irq on falling and rising edge */
> +    exti_writel(EXTI_IMR1, 0x00000001);
> +    exti_writel(EXTI_RTSR1, 0x00000001);
> +    exti_writel(EXTI_FTSR1, 0x00000000);
> +
> +    /* Test that an irq is raised on rising and falling edge */
> +    qtest_set_irq_in(global_qtest, "/machine/unattached/device[0]/exti",
> +                     NULL, 0, 1);
> +
> +    pr1 = exti_readl(EXTI_PR1);
> +    g_assert_cmpuint(pr1, ==, 0x00000001);
> +    g_assert_true(check_nvic_pending(EXTI0_IRQ));
> +
> +    qtest_set_irq_in(global_qtest, "/machine/unattached/device[0]/exti",
> +                     NULL, 0, 0);
> +
> +    pr1 = exti_readl(EXTI_PR1);
> +    g_assert_cmpuint(pr1, ==, 0x00000001);
> +    g_assert_true(check_nvic_pending(EXTI0_IRQ));
> +
> +    /* Clean the test */
> +    exti_writel(EXTI_PR1, 0x00000001);
> +    pr1 = exti_readl(EXTI_PR1);
> +    g_assert_cmpuint(pr1, ==, 0x00000000);
> +    unpend_nvic_irq(EXTI0_IRQ);
> +    g_assert_false(check_nvic_pending(EXTI0_IRQ));
> +
> +    /* Configure EXTI line 0 irq without selecting an edge trigger */
> +    exti_writel(EXTI_IMR1, 0x00000001);
> +    exti_writel(EXTI_RTSR1, 0x00000000);
> +    exti_writel(EXTI_FTSR1, 0x00000000);
> +
> +    /* Test that no irq is raised */
> +    qtest_set_irq_in(global_qtest, "/machine/unattached/device[0]/exti",
> +                     NULL, 0, 1);
> +
> +    pr1 = exti_readl(EXTI_PR1);
> +    g_assert_cmpuint(pr1, ==, 0x00000000);
> +    g_assert_false(check_nvic_pending(EXTI0_IRQ));
> +
> +    qtest_set_irq_in(global_qtest, "/machine/unattached/device[0]/exti",
> +                     NULL, 0, 0);
> +
> +    pr1 = exti_readl(EXTI_PR1);
> +    g_assert_cmpuint(pr1, ==, 0x00000000);
> +    g_assert_false(check_nvic_pending(EXTI0_IRQ));
> +}
> +
> +static void test_no_software_interrupt(void)
> +{
> +    /*
> +     * Test that software irq doesn't happen when :
> +     * - corresponding bit in IMR isn't set
> +     * - SWIER is set to 1 before IMR is set to 1
> +     */
> +
> +    /*
> +     * Testing interrupt line EXTI0
> +     * Bit 0 in EXTI_*1 registers (EXTI0) corresponds to GPIO Px_0
> +     */
> +
> +    enable_nvic_irq(EXTI0_IRQ);
> +    /* Check that there are no interrupts already pending in PR */
> +    uint32_t pr1 = exti_readl(EXTI_PR1);
> +    g_assert_cmpuint(pr1, ==, 0x00000000);
> +    /* Check that this specific interrupt isn't pending in NVIC */
> +    g_assert_false(check_nvic_pending(EXTI0_IRQ));
> +
> +    /* Mask interrupt line EXTI0 */
> +    exti_writel(EXTI_IMR1, 0x00000000);
> +    /* Set the corresponding SWIER bit from '0' to '1' */
> +    exti_writel(EXTI_SWIER1, 0x00000000);
> +    exti_writel(EXTI_SWIER1, 0x00000001);
> +
> +    /* Check that the write in SWIER was effective */
> +    uint32_t swier1 = exti_readl(EXTI_SWIER1);
> +    g_assert_cmpuint(swier1, ==, 0x00000001);
> +    /* Check that the pending bit in PR wasn't set */
> +    pr1 = exti_readl(EXTI_PR1);
> +    g_assert_cmpuint(pr1, ==, 0x00000000);
> +    /* Check that the interrupt isn't pending in NVIC */
> +    g_assert_false(check_nvic_pending(EXTI0_IRQ));
> +
> +    /* Enable interrupt line EXTI0 */
> +    exti_writel(EXTI_IMR1, 0x00000001);
> +
> +    /* Check that the pending bit in PR wasn't set */
> +    pr1 = exti_readl(EXTI_PR1);
> +    g_assert_cmpuint(pr1, ==, 0x00000000);
> +    /* Check that the interrupt isn't pending in NVIC */
> +    g_assert_false(check_nvic_pending(EXTI0_IRQ));
> +
> +    /*
> +     * Testing interrupt line EXTI35
> +     * Bit 3 in EXTI_*2 registers (EXTI35) corresponds to PVM 1 Wakeup
> +     */
> +
> +    enable_nvic_irq(EXTI35_IRQ);
> +    /* Check that there are no interrupts already pending in PR */
> +    uint32_t pr2 = exti_readl(EXTI_PR2);
> +    g_assert_cmpuint(pr2, ==, 0x00000000);
> +    /* Check that this specific interrupt isn't pending in NVIC */
> +    g_assert_false(check_nvic_pending(EXTI35_IRQ));
> +
> +    /* Mask interrupt line EXTI35 */
> +    exti_writel(EXTI_IMR2, 0x00000000);
> +    /* Set the corresponding SWIER bit from '0' to '1' */
> +    exti_writel(EXTI_SWIER2, 0x00000000);
> +    exti_writel(EXTI_SWIER2, 0x00000008);
> +
> +    /* Check that the write in SWIER was effective */
> +    uint32_t swier2 = exti_readl(EXTI_SWIER2);
> +    g_assert_cmpuint(swier2, ==, 0x00000008);
> +    /* Check that the pending bit in PR wasn't set */
> +    pr2 = exti_readl(EXTI_PR2);
> +    g_assert_cmpuint(pr2, ==, 0x00000000);
> +    /* Check that the interrupt isn't pending in NVIC */
> +    g_assert_false(check_nvic_pending(EXTI35_IRQ));
> +
> +    /* Enable interrupt line EXTI35 */
> +    exti_writel(EXTI_IMR2, 0x00000008);
> +
> +    /* Check that the pending bit in PR wasn't set */
> +    pr2 = exti_readl(EXTI_PR2);
> +    g_assert_cmpuint(pr2, ==, 0x00000000);
> +    /* Check that the interrupt isn't pending in NVIC */
> +    g_assert_false(check_nvic_pending(EXTI35_IRQ));
> +}
> +
> +static void test_masked_interrupt(void)
> +{
> +    /*
> +     * Test that irq doesn't happen when :
> +     * - corresponding bit in IMR isn't set
> +     * - SWIER is set to 1 before IMR is set to 1
> +     */
> +
> +    /*
> +     * Testing interrupt line EXTI1
> +     * with rising edge from GPIOx pin 1
> +     */
> +
> +    enable_nvic_irq(EXTI1_IRQ);
> +    /* Check that there are no interrupts already pending in PR */
> +    uint32_t pr1 = exti_readl(EXTI_PR1);
> +    g_assert_cmpuint(pr1, ==, 0x00000000);
> +    /* Check that this specific interrupt isn't pending in NVIC */
> +    g_assert_false(check_nvic_pending(EXTI1_IRQ));
> +
> +    /* Mask interrupt line EXTI1 */
> +    exti_writel(EXTI_IMR1, 0x00000000);
> +
> +    /* Configure interrupt on rising edge */
> +    exti_writel(EXTI_RTSR1, 0x00000002);
> +
> +    /* Simulate rising edge from GPIO line 1 */
> +    qtest_set_irq_in(global_qtest, "/machine/unattached/device[0]/exti",
> +                     NULL, 1, 1);
> +
> +    /* Check that the pending bit in PR wasn't set */
> +    pr1 = exti_readl(EXTI_PR1);
> +    g_assert_cmpuint(pr1, ==, 0x00000000);
> +    /* Check that the interrupt isn't pending in NVIC */
> +    g_assert_false(check_nvic_pending(EXTI1_IRQ));
> +
> +    /* Enable interrupt line EXTI1 */
> +    exti_writel(EXTI_IMR1, 0x00000002);
> +
> +    /* Check that the pending bit in PR wasn't set */
> +    pr1 = exti_readl(EXTI_PR1);
> +    g_assert_cmpuint(pr1, ==, 0x00000000);
> +    /* Check that the interrupt isn't pending in NVIC */
> +    g_assert_false(check_nvic_pending(EXTI1_IRQ));
> +}
> +
> +static void test_interrupt(void)
> +{
> +    /*
> +     * Test that we can launch an irq by :
> +     * - enabling its line in IMR
> +     * - configuring interrupt on rising edge
> +     * - and then setting the input line from '0' to '1'
> +     *
> +     * And that the interruption stays pending in NVIC
> +     * even after clearing the pending bit in PR.
> +     */
> +
> +    /*
> +     * Testing interrupt line EXTI1
> +     * with rising edge from GPIOx pin 1
> +     */
> +
> +    enable_nvic_irq(EXTI1_IRQ);
> +    /* Check that there are no interrupts already pending in PR */
> +    uint32_t pr1 = exti_readl(EXTI_PR1);
> +    g_assert_cmpuint(pr1, ==, 0x00000000);
> +    /* Check that this specific interrupt isn't pending in NVIC */
> +    g_assert_false(check_nvic_pending(EXTI1_IRQ));
> +
> +    /* Enable interrupt line EXTI1 */
> +    exti_writel(EXTI_IMR1, 0x00000002);
> +
> +    /* Configure interrupt on rising edge */
> +    exti_writel(EXTI_RTSR1, 0x00000002);
> +
> +    /* Simulate rising edge from GPIO line 1 */
> +    qtest_set_irq_in(global_qtest, "/machine/unattached/device[0]/exti",
> +                     NULL, 1, 1);
> +
> +    /* Check that the pending bit in PR was set */
> +    pr1 = exti_readl(EXTI_PR1);
> +    g_assert_cmpuint(pr1, ==, 0x00000002);
> +    /* Check that the interrupt is pending in NVIC */
> +    g_assert_true(check_nvic_pending(EXTI1_IRQ));
> +
> +    /* Clear the pending bit in PR */
> +    exti_writel(EXTI_PR1, 0x00000002);
> +
> +    /* Check that the write in PR was effective */
> +    pr1 = exti_readl(EXTI_PR1);
> +    g_assert_cmpuint(pr1, ==, 0x00000000);
> +    /* Check that the interrupt is still pending in the NVIC */
> +    g_assert_true(check_nvic_pending(EXTI1_IRQ));
> +
> +    /* Clean NVIC */
> +    unpend_nvic_irq(EXTI1_IRQ);
> +    g_assert_false(check_nvic_pending(EXTI1_IRQ));
> +}
> +
> +int main(int argc, char **argv)
> +{
> +    int ret;
> +
> +    g_test_init(&argc, &argv, NULL);
> +    g_test_set_nonfatal_assertions();
> +    qtest_add_func("stm32l4x5/exti/direct_lines", test_direct_lines_write);
> +    qtest_add_func("stm32l4x5/exti/reserved_bits", test_reserved_bits_write);
> +    qtest_add_func("stm32l4x5/exti/reg_write_read", test_reg_write_read);
> +    qtest_add_func("stm32l4x5/exti/no_software_interrupt",
> +                   test_no_software_interrupt);
> +    qtest_add_func("stm32l4x5/exti/software_interrupt",
> +                   test_software_interrupt);
> +    qtest_add_func("stm32l4x5/exti/masked_interrupt", test_masked_interrupt);
> +    qtest_add_func("stm32l4x5/exti/interrupt", test_interrupt);
> +    qtest_add_func("stm32l4x5/exti/test_edge_selector", test_edge_selector);
> +
> +    qtest_start("-machine b-l475e-iot01a");
> +    ret = g_test_run();
> +    qtest_end();
> +
> +    return ret;
> +}
> --
> 2.43.0
>
>
Philippe Mathieu-Daudé Jan. 4, 2024, 1:05 p.m. UTC | #2
+Markus for QOM tree

On 28/12/23 17:19, Inès Varhol wrote:
> Signed-off-by: Arnaud Minier <arnaud.minier@telecom-paris.fr>
> Signed-off-by: Inès Varhol <ines.varhol@telecom-paris.fr>
> ---
>   tests/qtest/meson.build           |   5 +
>   tests/qtest/stm32l4x5_exti-test.c | 596 ++++++++++++++++++++++++++++++
>   2 files changed, 601 insertions(+)
>   create mode 100644 tests/qtest/stm32l4x5_exti-test.c
> 
> diff --git a/tests/qtest/meson.build b/tests/qtest/meson.build
> index 47dabf91d0..d5126f4d86 100644
> --- a/tests/qtest/meson.build
> +++ b/tests/qtest/meson.build
> @@ -194,6 +194,10 @@ qtests_aspeed = \
>     ['aspeed_hace-test',
>      'aspeed_smc-test',
>      'aspeed_gpio-test']
> +
> +qtests_stm32l4x5 = \
> +  ['stm32l4x5_exti-test']
> +
>   qtests_arm = \
>     (config_all_devices.has_key('CONFIG_MPS2') ? ['sse-timer-test'] : []) + \
>     (config_all_devices.has_key('CONFIG_CMSDK_APB_DUALTIMER') ? ['cmsdk-apb-dualtimer-test'] : []) + \
> @@ -207,6 +211,7 @@ qtests_arm = \
>     (config_all_devices.has_key('CONFIG_TPM_TIS_I2C') ? ['tpm-tis-i2c-test'] : []) + \
>     (config_all_devices.has_key('CONFIG_VEXPRESS') ? ['test-arm-mptimer'] : []) + \
>     (config_all_devices.has_key('CONFIG_MICROBIT') ? ['microbit-test'] : []) + \
> +  (config_all_devices.has_key('CONFIG_STM32L4X5_SOC') ? qtests_stm32l4x5 : []) + \
>     ['arm-cpu-features',
>      'boot-serial-test']
>   
> diff --git a/tests/qtest/stm32l4x5_exti-test.c b/tests/qtest/stm32l4x5_exti-test.c
> new file mode 100644
> index 0000000000..60c8297246
> --- /dev/null
> +++ b/tests/qtest/stm32l4x5_exti-test.c
> @@ -0,0 +1,596 @@
> +/*
> + * QTest testcase for STM32L4x5_EXTI
> + *
> + * Copyright (c) 2023 Arnaud Minier <arnaud.minier@telecom-paris.fr>
> + * Copyright (c) 2023 Inès Varhol <ines.varhol@telecom-paris.fr>
> + *
> + * This work is licensed under the terms of the GNU GPL, version 2 or later.
> + * See the COPYING file in the top-level directory.
> + */
> +
> +#include "qemu/osdep.h"
> +#include "libqtest-single.h"
> +
> +#define EXTI_BASE_ADDR 0x40010400
> +#define EXTI_IMR1 0x00
> +#define EXTI_EMR1 0x04
> +#define EXTI_RTSR1 0x08
> +#define EXTI_FTSR1 0x0C
> +#define EXTI_SWIER1 0x10
> +#define EXTI_PR1 0x14
> +#define EXTI_IMR2 0x20
> +#define EXTI_EMR2 0x24
> +#define EXTI_RTSR2 0x28
> +#define EXTI_FTSR2 0x2C
> +#define EXTI_SWIER2 0x30
> +#define EXTI_PR2 0x34
> +
> +#define NVIC_ISER 0xE000E100
> +#define NVIC_ISPR 0xE000E200
> +#define NVIC_ICPR 0xE000E280
> +
> +#define EXTI0_IRQ 6
> +#define EXTI1_IRQ 7
> +#define EXTI35_IRQ 1
> +
> +static void enable_nvic_irq(unsigned int n)
> +{
> +    writel(NVIC_ISER, 1 << n);
> +}
> +
> +static void unpend_nvic_irq(unsigned int n)
> +{
> +    writel(NVIC_ICPR, 1 << n);
> +}
> +
> +static bool check_nvic_pending(unsigned int n)
> +{
> +    return readl(NVIC_ISPR) & (1 << n);
> +}
> +
> +static void exti_writel(unsigned int offset, uint32_t value)
> +{
> +    writel(EXTI_BASE_ADDR + offset, value);
> +}
> +
> +static uint32_t exti_readl(unsigned int offset)
> +{
> +    return readl(EXTI_BASE_ADDR + offset);
> +}
> +
> +static void test_reg_write_read(void)
> +{
> +    /* Test that non-reserved bits in xMR and xTSR can be set and cleared */
> +
> +    exti_writel(EXTI_IMR1, 0xFFFFFFFF);
> +    uint32_t imr1 = exti_readl(EXTI_IMR1);
> +    g_assert_cmpuint(imr1, ==, 0xFFFFFFFF);
> +    exti_writel(EXTI_IMR1, 0x00000000);
> +    imr1 = exti_readl(EXTI_IMR1);
> +    g_assert_cmpuint(imr1, ==, 0x00000000);
> +
> +    exti_writel(EXTI_EMR1, 0xFFFFFFFF);
> +    uint32_t emr1 = exti_readl(EXTI_EMR1);
> +    g_assert_cmpuint(emr1, ==, 0xFFFFFFFF);
> +    exti_writel(EXTI_EMR1, 0x00000000);
> +    emr1 = exti_readl(EXTI_EMR1);
> +    g_assert_cmpuint(emr1, ==, 0x00000000);
> +
> +    exti_writel(EXTI_RTSR1, 0xFFFFFFFF);
> +    uint32_t rtsr1 = exti_readl(EXTI_RTSR1);
> +    g_assert_cmpuint(rtsr1, ==, 0x007DFFFF);
> +    exti_writel(EXTI_RTSR1, 0x00000000);
> +    rtsr1 = exti_readl(EXTI_RTSR1);
> +    g_assert_cmpuint(rtsr1, ==, 0x00000000);
> +
> +    exti_writel(EXTI_FTSR1, 0xFFFFFFFF);
> +    uint32_t ftsr1 = exti_readl(EXTI_FTSR1);
> +    g_assert_cmpuint(ftsr1, ==, 0x007DFFFF);
> +    exti_writel(EXTI_FTSR1, 0x00000000);
> +    ftsr1 = exti_readl(EXTI_FTSR1);
> +    g_assert_cmpuint(ftsr1, ==, 0x00000000);
> +
> +    exti_writel(EXTI_IMR2, 0xFFFFFFFF);
> +    uint32_t imr2 = exti_readl(EXTI_IMR2);
> +    g_assert_cmpuint(imr2, ==, 0x000000FF);
> +    exti_writel(EXTI_IMR2, 0x00000000);
> +    imr2 = exti_readl(EXTI_IMR2);
> +    g_assert_cmpuint(imr2, ==, 0x00000000);
> +
> +    exti_writel(EXTI_EMR2, 0xFFFFFFFF);
> +    uint32_t emr2 = exti_readl(EXTI_EMR2);
> +    g_assert_cmpuint(emr2, ==, 0x000000FF);
> +    exti_writel(EXTI_EMR2, 0x00000000);
> +    emr2 = exti_readl(EXTI_EMR2);
> +    g_assert_cmpuint(emr2, ==, 0x00000000);
> +
> +    exti_writel(EXTI_RTSR2, 0xFFFFFFFF);
> +    uint32_t rtsr2 = exti_readl(EXTI_RTSR2);
> +    g_assert_cmpuint(rtsr2, ==, 0x00000078);
> +    exti_writel(EXTI_RTSR2, 0x00000000);
> +    rtsr2 = exti_readl(EXTI_RTSR2);
> +    g_assert_cmpuint(rtsr2, ==, 0x00000000);
> +
> +    exti_writel(EXTI_FTSR2, 0xFFFFFFFF);
> +    uint32_t ftsr2 = exti_readl(EXTI_FTSR2);
> +    g_assert_cmpuint(ftsr2, ==, 0x00000078);
> +    exti_writel(EXTI_FTSR2, 0x00000000);
> +    ftsr2 = exti_readl(EXTI_FTSR2);
> +    g_assert_cmpuint(ftsr2, ==, 0x00000000);
> +}
> +
> +static void test_direct_lines_write(void)
> +{
> +    /* Test that direct lines reserved bits are not written to */
> +
> +    exti_writel(EXTI_RTSR1, 0xFF820000);
> +    uint32_t rtsr1 = exti_readl(EXTI_RTSR1);
> +    g_assert_cmpuint(rtsr1, ==, 0x00000000);
> +
> +    exti_writel(EXTI_FTSR1, 0xFF820000);
> +    uint32_t ftsr1 = exti_readl(EXTI_FTSR1);
> +    g_assert_cmpuint(ftsr1, ==, 0x00000000);
> +
> +    exti_writel(EXTI_SWIER1, 0xFF820000);
> +    uint32_t swier1 = exti_readl(EXTI_SWIER1);
> +    g_assert_cmpuint(swier1, ==, 0x00000000);
> +
> +    exti_writel(EXTI_PR1, 0xFF820000);
> +    uint32_t pr1 = exti_readl(EXTI_PR1);
> +    g_assert_cmpuint(pr1, ==, 0x00000000);
> +
> +    exti_writel(EXTI_RTSR2, 0x00000087);
> +    const uint32_t rtsr2 = exti_readl(EXTI_RTSR2);
> +    g_assert_cmpuint(rtsr2, ==, 0x00000000);
> +
> +    exti_writel(EXTI_FTSR2, 0x00000087);
> +    const uint32_t ftsr2 = exti_readl(EXTI_FTSR2);
> +    g_assert_cmpuint(ftsr2, ==, 0x00000000);
> +
> +    exti_writel(EXTI_SWIER2, 0x00000087);
> +    const uint32_t swier2 = exti_readl(EXTI_SWIER2);
> +    g_assert_cmpuint(swier2, ==, 0x00000000);
> +
> +    exti_writel(EXTI_PR2, 0x00000087);
> +    const uint32_t pr2 = exti_readl(EXTI_PR2);
> +    g_assert_cmpuint(pr2, ==, 0x00000000);
> +}
> +
> +static void test_reserved_bits_write(void)
> +{
> +    /* Test that reserved bits stay are not written to */
> +
> +    exti_writel(EXTI_IMR2, 0xFFFFFF00);
> +    uint32_t imr2 = exti_readl(EXTI_IMR2);
> +    g_assert_cmpuint(imr2, ==, 0x00000000);
> +
> +    exti_writel(EXTI_EMR2, 0xFFFFFF00);
> +    uint32_t emr2 = exti_readl(EXTI_EMR2);
> +    g_assert_cmpuint(emr2, ==, 0x00000000);
> +
> +    exti_writel(EXTI_RTSR2, 0xFFFFFF00);
> +    const uint32_t rtsr2 = exti_readl(EXTI_RTSR2);
> +    g_assert_cmpuint(rtsr2, ==, 0x00000000);
> +
> +    exti_writel(EXTI_FTSR2, 0xFFFFFF00);
> +    const uint32_t ftsr2 = exti_readl(EXTI_FTSR2);
> +    g_assert_cmpuint(ftsr2, ==, 0x00000000);
> +
> +    exti_writel(EXTI_SWIER2, 0xFFFFFF00);
> +    const uint32_t swier2 = exti_readl(EXTI_SWIER2);
> +    g_assert_cmpuint(swier2, ==, 0x00000000);
> +
> +    exti_writel(EXTI_PR2, 0xFFFFFF00);
> +    const uint32_t pr2 = exti_readl(EXTI_PR2);
> +    g_assert_cmpuint(pr2, ==, 0x00000000);
> +}
> +
> +static void test_software_interrupt(void)
> +{
> +    /*
> +     * Test that we can launch a software irq by :
> +     * - enabling its line in IMR
> +     * - and then setting a bit from '0' to '1' in SWIER
> +     *
> +     * And that the interruption stays pending in NVIC
> +     * even after clearing the pending bit in PR.
> +     */
> +
> +    /*
> +     * Testing interrupt line EXTI0
> +     * Bit 0 in EXTI_*1 registers (EXTI0) corresponds to GPIO Px_0
> +     */
> +
> +    enable_nvic_irq(EXTI0_IRQ);
> +    /* Check that there are no interrupts already pending in PR */
> +    uint32_t pr1 = exti_readl(EXTI_PR1);
> +    g_assert_cmpuint(pr1, ==, 0x00000000);
> +    /* Check that this specific interrupt isn't pending in NVIC */
> +    g_assert_false(check_nvic_pending(EXTI0_IRQ));
> +
> +    /* Enable interrupt line EXTI0 */
> +    exti_writel(EXTI_IMR1, 0x00000001);
> +    /* Set the right SWIER bit from '0' to '1' */
> +    exti_writel(EXTI_SWIER1, 0x00000000);
> +    exti_writel(EXTI_SWIER1, 0x00000001);
> +
> +    /* Check that the write in SWIER was effective */
> +    uint32_t swier1 = exti_readl(EXTI_SWIER1);
> +    g_assert_cmpuint(swier1, ==, 0x00000001);
> +    /* Check that the corresponding pending bit in PR is set */
> +    pr1 = exti_readl(EXTI_PR1);
> +    g_assert_cmpuint(pr1, ==, 0x00000001);
> +    /* Check that the corresponding interrupt is pending in the NVIC */
> +    g_assert_true(check_nvic_pending(EXTI0_IRQ));
> +
> +    /* Clear the pending bit in PR */
> +    exti_writel(EXTI_PR1, 0x00000001);
> +
> +    /* Check that the write in PR was effective */
> +    pr1 = exti_readl(EXTI_PR1);
> +    g_assert_cmpuint(pr1, ==, 0x00000000);
> +    /* Check that the corresponding bit in SWIER was cleared */
> +    swier1 = exti_readl(EXTI_SWIER1);
> +    g_assert_cmpuint(swier1, ==, 0x00000000);
> +    /* Check that the interrupt is still pending in the NVIC */
> +    g_assert_true(check_nvic_pending(EXTI0_IRQ));
> +
> +    /*
> +     * Testing interrupt line EXTI35
> +     * Bit 3 in EXTI_*2 registers (EXTI35) corresponds to PVM 1 Wakeup
> +     */
> +
> +    enable_nvic_irq(EXTI35_IRQ);
> +    /* Check that there are no interrupts already pending */
> +    uint32_t pr2 = exti_readl(EXTI_PR2);
> +    g_assert_cmpuint(pr2, ==, 0x00000000);
> +    g_assert_false(check_nvic_pending(EXTI35_IRQ));
> +
> +    /* Enable interrupt line EXTI0 */
> +    exti_writel(EXTI_IMR2, 0x00000008);
> +    /* Set the right SWIER bit from '0' to '1' */
> +    exti_writel(EXTI_SWIER2, 0x00000000);
> +    exti_writel(EXTI_SWIER2, 0x00000008);
> +
> +    /* Check that the write in SWIER was effective */
> +    uint32_t swier2 = exti_readl(EXTI_SWIER2);
> +    g_assert_cmpuint(swier2, ==, 0x00000008);
> +    /* Check that the corresponding pending bit in PR is set */
> +    pr2 = exti_readl(EXTI_PR2);
> +    g_assert_cmpuint(pr2, ==, 0x00000008);
> +    /* Check that the corresponding interrupt is pending in the NVIC */
> +    g_assert_true(check_nvic_pending(EXTI35_IRQ));
> +
> +    /* Clear the pending bit in PR */
> +    exti_writel(EXTI_PR2, 0x00000008);
> +
> +    /* Check that the write in PR was effective */
> +    pr2 = exti_readl(EXTI_PR2);
> +    g_assert_cmpuint(pr2, ==, 0x00000000);
> +    /* Check that the corresponding bit in SWIER was cleared */
> +    swier2 = exti_readl(EXTI_SWIER2);
> +    g_assert_cmpuint(swier2, ==, 0x00000000);
> +    /* Check that the interrupt is still pending in the NVIC */
> +    g_assert_true(check_nvic_pending(EXTI35_IRQ));
> +
> +    /* Clean NVIC */
> +    unpend_nvic_irq(EXTI0_IRQ);
> +    g_assert_false(check_nvic_pending(EXTI0_IRQ));
> +    unpend_nvic_irq(EXTI35_IRQ);
> +    g_assert_false(check_nvic_pending(EXTI35_IRQ));
> +}
> +
> +static void test_edge_selector(void)
> +{
> +    enable_nvic_irq(EXTI0_IRQ);
> +
> +    /* Configure EXTI line 0 irq on rising edge */
> +    qtest_set_irq_in(global_qtest, "/machine/unattached/device[0]/exti",

Markus, this qtest use seems to expect some stability in QOM path...

Inès, Arnaud, having the SoC unattached is dubious, it belongs to
the machine.

(qemu) info qom-tree
/machine (b-l475e-iot01a-machine)
   /SYSCLK (clock)
   /peripheral (container)
   /peripheral-anon (container)
   /unattached (container)
     /device[0] (stm32l4x5xg-soc)

Eh I don't see the 'exti' here...

Indeed the test fails:

17/35 qemu:qtest+qtest-arm / qtest-arm/test-arm-mptimer 
   OK              0.44s   61 subtests passed
▶ 18/35 /arm/stm32l4x5/exti/reg_write_read 
   FAIL
▶ 18/35 /arm/stm32l4x5/exti/no_software_interrupt 
   FAIL
▶ 18/35 /arm/stm32l4x5/exti/software_interrupt 
   FAIL
▶ 18/35 /arm/stm32l4x5/exti/masked_interrupt 
   FAIL
▶ 18/35 /arm/stm32l4x5/exti/interrupt 
   FAIL
▶ 18/35 /arm/stm32l4x5/exti/test_edge_selector 
   FAIL
Listing only the last 100 lines from a long log.
**
ERROR:../../tests/qtest/stm32l4x5_exti-test.c:102:test_reg_write_read: 
code should not be reached
**
ERROR:../../tests/qtest/stm32l4x5_exti-test.c:109:test_reg_write_read: 
assertion failed (rtsr2 == 0x00000078): (0 == 120)
**
ERROR:../../tests/qtest/stm32l4x5_exti-test.c:109:test_reg_write_read: 
code should not be reached
**
ERROR:../../tests/qtest/stm32l4x5_exti-test.c:116:test_reg_write_read: 
assertion failed (ftsr2 == 0x00000078): (0 == 120)
**
ERROR:../../tests/qtest/stm32l4x5_exti-test.c:116:test_reg_write_read: 
code should not be reached
**
ERROR:../../tests/qtest/stm32l4x5_exti-test.c:421:test_no_software_interrupt: 
assertion failed (swier1 == 0x00000001): (0 == 1)
...

You should re-order patches 2 <-> 3.

> +                     NULL, 0, 1);
> +    exti_writel(EXTI_IMR1, 0x00000001);
> +    exti_writel(EXTI_RTSR1, 0x00000001);
> +    exti_writel(EXTI_FTSR1, 0x00000000);
> +
> +    /* Test that an irq is raised on rising edge only */
> +    qtest_set_irq_in(global_qtest, "/machine/unattached/device[0]/exti",
> +                     NULL, 0, 0);
> +
> +    uint32_t pr1 = exti_readl(EXTI_PR1);
> +    g_assert_cmpuint(pr1, ==, 0x00000000);
> +    g_assert_false(check_nvic_pending(EXTI0_IRQ));
> +
> +    qtest_set_irq_in(global_qtest, "/machine/unattached/device[0]/exti",
> +                     NULL, 0, 1);
> +
> +    pr1 = exti_readl(EXTI_PR1);
> +    g_assert_cmpuint(pr1, ==, 0x00000001);
> +    g_assert_true(check_nvic_pending(EXTI0_IRQ));
> +
> +    /* Clean the test */
> +    exti_writel(EXTI_PR1, 0x00000001);
> +    pr1 = exti_readl(EXTI_PR1);
> +    g_assert_cmpuint(pr1, ==, 0x00000000);
> +    unpend_nvic_irq(EXTI0_IRQ);
> +    g_assert_false(check_nvic_pending(EXTI0_IRQ));
> +
> +    /* Configure EXTI line 0 irq on falling edge */
> +    qtest_set_irq_in(global_qtest, "/machine/unattached/device[0]/exti",
> +                     NULL, 0, 0);
> +    exti_writel(EXTI_IMR1, 0x00000001);
> +    exti_writel(EXTI_RTSR1, 0x00000000);
> +    exti_writel(EXTI_FTSR1, 0x00000001);
> +
> +    /* Test that an irq is raised on falling edge only */
> +    qtest_set_irq_in(global_qtest, "/machine/unattached/device[0]/exti",
> +                     NULL, 0, 1);
> +
> +    pr1 = exti_readl(EXTI_PR1);
> +    g_assert_cmpuint(pr1, ==, 0x00000000);
> +    g_assert_false(check_nvic_pending(EXTI0_IRQ));
> +
> +    qtest_set_irq_in(global_qtest, "/machine/unattached/device[0]/exti",
> +                     NULL, 0, 0);
> +
> +    pr1 = exti_readl(EXTI_PR1);
> +    g_assert_cmpuint(pr1, ==, 0x00000001);
> +    g_assert_true(check_nvic_pending(EXTI0_IRQ));
> +
> +    /* Clean the test */
> +    exti_writel(EXTI_PR1, 0x00000001);
> +    pr1 = exti_readl(EXTI_PR1);
> +    g_assert_cmpuint(pr1, ==, 0x00000000);
> +    unpend_nvic_irq(EXTI0_IRQ);
> +    g_assert_false(check_nvic_pending(EXTI0_IRQ));
> +
> +    /* Configure EXTI line 0 irq on falling and rising edge */
> +    exti_writel(EXTI_IMR1, 0x00000001);
> +    exti_writel(EXTI_RTSR1, 0x00000001);
> +    exti_writel(EXTI_FTSR1, 0x00000000);
> +
> +    /* Test that an irq is raised on rising and falling edge */
> +    qtest_set_irq_in(global_qtest, "/machine/unattached/device[0]/exti",
> +                     NULL, 0, 1);
> +
> +    pr1 = exti_readl(EXTI_PR1);
> +    g_assert_cmpuint(pr1, ==, 0x00000001);
> +    g_assert_true(check_nvic_pending(EXTI0_IRQ));
> +
> +    qtest_set_irq_in(global_qtest, "/machine/unattached/device[0]/exti",
> +                     NULL, 0, 0);
> +
> +    pr1 = exti_readl(EXTI_PR1);
> +    g_assert_cmpuint(pr1, ==, 0x00000001);
> +    g_assert_true(check_nvic_pending(EXTI0_IRQ));
> +
> +    /* Clean the test */
> +    exti_writel(EXTI_PR1, 0x00000001);
> +    pr1 = exti_readl(EXTI_PR1);
> +    g_assert_cmpuint(pr1, ==, 0x00000000);
> +    unpend_nvic_irq(EXTI0_IRQ);
> +    g_assert_false(check_nvic_pending(EXTI0_IRQ));
> +
> +    /* Configure EXTI line 0 irq without selecting an edge trigger */
> +    exti_writel(EXTI_IMR1, 0x00000001);
> +    exti_writel(EXTI_RTSR1, 0x00000000);
> +    exti_writel(EXTI_FTSR1, 0x00000000);
> +
> +    /* Test that no irq is raised */
> +    qtest_set_irq_in(global_qtest, "/machine/unattached/device[0]/exti",
> +                     NULL, 0, 1);
> +
> +    pr1 = exti_readl(EXTI_PR1);
> +    g_assert_cmpuint(pr1, ==, 0x00000000);
> +    g_assert_false(check_nvic_pending(EXTI0_IRQ));
> +
> +    qtest_set_irq_in(global_qtest, "/machine/unattached/device[0]/exti",
> +                     NULL, 0, 0);
> +
> +    pr1 = exti_readl(EXTI_PR1);
> +    g_assert_cmpuint(pr1, ==, 0x00000000);
> +    g_assert_false(check_nvic_pending(EXTI0_IRQ));
> +}
> +
> +static void test_no_software_interrupt(void)
> +{
> +    /*
> +     * Test that software irq doesn't happen when :
> +     * - corresponding bit in IMR isn't set
> +     * - SWIER is set to 1 before IMR is set to 1
> +     */
> +
> +    /*
> +     * Testing interrupt line EXTI0
> +     * Bit 0 in EXTI_*1 registers (EXTI0) corresponds to GPIO Px_0
> +     */
> +
> +    enable_nvic_irq(EXTI0_IRQ);
> +    /* Check that there are no interrupts already pending in PR */
> +    uint32_t pr1 = exti_readl(EXTI_PR1);
> +    g_assert_cmpuint(pr1, ==, 0x00000000);
> +    /* Check that this specific interrupt isn't pending in NVIC */
> +    g_assert_false(check_nvic_pending(EXTI0_IRQ));
> +
> +    /* Mask interrupt line EXTI0 */
> +    exti_writel(EXTI_IMR1, 0x00000000);
> +    /* Set the corresponding SWIER bit from '0' to '1' */
> +    exti_writel(EXTI_SWIER1, 0x00000000);
> +    exti_writel(EXTI_SWIER1, 0x00000001);
> +
> +    /* Check that the write in SWIER was effective */
> +    uint32_t swier1 = exti_readl(EXTI_SWIER1);
> +    g_assert_cmpuint(swier1, ==, 0x00000001);
> +    /* Check that the pending bit in PR wasn't set */
> +    pr1 = exti_readl(EXTI_PR1);
> +    g_assert_cmpuint(pr1, ==, 0x00000000);
> +    /* Check that the interrupt isn't pending in NVIC */
> +    g_assert_false(check_nvic_pending(EXTI0_IRQ));
> +
> +    /* Enable interrupt line EXTI0 */
> +    exti_writel(EXTI_IMR1, 0x00000001);
> +
> +    /* Check that the pending bit in PR wasn't set */
> +    pr1 = exti_readl(EXTI_PR1);
> +    g_assert_cmpuint(pr1, ==, 0x00000000);
> +    /* Check that the interrupt isn't pending in NVIC */
> +    g_assert_false(check_nvic_pending(EXTI0_IRQ));
> +
> +    /*
> +     * Testing interrupt line EXTI35
> +     * Bit 3 in EXTI_*2 registers (EXTI35) corresponds to PVM 1 Wakeup
> +     */
> +
> +    enable_nvic_irq(EXTI35_IRQ);
> +    /* Check that there are no interrupts already pending in PR */
> +    uint32_t pr2 = exti_readl(EXTI_PR2);
> +    g_assert_cmpuint(pr2, ==, 0x00000000);
> +    /* Check that this specific interrupt isn't pending in NVIC */
> +    g_assert_false(check_nvic_pending(EXTI35_IRQ));
> +
> +    /* Mask interrupt line EXTI35 */
> +    exti_writel(EXTI_IMR2, 0x00000000);
> +    /* Set the corresponding SWIER bit from '0' to '1' */
> +    exti_writel(EXTI_SWIER2, 0x00000000);
> +    exti_writel(EXTI_SWIER2, 0x00000008);
> +
> +    /* Check that the write in SWIER was effective */
> +    uint32_t swier2 = exti_readl(EXTI_SWIER2);
> +    g_assert_cmpuint(swier2, ==, 0x00000008);
> +    /* Check that the pending bit in PR wasn't set */
> +    pr2 = exti_readl(EXTI_PR2);
> +    g_assert_cmpuint(pr2, ==, 0x00000000);
> +    /* Check that the interrupt isn't pending in NVIC */
> +    g_assert_false(check_nvic_pending(EXTI35_IRQ));
> +
> +    /* Enable interrupt line EXTI35 */
> +    exti_writel(EXTI_IMR2, 0x00000008);
> +
> +    /* Check that the pending bit in PR wasn't set */
> +    pr2 = exti_readl(EXTI_PR2);
> +    g_assert_cmpuint(pr2, ==, 0x00000000);
> +    /* Check that the interrupt isn't pending in NVIC */
> +    g_assert_false(check_nvic_pending(EXTI35_IRQ));
> +}
> +
> +static void test_masked_interrupt(void)
> +{
> +    /*
> +     * Test that irq doesn't happen when :
> +     * - corresponding bit in IMR isn't set
> +     * - SWIER is set to 1 before IMR is set to 1
> +     */
> +
> +    /*
> +     * Testing interrupt line EXTI1
> +     * with rising edge from GPIOx pin 1
> +     */
> +
> +    enable_nvic_irq(EXTI1_IRQ);
> +    /* Check that there are no interrupts already pending in PR */
> +    uint32_t pr1 = exti_readl(EXTI_PR1);
> +    g_assert_cmpuint(pr1, ==, 0x00000000);
> +    /* Check that this specific interrupt isn't pending in NVIC */
> +    g_assert_false(check_nvic_pending(EXTI1_IRQ));
> +
> +    /* Mask interrupt line EXTI1 */
> +    exti_writel(EXTI_IMR1, 0x00000000);
> +
> +    /* Configure interrupt on rising edge */
> +    exti_writel(EXTI_RTSR1, 0x00000002);
> +
> +    /* Simulate rising edge from GPIO line 1 */
> +    qtest_set_irq_in(global_qtest, "/machine/unattached/device[0]/exti",
> +                     NULL, 1, 1);
> +
> +    /* Check that the pending bit in PR wasn't set */
> +    pr1 = exti_readl(EXTI_PR1);
> +    g_assert_cmpuint(pr1, ==, 0x00000000);
> +    /* Check that the interrupt isn't pending in NVIC */
> +    g_assert_false(check_nvic_pending(EXTI1_IRQ));
> +
> +    /* Enable interrupt line EXTI1 */
> +    exti_writel(EXTI_IMR1, 0x00000002);
> +
> +    /* Check that the pending bit in PR wasn't set */
> +    pr1 = exti_readl(EXTI_PR1);
> +    g_assert_cmpuint(pr1, ==, 0x00000000);
> +    /* Check that the interrupt isn't pending in NVIC */
> +    g_assert_false(check_nvic_pending(EXTI1_IRQ));
> +}
> +
> +static void test_interrupt(void)
> +{
> +    /*
> +     * Test that we can launch an irq by :
> +     * - enabling its line in IMR
> +     * - configuring interrupt on rising edge
> +     * - and then setting the input line from '0' to '1'
> +     *
> +     * And that the interruption stays pending in NVIC
> +     * even after clearing the pending bit in PR.
> +     */
> +
> +    /*
> +     * Testing interrupt line EXTI1
> +     * with rising edge from GPIOx pin 1
> +     */
> +
> +    enable_nvic_irq(EXTI1_IRQ);
> +    /* Check that there are no interrupts already pending in PR */
> +    uint32_t pr1 = exti_readl(EXTI_PR1);
> +    g_assert_cmpuint(pr1, ==, 0x00000000);
> +    /* Check that this specific interrupt isn't pending in NVIC */
> +    g_assert_false(check_nvic_pending(EXTI1_IRQ));
> +
> +    /* Enable interrupt line EXTI1 */
> +    exti_writel(EXTI_IMR1, 0x00000002);
> +
> +    /* Configure interrupt on rising edge */
> +    exti_writel(EXTI_RTSR1, 0x00000002);
> +
> +    /* Simulate rising edge from GPIO line 1 */
> +    qtest_set_irq_in(global_qtest, "/machine/unattached/device[0]/exti",
> +                     NULL, 1, 1);
> +
> +    /* Check that the pending bit in PR was set */
> +    pr1 = exti_readl(EXTI_PR1);
> +    g_assert_cmpuint(pr1, ==, 0x00000002);
> +    /* Check that the interrupt is pending in NVIC */
> +    g_assert_true(check_nvic_pending(EXTI1_IRQ));
> +
> +    /* Clear the pending bit in PR */
> +    exti_writel(EXTI_PR1, 0x00000002);
> +
> +    /* Check that the write in PR was effective */
> +    pr1 = exti_readl(EXTI_PR1);
> +    g_assert_cmpuint(pr1, ==, 0x00000000);
> +    /* Check that the interrupt is still pending in the NVIC */
> +    g_assert_true(check_nvic_pending(EXTI1_IRQ));
> +
> +    /* Clean NVIC */
> +    unpend_nvic_irq(EXTI1_IRQ);
> +    g_assert_false(check_nvic_pending(EXTI1_IRQ));
> +}
> +
> +int main(int argc, char **argv)
> +{
> +    int ret;
> +
> +    g_test_init(&argc, &argv, NULL);
> +    g_test_set_nonfatal_assertions();
> +    qtest_add_func("stm32l4x5/exti/direct_lines", test_direct_lines_write);
> +    qtest_add_func("stm32l4x5/exti/reserved_bits", test_reserved_bits_write);
> +    qtest_add_func("stm32l4x5/exti/reg_write_read", test_reg_write_read);
> +    qtest_add_func("stm32l4x5/exti/no_software_interrupt",
> +                   test_no_software_interrupt);
> +    qtest_add_func("stm32l4x5/exti/software_interrupt",
> +                   test_software_interrupt);
> +    qtest_add_func("stm32l4x5/exti/masked_interrupt", test_masked_interrupt);
> +    qtest_add_func("stm32l4x5/exti/interrupt", test_interrupt);
> +    qtest_add_func("stm32l4x5/exti/test_edge_selector", test_edge_selector);
> +
> +    qtest_start("-machine b-l475e-iot01a");
> +    ret = g_test_run();
> +    qtest_end();
> +
> +    return ret;
> +}
Philippe Mathieu-Daudé Jan. 4, 2024, 1:33 p.m. UTC | #3
On 28/12/23 17:19, Inès Varhol wrote:
> Signed-off-by: Arnaud Minier <arnaud.minier@telecom-paris.fr>
> Signed-off-by: Inès Varhol <ines.varhol@telecom-paris.fr>
> ---
>   tests/qtest/meson.build           |   5 +
>   tests/qtest/stm32l4x5_exti-test.c | 596 ++++++++++++++++++++++++++++++
>   2 files changed, 601 insertions(+)
>   create mode 100644 tests/qtest/stm32l4x5_exti-test.c

Once the SoC parentship fixed in based series, this patch
requires:

-- >8 --
diff --git a/tests/qtest/stm32l4x5_exti-test.c 
b/tests/qtest/stm32l4x5_exti-test.c
index 60c8297246..543199cd4d 100644
--- a/tests/qtest/stm32l4x5_exti-test.c
+++ b/tests/qtest/stm32l4x5_exti-test.c
@@ -287,4 +287,3 @@ static void test_edge_selector(void)
      /* Configure EXTI line 0 irq on rising edge */
-    qtest_set_irq_in(global_qtest, "/machine/unattached/device[0]/exti",
-                     NULL, 0, 1);
+    qtest_set_irq_in(global_qtest, "/machine/soc/exti", NULL, 0, 1);
      exti_writel(EXTI_IMR1, 0x00000001);
@@ -294,4 +293,3 @@ static void test_edge_selector(void)
      /* Test that an irq is raised on rising edge only */
-    qtest_set_irq_in(global_qtest, "/machine/unattached/device[0]/exti",
-                     NULL, 0, 0);
+    qtest_set_irq_in(global_qtest, "/machine/soc/exti", NULL, 0, 0);

@@ -301,4 +299,3 @@ static void test_edge_selector(void)

-    qtest_set_irq_in(global_qtest, "/machine/unattached/device[0]/exti",
-                     NULL, 0, 1);
+    qtest_set_irq_in(global_qtest, "/machine/soc/exti", NULL, 0, 1);

@@ -316,4 +313,3 @@ static void test_edge_selector(void)
      /* Configure EXTI line 0 irq on falling edge */
-    qtest_set_irq_in(global_qtest, "/machine/unattached/device[0]/exti",
-                     NULL, 0, 0);
+    qtest_set_irq_in(global_qtest, "/machine/soc/exti", NULL, 0, 0);
      exti_writel(EXTI_IMR1, 0x00000001);
@@ -323,4 +319,3 @@ static void test_edge_selector(void)
      /* Test that an irq is raised on falling edge only */
-    qtest_set_irq_in(global_qtest, "/machine/unattached/device[0]/exti",
-                     NULL, 0, 1);
+    qtest_set_irq_in(global_qtest, "/machine/soc/exti", NULL, 0, 1);

@@ -330,4 +325,3 @@ static void test_edge_selector(void)

-    qtest_set_irq_in(global_qtest, "/machine/unattached/device[0]/exti",
-                     NULL, 0, 0);
+    qtest_set_irq_in(global_qtest, "/machine/soc/exti", NULL, 0, 0);

@@ -350,4 +344,3 @@ static void test_edge_selector(void)
      /* Test that an irq is raised on rising and falling edge */
-    qtest_set_irq_in(global_qtest, "/machine/unattached/device[0]/exti",
-                     NULL, 0, 1);
+    qtest_set_irq_in(global_qtest, "/machine/soc/exti", NULL, 0, 1);

@@ -357,4 +350,3 @@ static void test_edge_selector(void)

-    qtest_set_irq_in(global_qtest, "/machine/unattached/device[0]/exti",
-                     NULL, 0, 0);
+    qtest_set_irq_in(global_qtest, "/machine/soc/exti", NULL, 0, 0);

@@ -377,4 +369,3 @@ static void test_edge_selector(void)
      /* Test that no irq is raised */
-    qtest_set_irq_in(global_qtest, "/machine/unattached/device[0]/exti",
-                     NULL, 0, 1);
+    qtest_set_irq_in(global_qtest, "/machine/soc/exti", NULL, 0, 1);

@@ -384,4 +375,3 @@ static void test_edge_selector(void)

-    qtest_set_irq_in(global_qtest, "/machine/unattached/device[0]/exti",
-                     NULL, 0, 0);
+    qtest_set_irq_in(global_qtest, "/machine/soc/exti", NULL, 0, 0);

@@ -500,4 +490,3 @@ static void test_masked_interrupt(void)
      /* Simulate rising edge from GPIO line 1 */
-    qtest_set_irq_in(global_qtest, "/machine/unattached/device[0]/exti",
-                     NULL, 1, 1);
+    qtest_set_irq_in(global_qtest, "/machine/soc/exti", NULL, 1, 1);

@@ -550,4 +539,3 @@ static void test_interrupt(void)
      /* Simulate rising edge from GPIO line 1 */
-    qtest_set_irq_in(global_qtest, "/machine/unattached/device[0]/exti",
-                     NULL, 1, 1);
+    qtest_set_irq_in(global_qtest, "/machine/soc/exti", NULL, 1, 1);
---

Note you could use a helper to ease readability:

static void exti_set_irq(int num, int lvl)
{
    qtest_set_irq_in(global_qtest, "/machine/soc/exti", NULL, num, lvl);
}

Tested-by: Philippe Mathieu-Daudé <philmd@linaro.org>
inesvarhol Jan. 4, 2024, 1:37 p.m. UTC | #4
Le jeudi 4 janvier 2024 à 14:05, Philippe Mathieu-Daudé <philmd@linaro.org> a écrit :

Hello,

> > +static void test_edge_selector(void)
> > +{
> > + enable_nvic_irq(EXTI0_IRQ);
> > +
> > + / Configure EXTI line 0 irq on rising edge */
> > + qtest_set_irq_in(global_qtest, "/machine/unattached/device[0]/exti",
> 
> 
> Markus, this qtest use seems to expect some stability in QOM path...
> 
> Inès, Arnaud, having the SoC unattached is dubious, it belongs to
> the machine.

Noted, we will fix that. 
Should we be concerned about the "stability in QOM path" ?

> 
> (qemu) info qom-tree
> /machine (b-l475e-iot01a-machine)
> /SYSCLK (clock)
> /peripheral (container)
> /peripheral-anon (container)
> /unattached (container)
> /device[0] (stm32l4x5xg-soc)
> 
> Eh I don't see the 'exti' here...
> 
> Indeed the test fails:
> 
> 17/35 qemu:qtest+qtest-arm / qtest-arm/test-arm-mptimer
> OK 0.44s 61 subtests passed
> ▶ 18/35 /arm/stm32l4x5/exti/reg_write_read
> FAIL
> ▶ 18/35 /arm/stm32l4x5/exti/no_software_interrupt
> FAIL
> ▶ 18/35 /arm/stm32l4x5/exti/software_interrupt
> FAIL
> ▶ 18/35 /arm/stm32l4x5/exti/masked_interrupt
> FAIL
> ▶ 18/35 /arm/stm32l4x5/exti/interrupt
> FAIL
> ▶ 18/35 /arm/stm32l4x5/exti/test_edge_selector
> FAIL
> Listing only the last 100 lines from a long log.

Yes indeed, the tests fail in this 2nd commit as the EXTI device isn't connected to the SoC yet (3rd commit).
I forgot to mention it in this in this version :/
Swapping the 2nd and 3rd commmit seems more straightforward to do ?
inesvarhol Jan. 4, 2024, 1:45 p.m. UTC | #5
Le jeudi 4 janvier 2024 à 14:33, Philippe Mathieu-Daudé <philmd@linaro.org> a écrit :


> On 28/12/23 17:19, Inès Varhol wrote:
>
> > Signed-off-by: Arnaud Minier arnaud.minier@telecom-paris.fr
> > Signed-off-by: Inès Varhol ines.varhol@telecom-paris.fr
> > ---
> > tests/qtest/meson.build | 5 +
> > tests/qtest/stm32l4x5_exti-test.c | 596 ++++++++++++++++++++++++++++++
> > 2 files changed, 601 insertions(+)
> > create mode 100644 tests/qtest/stm32l4x5_exti-test.c
>
>
> Once the SoC parentship fixed in based series, this patch
> requires:
>
> -- >8 --
>
> diff --git a/tests/qtest/stm32l4x5_exti-test.c
> b/tests/qtest/stm32l4x5_exti-test.c
> index 60c8297246..543199cd4d 100644
> --- a/tests/qtest/stm32l4x5_exti-test.c
> +++ b/tests/qtest/stm32l4x5_exti-test.c
> @@ -287,4 +287,3 @@ static void test_edge_selector(void)
> /* Configure EXTI line 0 irq on rising edge /
> - qtest_set_irq_in(global_qtest, "/machine/unattached/device[0]/exti",
> - NULL, 0, 1);
> + qtest_set_irq_in(global_qtest, "/machine/soc/exti", NULL, 0, 1);
> exti_writel(EXTI_IMR1, 0x00000001);
> @@ -294,4 +293,3 @@ static void test_edge_selector(void)
> / Test that an irq is raised on rising edge only /
> - qtest_set_irq_in(global_qtest, "/machine/unattached/device[0]/exti",
> - NULL, 0, 0);
> + qtest_set_irq_in(global_qtest, "/machine/soc/exti", NULL, 0, 0);
>
> @@ -301,4 +299,3 @@ static void test_edge_selector(void)
>
> - qtest_set_irq_in(global_qtest, "/machine/unattached/device[0]/exti",
> - NULL, 0, 1);
> + qtest_set_irq_in(global_qtest, "/machine/soc/exti", NULL, 0, 1);
>
> @@ -316,4 +313,3 @@ static void test_edge_selector(void)
> / Configure EXTI line 0 irq on falling edge /
> - qtest_set_irq_in(global_qtest, "/machine/unattached/device[0]/exti",
> - NULL, 0, 0);
> + qtest_set_irq_in(global_qtest, "/machine/soc/exti", NULL, 0, 0);
> exti_writel(EXTI_IMR1, 0x00000001);
> @@ -323,4 +319,3 @@ static void test_edge_selector(void)
> / Test that an irq is raised on falling edge only /
> - qtest_set_irq_in(global_qtest, "/machine/unattached/device[0]/exti",
> - NULL, 0, 1);
> + qtest_set_irq_in(global_qtest, "/machine/soc/exti", NULL, 0, 1);
>
> @@ -330,4 +325,3 @@ static void test_edge_selector(void)
>
> - qtest_set_irq_in(global_qtest, "/machine/unattached/device[0]/exti",
> - NULL, 0, 0);
> + qtest_set_irq_in(global_qtest, "/machine/soc/exti", NULL, 0, 0);
>
> @@ -350,4 +344,3 @@ static void test_edge_selector(void)
> / Test that an irq is raised on rising and falling edge /
> - qtest_set_irq_in(global_qtest, "/machine/unattached/device[0]/exti",
> - NULL, 0, 1);
> + qtest_set_irq_in(global_qtest, "/machine/soc/exti", NULL, 0, 1);
>
> @@ -357,4 +350,3 @@ static void test_edge_selector(void)
>
> - qtest_set_irq_in(global_qtest, "/machine/unattached/device[0]/exti",
> - NULL, 0, 0);
> + qtest_set_irq_in(global_qtest, "/machine/soc/exti", NULL, 0, 0);
>
> @@ -377,4 +369,3 @@ static void test_edge_selector(void)
> / Test that no irq is raised /
> - qtest_set_irq_in(global_qtest, "/machine/unattached/device[0]/exti",
> - NULL, 0, 1);
> + qtest_set_irq_in(global_qtest, "/machine/soc/exti", NULL, 0, 1);
>
> @@ -384,4 +375,3 @@ static void test_edge_selector(void)
>
> - qtest_set_irq_in(global_qtest, "/machine/unattached/device[0]/exti",
> - NULL, 0, 0);
> + qtest_set_irq_in(global_qtest, "/machine/soc/exti", NULL, 0, 0);
>
> @@ -500,4 +490,3 @@ static void test_masked_interrupt(void)
> / Simulate rising edge from GPIO line 1 /
> - qtest_set_irq_in(global_qtest, "/machine/unattached/device[0]/exti",
> - NULL, 1, 1);
> + qtest_set_irq_in(global_qtest, "/machine/soc/exti", NULL, 1, 1);
>
> @@ -550,4 +539,3 @@ static void test_interrupt(void)
> / Simulate rising edge from GPIO line 1 */
> - qtest_set_irq_in(global_qtest, "/machine/unattached/device[0]/exti",
> - NULL, 1, 1);
> + qtest_set_irq_in(global_qtest, "/machine/soc/exti", NULL, 1, 1);
> ---
>
> Note you could use a helper to ease readability:
>
> static void exti_set_irq(int num, int lvl)
> {
> qtest_set_irq_in(global_qtest, "/machine/soc/exti", NULL, num, lvl);
> }
>
> Tested-by: Philippe Mathieu-Daudé philmd@linaro.org

Ok thank you a lot !
And yes, we'll swap the 2nd and 3rd commit as you said.
Philippe Mathieu-Daudé Jan. 5, 2024, 10:13 a.m. UTC | #6
(+Mark & Eduardo)

On 4/1/24 14:37, inesvarhol wrote:
> 
> Le jeudi 4 janvier 2024 à 14:05, Philippe Mathieu-Daudé <philmd@linaro.org> a écrit :
> 
> Hello,
> 
>>> +static void test_edge_selector(void)
>>> +{
>>> + enable_nvic_irq(EXTI0_IRQ);
>>> +
>>> + / Configure EXTI line 0 irq on rising edge */
>>> + qtest_set_irq_in(global_qtest, "/machine/unattached/device[0]/exti",
>>
>>
>> Markus, this qtest use seems to expect some stability in QOM path...
>>
>> Inès, Arnaud, having the SoC unattached is dubious, it belongs to
>> the machine.
> 
> Noted, we will fix that.
> Should we be concerned about the "stability in QOM path" ?

Don't worry about this Inès, I wanted to raise Markus attention on this.

You showed a legit use of stable QOM path, and Markus told me recently
there is no contract for QOM paths (it shouldn't be considered as a
stable API). IIRC Markus explanation, "/unattached" container was
added as a temporary hack to allow migrating QDev objects to QOM (see
around commit da57febfed "qdev: give all devices a canonical path",
11 years ago).

I agree anything under "/unattached" can be expected to be stable
(but we need a community consensus). Then the big question remaining
is "can any qom-path out of /unattached be considered stable?"

Regards,

Phil.
Philippe Mathieu-Daudé Jan. 5, 2024, 10:19 a.m. UTC | #7
On 5/1/24 11:13, Philippe Mathieu-Daudé wrote:
> (+Mark & Eduardo)
> 
> On 4/1/24 14:37, inesvarhol wrote:
>>
>> Le jeudi 4 janvier 2024 à 14:05, Philippe Mathieu-Daudé 
>> <philmd@linaro.org> a écrit :
>>
>> Hello,
>>
>>>> +static void test_edge_selector(void)
>>>> +{
>>>> + enable_nvic_irq(EXTI0_IRQ);
>>>> +
>>>> + / Configure EXTI line 0 irq on rising edge */
>>>> + qtest_set_irq_in(global_qtest, "/machine/unattached/device[0]/exti",
>>>
>>>
>>> Markus, this qtest use seems to expect some stability in QOM path...
>>>
>>> Inès, Arnaud, having the SoC unattached is dubious, it belongs to
>>> the machine.
>>
>> Noted, we will fix that.
>> Should we be concerned about the "stability in QOM path" ?
> 
> Don't worry about this Inès, I wanted to raise Markus attention on this.
> 
> You showed a legit use of stable QOM path, and Markus told me recently
> there is no contract for QOM paths (it shouldn't be considered as a
> stable API). IIRC Markus explanation, "/unattached" container was
> added as a temporary hack to allow migrating QDev objects to QOM (see
> around commit da57febfed "qdev: give all devices a canonical path",
> 11 years ago).

Hmm am I getting confused with "/peripheral-anon" (commit 8eb02831af
"dev: add an anonymous peripheral container")?

> I agree anything under "/unattached" can be expected to be stable
> (but we need a community consensus). Then the big question remaining
> is "can any qom-path out of /unattached be considered stable?"
> 
> Regards,
> 
> Phil.
Daniel P. Berrangé Jan. 5, 2024, 10:24 a.m. UTC | #8
On Thu, Jan 04, 2024 at 01:37:22PM +0000, inesvarhol wrote:
> 
> Le jeudi 4 janvier 2024 à 14:05, Philippe Mathieu-Daudé <philmd@linaro.org> a écrit :
> 
> Hello,
> 
> > > +static void test_edge_selector(void)
> > > +{
> > > + enable_nvic_irq(EXTI0_IRQ);
> > > +
> > > + / Configure EXTI line 0 irq on rising edge */
> > > + qtest_set_irq_in(global_qtest, "/machine/unattached/device[0]/exti",
> > 
> > 
> > Markus, this qtest use seems to expect some stability in QOM path...
> > 
> > Inès, Arnaud, having the SoC unattached is dubious, it belongs to
> > the machine.
> 
> Noted, we will fix that. 
> Should we be concerned about the "stability in QOM path" ?

QTest is a functional test harness that intentionally has knowledge
about QEMU internals.

IOW, usage of particular QOM path in qtest does *not* imply that
QOM path needs to be stable.  If QEMU internals change for whatever
reason, it is expected that QTests may need some updates to match.

QOM path stability only matters if there's a mgmt app facing use
case, which requires the app to have hardcoded knowledge of the
path.

Even a mgmt app can use unstable QOM paths, provided it has a way
to dynamically detect the path to be used, instead of hardcoding
it.


None the less, you may still choose to move it out of /unattached
at your discretion.

With regards,
Daniel
Philippe Mathieu-Daudé Jan. 5, 2024, 10:16 p.m. UTC | #9
On 5/1/24 11:24, Daniel P. Berrangé wrote:
> On Thu, Jan 04, 2024 at 01:37:22PM +0000, inesvarhol wrote:
>>
>> Le jeudi 4 janvier 2024 à 14:05, Philippe Mathieu-Daudé <philmd@linaro.org> a écrit :
>>
>> Hello,
>>
>>>> +static void test_edge_selector(void)
>>>> +{
>>>> + enable_nvic_irq(EXTI0_IRQ);
>>>> +
>>>> + / Configure EXTI line 0 irq on rising edge */
>>>> + qtest_set_irq_in(global_qtest, "/machine/unattached/device[0]/exti",
>>>
>>>
>>> Markus, this qtest use seems to expect some stability in QOM path...
>>>
>>> Inès, Arnaud, having the SoC unattached is dubious, it belongs to
>>> the machine.
>>
>> Noted, we will fix that.
>> Should we be concerned about the "stability in QOM path" ?
> 
> QTest is a functional test harness that intentionally has knowledge
> about QEMU internals.
> 
> IOW, usage of particular QOM path in qtest does *not* imply that
> QOM path needs to be stable.  If QEMU internals change for whatever
> reason, it is expected that QTests may need some updates to match.

Good point.

> QOM path stability only matters if there's a mgmt app facing use
> case, which requires the app to have hardcoded knowledge of the
> path.
> 
> Even a mgmt app can use unstable QOM paths, provided it has a way
> to dynamically detect the path to be used, instead of hardcoding
> it.

I can understand this use to lookup "on which CDROM tray is
inserted the blkdrv named FOO", but to find a component on a
well specified system on chip, this is overkill.

> None the less, you may still choose to move it out of /unattached
> at your discretion.

Yeah we should clean those...

Thanks for clarifying,

Phil.
Mark Cave-Ayland Jan. 7, 2024, 2:04 p.m. UTC | #10
On 05/01/2024 10:13, Philippe Mathieu-Daudé wrote:

> (+Mark & Eduardo)
> 
> On 4/1/24 14:37, inesvarhol wrote:
>>
>> Le jeudi 4 janvier 2024 à 14:05, Philippe Mathieu-Daudé <philmd@linaro.org> a écrit :
>>
>> Hello,
>>
>>>> +static void test_edge_selector(void)
>>>> +{
>>>> + enable_nvic_irq(EXTI0_IRQ);
>>>> +
>>>> + / Configure EXTI line 0 irq on rising edge */
>>>> + qtest_set_irq_in(global_qtest, "/machine/unattached/device[0]/exti",
>>>
>>>
>>> Markus, this qtest use seems to expect some stability in QOM path...
>>>
>>> Inès, Arnaud, having the SoC unattached is dubious, it belongs to
>>> the machine.
>>
>> Noted, we will fix that.
>> Should we be concerned about the "stability in QOM path" ?
> 
> Don't worry about this Inès, I wanted to raise Markus attention on this.
> 
> You showed a legit use of stable QOM path, and Markus told me recently
> there is no contract for QOM paths (it shouldn't be considered as a
> stable API). IIRC Markus explanation, "/unattached" container was
> added as a temporary hack to allow migrating QDev objects to QOM (see
> around commit da57febfed "qdev: give all devices a canonical path",
> 11 years ago).
> 
> I agree anything under "/unattached" can be expected to be stable
> (but we need a community consensus). Then the big question remaining
> is "can any qom-path out of /unattached be considered stable?"

For the moment I would definitely say no, and that is mainly because if we were to 
assume that QOM paths were stable today then I can see it being a barrier to updating 
older code to meet our current guidelines.

These days I think more about QOM paths being related to the lifecycle of the objects 
e.g. a machine object has child devices, which may also consist of a number of other 
children in the case of a multi-function device. For me this means that using 
object_resolve_path_component() to look up a child object seems reasonable, in 
contrast with expecting the entire path to be stable.

One thing I think about often is whether the use of device[n] is suitable within QOM 
tree. For example, if I have a command line like:

   -device foo,myprop=prop0,id=fooid0 -device foo,myprop=prop1,id=fooid1

currently they would appear in "info qom-tree" as:

   /machine
     /unattached
       /device[0] (foo)
       /device[1] (foo)

whereas it feels this could be done better as:

   /machine
     /unattached
       /foo[0] (fooid0)
       /foo[1] (fooid1)

This would automatically place devices of the same type within a QOM array to allow 
them to be accessed separately by type, or even directly via the "id" if we assume 
they are unique. In particular if you have a machine with 2 foo in-built devices you 
could then potentially configure them separately using -global foo[0].myprop=newprop0 
and/or -global foo[1].myprop=newprop1 which is something that currently isn't possible.


ATB,

Mark.
Markus Armbruster Jan. 8, 2024, 4:15 p.m. UTC | #11
Philippe Mathieu-Daudé <philmd@linaro.org> writes:

> On 5/1/24 11:13, Philippe Mathieu-Daudé wrote:
>> (+Mark & Eduardo)
>> On 4/1/24 14:37, inesvarhol wrote:
>>>
>>> Le jeudi 4 janvier 2024 à 14:05, Philippe Mathieu-Daudé <philmd@linaro.org> a écrit :
>>>
>>> Hello,
>>>
>>>>> +static void test_edge_selector(void)
>>>>> +{
>>>>> + enable_nvic_irq(EXTI0_IRQ);
>>>>> +
>>>>> + / Configure EXTI line 0 irq on rising edge */
>>>>> + qtest_set_irq_in(global_qtest, "/machine/unattached/device[0]/exti",
>>>>
>>>>
>>>> Markus, this qtest use seems to expect some stability in QOM path...
>>>>
>>>> Inès, Arnaud, having the SoC unattached is dubious, it belongs to
>>>> the machine.
>>>
>>> Noted, we will fix that.
>>> Should we be concerned about the "stability in QOM path" ?
>>
>> Don't worry about this Inès, I wanted to raise Markus attention on this.
>>
>> You showed a legit use of stable QOM path, and Markus told me recently
>> there is no contract for QOM paths (it shouldn't be considered as a
>> stable API). IIRC Markus explanation, "/unattached" container was
>> added as a temporary hack to allow migrating QDev objects to QOM (see
>> around commit da57febfed "qdev: give all devices a canonical path",
>> 11 years ago).

I'm not sure the hack was intended to be temporary.  Doesn't matter now.

The connection between parent and child is a child property of the
parent.  Like all properties, it has a name.  These names are the
components of the canonical path.

When the code that creates the child makes the connection, it can give
the property a sensible name.

When it doesn't, we sometimes do it in generic code, using the
/machine/unattached orphanage, and a name that contains a counter, like

    /machine/unattached/device[N]
    /machine/unattached/non-qdev-gpio[N]

The actual name depends on execution order, because the counter value
does.  Brittle.

> Hmm am I getting confused with "/peripheral-anon" (commit 8eb02831af
> "dev: add an anonymous peripheral container")?

Not the same, but related.  Devices added with -device / device_add go
into /machine/peripheral/ID when they have id=ID, else into
/machine/peripheral/anon/device[N].  Before the commit you quoted, the
latter were orphaned I believe.

>> I agree anything under "/unattached" can be expected to be stable
>> (but we need a community consensus). Then the big question remaining
>> is "can any qom-path out of /unattached be considered stable?"

Backwards?  Keeping /machine/unattached/FOO[N] stable is harder then the
paths the code picks explicitly.
Markus Armbruster Jan. 8, 2024, 4:21 p.m. UTC | #12
Mark Cave-Ayland <mark.cave-ayland@ilande.co.uk> writes:

> On 05/01/2024 10:13, Philippe Mathieu-Daudé wrote:
>
>> (+Mark & Eduardo)
>> On 4/1/24 14:37, inesvarhol wrote:
>>>
>>> Le jeudi 4 janvier 2024 à 14:05, Philippe Mathieu-Daudé <philmd@linaro.org> a écrit :
>>>
>>> Hello,
>>>
>>>>> +static void test_edge_selector(void)
>>>>> +{
>>>>> + enable_nvic_irq(EXTI0_IRQ);
>>>>> +
>>>>> + / Configure EXTI line 0 irq on rising edge */
>>>>> + qtest_set_irq_in(global_qtest, "/machine/unattached/device[0]/exti",
>>>>
>>>>
>>>> Markus, this qtest use seems to expect some stability in QOM path...
>>>>
>>>> Inès, Arnaud, having the SoC unattached is dubious, it belongs to
>>>> the machine.
>>>
>>> Noted, we will fix that.
>>> Should we be concerned about the "stability in QOM path" ?
>>
>> Don't worry about this Inès, I wanted to raise Markus attention on this.
>>
>> You showed a legit use of stable QOM path, and Markus told me recently
>> there is no contract for QOM paths (it shouldn't be considered as a
>> stable API). IIRC Markus explanation, "/unattached" container was
>> added as a temporary hack to allow migrating QDev objects to QOM (see
>> around commit da57febfed "qdev: give all devices a canonical path",
>> 11 years ago).
>>
>> I agree anything under "/unattached" can be expected to be stable
>> (but we need a community consensus). Then the big question remaining
>> is "can any qom-path out of /unattached be considered stable?"
>
> For the moment I would definitely say no, and that is mainly because if we were to assume that QOM paths were stable today then I can see it being a barrier to updating older code to meet our current guidelines.
>
> These days I think more about QOM paths being related to the lifecycle of the objects e.g. a machine object has child devices, which may also consist of a number of other children in the case of a multi-function device. For me this means that using object_resolve_path_component() to look up a child object seems reasonable, in contrast with expecting the entire path to be stable.
>
> One thing I think about often is whether the use of device[n] is suitable within QOM tree. For example, if I have a command line like:
>
>   -device foo,myprop=prop0,id=fooid0 -device foo,myprop=prop1,id=fooid1
>
> currently they would appear in "info qom-tree" as:
>
>   /machine
>     /unattached
>       /device[0] (foo)
>       /device[1] (foo)

Actually

    /machine
      /peripheral (container)
        /fooid0 (foo
        /fooid1 (foo)

If you omit id=..., you get

    /machine
      /peripheral-anon (container)
        /device[2] (usb-mouse)
        /device[3] (usb-mouse)

or similar; the actual numbers in [brackets] depend on the board.

> whereas it feels this could be done better as:
>
>   /machine
>     /unattached
>       /foo[0] (fooid0)
>       /foo[1] (fooid1)
>
> This would automatically place devices of the same type within a QOM array to allow them to be accessed separately by type, or even directly via the "id" if we assume they are unique. In particular if you have a machine with 2 foo in-built devices you could then potentially configure them separately using -global foo[0].myprop=newprop0 and/or -global foo[1].myprop=newprop1 which is something that currently isn't possible.
>
>
> ATB,
>
> Mark.
diff mbox series

Patch

diff --git a/tests/qtest/meson.build b/tests/qtest/meson.build
index 47dabf91d0..d5126f4d86 100644
--- a/tests/qtest/meson.build
+++ b/tests/qtest/meson.build
@@ -194,6 +194,10 @@  qtests_aspeed = \
   ['aspeed_hace-test',
    'aspeed_smc-test',
    'aspeed_gpio-test']
+
+qtests_stm32l4x5 = \
+  ['stm32l4x5_exti-test']
+
 qtests_arm = \
   (config_all_devices.has_key('CONFIG_MPS2') ? ['sse-timer-test'] : []) + \
   (config_all_devices.has_key('CONFIG_CMSDK_APB_DUALTIMER') ? ['cmsdk-apb-dualtimer-test'] : []) + \
@@ -207,6 +211,7 @@  qtests_arm = \
   (config_all_devices.has_key('CONFIG_TPM_TIS_I2C') ? ['tpm-tis-i2c-test'] : []) + \
   (config_all_devices.has_key('CONFIG_VEXPRESS') ? ['test-arm-mptimer'] : []) + \
   (config_all_devices.has_key('CONFIG_MICROBIT') ? ['microbit-test'] : []) + \
+  (config_all_devices.has_key('CONFIG_STM32L4X5_SOC') ? qtests_stm32l4x5 : []) + \
   ['arm-cpu-features',
    'boot-serial-test']
 
diff --git a/tests/qtest/stm32l4x5_exti-test.c b/tests/qtest/stm32l4x5_exti-test.c
new file mode 100644
index 0000000000..60c8297246
--- /dev/null
+++ b/tests/qtest/stm32l4x5_exti-test.c
@@ -0,0 +1,596 @@ 
+/*
+ * QTest testcase for STM32L4x5_EXTI
+ *
+ * Copyright (c) 2023 Arnaud Minier <arnaud.minier@telecom-paris.fr>
+ * Copyright (c) 2023 Inès Varhol <ines.varhol@telecom-paris.fr>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+
+#include "qemu/osdep.h"
+#include "libqtest-single.h"
+
+#define EXTI_BASE_ADDR 0x40010400
+#define EXTI_IMR1 0x00
+#define EXTI_EMR1 0x04
+#define EXTI_RTSR1 0x08
+#define EXTI_FTSR1 0x0C
+#define EXTI_SWIER1 0x10
+#define EXTI_PR1 0x14
+#define EXTI_IMR2 0x20
+#define EXTI_EMR2 0x24
+#define EXTI_RTSR2 0x28
+#define EXTI_FTSR2 0x2C
+#define EXTI_SWIER2 0x30
+#define EXTI_PR2 0x34
+
+#define NVIC_ISER 0xE000E100
+#define NVIC_ISPR 0xE000E200
+#define NVIC_ICPR 0xE000E280
+
+#define EXTI0_IRQ 6
+#define EXTI1_IRQ 7
+#define EXTI35_IRQ 1
+
+static void enable_nvic_irq(unsigned int n)
+{
+    writel(NVIC_ISER, 1 << n);
+}
+
+static void unpend_nvic_irq(unsigned int n)
+{
+    writel(NVIC_ICPR, 1 << n);
+}
+
+static bool check_nvic_pending(unsigned int n)
+{
+    return readl(NVIC_ISPR) & (1 << n);
+}
+
+static void exti_writel(unsigned int offset, uint32_t value)
+{
+    writel(EXTI_BASE_ADDR + offset, value);
+}
+
+static uint32_t exti_readl(unsigned int offset)
+{
+    return readl(EXTI_BASE_ADDR + offset);
+}
+
+static void test_reg_write_read(void)
+{
+    /* Test that non-reserved bits in xMR and xTSR can be set and cleared */
+
+    exti_writel(EXTI_IMR1, 0xFFFFFFFF);
+    uint32_t imr1 = exti_readl(EXTI_IMR1);
+    g_assert_cmpuint(imr1, ==, 0xFFFFFFFF);
+    exti_writel(EXTI_IMR1, 0x00000000);
+    imr1 = exti_readl(EXTI_IMR1);
+    g_assert_cmpuint(imr1, ==, 0x00000000);
+
+    exti_writel(EXTI_EMR1, 0xFFFFFFFF);
+    uint32_t emr1 = exti_readl(EXTI_EMR1);
+    g_assert_cmpuint(emr1, ==, 0xFFFFFFFF);
+    exti_writel(EXTI_EMR1, 0x00000000);
+    emr1 = exti_readl(EXTI_EMR1);
+    g_assert_cmpuint(emr1, ==, 0x00000000);
+
+    exti_writel(EXTI_RTSR1, 0xFFFFFFFF);
+    uint32_t rtsr1 = exti_readl(EXTI_RTSR1);
+    g_assert_cmpuint(rtsr1, ==, 0x007DFFFF);
+    exti_writel(EXTI_RTSR1, 0x00000000);
+    rtsr1 = exti_readl(EXTI_RTSR1);
+    g_assert_cmpuint(rtsr1, ==, 0x00000000);
+
+    exti_writel(EXTI_FTSR1, 0xFFFFFFFF);
+    uint32_t ftsr1 = exti_readl(EXTI_FTSR1);
+    g_assert_cmpuint(ftsr1, ==, 0x007DFFFF);
+    exti_writel(EXTI_FTSR1, 0x00000000);
+    ftsr1 = exti_readl(EXTI_FTSR1);
+    g_assert_cmpuint(ftsr1, ==, 0x00000000);
+
+    exti_writel(EXTI_IMR2, 0xFFFFFFFF);
+    uint32_t imr2 = exti_readl(EXTI_IMR2);
+    g_assert_cmpuint(imr2, ==, 0x000000FF);
+    exti_writel(EXTI_IMR2, 0x00000000);
+    imr2 = exti_readl(EXTI_IMR2);
+    g_assert_cmpuint(imr2, ==, 0x00000000);
+
+    exti_writel(EXTI_EMR2, 0xFFFFFFFF);
+    uint32_t emr2 = exti_readl(EXTI_EMR2);
+    g_assert_cmpuint(emr2, ==, 0x000000FF);
+    exti_writel(EXTI_EMR2, 0x00000000);
+    emr2 = exti_readl(EXTI_EMR2);
+    g_assert_cmpuint(emr2, ==, 0x00000000);
+
+    exti_writel(EXTI_RTSR2, 0xFFFFFFFF);
+    uint32_t rtsr2 = exti_readl(EXTI_RTSR2);
+    g_assert_cmpuint(rtsr2, ==, 0x00000078);
+    exti_writel(EXTI_RTSR2, 0x00000000);
+    rtsr2 = exti_readl(EXTI_RTSR2);
+    g_assert_cmpuint(rtsr2, ==, 0x00000000);
+
+    exti_writel(EXTI_FTSR2, 0xFFFFFFFF);
+    uint32_t ftsr2 = exti_readl(EXTI_FTSR2);
+    g_assert_cmpuint(ftsr2, ==, 0x00000078);
+    exti_writel(EXTI_FTSR2, 0x00000000);
+    ftsr2 = exti_readl(EXTI_FTSR2);
+    g_assert_cmpuint(ftsr2, ==, 0x00000000);
+}
+
+static void test_direct_lines_write(void)
+{
+    /* Test that direct lines reserved bits are not written to */
+
+    exti_writel(EXTI_RTSR1, 0xFF820000);
+    uint32_t rtsr1 = exti_readl(EXTI_RTSR1);
+    g_assert_cmpuint(rtsr1, ==, 0x00000000);
+
+    exti_writel(EXTI_FTSR1, 0xFF820000);
+    uint32_t ftsr1 = exti_readl(EXTI_FTSR1);
+    g_assert_cmpuint(ftsr1, ==, 0x00000000);
+
+    exti_writel(EXTI_SWIER1, 0xFF820000);
+    uint32_t swier1 = exti_readl(EXTI_SWIER1);
+    g_assert_cmpuint(swier1, ==, 0x00000000);
+
+    exti_writel(EXTI_PR1, 0xFF820000);
+    uint32_t pr1 = exti_readl(EXTI_PR1);
+    g_assert_cmpuint(pr1, ==, 0x00000000);
+
+    exti_writel(EXTI_RTSR2, 0x00000087);
+    const uint32_t rtsr2 = exti_readl(EXTI_RTSR2);
+    g_assert_cmpuint(rtsr2, ==, 0x00000000);
+
+    exti_writel(EXTI_FTSR2, 0x00000087);
+    const uint32_t ftsr2 = exti_readl(EXTI_FTSR2);
+    g_assert_cmpuint(ftsr2, ==, 0x00000000);
+
+    exti_writel(EXTI_SWIER2, 0x00000087);
+    const uint32_t swier2 = exti_readl(EXTI_SWIER2);
+    g_assert_cmpuint(swier2, ==, 0x00000000);
+
+    exti_writel(EXTI_PR2, 0x00000087);
+    const uint32_t pr2 = exti_readl(EXTI_PR2);
+    g_assert_cmpuint(pr2, ==, 0x00000000);
+}
+
+static void test_reserved_bits_write(void)
+{
+    /* Test that reserved bits stay are not written to */
+
+    exti_writel(EXTI_IMR2, 0xFFFFFF00);
+    uint32_t imr2 = exti_readl(EXTI_IMR2);
+    g_assert_cmpuint(imr2, ==, 0x00000000);
+
+    exti_writel(EXTI_EMR2, 0xFFFFFF00);
+    uint32_t emr2 = exti_readl(EXTI_EMR2);
+    g_assert_cmpuint(emr2, ==, 0x00000000);
+
+    exti_writel(EXTI_RTSR2, 0xFFFFFF00);
+    const uint32_t rtsr2 = exti_readl(EXTI_RTSR2);
+    g_assert_cmpuint(rtsr2, ==, 0x00000000);
+
+    exti_writel(EXTI_FTSR2, 0xFFFFFF00);
+    const uint32_t ftsr2 = exti_readl(EXTI_FTSR2);
+    g_assert_cmpuint(ftsr2, ==, 0x00000000);
+
+    exti_writel(EXTI_SWIER2, 0xFFFFFF00);
+    const uint32_t swier2 = exti_readl(EXTI_SWIER2);
+    g_assert_cmpuint(swier2, ==, 0x00000000);
+
+    exti_writel(EXTI_PR2, 0xFFFFFF00);
+    const uint32_t pr2 = exti_readl(EXTI_PR2);
+    g_assert_cmpuint(pr2, ==, 0x00000000);
+}
+
+static void test_software_interrupt(void)
+{
+    /*
+     * Test that we can launch a software irq by :
+     * - enabling its line in IMR
+     * - and then setting a bit from '0' to '1' in SWIER
+     *
+     * And that the interruption stays pending in NVIC
+     * even after clearing the pending bit in PR.
+     */
+
+    /*
+     * Testing interrupt line EXTI0
+     * Bit 0 in EXTI_*1 registers (EXTI0) corresponds to GPIO Px_0
+     */
+
+    enable_nvic_irq(EXTI0_IRQ);
+    /* Check that there are no interrupts already pending in PR */
+    uint32_t pr1 = exti_readl(EXTI_PR1);
+    g_assert_cmpuint(pr1, ==, 0x00000000);
+    /* Check that this specific interrupt isn't pending in NVIC */
+    g_assert_false(check_nvic_pending(EXTI0_IRQ));
+
+    /* Enable interrupt line EXTI0 */
+    exti_writel(EXTI_IMR1, 0x00000001);
+    /* Set the right SWIER bit from '0' to '1' */
+    exti_writel(EXTI_SWIER1, 0x00000000);
+    exti_writel(EXTI_SWIER1, 0x00000001);
+
+    /* Check that the write in SWIER was effective */
+    uint32_t swier1 = exti_readl(EXTI_SWIER1);
+    g_assert_cmpuint(swier1, ==, 0x00000001);
+    /* Check that the corresponding pending bit in PR is set */
+    pr1 = exti_readl(EXTI_PR1);
+    g_assert_cmpuint(pr1, ==, 0x00000001);
+    /* Check that the corresponding interrupt is pending in the NVIC */
+    g_assert_true(check_nvic_pending(EXTI0_IRQ));
+
+    /* Clear the pending bit in PR */
+    exti_writel(EXTI_PR1, 0x00000001);
+
+    /* Check that the write in PR was effective */
+    pr1 = exti_readl(EXTI_PR1);
+    g_assert_cmpuint(pr1, ==, 0x00000000);
+    /* Check that the corresponding bit in SWIER was cleared */
+    swier1 = exti_readl(EXTI_SWIER1);
+    g_assert_cmpuint(swier1, ==, 0x00000000);
+    /* Check that the interrupt is still pending in the NVIC */
+    g_assert_true(check_nvic_pending(EXTI0_IRQ));
+
+    /*
+     * Testing interrupt line EXTI35
+     * Bit 3 in EXTI_*2 registers (EXTI35) corresponds to PVM 1 Wakeup
+     */
+
+    enable_nvic_irq(EXTI35_IRQ);
+    /* Check that there are no interrupts already pending */
+    uint32_t pr2 = exti_readl(EXTI_PR2);
+    g_assert_cmpuint(pr2, ==, 0x00000000);
+    g_assert_false(check_nvic_pending(EXTI35_IRQ));
+
+    /* Enable interrupt line EXTI0 */
+    exti_writel(EXTI_IMR2, 0x00000008);
+    /* Set the right SWIER bit from '0' to '1' */
+    exti_writel(EXTI_SWIER2, 0x00000000);
+    exti_writel(EXTI_SWIER2, 0x00000008);
+
+    /* Check that the write in SWIER was effective */
+    uint32_t swier2 = exti_readl(EXTI_SWIER2);
+    g_assert_cmpuint(swier2, ==, 0x00000008);
+    /* Check that the corresponding pending bit in PR is set */
+    pr2 = exti_readl(EXTI_PR2);
+    g_assert_cmpuint(pr2, ==, 0x00000008);
+    /* Check that the corresponding interrupt is pending in the NVIC */
+    g_assert_true(check_nvic_pending(EXTI35_IRQ));
+
+    /* Clear the pending bit in PR */
+    exti_writel(EXTI_PR2, 0x00000008);
+
+    /* Check that the write in PR was effective */
+    pr2 = exti_readl(EXTI_PR2);
+    g_assert_cmpuint(pr2, ==, 0x00000000);
+    /* Check that the corresponding bit in SWIER was cleared */
+    swier2 = exti_readl(EXTI_SWIER2);
+    g_assert_cmpuint(swier2, ==, 0x00000000);
+    /* Check that the interrupt is still pending in the NVIC */
+    g_assert_true(check_nvic_pending(EXTI35_IRQ));
+
+    /* Clean NVIC */
+    unpend_nvic_irq(EXTI0_IRQ);
+    g_assert_false(check_nvic_pending(EXTI0_IRQ));
+    unpend_nvic_irq(EXTI35_IRQ);
+    g_assert_false(check_nvic_pending(EXTI35_IRQ));
+}
+
+static void test_edge_selector(void)
+{
+    enable_nvic_irq(EXTI0_IRQ);
+
+    /* Configure EXTI line 0 irq on rising edge */
+    qtest_set_irq_in(global_qtest, "/machine/unattached/device[0]/exti",
+                     NULL, 0, 1);
+    exti_writel(EXTI_IMR1, 0x00000001);
+    exti_writel(EXTI_RTSR1, 0x00000001);
+    exti_writel(EXTI_FTSR1, 0x00000000);
+
+    /* Test that an irq is raised on rising edge only */
+    qtest_set_irq_in(global_qtest, "/machine/unattached/device[0]/exti",
+                     NULL, 0, 0);
+
+    uint32_t pr1 = exti_readl(EXTI_PR1);
+    g_assert_cmpuint(pr1, ==, 0x00000000);
+    g_assert_false(check_nvic_pending(EXTI0_IRQ));
+
+    qtest_set_irq_in(global_qtest, "/machine/unattached/device[0]/exti",
+                     NULL, 0, 1);
+
+    pr1 = exti_readl(EXTI_PR1);
+    g_assert_cmpuint(pr1, ==, 0x00000001);
+    g_assert_true(check_nvic_pending(EXTI0_IRQ));
+
+    /* Clean the test */
+    exti_writel(EXTI_PR1, 0x00000001);
+    pr1 = exti_readl(EXTI_PR1);
+    g_assert_cmpuint(pr1, ==, 0x00000000);
+    unpend_nvic_irq(EXTI0_IRQ);
+    g_assert_false(check_nvic_pending(EXTI0_IRQ));
+
+    /* Configure EXTI line 0 irq on falling edge */
+    qtest_set_irq_in(global_qtest, "/machine/unattached/device[0]/exti",
+                     NULL, 0, 0);
+    exti_writel(EXTI_IMR1, 0x00000001);
+    exti_writel(EXTI_RTSR1, 0x00000000);
+    exti_writel(EXTI_FTSR1, 0x00000001);
+
+    /* Test that an irq is raised on falling edge only */
+    qtest_set_irq_in(global_qtest, "/machine/unattached/device[0]/exti",
+                     NULL, 0, 1);
+
+    pr1 = exti_readl(EXTI_PR1);
+    g_assert_cmpuint(pr1, ==, 0x00000000);
+    g_assert_false(check_nvic_pending(EXTI0_IRQ));
+
+    qtest_set_irq_in(global_qtest, "/machine/unattached/device[0]/exti",
+                     NULL, 0, 0);
+
+    pr1 = exti_readl(EXTI_PR1);
+    g_assert_cmpuint(pr1, ==, 0x00000001);
+    g_assert_true(check_nvic_pending(EXTI0_IRQ));
+
+    /* Clean the test */
+    exti_writel(EXTI_PR1, 0x00000001);
+    pr1 = exti_readl(EXTI_PR1);
+    g_assert_cmpuint(pr1, ==, 0x00000000);
+    unpend_nvic_irq(EXTI0_IRQ);
+    g_assert_false(check_nvic_pending(EXTI0_IRQ));
+
+    /* Configure EXTI line 0 irq on falling and rising edge */
+    exti_writel(EXTI_IMR1, 0x00000001);
+    exti_writel(EXTI_RTSR1, 0x00000001);
+    exti_writel(EXTI_FTSR1, 0x00000000);
+
+    /* Test that an irq is raised on rising and falling edge */
+    qtest_set_irq_in(global_qtest, "/machine/unattached/device[0]/exti",
+                     NULL, 0, 1);
+
+    pr1 = exti_readl(EXTI_PR1);
+    g_assert_cmpuint(pr1, ==, 0x00000001);
+    g_assert_true(check_nvic_pending(EXTI0_IRQ));
+
+    qtest_set_irq_in(global_qtest, "/machine/unattached/device[0]/exti",
+                     NULL, 0, 0);
+
+    pr1 = exti_readl(EXTI_PR1);
+    g_assert_cmpuint(pr1, ==, 0x00000001);
+    g_assert_true(check_nvic_pending(EXTI0_IRQ));
+
+    /* Clean the test */
+    exti_writel(EXTI_PR1, 0x00000001);
+    pr1 = exti_readl(EXTI_PR1);
+    g_assert_cmpuint(pr1, ==, 0x00000000);
+    unpend_nvic_irq(EXTI0_IRQ);
+    g_assert_false(check_nvic_pending(EXTI0_IRQ));
+
+    /* Configure EXTI line 0 irq without selecting an edge trigger */
+    exti_writel(EXTI_IMR1, 0x00000001);
+    exti_writel(EXTI_RTSR1, 0x00000000);
+    exti_writel(EXTI_FTSR1, 0x00000000);
+
+    /* Test that no irq is raised */
+    qtest_set_irq_in(global_qtest, "/machine/unattached/device[0]/exti",
+                     NULL, 0, 1);
+
+    pr1 = exti_readl(EXTI_PR1);
+    g_assert_cmpuint(pr1, ==, 0x00000000);
+    g_assert_false(check_nvic_pending(EXTI0_IRQ));
+
+    qtest_set_irq_in(global_qtest, "/machine/unattached/device[0]/exti",
+                     NULL, 0, 0);
+
+    pr1 = exti_readl(EXTI_PR1);
+    g_assert_cmpuint(pr1, ==, 0x00000000);
+    g_assert_false(check_nvic_pending(EXTI0_IRQ));
+}
+
+static void test_no_software_interrupt(void)
+{
+    /*
+     * Test that software irq doesn't happen when :
+     * - corresponding bit in IMR isn't set
+     * - SWIER is set to 1 before IMR is set to 1
+     */
+
+    /*
+     * Testing interrupt line EXTI0
+     * Bit 0 in EXTI_*1 registers (EXTI0) corresponds to GPIO Px_0
+     */
+
+    enable_nvic_irq(EXTI0_IRQ);
+    /* Check that there are no interrupts already pending in PR */
+    uint32_t pr1 = exti_readl(EXTI_PR1);
+    g_assert_cmpuint(pr1, ==, 0x00000000);
+    /* Check that this specific interrupt isn't pending in NVIC */
+    g_assert_false(check_nvic_pending(EXTI0_IRQ));
+
+    /* Mask interrupt line EXTI0 */
+    exti_writel(EXTI_IMR1, 0x00000000);
+    /* Set the corresponding SWIER bit from '0' to '1' */
+    exti_writel(EXTI_SWIER1, 0x00000000);
+    exti_writel(EXTI_SWIER1, 0x00000001);
+
+    /* Check that the write in SWIER was effective */
+    uint32_t swier1 = exti_readl(EXTI_SWIER1);
+    g_assert_cmpuint(swier1, ==, 0x00000001);
+    /* Check that the pending bit in PR wasn't set */
+    pr1 = exti_readl(EXTI_PR1);
+    g_assert_cmpuint(pr1, ==, 0x00000000);
+    /* Check that the interrupt isn't pending in NVIC */
+    g_assert_false(check_nvic_pending(EXTI0_IRQ));
+
+    /* Enable interrupt line EXTI0 */
+    exti_writel(EXTI_IMR1, 0x00000001);
+
+    /* Check that the pending bit in PR wasn't set */
+    pr1 = exti_readl(EXTI_PR1);
+    g_assert_cmpuint(pr1, ==, 0x00000000);
+    /* Check that the interrupt isn't pending in NVIC */
+    g_assert_false(check_nvic_pending(EXTI0_IRQ));
+
+    /*
+     * Testing interrupt line EXTI35
+     * Bit 3 in EXTI_*2 registers (EXTI35) corresponds to PVM 1 Wakeup
+     */
+
+    enable_nvic_irq(EXTI35_IRQ);
+    /* Check that there are no interrupts already pending in PR */
+    uint32_t pr2 = exti_readl(EXTI_PR2);
+    g_assert_cmpuint(pr2, ==, 0x00000000);
+    /* Check that this specific interrupt isn't pending in NVIC */
+    g_assert_false(check_nvic_pending(EXTI35_IRQ));
+
+    /* Mask interrupt line EXTI35 */
+    exti_writel(EXTI_IMR2, 0x00000000);
+    /* Set the corresponding SWIER bit from '0' to '1' */
+    exti_writel(EXTI_SWIER2, 0x00000000);
+    exti_writel(EXTI_SWIER2, 0x00000008);
+
+    /* Check that the write in SWIER was effective */
+    uint32_t swier2 = exti_readl(EXTI_SWIER2);
+    g_assert_cmpuint(swier2, ==, 0x00000008);
+    /* Check that the pending bit in PR wasn't set */
+    pr2 = exti_readl(EXTI_PR2);
+    g_assert_cmpuint(pr2, ==, 0x00000000);
+    /* Check that the interrupt isn't pending in NVIC */
+    g_assert_false(check_nvic_pending(EXTI35_IRQ));
+
+    /* Enable interrupt line EXTI35 */
+    exti_writel(EXTI_IMR2, 0x00000008);
+
+    /* Check that the pending bit in PR wasn't set */
+    pr2 = exti_readl(EXTI_PR2);
+    g_assert_cmpuint(pr2, ==, 0x00000000);
+    /* Check that the interrupt isn't pending in NVIC */
+    g_assert_false(check_nvic_pending(EXTI35_IRQ));
+}
+
+static void test_masked_interrupt(void)
+{
+    /*
+     * Test that irq doesn't happen when :
+     * - corresponding bit in IMR isn't set
+     * - SWIER is set to 1 before IMR is set to 1
+     */
+
+    /*
+     * Testing interrupt line EXTI1
+     * with rising edge from GPIOx pin 1
+     */
+
+    enable_nvic_irq(EXTI1_IRQ);
+    /* Check that there are no interrupts already pending in PR */
+    uint32_t pr1 = exti_readl(EXTI_PR1);
+    g_assert_cmpuint(pr1, ==, 0x00000000);
+    /* Check that this specific interrupt isn't pending in NVIC */
+    g_assert_false(check_nvic_pending(EXTI1_IRQ));
+
+    /* Mask interrupt line EXTI1 */
+    exti_writel(EXTI_IMR1, 0x00000000);
+
+    /* Configure interrupt on rising edge */
+    exti_writel(EXTI_RTSR1, 0x00000002);
+
+    /* Simulate rising edge from GPIO line 1 */
+    qtest_set_irq_in(global_qtest, "/machine/unattached/device[0]/exti",
+                     NULL, 1, 1);
+
+    /* Check that the pending bit in PR wasn't set */
+    pr1 = exti_readl(EXTI_PR1);
+    g_assert_cmpuint(pr1, ==, 0x00000000);
+    /* Check that the interrupt isn't pending in NVIC */
+    g_assert_false(check_nvic_pending(EXTI1_IRQ));
+
+    /* Enable interrupt line EXTI1 */
+    exti_writel(EXTI_IMR1, 0x00000002);
+
+    /* Check that the pending bit in PR wasn't set */
+    pr1 = exti_readl(EXTI_PR1);
+    g_assert_cmpuint(pr1, ==, 0x00000000);
+    /* Check that the interrupt isn't pending in NVIC */
+    g_assert_false(check_nvic_pending(EXTI1_IRQ));
+}
+
+static void test_interrupt(void)
+{
+    /*
+     * Test that we can launch an irq by :
+     * - enabling its line in IMR
+     * - configuring interrupt on rising edge
+     * - and then setting the input line from '0' to '1'
+     *
+     * And that the interruption stays pending in NVIC
+     * even after clearing the pending bit in PR.
+     */
+
+    /*
+     * Testing interrupt line EXTI1
+     * with rising edge from GPIOx pin 1
+     */
+
+    enable_nvic_irq(EXTI1_IRQ);
+    /* Check that there are no interrupts already pending in PR */
+    uint32_t pr1 = exti_readl(EXTI_PR1);
+    g_assert_cmpuint(pr1, ==, 0x00000000);
+    /* Check that this specific interrupt isn't pending in NVIC */
+    g_assert_false(check_nvic_pending(EXTI1_IRQ));
+
+    /* Enable interrupt line EXTI1 */
+    exti_writel(EXTI_IMR1, 0x00000002);
+
+    /* Configure interrupt on rising edge */
+    exti_writel(EXTI_RTSR1, 0x00000002);
+
+    /* Simulate rising edge from GPIO line 1 */
+    qtest_set_irq_in(global_qtest, "/machine/unattached/device[0]/exti",
+                     NULL, 1, 1);
+
+    /* Check that the pending bit in PR was set */
+    pr1 = exti_readl(EXTI_PR1);
+    g_assert_cmpuint(pr1, ==, 0x00000002);
+    /* Check that the interrupt is pending in NVIC */
+    g_assert_true(check_nvic_pending(EXTI1_IRQ));
+
+    /* Clear the pending bit in PR */
+    exti_writel(EXTI_PR1, 0x00000002);
+
+    /* Check that the write in PR was effective */
+    pr1 = exti_readl(EXTI_PR1);
+    g_assert_cmpuint(pr1, ==, 0x00000000);
+    /* Check that the interrupt is still pending in the NVIC */
+    g_assert_true(check_nvic_pending(EXTI1_IRQ));
+
+    /* Clean NVIC */
+    unpend_nvic_irq(EXTI1_IRQ);
+    g_assert_false(check_nvic_pending(EXTI1_IRQ));
+}
+
+int main(int argc, char **argv)
+{
+    int ret;
+
+    g_test_init(&argc, &argv, NULL);
+    g_test_set_nonfatal_assertions();
+    qtest_add_func("stm32l4x5/exti/direct_lines", test_direct_lines_write);
+    qtest_add_func("stm32l4x5/exti/reserved_bits", test_reserved_bits_write);
+    qtest_add_func("stm32l4x5/exti/reg_write_read", test_reg_write_read);
+    qtest_add_func("stm32l4x5/exti/no_software_interrupt",
+                   test_no_software_interrupt);
+    qtest_add_func("stm32l4x5/exti/software_interrupt",
+                   test_software_interrupt);
+    qtest_add_func("stm32l4x5/exti/masked_interrupt", test_masked_interrupt);
+    qtest_add_func("stm32l4x5/exti/interrupt", test_interrupt);
+    qtest_add_func("stm32l4x5/exti/test_edge_selector", test_edge_selector);
+
+    qtest_start("-machine b-l475e-iot01a");
+    ret = g_test_run();
+    qtest_end();
+
+    return ret;
+}