Skip to contentSkip to conversation

hw/arm/sabrelite: Add FlexCAN support

PATCH Superseded Matyáš Bobek 3 participants 17 messages Dec 15, 2025 – Jan 16

Subsystems (2)

v1 of 6 Superseded Latest is v6 →

lore
Share & tools
lore
#0 lore raw

cover hw/arm/sabrelite: Add FlexCAN support

This series adds emulation of the FlexCAN CAN controller, version 2, found in NXP i.MX6 series SoCs. The controller is integrated into fsl-imx6 and the Sabrelite ARM board.

The chip has two FlexCAN controllers, but the Linux Sabrelite device tree enables only one by default. Linux kernel with both controllers enabled has been tested to work properly (using a custom device tree).
See docs/system/devices/can.rst for an example of QEMU command line invocation.

More information about the implementation can be found in my bachelor thesis [1].

[1] http://dspace.cvut.cz/bitstream/handle/10467/122654/F3-BP-2025-Bobek-Matyas-BP_Bobek_FlexCAN_final_4.pdf

Matyáš Bobek (6):
hw/arm/sabrelite: Convert machine to full class hw/misc/imx6_ccm: Add PLL3 and CAN clock hw/net/can/flexcan: NXP FlexCAN core emulation hw/arm: Plug FlexCAN into FSL_IMX6 and Sabrelite
tests: Add qtests for FlexCAN docs/arm/sabrelite: Mention FlexCAN support

 MAINTAINERS                   |    2 +
 docs/system/arm/sabrelite.rst |    1 +
 docs/system/devices/can.rst   |   20 +
 hw/arm/Kconfig                |    1 +
 hw/arm/fsl-imx6.c             |   29 +
 hw/arm/sabrelite.c            |   70 +-
 hw/misc/imx6_ccm.c            |   24 +
 hw/misc/trace-events          |    2 +
 hw/net/Kconfig                |    5 +
 hw/net/can/flexcan.c          | 1469 +++++++++++++++++++++++++++++++++
 hw/net/can/flexcan_regs.h     |  188 +++++
 hw/net/can/meson.build        |    1 +
 hw/net/can/trace-events       |   18 +
 include/hw/arm/fsl-imx6.h     |    7 +
 include/hw/misc/imx6_ccm.h    |    4 +
 include/hw/misc/imx_ccm.h     |    1 +
 include/hw/net/flexcan.h      |  153 ++++
 tests/qtest/flexcan-test.c    |  411 +++++++++
 tests/qtest/meson.build       |    1 +
 19 files changed, 2397 insertions(+), 10 deletions(-)
 create mode 100644 hw/net/can/flexcan.c
 create mode 100644 hw/net/can/flexcan_regs.h
 create mode 100644 include/hw/net/flexcan.h
 create mode 100644 tests/qtest/flexcan-test.c
Signature · 1 line
2.52.0
#1 lore raw

PATCH 1/6 hw/arm/sabrelite: Convert machine to full class

Define SABRELITE_MACHINE manually instead of
DEFINE_MACHINE_ARM to allow "canbus*" machine
properties to be added later.

---
 hw/arm/sabrelite.c | 54 +++++++++++++++++++++++++++++++++++++---------
 1 file changed, 44 insertions(+), 10 deletions(-)
hw/arm/sabrelite.c +44 -10
index 5b4ab7d77a..29418af190 100644
@@ -20,6 +20,16 @@
 #include "qemu/error-report.h"
 #include "system/qtest.h"
 
+typedef struct SabreliteMachineState {
+    MachineState parent_obj;
+    FslIMX6State soc;
+
+    struct arm_boot_info binfo;
+} Sabrelite;
+
+#define TYPE_SABRELITE_MACHINE MACHINE_TYPE_NAME("sabrelite")
+OBJECT_DECLARE_SIMPLE_TYPE(SabreliteMachineState, SABRELITE_MACHINE)
+
 static struct arm_boot_info sabrelite_binfo = {
     /* DDR memory start */
     .loader_start = FSL_IMX6_MMDC_ADDR,
@@ -41,7 +51,7 @@ static void sabrelite_reset_secondary(ARMCPU *cpu,
 
 static void sabrelite_init(MachineState *machine)
 {
-    FslIMX6State *s;
+    Sabrelite *s = SABRELITE_MACHINE(machine);
 
     /* Check the amount of memory is compatible with the SOC */
     if (machine->ram_size > FSL_IMX6_MMDC_SIZE) {
@@ -50,13 +60,12 @@ static void sabrelite_init(MachineState *machine)
         exit(1);
     }
 
-    s = FSL_IMX6(object_new(TYPE_FSL_IMX6));
-    object_property_add_child(OBJECT(machine), "soc", OBJECT(s));
+    object_initialize_child(OBJECT(machine), "soc", &s->soc, TYPE_FSL_IMX6);
 
     /* Ethernet PHY address is 6 */
-    object_property_set_int(OBJECT(s), "fec-phy-num", 6, &error_fatal);
+    object_property_set_int(OBJECT(&s->soc), "fec-phy-num", 6, &error_fatal);
 
-    qdev_realize(DEVICE(s), NULL, &error_fatal);
+    qdev_realize(DEVICE(&s->soc), NULL, &error_fatal);
 
     memory_region_add_subregion(get_system_memory(), FSL_IMX6_MMDC_ADDR,
                                 machine->ram);
@@ -70,7 +79,7 @@ static void sabrelite_init(MachineState *machine)
         /* Add the sst25vf016b NOR FLASH memory to first SPI */
         Object *spi_dev;
 
-        spi_dev = object_resolve_path_component(OBJECT(s), "spi1");
+        spi_dev = object_resolve_path_component(OBJECT(&s->soc), "spi1");
         if (spi_dev) {
             SSIBus *spi_bus;
 
@@ -89,23 +98,33 @@ static void sabrelite_init(MachineState *machine)
                 qdev_realize_and_unref(flash_dev, BUS(spi_bus), &error_fatal);
 
                 cs_line = qdev_get_gpio_in_named(flash_dev, SSI_GPIO_CS, 0);
-                qdev_connect_gpio_out(DEVICE(&s->gpio[2]), 19, cs_line);
+                qdev_connect_gpio_out(DEVICE(&s->soc.gpio[2]), 19, cs_line);
             }
         }
     }
 
+
     sabrelite_binfo.ram_size = machine->ram_size;
     sabrelite_binfo.secure_boot = true;
     sabrelite_binfo.write_secondary_boot = sabrelite_write_secondary;
     sabrelite_binfo.secondary_cpu_reset_hook = sabrelite_reset_secondary;
 
     if (!qtest_enabled()) {
-        arm_load_kernel(&s->cpu[0], machine, &sabrelite_binfo);
+        arm_load_kernel(&s->soc.cpu[0], machine, &sabrelite_binfo);
     }
 }
 
-static void sabrelite_machine_init(MachineClass *mc)
+static void sabrelite_machine_instance_init(Object *obj)
 {
+    Sabrelite *s = SABRELITE_MACHINE(obj);
+
+    (void)s;
+}
+
+static void sabrelite_machine_class_init(ObjectClass *oc, const void *data)
+{
+    MachineClass *mc = MACHINE_CLASS(oc);
+
     mc->desc = "Freescale i.MX6 Quad SABRE Lite Board (Cortex-A9)";
     mc->init = sabrelite_init;
     mc->max_cpus = FSL_IMX6_NUM_CPUS;
@@ -114,4 +133,19 @@ static void sabrelite_machine_init(MachineClass *mc)
     mc->auto_create_sdcard = true;
 }
 
-DEFINE_MACHINE_ARM("sabrelite", sabrelite_machine_init)
+static const TypeInfo sabrelite_machine_init_typeinfo = {
+    .name       = TYPE_SABRELITE_MACHINE,
+    .parent     = TYPE_MACHINE,
+    .class_init = sabrelite_machine_class_init,
+    .instance_init = sabrelite_machine_instance_init,
+    .instance_size = sizeof(Sabrelite),
+    .abstract   = false,
+    .interfaces = arm_machine_interfaces,
+};
+
+static void sabrelite_machine_init_register_types(void)
+{
+    type_register_static(&sabrelite_machine_init_typeinfo);
+}
+
+type_init(sabrelite_machine_init_register_types)
Signature · 1 line
2.52.0
#2 lore raw

Re: [PATCH v1 1/6] hw/arm/sabrelite: Convert machine to full class

Am 15. Dezember 2025 20:03:10 UTC schrieb "Matyáš Bobek" <[email protected]>:

Quoted 14 lines

Define SABRELITE_MACHINE manually instead of
DEFINE_MACHINE_ARM to allow "canbus*" machine
properties to be added later.

---
hw/arm/sabrelite.c | 54 +++++++++++++++++++++++++++++++++++++---------

1 file changed, 44 insertions(+), 10 deletions(-)
hw/arm/sabrelite.c +0 -0
index 5b4ab7d77a..29418af190 100644

hw/arm/sabrelite.c line 23

Quoted 4 lines
hw/arm/sabrelite.c +1 -0
@@ -20,6 +20,16 @@
 #include "qemu/error-report.h"
 #include "system/qtest.h"
 
+typedef struct SabreliteMachineState {

No need for a typedef here since a "SabreliteMachineState" typedef is introduced by OBJECT_DECLARE_SIMPLE_TYPE below which should be used instead.

hw/arm/sabrelite.c line 24

Quoted 1 line
hw/arm/sabrelite.c +1 -0
+    MachineState parent_obj;

Add one blank line here to visually separate the base class.

hw/arm/sabrelite.c line 27

Quoted 3 lines
hw/arm/sabrelite.c +3 -0
+    FslIMX6State soc;
+
+    struct arm_boot_info binfo;

"binfo" is unused.

hw/arm/sabrelite.c line 28

Quoted 1 line
hw/arm/sabrelite.c +1 -0
+} Sabrelite;

So your struct may look like this:

struct SabreliteMachineState {
    MachineState parent_obj;

    FslIMX6State soc;
};
Quoted 43 lines
hw/arm/sabrelite.c +9 -6
+
+#define TYPE_SABRELITE_MACHINE MACHINE_TYPE_NAME("sabrelite")
+OBJECT_DECLARE_SIMPLE_TYPE(SabreliteMachineState, SABRELITE_MACHINE)
+
 static struct arm_boot_info sabrelite_binfo = {
    /* DDR memory start */
    .loader_start = FSL_IMX6_MMDC_ADDR,
@@ -41,7 +51,7 @@ static void sabrelite_reset_secondary(ARMCPU *cpu,
 
 static void sabrelite_init(MachineState *machine)
 {
-    FslIMX6State *s;
+    Sabrelite *s = SABRELITE_MACHINE(machine);
 
    /* Check the amount of memory is compatible with the SOC */
    if (machine->ram_size > FSL_IMX6_MMDC_SIZE) {
@@ -50,13 +60,12 @@ static void sabrelite_init(MachineState *machine)
        exit(1);
    }
 
-    s = FSL_IMX6(object_new(TYPE_FSL_IMX6));
-    object_property_add_child(OBJECT(machine), "soc", OBJECT(s));
+    object_initialize_child(OBJECT(machine), "soc", &s->soc, TYPE_FSL_IMX6);
 
    /* Ethernet PHY address is 6 */
-    object_property_set_int(OBJECT(s), "fec-phy-num", 6, &error_fatal);
+    object_property_set_int(OBJECT(&s->soc), "fec-phy-num", 6, &error_fatal);
 
-    qdev_realize(DEVICE(s), NULL, &error_fatal);
+    qdev_realize(DEVICE(&s->soc), NULL, &error_fatal);
 
    memory_region_add_subregion(get_system_memory(), FSL_IMX6_MMDC_ADDR,
                                machine->ram);
@@ -70,7 +79,7 @@ static void sabrelite_init(MachineState *machine)
        /* Add the sst25vf016b NOR FLASH memory to first SPI */
        Object *spi_dev;
 
-        spi_dev = object_resolve_path_component(OBJECT(s), "spi1");
+        spi_dev = object_resolve_path_component(OBJECT(&s->soc), "spi1");
        if (spi_dev) {
            SSIBus *spi_bus;
 

hw/arm/sabrelite.c static void sabrelite_init(MachineState *machine) · line 106

Quoted 10 lines
hw/arm/sabrelite.c +2 -1
@@ -89,23 +98,33 @@ static void sabrelite_init(MachineState *machine)
                qdev_realize_and_unref(flash_dev, BUS(spi_bus), &error_fatal);
 
                cs_line = qdev_get_gpio_in_named(flash_dev, SSI_GPIO_CS, 0);
-                qdev_connect_gpio_out(DEVICE(&s->gpio[2]), 19, cs_line);
+                qdev_connect_gpio_out(DEVICE(&s->soc.gpio[2]), 19, cs_line);
            }
        }
    }
 
+

Extra line here. Please remove to keep the style consistent.

Quoted 7 lines
hw/arm/sabrelite.c +0 -1
    sabrelite_binfo.ram_size = machine->ram_size;
    sabrelite_binfo.secure_boot = true;
    sabrelite_binfo.write_secondary_boot = sabrelite_write_secondary;
    sabrelite_binfo.secondary_cpu_reset_hook = sabrelite_reset_secondary;
 
    if (!qtest_enabled()) {
-        arm_load_kernel(&s->cpu[0], machine, &sabrelite_binfo);

hw/arm/sabrelite.c static void sabrelite_init(MachineState *machine) · line 121

Quoted 10 lines
hw/arm/sabrelite.c +5 -1
+        arm_load_kernel(&s->soc.cpu[0], machine, &sabrelite_binfo);
    }
 }
 
-static void sabrelite_machine_init(MachineClass *mc)
+static void sabrelite_machine_instance_init(Object *obj)
 {
+    Sabrelite *s = SABRELITE_MACHINE(obj);
+
+    (void)s;

Unused variable. This normally results in a warning, so shouldn't be done. I suggest deferring introduction of this method to patch "hw/arm: Plug FlexCAN into FSL_IMX6 and Sabrelite".

You may also consider splitting this patch in two, one which open codes the DEFINE_MACHINE_ARM macro and a second one introducing the dedicated struct SabreliteMachineState.

Best regards,
Bernhard

Quoted 30 lines
hw/arm/sabrelite.c +22 -1
+}
+
+static void sabrelite_machine_class_init(ObjectClass *oc, const void *data)
+{
+    MachineClass *mc = MACHINE_CLASS(oc);
+
    mc->desc = "Freescale i.MX6 Quad SABRE Lite Board (Cortex-A9)";
    mc->init = sabrelite_init;
    mc->max_cpus = FSL_IMX6_NUM_CPUS;
@@ -114,4 +133,19 @@ static void sabrelite_machine_init(MachineClass *mc)
    mc->auto_create_sdcard = true;
 }
 
-DEFINE_MACHINE_ARM("sabrelite", sabrelite_machine_init)
+static const TypeInfo sabrelite_machine_init_typeinfo = {
+    .name       = TYPE_SABRELITE_MACHINE,
+    .parent     = TYPE_MACHINE,
+    .class_init = sabrelite_machine_class_init,
+    .instance_init = sabrelite_machine_instance_init,
+    .instance_size = sizeof(Sabrelite),
+    .abstract   = false,
+    .interfaces = arm_machine_interfaces,
+};
+
+static void sabrelite_machine_init_register_types(void)
+{
+    type_register_static(&sabrelite_machine_init_typeinfo);
+}
+
+type_init(sabrelite_machine_init_register_types)
#3 lore raw

PATCH 2/6 hw/misc/imx6_ccm: Add PLL3 and CAN clock

In reply to Matyáš Bobek

Add fixed frequency (480 MHz) PLL3, of which the FlexCAN clock is derived, and compute FlexCAN frequency based on divider configuration.

---
 hw/misc/imx6_ccm.c         | 24 ++++++++++++++++++++++++
 hw/misc/trace-events       |  2 ++
 include/hw/misc/imx6_ccm.h |  4 ++++
 include/hw/misc/imx_ccm.h  |  1 +
 4 files changed, 31 insertions(+)
hw/misc/imx6_ccm.c +24 -0
index a10b67d396..45fdd0d5a8 100644
@@ -257,6 +257,15 @@ static uint64_t imx6_analog_get_pll2_clk(IMX6CCMState *dev)
     return freq;
 }
 
+static uint64_t imx6_analog_get_pll3_clk(IMX6CCMState *dev)
+{
+    uint64_t freq = 480000000;
+
+    trace_imx6_analog_get_pll3_clk(freq);
+
+    return freq;
+}
+
 static uint64_t imx6_analog_get_pll2_pfd0_clk(IMX6CCMState *dev)
 {
     uint64_t freq = 0;
@@ -344,6 +353,18 @@ static uint64_t imx6_ccm_get_per_clk(IMX6CCMState *dev)
     return freq;
 }
 
+static uint64_t imx6_ccm_get_can_clk(IMX6CCMState *dev)
+{
+    uint64_t freq = 0;
+
+    freq = imx6_analog_get_pll3_clk(dev) / 8;
+    freq /= (1 + EXTRACT(dev->ccm[CCM_CSCMR2], CAN_CLK_PODF));
+
+    trace_imx6_ccm_get_can_clk(freq);
+
+    return freq;
+}
+
 static uint32_t imx6_ccm_get_clock_frequency(IMXCCMState *dev, IMXClk clock)
 {
     uint32_t freq = 0;
@@ -358,6 +379,9 @@ static uint32_t imx6_ccm_get_clock_frequency(IMXCCMState *dev, IMXClk clock)
     case CLK_IPG_HIGH:
         freq = imx6_ccm_get_per_clk(s);
         break;
+    case CLK_CAN:
+        freq = imx6_ccm_get_can_clk(s);
+        break;
     case CLK_32k:
         freq = CKIL_FREQ;
         break;
hw/misc/trace-events +2 -0
index eeb9243898..7c4f1c45b8 100644
@@ -242,11 +242,13 @@ imx6_analog_get_periph_clk(uint32_t freq) "freq = %u Hz"
 imx6_analog_get_pll2_clk(uint32_t freq) "freq = %u Hz"
 imx6_analog_get_pll2_pfd0_clk(uint32_t freq) "freq = %u Hz"
 imx6_analog_get_pll2_pfd2_clk(uint32_t freq) "freq = %u Hz"
+imx6_analog_get_pll3_clk(uint32_t freq) "freq = %u Hz"
 imx6_analog_read(const char *reg, uint32_t value) "reg[%s] => 0x%" PRIx32
 imx6_analog_write(const char *reg, uint32_t value) "reg[%s] <= 0x%" PRIx32
 imx6_ccm_get_ahb_clk(uint32_t freq) "freq = %u Hz"
 imx6_ccm_get_ipg_clk(uint32_t freq) "freq = %u Hz"
 imx6_ccm_get_per_clk(uint32_t freq) "freq = %u Hz"
+imx6_ccm_get_can_clk(uint32_t freq) "freq = %u Hz"
 imx6_ccm_get_clock_frequency(unsigned clock, uint32_t freq) "(Clock = %d) = %u"
 imx6_ccm_read(const char *reg, uint32_t value) "reg[%s] => 0x%" PRIx32
 imx6_ccm_reset(void) ""
include/hw/misc/imx6_ccm.h +4 -0
index ccf46d7353..f498732727 100644
@@ -164,6 +164,10 @@
 #define PERCLK_PODF_SHIFT        (0)
 #define PERCLK_PODF_LENGTH       (6)
 
+/* CCM_CSCMR2 */
+#define CAN_CLK_PODF_SHIFT        (2)
+#define CAN_CLK_PODF_LENGTH       (6)
+
 /* CCM_ANALOG_PFD_528 */
 #define PFD0_FRAC_SHIFT          (0)
 #define PFD0_FRAC_LENGTH         (6)
include/hw/misc/imx_ccm.h +1 -0
index 7e5678e972..9ce3adf332 100644
@@ -46,6 +46,7 @@ typedef enum  {
     CLK_EXT,
     CLK_HIGH_DIV,
     CLK_HIGH,
+    CLK_CAN,
 } IMXClk;
 
 struct IMXCCMClass {
Signature · 1 line
2.52.0
Reviewed-by on v1 2/6
#4 lore raw

Re: [PATCH v1 2/6] hw/misc/imx6_ccm: Add PLL3 and CAN clock

Am 15. Dezember 2025 20:03:11 UTC schrieb "Matyáš Bobek" <[email protected]>:

Quoted 84 lines

Add fixed frequency (480 MHz) PLL3, of which the FlexCAN clock is derived, and compute FlexCAN frequency based on divider configuration.

---

hw/misc/imx6_ccm.c         | 24 ++++++++++++++++++++++++
hw/misc/trace-events       |  2 ++
include/hw/misc/imx6_ccm.h |  4 ++++
include/hw/misc/imx_ccm.h  |  1 +
4 files changed, 31 insertions(+)
hw/misc/imx6_ccm.c +24 -0
index a10b67d396..45fdd0d5a8 100644
@@ -257,6 +257,15 @@ static uint64_t imx6_analog_get_pll2_clk(IMX6CCMState *dev)
    return freq;
 }
 
+static uint64_t imx6_analog_get_pll3_clk(IMX6CCMState *dev)
+{
+    uint64_t freq = 480000000;
+
+    trace_imx6_analog_get_pll3_clk(freq);
+
+    return freq;
+}
+
 static uint64_t imx6_analog_get_pll2_pfd0_clk(IMX6CCMState *dev)
 {
    uint64_t freq = 0;
@@ -344,6 +353,18 @@ static uint64_t imx6_ccm_get_per_clk(IMX6CCMState *dev)
    return freq;
 }
 
+static uint64_t imx6_ccm_get_can_clk(IMX6CCMState *dev)
+{
+    uint64_t freq = 0;
+
+    freq = imx6_analog_get_pll3_clk(dev) / 8;
+    freq /= (1 + EXTRACT(dev->ccm[CCM_CSCMR2], CAN_CLK_PODF));
+
+    trace_imx6_ccm_get_can_clk(freq);
+
+    return freq;
+}
+
 static uint32_t imx6_ccm_get_clock_frequency(IMXCCMState *dev, IMXClk clock)
 {
    uint32_t freq = 0;
@@ -358,6 +379,9 @@ static uint32_t imx6_ccm_get_clock_frequency(IMXCCMState *dev, IMXClk clock)
    case CLK_IPG_HIGH:
        freq = imx6_ccm_get_per_clk(s);
        break;
+    case CLK_CAN:
+        freq = imx6_ccm_get_can_clk(s);
+        break;
    case CLK_32k:
        freq = CKIL_FREQ;
        break;
hw/misc/trace-events +0 -0
index eeb9243898..7c4f1c45b8 100644
@@ -242,11 +242,13 @@ imx6_analog_get_periph_clk(uint32_t freq) "freq = %u Hz"

imx6_analog_get_pll2_clk(uint32_t freq) "freq = %u Hz" imx6_analog_get_pll2_pfd0_clk(uint32_t freq) "freq = %u Hz" imx6_analog_get_pll2_pfd2_clk(uint32_t freq) "freq = %u Hz" +imx6_analog_get_pll3_clk(uint32_t freq) "freq = %u Hz" imx6_analog_read(const char *reg, uint32_t value) "reg[%s] => 0x%" PRIx32 imx6_analog_write(const char *reg, uint32_t value) "reg[%s] <= 0x%" PRIx32 imx6_ccm_get_ahb_clk(uint32_t freq) "freq = %u Hz" imx6_ccm_get_ipg_clk(uint32_t freq) "freq = %u Hz" imx6_ccm_get_per_clk(uint32_t freq) "freq = %u Hz" +imx6_ccm_get_can_clk(uint32_t freq) "freq = %u Hz" imx6_ccm_get_clock_frequency(unsigned clock, uint32_t freq) "(Clock = %d) = %u" imx6_ccm_read(const char *reg, uint32_t value) "reg[%s] => 0x%" PRIx32 imx6_ccm_reset(void) ""

include/hw/misc/imx6_ccm.h +0 -0
index ccf46d7353..f498732727 100644

include/hw/misc/imx6_ccm.h line 169

Quoted 6 lines
include/hw/misc/imx6_ccm.h +3 -0
@@ -164,6 +164,10 @@
 #define PERCLK_PODF_SHIFT        (0)
 #define PERCLK_PODF_LENGTH       (6)
 
+/* CCM_CSCMR2 */
+#define CAN_CLK_PODF_SHIFT        (2)
+#define CAN_CLK_PODF_LENGTH       (6)

Small nitpick: Alignment of the values is off by one compared to the other defines.

With that fixed:

Quoted 16 lines
include/hw/misc/imx6_ccm.h +1 -0
+
 /* CCM_ANALOG_PFD_528 */
 #define PFD0_FRAC_SHIFT          (0)
 #define PFD0_FRAC_LENGTH         (6)
include/hw/misc/imx_ccm.h +1 -0
index 7e5678e972..9ce3adf332 100644
@@ -46,6 +46,7 @@ typedef enum  {
    CLK_EXT,
    CLK_HIGH_DIV,
    CLK_HIGH,
+    CLK_CAN,
 } IMXClk;
 
 struct IMXCCMClass {
#5 lore raw

PATCH 3/6 hw/net/can/flexcan: NXP FlexCAN core emulation

In reply to Matyáš Bobek

This commit adds the FlexCAN2 emulator implementation and plugs it into Meson and Kconfig.
FlexCAN2 version can be found in i.MX6 SoCs and others.

More information about the implementation can be found in [1].

[1] http://dspace.cvut.cz/bitstream/handle/10467/122654/F3-BP-2025-Bobek-Matyas-BP_Bobek_FlexCAN_final_4.pdf

---
 MAINTAINERS               |    1 +
 hw/net/Kconfig            |    5 +
 hw/net/can/flexcan.c      | 1469 +++++++++++++++++++++++++++++++++++++
 hw/net/can/flexcan_regs.h |  188 +++++
 hw/net/can/meson.build    |    1 +
 hw/net/can/trace-events   |   18 +
 include/hw/net/flexcan.h  |  153 ++++
 7 files changed, 1835 insertions(+)
 create mode 100644 hw/net/can/flexcan.c
 create mode 100644 hw/net/can/flexcan_regs.h
 create mode 100644 include/hw/net/flexcan.h
MAINTAINERS +1 -0
index 63e9ba521b..a0b152939b 100644
@@ -2858,6 +2858,7 @@ W: https://canbus.pages.fel.cvut.cz/
 F: net/can/*
 F: hw/net/can/*
 F: include/net/can_*.h
+F: include/hw/net/flexcan.h
 F: docs/system/devices/can.rst
 
 OpenPIC interrupt controller
hw/net/Kconfig +5 -0
index 2b513d6895..160c311dcd 100644
@@ -160,3 +160,8 @@ config CAN_CTUCANFD_PCI
     default y if PCI_DEVICES
     depends on PCI && CAN_CTUCANFD
     select CAN_BUS
+
+config CAN_FLEXCAN
+    bool
+    default y
+    select CAN_BUS
hw/net/can/flexcan.c new file +1469 -0

1,469 lines not on this page · raw message

hw/net/can/flexcan_regs.h new file +188 -0

188 lines not on this page · raw message

hw/net/can/meson.build +1 -0
index 7382344628..401afde2e4 100644
@@ -6,3 +6,4 @@ system_ss.add(when: 'CONFIG_CAN_CTUCANFD', if_true: files('ctucan_core.c'))
 system_ss.add(when: 'CONFIG_CAN_CTUCANFD_PCI', if_true: files('ctucan_pci.c'))
 system_ss.add(when: 'CONFIG_XLNX_ZYNQMP', if_true: files('xlnx-zynqmp-can.c'))
 system_ss.add(when: 'CONFIG_XLNX_VERSAL', if_true: files('xlnx-versal-canfd.c'))
+system_ss.add(when: 'CONFIG_CAN_FLEXCAN', if_true: files('flexcan.c'))
hw/net/can/trace-events +18 -0
index de64ac1b31..83f574e81c 100644
@@ -1,3 +1,21 @@
+# flexcan.c
+flexcan_irq_update(void *inst, uint32_t mb_irqs1, uint32_t mb_irqs2, int setting) "%p: irqs1 0x%08x irqs2 0x%08x request %i"
+flexcan_set_mcr(void* inst, const char *enabled, const char *freeze, const char *fifo, const char *rdy, const char *sync) "%p: %s %s %s %s %s"
+flexcan_mb_write(void *inst, int mbidx, const char *code, int is_mailbox, uint32_t ctrl, uint32_t id) "%p: mbidx %i code %s is_mailbox %i ctrl 0x%08x id 0x%08x"
+flexcan_mb_lock(void *inst, int mbidx, int had_rx_code) "%p: mbidx %i had_rx_code %i"
+flexcan_mb_unlock(void *inst, int mbidx, const char *pending_frame) "%p: mbidx %i%s"
+flexcan_fifo_pop(void *inst, int non_empty_before, int non_empty_after) "%p: non_empty before %i non_empty_after %i"
+flexcan_fifo_push(void *inst, int n_occupied) "%p: n_slots_occupied %i"
+flexcan_reset(void* inst) "%p: resetting"
+flexcan_mem_op(void *inst, const char *op, uint32_t v, int offset, const char *reg_name, int size) "%p: %s 0x%08x at offset %i register %s size %i"
+flexcan_mem_accepts(void *inst, int offset, int size, int is_write, int is_supv) "%p: denied access: offset %i size %i is_write %i is_supv %i"
+flexcan_get_timestamp(void *inst, int64_t time_elapsed_ms, uint32_t bitrate, uint64_t cycles, uint32_t shift, uint32_t timestamp) "%p: time_elapsed %" PRIi64 "ms bitrate %ub/s cycles %" PRIu64 " shift %u timestamp 0x%04x"
+flexcan_get_bitrate(void *inst, uint32_t pe_freq, uint32_t prediv, uint32_t s_freq, uint32_t tseg1, uint32_t tseg2, uint32_t quata_per_bit, uint32_t bitrate) "%p: pe_freq %uHz prescaler %u s_freq %uHz tseg1 %uq tseg2 %uq total %uq/b bitrate %ub/s"
+flexcan_timer_start(void *inst, uint32_t bitrate, uint32_t value) "%p: bitrate %ub/s value 0x%04x"
+flexcan_timer_stop(void *inst, uint32_t bitrate, uint32_t value) "%p: bitrate %ub/s value 0x%04x"
+flexcan_mb_rx_check_mb(void *inst, int mbidx, const char *code, int is_matched, int is_ftr, int is_serviced, int is_locked) "%p: checking mb %i code %s is_matched %i is_free_to_receive %i is_serviced %i is_locked %i"
+flexcan_receive(void *inst, size_t n_frames) "%p: received %zu frames"
+
 # xlnx-zynqmp-can.c
 xlnx_can_update_irq(uint32_t isr, uint32_t ier, uint32_t irq) "ISR: 0x%08x IER: 0x%08x IRQ: 0x%08x"
 xlnx_can_reset(uint32_t val) "Resetting controller with value = 0x%08x"
include/hw/net/flexcan.h new file +153 -0

153 lines not on this page · raw message

Signature · 1 line
2.52.0
#6 lore raw

Re: [PATCH v1 3/6] hw/net/can/flexcan: NXP FlexCAN core emulation

Am 15. Dezember 2025 20:03:12 UTC schrieb "Matyáš Bobek" <[email protected]>:

Quoted 39 lines

This commit adds the FlexCAN2 emulator implementation and plugs it into Meson and Kconfig.
FlexCAN2 version can be found in i.MX6 SoCs and others.

More information about the implementation can be found in [1].

[1] http://dspace.cvut.cz/bitstream/handle/10467/122654/F3-BP-2025-Bobek-Matyas-BP_Bobek_FlexCAN_final_4.pdf

---

MAINTAINERS               |    1 +
hw/net/Kconfig            |    5 +
hw/net/can/flexcan.c      | 1469 +++++++++++++++++++++++++++++++++++++
hw/net/can/flexcan_regs.h |  188 +++++
hw/net/can/meson.build    |    1 +
hw/net/can/trace-events   |   18 +
include/hw/net/flexcan.h  |  153 ++++
7 files changed, 1835 insertions(+)

create mode 100644 hw/net/can/flexcan.c
create mode 100644 hw/net/can/flexcan_regs.h
create mode 100644 include/hw/net/flexcan.h

MAINTAINERS +0 -0
index 63e9ba521b..a0b152939b 100644
@@ -2858,6 +2858,7 @@ W: https://canbus.pages.fel.cvut.cz/

F: net/can/*
F: hw/net/can/*
F: include/net/can_*.h
+F: include/hw/net/flexcan.h
F: docs/system/devices/can.rst

OpenPIC interrupt controller

hw/net/Kconfig +0 -0
index 2b513d6895..160c311dcd 100644

hw/net/Kconfig config CAN_CTUCANFD_PCI · line 166

Quoted 7 lines
hw/net/Kconfig +4 -0
@@ -160,3 +160,8 @@ config CAN_CTUCANFD_PCI
    default y if PCI_DEVICES
    depends on PCI && CAN_CTUCANFD
    select CAN_BUS
+
+config CAN_FLEXCAN
+    bool
+    default y

I think the "default y" isn't needed since CONFIG_FSL_IMX6 will select it.

However, I need to add a "depends on IMX" to prevent linker errors. You might be able to reproduce this by enabling other architectures than arm-softmmu.

Quoted 169 lines
hw/net/Kconfig +1 -0
+    select CAN_BUS
hw/net/can/flexcan.c new file +162 -0
new file mode 100644
index 0000000000..1f50dd1d5b
@@ -0,0 +1,1469 @@
+/*
+ * QEMU model of the NXP FLEXCAN device.
+ *
+ * This implementation is based on the following reference manual:
+ * i.MX 6Dual/6Quad Applications Processor Reference Manual
+ * Document Number: IMX6DQRM, Rev. 6, 05/2020
+ *
+ * Copyright (c) 2025 Matyas Bobek <[email protected]>
+ *
+ * Based on CTU CAN FD emulation implemented by Jan Charvat.
+ *
+ * This code is licensed under the GPL version 2 or later.  See
+ * the COPYING file in the top-level directory.
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/log.h"
+#include "hw/sysbus.h"
+#include "qapi/error.h"
+#include "hw/irq.h"
+#include "migration/vmstate.h"
+#include "net/can_emu.h"
+#include "hw/qdev-properties.h"
+#include "trace.h"
+
+#include "hw/net/flexcan.h"
+#include "flexcan_regs.h"
+#include "qemu/timer.h"
+
+#define USE(var) (void)var;
+
+#define DEBUG_FLEXCAN 1
+#ifndef DEBUG_FLEXCAN
+#define DEBUG_FLEXCAN 0
+#endif
+
+/*
+ * Indicates MB w/ received frame has not been serviced yet
+ * This is an emulator-only flag in position of unused (reserved) bit
+ * of message buffer control register
+ */
+#define FLEXCAN_MB_CNT_NOT_SRV          BIT(23)
+/**
+ * if no MB is locked, FlexcanState.locked_mb
+ * is set to FLEXCAN_NO_MB_LOCKED
+ */
+#define FLEXCAN_NO_MB_LOCKED            -1
+/**
+ * if no frame is waiting in the SMB, FlexcanState.smb_target_mbid
+ * is set to FLEXCAN_SMB_EMPTY
+ */
+#define FLEXCAN_SMB_EMPTY               -1
+/**
+ * When the module is disabled or in freeze mode,
+ * the timer is not running. That is indicated by setting
+ * FlexcanState.timer_start to FLEXCAN_TIMER_STOPPED.
+ */
+#define FLEXCAN_TIMER_STOPPED           -1
+
+/**
+ * defines the end of the memory space of the implemented registers
+ *
+ * also prevents addressing memory after FlexcanRegs end
+ */
+#define FLEXCAN_ADDR_SPC_END offsetof(FlexcanRegs, _reserved6)
+QEMU_BUILD_BUG_ON(FLEXCAN_ADDR_SPC_END > sizeof(FlexcanRegs));
+
+/* These constants are returned by flexcan_fifo_rx() and flexcan_mb_rx(), */
+/* Retry the other receiving mechanism (ie. message bufer or mailbox). */
+#define FLEXCAN_RX_SEARCH_RETRY 0
+/* The frame was received and stored. */
+#define FLEXCAN_RX_SEARCH_ACCEPT 1
+/* The frame was filtered out and dropped. */
+#define FLEXCAN_RX_SEARCH_DROPPED 2
+
+/*
+ * These constants are returned by flexcan_mb_rx_check_mb().
+ * See flexcan_mb_rx_check_mb() kerneldoc for details.
+ */
+#define FLEXCAN_CHECK_MB_NIL 0
+#define FLEXCAN_CHECK_MB_MATCH 3
+#define FLEXCAN_CHECK_MB_MATCH_NON_FREE 1
+#define FLEXCAN_CHECK_MB_MATCH_LOCKED 5
+
+static const FlexcanRegs flexcan_regs_write_mask = {
+    .mcr = 0xF6EB337F,
+    .ctrl = 0xFFFFFFFF,
+    .timer = 0xFFFFFFFF,
+    .tcr = 0xFFFFFFFF,
+    .rxmgmask = 0xFFFFFFFF,
+    .rx14mask = 0xFFFFFFFF,
+    .rx15mask = 0xFFFFFFFF,
+    .ecr = 0xFFFFFFFF,
+    .esr = 0xFFFFFFFF,
+    .imask2 = 0xFFFFFFFF,
+    .imask1 = 0xFFFFFFFF,
+    .iflag2 = 0,
+    .iflag1 = 0,
+    .ctrl2 = 0xFFFFFFFF,
+    .esr2 = 0,
+    .imeur = 0,
+    .lrfr = 0,
+    .crcr = 0,
+    .rxfgmask = 0xFFFFFFFF,
+    .rxfir = 0,
+    .cbt = 0,
+    ._reserved2 = 0,
+    .dbg1 = 0,
+    .dbg2 = 0,
+    .mbs = { [0 ... 63] = {
+        .can_ctrl = 0xFFFFFFFF & ~FLEXCAN_MB_CNT_NOT_SRV,
+        .can_id = 0xFFFFFFFF,
+        .data = { 0xFFFFFFFF, 0xFFFFFFFF },
+    } },
+    ._reserved4 = {0},
+    .rximr = { [0 ... 63] = 0xFFFFFFFF },
+    ._reserved5 = {0},
+    .gfwr_mx6 = 0xFFFFFFFF,
+    ._reserved6 = {0},
+    ._reserved8 = {0},
+    .rx_smb0_raw = {0, 0, 0, 0},
+    .rx_smb1 = {0, 0, 0, 0},
+};
+static const FlexcanRegs flexcan_regs_reset_mask = {
+    .mcr = 0x80000000,
+    .ctrl = 0xFFFFFFFF,
+    .timer = 0,
+    .tcr = 0,
+    .rxmgmask = 0xFFFFFFFF,
+    .rx14mask = 0xFFFFFFFF,
+    .rx15mask = 0xFFFFFFFF,
+    .ecr = 0,
+    .esr = 0,
+    .imask2 = 0,
+    .imask1 = 0,
+    .iflag2 = 0,
+    .iflag1 = 0,
+    .ctrl2 = 0xFFFFFFFF,
+    .esr2 = 0,
+    .imeur = 0,
+    .lrfr = 0,
+    .crcr = 0,
+    .rxfgmask = 0xFFFFFFFF,
+    .rxfir = 0xFFFFFFFF,
+    .cbt = 0,
+    ._reserved2 = 0,
+    .dbg1 = 0,
+    .dbg2 = 0,
+    .mb = {0xFFFFFFFF},
+    ._reserved4 = {0},
+    .rximr = {0xFFFFFFFF},
+    ._reserved5 = {0},
+    .gfwr_mx6 = 0,
+    ._reserved6 = {0},
+    ._reserved8 = {0},
+    .rx_smb0_raw = {0, 0, 0, 0},
+    .rx_smb1 = {0, 0, 0, 0},
+};
+
+#if DEBUG_FLEXCAN

hw/net/can/flexcan.c line 172

Quoted 10 lines
hw/net/can/flexcan.c new file +10 -0
+
+#define DPRINTF(fmt, args...) \
+    fprintf(stderr, "(%p)[%s]%s: " fmt , (void *)s, TYPE_CAN_FLEXCAN, \
+            __func__, ##args);
+
+#else /* DEBUG_FLEXCAN */
+
+#define DPRINTF(fmt, args...) do { } while (0)
+
+#endif /* DEBUG_FLEXCAN */

Please no new DPRINTF. Use tracing and logging instead.

Quoted 89 lines
hw/net/can/flexcan.c new file +89 -0
+
+#define FLEXCAN_DBG_BUF_LEN 16
+
+static const char *flexcan_dbg_mb_code_strs[16] = {
+    "INACTIVE_RX",
+    "FULL",
+    "EMPTY",
+    "OVERRUN",
+    "INACTIVE_TX",
+    "RANSWER",
+    "DATA",
+    "TANSWER"
+};
+
+/**
+ * flexcan_dbg_mb_code() - Get the string representation of a mailbox code
+ * @mb_ctrl: The mailbox control register value
+ * @buf: The buffer to store the string representation
+ *
+ * Return: Either constant string or string formatted into @buf
+ */
+static const char *flexcan_dbg_mb_code(uint32_t mb_ctrl, char *buf)
+{
+    uint32_t code = mb_ctrl & FLEXCAN_MB_CODE_MASK;
+    uint32_t code_idx = code >> 24;
+    if (code == FLEXCAN_MB_CODE_TX_ABORT) {
+        return "ABORT";
+    }
+
+    const char *code_str = flexcan_dbg_mb_code_strs[code_idx >> 1];
+    if (code_idx & 1) {
+        g_snprintf(buf, FLEXCAN_DBG_BUF_LEN, "%s+BUSY", code_str);
+        return buf;
+    } else {
+        return code_str;
+    }
+}
+
+static const char *flexcan_dbg_reg_name_fixed(hwaddr addr)
+{
+    if (addr >= FLEXCAN_ADDR_SPC_END) {
+        return "OUT-OF-RANGE";
+    }
+
+    switch (addr) {
+    case offsetof(FlexcanRegs, mcr):
+        return "MCR";
+    case offsetof(FlexcanRegs, ctrl):
+        return "CTRL";
+    case offsetof(FlexcanRegs, timer):
+        return "TIMER";
+    case offsetof(FlexcanRegs, esr):
+        return "ESR";
+    case offsetof(FlexcanRegs, rxmgmask):
+        return "RXMGMASK";
+    case offsetof(FlexcanRegs, rx14mask):
+        return "RX14MASK";
+    case offsetof(FlexcanRegs, rx15mask):
+        return "RX15MASK";
+    case offsetof(FlexcanRegs, rxfgmask):
+        return "RXFGMASK";
+    case offsetof(FlexcanRegs, ecr):
+        return "ECR";
+    case offsetof(FlexcanRegs, ctrl2):
+        return "CTRL2";
+    case offsetof(FlexcanRegs, imask2):
+        return "IMASK2";
+    case offsetof(FlexcanRegs, imask1):
+        return "IMASK1";
+    case offsetof(FlexcanRegs, iflag2):
+        return "IFLAG2";
+    case offsetof(FlexcanRegs, iflag1):
+        return "IFLAG1";
+    }
+    return NULL;
+}
+
+static inline void flexcan_trace_mem_op(FlexcanState *s, hwaddr addr,
+                                        uint32_t value, int size, bool is_wr)
+{
+    if (trace_event_get_state_backends(TRACE_FLEXCAN_MEM_OP)) {
+        const char *reg_name = "unknown";
+        char reg_name_buf[FLEXCAN_DBG_BUF_LEN] = { 0 };
+        const char *reg_name_fixed = flexcan_dbg_reg_name_fixed(addr);
+        const char *op_string = is_wr ? "write" : "read";
+
+        if (reg_name_fixed) {
+            reg_name = reg_name_fixed;
+        } else if (addr >= 0x80 && addr < 0x480) {

hw/net/can/flexcan.c line 271

Quoted 10 lines
hw/net/can/flexcan.c new file +10 -0
+            int mbidx = (addr - 0x80) / 16;
+            g_snprintf(reg_name_buf, sizeof(reg_name_buf), "MB%i", mbidx);
+            reg_name = reg_name_buf;
+        } else if (addr >= 0x880 && addr < 0x9e0) {
+            int id = (addr - 0x880) / 4;
+            g_snprintf(reg_name_buf, sizeof(reg_name_buf), "RXIMR%i", id);
+            reg_name = reg_name_buf;
+        }
+
+        trace_flexcan_mem_op(s, op_string, value, addr, reg_name, size);

Better use "DEVICE(s)->canonical_path" instead of the "s" parameter which results in much more readable trace messages. This applies to all trace functions.

hw/net/can/flexcan.c line 278

Quoted 7 lines
hw/net/can/flexcan.c new file +7 -0
+    }
+}
+
+static const struct MemoryRegionOps flexcan_ops = {
+    .read = flexcan_mem_read,
+    .write = flexcan_mem_write,
+    .endianness = DEVICE_NATIVE_ENDIAN,

Please use DEVICE_LITTLE_ENDIAN for now since DEVICE_NATIVE_ENDIAN is deprecated, and checkpatch.pl actually flags it. While there may be big endian variants of this IP core, we don't need to worry about these at the moment since this device model already depends on the IMX clock module.

Quoted 92 lines
hw/net/can/flexcan.c new file +92 -0
+    .valid = {
+        .min_access_size = 1,
+        .max_access_size = 4,
+        .unaligned = true,
+        .accepts = flexcan_mem_accepts
+    },
+    .impl = {
+        .min_access_size = 4,
+        .max_access_size = 4,
+        .unaligned = false
+    },
+};
+
+static int flexcan_mb_rx(FlexcanState *s, const qemu_can_frame *frame);
+static void flexcan_mb_unlock(FlexcanState *s);
+
+/* ========== Mailbox Utils ========== */
+
+/**
+ * flexcan_mailbox_count() - Get number of enabled mailboxes
+ * @s: FlexCAN device pointer
+ *
+ * Count is based on MCR[MAXMB] field. Note that some of those mailboxes
+ * might be part of queue or queue ID filters or ordinary message buffers.
+ */
+static inline int flexcan_enabled_mailbox_count(const FlexcanState *s)
+{
+    return (s->regs.mcr & FLEXCAN_MCR_MAXMB(UINT32_MAX)) + 1;
+}
+
+/**
+ * flexcan_get_first_message_buffer() - Get pointer to first message buffer
+ * @s: FlexCAN device pointer
+ *
+ * In context of this function, message buffer means a mailbox which is not
+ * a queue element nor a queue filter. Note this function does not take
+ * MCR[MAXMB] into account, meaning that the returned mailbox
+ * might be disabled.
+ */
+static FlexcanRegsMessageBuffer *flexcan_get_first_message_buffer(
+                                 FlexcanState *s)
+{
+    if (s->regs.mcr & FLEXCAN_MCR_FEN) {
+        int rffn = (s->regs.ctrl2 & FLEXCAN_CTRL2_RFFN(UINT32_MAX)) >> 24;
+        return s->regs.mbs + 8 + 2 * rffn;
+    } else {
+        return s->regs.mbs;
+    }
+}
+
+/**
+ * flexcan_get_last_enabled_mailbox() - Get pointer to last enabled mailbox.
+ * @s: FlexCAN device pointer
+ *
+ * When used with flexcan_get_first_message_buffer(), all mailboxes *ptr in
+ * range `first_message_buffer() <= ptr <= last_enabled_mailbox` are valid
+ * message buffer mailboxes.
+ *
+ * Return: Last enabled mailbox in MCR[MAXMB] sense. The mailbox might be
+ * of any type.
+ */
+static inline FlexcanRegsMessageBuffer *flexcan_get_last_enabled_mailbox(
+                                 FlexcanState *s)
+{
+    return s->regs.mbs + flexcan_enabled_mailbox_count(s);
+}
+
+/**
+ * flexcan_get_first_filter_mailbox() - Get pointer to first queue filter.
+ * @s: FlexCAN device pointer
+ *
+ * This function does not check if FIFO is enabled.
+ *
+ * Return: Pointer to first queue filter element.
+ */
+static inline uint32_t *flexcan_get_first_filter_mailbox(FlexcanState *s)
+{
+    return (uint32_t *)(s->regs.mbs + 6);
+}
+
+/**
+ * flexcan_get_last_filter_mailbox() - Get pointer to last queue filter.
+ * @s: FlexCAN device pointer
+ *
+ * This function does not check if FIFO is enabled.
+ * All words in range [flexcan_get_first_filter_mailbox(),
+ * flexcan_get_last_filter_mailbox()] are queue filter elements, if queue
+ * is enabled.
+ *
+ * Return: Pointer to last queue filter element.
+ */
+static inline uint32_t *flexcan_get_last_filter_mailbox(FlexcanState *s)

hw/net/can/flexcan.c line 380

Quoted 10 lines
hw/net/can/flexcan.c new file +10 -0
+{
+    /* adding three to get pointer to the last word of the mailbox */
+    uint32_t *last_enabled_elem =
+        ((uint32_t *)flexcan_get_last_enabled_mailbox(s)) + 3;
+
+    int rffn = (s->regs.ctrl2 & FLEXCAN_CTRL2_RFFN(UINT32_MAX)) >> 24;
+    uint32_t *last_elem = (uint32_t *)(s->regs.mbs + 8 + 2 * rffn) - 1;
+
+    return MIN(last_elem, last_enabled_elem);
+}

The above two functions seem to be unused. Please remove.

Quoted 200 lines
hw/net/can/flexcan.c new file +200 -0
+
+/* ========== Free-running Timer ========== */
+static inline int64_t flexcan_get_time(void)
+{
+    return qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
+}
+
+/**
+ * flexcan_get_bitrate() - Calculate CAN bitrate (in Hz)
+ * @s: FlexCAN device pointer
+ *
+ * The bitrate is determined by FlexCAN configuration in CTRL1 register,
+ * and CCM co
+ */
+static uint32_t flexcan_get_bitrate(FlexcanState *s)
+{
+    uint32_t conf_presdiv = (s->regs.ctrl & FLEXCAN_CTRL_PRESDIV_MASK) >> 24;
+    uint32_t conf_pseg1 = (s->regs.ctrl & FLEXCAN_CTRL_PSEG1_MASK) >> 19;
+    uint32_t conf_pseg2 = (s->regs.ctrl & FLEXCAN_CTRL_PSEG2_MASK) >> 16;
+    uint32_t conf_propseg = s->regs.ctrl & FLEXCAN_CTRL_PROPSEG_MASK;
+
+    /* s_clock: CAN clock from CCM divivded by the prescaler */
+    assert(s->ccm);
+    uint32_t pe_freq = imx_ccm_get_clock_frequency(s->ccm, CLK_CAN);
+    uint32_t s_freq = pe_freq / (1 + conf_presdiv);
+
+    /* N of time quanta for segements */
+    uint32_t tseg1 = 2 + conf_pseg1 + conf_propseg;
+    uint32_t tseg2 = 1 + conf_pseg2;
+    uint32_t total_qpb = 1 + tseg1 + tseg2;
+
+    uint32_t bitrate = s_freq / total_qpb;
+
+    trace_flexcan_get_bitrate(s, pe_freq, 1 + conf_presdiv, s_freq, tseg1,
+                              tseg2, total_qpb, bitrate);
+    return bitrate;
+}
+
+/**
+ * int128_mul_6464() - Multiply two 64-bit integers into a 128-bit one
+ */
+static Int128 int128_muls_6464(int64_t ai, int64_t bi)
+{
+    uint64_t l, h;
+
+    muls64(&l, &h, ai, bi);
+    return int128_make128(l, h);
+}
+
+/**
+ * flexcan_get_timestamp() - Get current value of the 16-bit free-running timer
+ * @s: FlexCAN device pointer
+ * @mk_unique: if true, make the timestamp unique by incrementing it if needed
+ */
+static uint32_t flexcan_get_timestamp(FlexcanState *s, bool mk_unique)
+{
+    if (s->timer_start == FLEXCAN_TIMER_STOPPED) {
+        /* timer is not running, return last value */
+        trace_flexcan_get_timestamp(s, -1, 0, 0, 0, s->regs.timer);
+        return s->regs.timer;
+    }
+
+    int64_t current_time = flexcan_get_time();
+    int64_t elapsed_time_ns = current_time - s->timer_start;
+    int64_t elapsed_time_ms = elapsed_time_ns / 1000000;
+    if (elapsed_time_ns < 0) {
+        DPRINTF("timer overflow current_time=%li "
+                "timer_start=%li elapsed_time_ns=%li\n",
+                current_time, s->timer_start, elapsed_time_ns);
+        return 0xFFFF;
+    }
+
+    Int128 nanoseconds_in_second = int128_makes64(1000000000);
+    Int128 ncycles = int128_muls_6464(s->timer_freq, elapsed_time_ns);
+    Int128 cycles128 = int128_divs(ncycles, nanoseconds_in_second);
+    /* 64 bits hold for over 50k years at 10MHz */
+    uint64_t cycles = int128_getlo(cycles128);
+
+    uint32_t shift = 0;
+    if (mk_unique && cycles <= s->last_rx_timer_cycles) {
+        shift = 1;
+        cycles = s->last_rx_timer_cycles + shift;
+    }
+
+    s->last_rx_timer_cycles = cycles;
+    uint32_t rv = (uint32_t)cycles & 0xFFFF;
+
+    trace_flexcan_get_timestamp(s, elapsed_time_ms, s->timer_freq,
+                                cycles, shift, rv);
+    return rv;
+}
+
+/**
+ * flexcan_timer_start() - Start the free-running timer
+ * @s: FlexCAN device pointer
+ *
+ * This should be called when the module leaves freeze mode.
+ */
+static void flexcan_timer_start(FlexcanState *s)
+{
+    if (s->timer_start != FLEXCAN_TIMER_STOPPED) {
+        DPRINTF("module brought up, but timer is already running: "
+                "value=%" PRIu64 "\n", s->timer_start);
+    }
+    s->timer_freq = flexcan_get_bitrate(s);
+    s->timer_start = flexcan_get_time();
+    s->last_rx_timer_cycles = 0;
+
+    trace_flexcan_timer_start(s, s->timer_freq, s->regs.timer);
+}
+
+/**
+ * flexcan_timer_stop() - Stop the free-running timer
+ * @s: FlexCAN device pointer
+ *
+ * This should be called when the module enters freeze mode.
+ * Stores the current timestamp in the TIMER register.
+ */
+static void flexcan_timer_stop(FlexcanState *s)
+{
+    s->regs.timer = flexcan_get_timestamp(s, false);
+    s->timer_start = FLEXCAN_TIMER_STOPPED;
+
+    trace_flexcan_timer_stop(s, s->timer_freq, s->regs.timer);
+}
+
+/* ========== IRQ handling ========== */
+/**
+ * flexcan_irq_update() - Update qemu_irq line based on interrupt registers
+ * @s: FlexCAN device pointer
+ */
+static void flexcan_irq_update(FlexcanState *s)
+{
+    /* these are all interrupt sources from FlexCAN */
+    /* mailbox interrupt sources */
+    uint32_t mb_irqs1 = s->regs.iflag1 & s->regs.imask1;
+    uint32_t mb_irqs2 = s->regs.iflag2 & s->regs.imask2;
+
+    /**
+     * these interrupts aren't currently used and they can never be raised
+     *
+     * bool irq_wake_up = (s->regs.mcr & FLEXCAN_MCR_WAK_MSK) &&
+     *                    (s->regs.ecr & FLEXCAN_ESR_WAK_INT);
+     * bool irq_bus_off = (s->regs.ctrl & FLEXCAN_CTRL_BOFF_MSK) &&
+     *                    (s->regs.ecr & FLEXCAN_ESR_BOFF_INT);
+     * bool irq_error = (s->regs.ctrl & FLEXCAN_CTRL_ERR_MSK) &&
+     *                  (s->regs.ecr & FLEXCAN_ESR_ERR_INT);
+     * bool irq_tx_warn = (s->regs.ctrl & FLEXCAN_CTRL_TWRN_MSK) &&
+     *                    (s->regs.ecr & FLEXCAN_ESR_TWRN_INT);
+     * bool irq_rx_warn = (s->regs.ctrl & FLEXCAN_CTRL_RWRN_MSK) &&
+     *                    (s->regs.ecr & FLEXCAN_ESR_RWRN_INT);
+     */
+
+    int irq_setting = (mb_irqs1 | mb_irqs2) ? 1 : 0;
+    trace_flexcan_irq_update(s, mb_irqs1, mb_irqs2, irq_setting);
+
+    qemu_set_irq(s->irq, irq_setting);
+}
+
+/**
+ * flexcan_irq_iflag_set() - Set IFLAG bit corresponding to MB mbidx
+ * @s: FlexCAN device pointer
+ * @mbidx: mailbox index
+ */
+static void flexcan_irq_iflag_set(FlexcanState *s, int mbidx)
+{
+    if (mbidx < 32) {
+        s->regs.iflag1 |= BIT(mbidx);
+    } else {
+        s->regs.iflag2 |= BIT(mbidx - 32);
+    }
+}
+
+/**
+ * flexcan_irq_iflag_clear() - Clear IFLAG bit corresponding to MB mbidx
+ * @s: FlexCAN device pointer
+ * @mbidx: mailbox index
+ */
+static void flexcan_irq_iflag_clear(FlexcanState *s, int mbidx)
+{
+    if (mbidx < 32) {
+        s->regs.iflag1 &= ~BIT(mbidx);
+    } else {
+        s->regs.iflag2 &= ~BIT(mbidx - 32);
+    }
+}
+
+/* ========== RESET ========== */
+static void flexcan_reset_local_state(FlexcanState *s)
+{
+    uint32_t *reset_mask = (uint32_t *)&flexcan_regs_reset_mask;
+    for (int i = 0; i < (sizeof(FlexcanRegs) / 4); i++) {
+        s->regs_raw[i] &= reset_mask[i];
+    }
+
+    s->regs.mcr |= 0x5980000F;
+    s->locked_mbidx = FLEXCAN_NO_MB_LOCKED;
+    s->smb_target_mbidx = FLEXCAN_SMB_EMPTY;
+    s->timer_start = FLEXCAN_TIMER_STOPPED;
+

hw/net/can/flexcan.c line 590

Quoted 10 lines
hw/net/can/flexcan.c new file +10 -0
+    trace_flexcan_reset(s);
+}
+
+static void flexcan_soft_reset(FlexcanState *s)
+{
+    if (s->regs.mcr & FLEXCAN_MCR_LPM_ACK) {
+        g_autofree char *path = object_get_canonical_path(OBJECT(s));
+        qemu_log_mask(LOG_GUEST_ERROR,
+                      "%s: invalid soft reset request in low-power mode",
+                      path);

Here you can also use "DEVICE(s)->canonical_path", eliminating the dynamic allocation and cleanup logic entirely. Same for other cases.

Quoted 309 lines
hw/net/can/flexcan.c new file +309 -0
+    }
+
+    flexcan_reset_local_state(s);
+}
+
+static void flexcan_reset_enter(Object *obj, ResetType type)
+{
+    FlexcanState *s = CAN_FLEXCAN(obj);
+
+    memset(&s->regs, 0, sizeof(s->regs));
+    flexcan_reset_local_state(s);
+}
+
+static void flexcan_reset_hold(Object *obj, ResetType type)
+{
+    FlexcanState *s = CAN_FLEXCAN(obj);
+
+    flexcan_irq_update(s);
+}
+
+
+/* ========== Operation mode control ========== */
+/**
+ * flexcan_update_esr() - Update ESR based on mode and CAN bus connection state
+ * @s: FlexCAN device pointer
+ */
+static void flexcan_update_esr(FlexcanState *s)
+{
+    bool is_running = (s->regs.mcr & FLEXCAN_MCR_NOT_RDY) == 0;
+    /* potentially, there could be other influences on ESR[SYNCH] */
+
+    if (is_running && s->canbus) {
+        s->regs.esr |= FLEXCAN_ESR_SYNCH | FLEXCAN_ESR_IDLE;
+    } else {
+        s->regs.esr &= ~(FLEXCAN_ESR_SYNCH | FLEXCAN_ESR_IDLE);
+    }
+}
+
+/**
+ * flexcan_update_esr() - Process MCR write
+ * @s: FlexCAN device pointer
+ * @pv: previously set MCR value
+ *
+ * This function expects the new MCR value to be already written in s->regs.mcr.
+ */
+static void flexcan_set_mcr(FlexcanState *s, const uint32_t pv)
+{
+    uint32_t cv = s->regs.mcr;
+
+    /* -- module disable mode -- */
+    if (!(pv & FLEXCAN_MCR_MDIS) && (cv & FLEXCAN_MCR_MDIS)) {
+        /* transition to Module Disable mode */
+        cv |= FLEXCAN_MCR_LPM_ACK;
+    } else if ((pv & FLEXCAN_MCR_MDIS) && !(cv & FLEXCAN_MCR_MDIS)) {
+        /* transition from Module Disable mode */
+        cv &= ~FLEXCAN_MCR_LPM_ACK;
+    }
+
+    /* -- soft reset -- */
+    if (!(cv & FLEXCAN_MCR_LPM_ACK) && (cv & FLEXCAN_MCR_SOFTRST)) {
+        flexcan_soft_reset(s);
+        cv = s->regs.mcr;
+    }
+
+    /* -- freeze mode -- */
+    if (!(cv & FLEXCAN_MCR_LPM_ACK) &&
+        (cv & FLEXCAN_MCR_FRZ) &&
+        (cv & FLEXCAN_MCR_HALT)) {
+        cv |= FLEXCAN_MCR_FRZ_ACK;
+    } else {
+        cv &= ~FLEXCAN_MCR_FRZ_ACK;
+    }
+
+    /* -- fifo mode -- */
+    if (
+        ((pv & FLEXCAN_MCR_FEN) && !(cv & FLEXCAN_MCR_FEN)) ||
+        (!(pv & FLEXCAN_MCR_FEN) && (cv & FLEXCAN_MCR_FEN))
+    ) {
+        /* clear iflags used by fifo */
+        s->regs.iflag1 &= ~(
+            FLEXCAN_IFLAG_RX_FIFO_AVAILABLE |
+            FLEXCAN_IFLAG_RX_FIFO_OVERFLOW |
+            FLEXCAN_IFLAG_RX_FIFO_WARN
+        );
+    }
+    if (!(pv & FLEXCAN_MCR_FEN) && (cv & FLEXCAN_MCR_FEN)) {
+        /* zero out fifo region, we rely on zeroed can_ctrl for empty slots */
+        memset(s->regs.mbs, 0,
+               FLEXCAN_FIFO_DEPTH * sizeof(FlexcanRegsMessageBuffer));
+    }
+
+    /*
+     * assert NOT_RDY bit if in disable,
+     * stop (not implemented) or freeze mode
+     */
+    if ((cv & FLEXCAN_MCR_LPM_ACK) || (cv & FLEXCAN_MCR_FRZ_ACK)) {
+        cv |= FLEXCAN_MCR_NOT_RDY;
+    } else {
+        cv &= ~FLEXCAN_MCR_NOT_RDY;
+    }
+
+    if ((pv & FLEXCAN_MCR_NOT_RDY) && !(cv & FLEXCAN_MCR_NOT_RDY)) {
+        /* module went up, start the timer */
+        flexcan_timer_start(s);
+    } else if (!(pv & FLEXCAN_MCR_NOT_RDY) && (cv & FLEXCAN_MCR_NOT_RDY)) {
+        /* module went down, store the current timer value */
+        flexcan_timer_stop(s);
+    }
+
+    s->regs.mcr = cv;
+    flexcan_update_esr(s);
+    trace_flexcan_set_mcr(
+        s,
+        cv & FLEXCAN_MCR_LPM_ACK ? "DISABLED" : "ENABLED",
+        (cv & FLEXCAN_MCR_FRZ_ACK || cv & FLEXCAN_MCR_LPM_ACK) ?
+            "FROZEN" : "RUNNING",
+        cv & FLEXCAN_MCR_FEN ? "FIFO" : "MAILBOX",
+        cv & FLEXCAN_MCR_NOT_RDY ? "NOT_RDY" : "RDY",
+        s->regs.esr & FLEXCAN_ESR_SYNCH ? "SYNC" : "NOSYNC"
+    );
+}
+
+/* ========== TX ========== */
+static void flexcan_transmit(FlexcanState *s, int mbidx)
+{
+    FlexcanRegsMessageBuffer *mb = &s->regs.mbs[mbidx];
+    qemu_can_frame frame = {
+        .flags = 0,
+    };
+
+    if ((s->regs.ctrl & FLEXCAN_CTRL_LOM) ||
+        (s->regs.mcr & FLEXCAN_MCR_NOT_RDY)) {
+        /* no transmiting in listen-only, freeze or low-power mode */
+        return;
+    }
+
+    if (mb->can_ctrl & FLEXCAN_MB_CNT_IDE) {
+        /* 29b ID stored in bits [0, 29) */
+        uint32_t id = mb->can_id & 0x1FFFFFFF;
+        frame.can_id = id | QEMU_CAN_EFF_FLAG;
+    } else {
+        /* 11b ID stored in bits [18, 29) */
+        uint32_t id = (mb->can_id & (0x7FF << 18)) >> 18;
+        frame.can_id = id;
+    }
+
+    frame.can_dlc = (mb->can_ctrl & (0xF << 16)) >> 16;
+
+    uint32_t *frame_data = (uint32_t *)&frame.data;
+    for (int i = 0; i < 2; i++) {
+        stl_be_p(&frame_data[i], mb->data[i]);
+    }
+
+    if (!(s->regs.mcr & FLEXCAN_MCR_SRX_DIS)) {
+        /* self-reception */
+        flexcan_mb_rx(s, &frame);
+    }
+    if (!(s->regs.ctrl & FLEXCAN_CTRL_LPB)) {
+        /* send to bus if not in loopback mode */
+        if (s->canbus) {
+            can_bus_client_send(&s->bus_client, &frame, 1);
+        } else {
+            /* todo: raise error (no ack) */
+        }
+    }
+
+    uint32_t timestamp = flexcan_get_timestamp(s, true);
+    mb->can_ctrl &= ~(FLEXCAN_MB_CODE_MASK | FLEXCAN_MB_CNT_TIMESTAMP_MASK);
+    mb->can_ctrl |= FLEXCAN_MB_CODE_TX_INACTIVE |
+                    FLEXCAN_MB_CNT_TIMESTAMP(timestamp);
+
+    /* todo: compute the CRC */
+    s->regs.crcr = FLEXCAN_CRCR_TXCRC(0) | FLEXCAN_CRCR_MBCRC(mbidx);
+
+    flexcan_irq_iflag_set(s, mbidx);
+}
+
+static void flexcan_mb_write(FlexcanState *s, int mbid)
+{
+    FlexcanRegsMessageBuffer *mb = &s->regs.mbs[mbid];
+
+    bool is_mailbox = (mb <= flexcan_get_last_enabled_mailbox(s)) &&
+                     (mb >= flexcan_get_first_message_buffer(s));
+
+    if (trace_event_get_state_backends(TRACE_FLEXCAN_MB_WRITE)) {
+        char code_str_buf[FLEXCAN_DBG_BUF_LEN] = { 0 };
+        const char *code_str = flexcan_dbg_mb_code(mb->can_ctrl, code_str_buf);
+        trace_flexcan_mb_write(s, mbid, code_str, is_mailbox, mb->can_ctrl,
+                               mb->can_id);
+    }
+
+    if (!is_mailbox) {
+        /**
+         * Disabled mailbox or mailbox in region of queue filters
+         * was updated. Either way there is nothing to do.
+         */
+        return;
+    }
+
+    /* any write to message buffer clears the not_serviced flag */
+    mb->can_ctrl &= ~FLEXCAN_MB_CNT_NOT_SRV;
+
+    /**
+     * todo: search for active tx mbs on transition from freeze/disable mode
+     */
+    switch (mb->can_ctrl & FLEXCAN_MB_CODE_MASK) {
+    case FLEXCAN_MB_CODE_TX_INACTIVE:
+        QEMU_FALLTHROUGH;
+    case FLEXCAN_MB_CODE_RX_INACTIVE:
+        QEMU_FALLTHROUGH;
+    case FLEXCAN_MB_CODE_RX_EMPTY:
+        QEMU_FALLTHROUGH;
+    case FLEXCAN_MB_CODE_RX_FULL:
+        QEMU_FALLTHROUGH;
+    case FLEXCAN_MB_CODE_RX_RANSWER:
+        break;
+
+    case FLEXCAN_MB_CODE_TX_DATA:
+        flexcan_transmit(s, mbid);
+        break;
+    case FLEXCAN_MB_CODE_TX_ABORT:
+        /*
+         * as transmission is instant, it can never be aborted
+         * we need to set CODE in C/S back to the previous code
+         */
+        mb->can_ctrl &= ~FLEXCAN_MB_CODE(1);
+        break;
+    case FLEXCAN_MB_CODE_TX_TANSWER:
+        break;
+    default:
+        /* prevent setting the busy bit */
+        mb->can_ctrl &= ~FLEXCAN_MB_CODE_RX_BUSY_BIT;
+        break;
+  }
+
+}
+
+/* ========== RX ========== */
+static void flexcan_mb_move_in(FlexcanState *s, const qemu_can_frame *frame,
+                               FlexcanRegsMessageBuffer *target_mb)
+{
+    memset(target_mb, 0, sizeof(FlexcanRegsMessageBuffer));
+
+    uint32_t frame_len = frame->can_dlc;
+    if (frame_len > 8) {
+        frame_len = 8;
+    }
+    uint32_t *frame_data = (uint32_t *)&frame->data;
+    for (int i = 0; i < 2; i++) {
+        target_mb->data[i] = ldl_be_p(&frame_data[i]);
+    }
+
+    int timestamp = flexcan_get_timestamp(s, true);
+    uint32_t new_code = 0;
+
+    switch (target_mb->can_ctrl & FLEXCAN_MB_CODE_MASK) {
+    case FLEXCAN_MB_CODE_RX_FULL:
+    case FLEXCAN_MB_CODE_RX_OVERRUN:
+        if (target_mb->can_ctrl & FLEXCAN_MB_CNT_NOT_SRV) {
+            new_code = FLEXCAN_MB_CODE_RX_OVERRUN;
+        } else {
+            new_code = FLEXCAN_MB_CODE_RX_FULL;
+        }
+        break;
+    case FLEXCAN_MB_CODE_RX_RANSWER:
+        assert(s->regs.ctrl2 & FLEXCAN_CTRL2_RRS);
+        new_code = FLEXCAN_MB_CODE_TX_TANSWER;
+        break;
+    default:
+        new_code = FLEXCAN_MB_CODE_RX_FULL;
+    }
+
+    target_mb->can_ctrl = new_code
+        | FLEXCAN_MB_CNT_TIMESTAMP(timestamp)
+        | FLEXCAN_MB_CNT_LENGTH(frame_len)
+        | FLEXCAN_MB_CNT_NOT_SRV
+        | FLEXCAN_MB_CNT_SRR; /* always set for received frames */
+    if (frame->can_id & QEMU_CAN_RTR_FLAG) {
+        target_mb->can_ctrl |= FLEXCAN_MB_CNT_RTR;
+    }
+
+    if (frame->can_id & QEMU_CAN_EFF_FLAG) {
+        target_mb->can_ctrl |= FLEXCAN_MB_CNT_IDE;
+        target_mb->can_id |= frame->can_id & QEMU_CAN_EFF_MASK;
+    } else {
+        target_mb->can_id |= (frame->can_id & QEMU_CAN_SFF_MASK) << 18;
+    }
+}
+static void flexcan_mb_lock(FlexcanState *s, int mbidx)
+{
+    FlexcanRegsMessageBuffer *mb = &s->regs.mbs[mbidx];
+    if ((mb > flexcan_get_last_enabled_mailbox(s)) ||
+        (mb < flexcan_get_first_message_buffer(s))) {
+        return;
+    }
+    switch (mb->can_ctrl & FLEXCAN_MB_CODE_MASK) {
+    case FLEXCAN_MB_CODE_RX_FULL:
+        QEMU_FALLTHROUGH;
+    case FLEXCAN_MB_CODE_RX_OVERRUN:
+        QEMU_FALLTHROUGH;
+    case FLEXCAN_MB_CODE_RX_RANSWER:
+        /* continue */
+        trace_flexcan_mb_lock(s, mbidx, 1);
+        break;
+    default:
+        trace_flexcan_mb_lock(s, mbidx, 0);
+        return;
+    }
+

hw/net/can/flexcan.c line 909

Quoted 10 lines
hw/net/can/flexcan.c new file +10 -0
+    s->locked_mbidx = mbidx;
+}
+
+static void flexcan_mb_unlock(FlexcanState *s)
+{
+    if (s->locked_mbidx == FLEXCAN_NO_MB_LOCKED) {
+        return;
+    }
+
+    int locked_mbidx = s->locked_mbidx;

All variable declarations should be at the top of a block. I'm not sure why but this is a common request, see e.g. https://lore.kernel.org/qemu-devel/[email protected]/ .

Quoted 277 lines
hw/net/can/flexcan.c new file +277 -0
+    assert(locked_mbidx >= 0 && locked_mbidx < FLEXCAN_MAILBOX_COUNT);
+    FlexcanRegsMessageBuffer *locked_mb = &s->regs.mbs[locked_mbidx];
+    s->locked_mbidx = FLEXCAN_NO_MB_LOCKED;
+
+    if (locked_mb >= flexcan_get_first_message_buffer(s) &&
+        locked_mb <= flexcan_get_last_enabled_mailbox(s)
+    ) {
+        /* mark the message buffer as serviced */
+        locked_mb->can_ctrl &= ~FLEXCAN_MB_CNT_NOT_SRV;
+    }
+
+    /* try move in from SMB */
+    bool has_pending_frame = locked_mbidx == s->smb_target_mbidx;
+    trace_flexcan_mb_unlock(s, locked_mbidx,
+                            has_pending_frame ? " PENDING FRAME IN SMB" : "");
+
+    /* todo: in low-power modes, this should be postponed until exit */
+    if (has_pending_frame) {
+        FlexcanRegsMessageBuffer *target_mb = &s->regs.mbs[locked_mbidx];
+        memcpy(target_mb, &s->regs.rx_smb0, sizeof(FlexcanRegsMessageBuffer));
+
+        memset(&s->regs.rx_smb0, 0, sizeof(FlexcanRegsMessageBuffer));
+        s->locked_mbidx = FLEXCAN_SMB_EMPTY;
+
+        flexcan_irq_iflag_set(s, locked_mbidx);
+    }
+}
+
+bool flexcan_can_receive(CanBusClientState *client)
+{
+    FlexcanState *s = container_of(client, FlexcanState, bus_client);
+    return !(s->regs.mcr & FLEXCAN_MCR_NOT_RDY);
+}
+
+/* --------- RX FIFO ---------- */
+
+/**
+ * flexcan_fifo_pop() - Pop message from FIFO and update IRQs
+ * @s: FlexCAN device pointer
+ *
+ * Does not require the queue to be non-empty.
+ */
+static void flexcan_fifo_pop(FlexcanState *s)
+{
+    if (s->regs.fifo.mb_back.can_ctrl != 0) {
+        /* move queue elements forward */
+        memmove(&s->regs.fifo.mb_back, &s->regs.fifo.mbs_queue[0],
+                sizeof(s->regs.fifo.mbs_queue));
+
+        /* clear the first-in slot */
+        memset(&s->regs.mbs[FLEXCAN_FIFO_DEPTH - 1], 0,
+               sizeof(FlexcanRegsMessageBuffer));
+
+        trace_flexcan_fifo_pop(s, 1, s->regs.fifo.mb_back.can_ctrl != 0);
+    } else {
+        trace_flexcan_fifo_pop(s, 0, 0);
+    }
+
+    if (s->regs.fifo.mb_back.can_ctrl != 0) {
+        flexcan_irq_iflag_set(s, I_FIFO_AVAILABLE);
+    } else {
+        flexcan_irq_iflag_clear(s, I_FIFO_AVAILABLE);
+    }
+}
+
+/**
+ * flexcan_fifo_find_free_slot() - Find the first free slot in the FIFO
+ * @s: FlexCAN device pointer
+ *
+ * Return: Pointer to the first free slot in the FIFO,
+ *         or NULL if the queue is full.
+ */
+static FlexcanRegsMessageBuffer *flexcan_fifo_find_free_slot(FlexcanState *s)
+{
+    for (int i = 0; i < FLEXCAN_FIFO_DEPTH; i++) {
+        FlexcanRegsMessageBuffer *mb = &s->regs.mbs[i];
+        if (mb->can_ctrl == 0) {
+            return mb;
+        }
+    }
+    return NULL;
+}
+
+/**
+ * flexcan_fifo_push() - Update FIFO IRQs after frame move-in
+ * @s: FlexCAN device pointer
+ * @slot: Target FIFO slot
+ *
+ * The usage is as follows:
+ * 1. Get free slot pointer using flexcan_fifo_find_free_slot()
+ * 2. Move the frame in if not NULL
+ * 3. Call flexcan_fifo_push() regardless of the NULL pointer
+ */
+static void flexcan_fifo_push(FlexcanState *s, FlexcanRegsMessageBuffer *slot)
+{
+    if (slot) {
+        int n_occupied = slot - s->regs.mbs;
+        if (n_occupied == 4) { /* 4 means the 5th slot was filled in */
+            /*
+             * fifo occupancy increased from 4 to 5,
+             * raising FIFO_WARN interrupt
+             */
+            flexcan_irq_iflag_set(s, I_FIFO_WARN);
+        }
+        flexcan_irq_iflag_set(s, I_FIFO_AVAILABLE);
+
+        trace_flexcan_fifo_push(s, n_occupied);
+    } else {
+        flexcan_irq_iflag_set(s, I_FIFO_OVERFLOW);
+
+        trace_flexcan_fifo_push(s, -1);
+    }
+}
+
+static int flexcan_fifo_rx(FlexcanState *s, const qemu_can_frame *buf)
+{
+    /* todo: filtering. return FLEXCAN_FIFO_RX_RETRY if filtered out */
+    if ((s->regs.mcr & FLEXCAN_MCR_IDAM_MASK) == FLEXCAN_MCR_IDAM_D) {
+        /* all frames rejected */
+        return FLEXCAN_RX_SEARCH_RETRY;
+    }
+
+    /* push message to queue if not full */
+    FlexcanRegsMessageBuffer *slot = flexcan_fifo_find_free_slot(s);
+    if (slot) {
+        flexcan_mb_move_in(s, buf, slot);
+    }
+    flexcan_fifo_push(s, slot);
+
+    return slot ? FLEXCAN_RX_SEARCH_ACCEPT : FLEXCAN_RX_SEARCH_DROPPED;
+}
+
+/* --------- RX message buffer ---------- */
+
+/**
+ * flexcan_mb_rx_check_mb() - Check if a message buffer matches a received frame
+ * @s: FlexCAN device pointer
+ * @buf: Frame to be received from CAN subsystem
+ * @mbid: Target mailbox index. The mailbox must be a valid message buffer.
+ *
+ * Return: FLEXCAN_CHECK_MB_NIL if the message buffer does not match.
+ *         FLEXCAN_CHECK_MB_MATCH if the message buffer matches the received
+ *                                frame and is free-to-receive,
+ *         FLEXCAN_CHECK_MB_MATCH_LOCKED if the message buffer matches,
+ *                                       but is locked,
+ *         FLEXCAN_CHECK_MB_MATCH_NON_FREE if the message buffer matches,
+ *                                         but is not free-to-receive
+ *                                         for some other reason.
+ */
+static int flexcan_mb_rx_check_mb(FlexcanState *s, const qemu_can_frame *buf,
+                                  int mbid)
+{
+    FlexcanRegsMessageBuffer *mb = &s->regs.mbs[mbid];
+    const bool is_rtr = !!(buf->can_id & QEMU_CAN_RTR_FLAG);
+    const bool is_serviced = !(mb->can_ctrl & FLEXCAN_MB_CNT_NOT_SRV);
+    const bool is_locked = s->locked_mbidx == mbid;
+
+    bool is_free_to_receive = false;
+    bool is_matched = false;
+
+    switch (mb->can_ctrl & FLEXCAN_MB_CODE_MASK) {
+    case FLEXCAN_MB_CODE_RX_RANSWER:
+        if (is_rtr && !(s->regs.ctrl2 & FLEXCAN_CTRL2_RRS)) {
+            /* todo: do the actual matching/filtering and RTR answer */
+            is_matched = true;
+        }
+        break;
+    case FLEXCAN_MB_CODE_RX_FULL:
+        QEMU_FALLTHROUGH;
+    case FLEXCAN_MB_CODE_RX_OVERRUN:
+        is_free_to_receive = is_serviced;
+        /* todo: do the actual matching/filtering */
+        is_matched = true;
+        break;
+    case FLEXCAN_MB_CODE_RX_EMPTY:
+        is_free_to_receive = true;
+        /* todo: do the actual matching/filtering */
+        is_matched = true;
+        break;
+    default:
+    break;
+    }
+
+    if (trace_event_get_state_backends(TRACE_FLEXCAN_MB_RX_CHECK_MB)) {
+        char code_str_buf[FLEXCAN_DBG_BUF_LEN] = { 0 };
+        const char *code_str = flexcan_dbg_mb_code(mb->can_ctrl, code_str_buf);
+        trace_flexcan_mb_rx_check_mb(s, mbid, code_str, is_matched,
+                                     is_free_to_receive, is_serviced,
+                                     is_locked);
+    }
+
+    if (is_matched && is_free_to_receive && !is_locked) {
+        return FLEXCAN_CHECK_MB_MATCH;
+    } else if (is_matched && !is_locked) {
+        return FLEXCAN_CHECK_MB_MATCH_NON_FREE;
+    } else if (is_matched) {
+        return FLEXCAN_CHECK_MB_MATCH_LOCKED;
+    } else {
+        return FLEXCAN_CHECK_MB_NIL;
+    }
+}
+static int flexcan_mb_rx(FlexcanState *s, const qemu_can_frame *buf)
+{
+    int last_not_free_to_receive_mbid = -1;
+    bool last_not_free_to_receive_locked = false;
+
+    FlexcanRegsMessageBuffer *first_mb = flexcan_get_first_message_buffer(s);
+    FlexcanRegsMessageBuffer *last_mb = flexcan_get_last_enabled_mailbox(s);
+    for (FlexcanRegsMessageBuffer *mb = first_mb;
+         mb <= last_mb; mb++) {
+        int mbid = mb - s->regs.mbs;
+        int r = flexcan_mb_rx_check_mb(s, buf, mbid);
+        if (r == FLEXCAN_CHECK_MB_MATCH) {
+            flexcan_mb_move_in(s, buf, mb);
+            flexcan_irq_iflag_set(s, mbid);
+            return FLEXCAN_RX_SEARCH_ACCEPT;
+        } else if (r == FLEXCAN_CHECK_MB_MATCH_NON_FREE) {
+            last_not_free_to_receive_mbid = mbid;
+            last_not_free_to_receive_locked = false;
+        } else if (r == FLEXCAN_CHECK_MB_MATCH_LOCKED) {
+            /*
+             * message buffer is locked,
+             * we can move in the message after it's unlocked
+             */
+            last_not_free_to_receive_mbid = mbid;
+            last_not_free_to_receive_locked = true;
+        }
+    }
+
+    if (last_not_free_to_receive_mbid >= -1) {
+        if (last_not_free_to_receive_locked) {
+            /*
+             * copy to temporary mailbox (SMB)
+             * it will be moved in when the mailbox is unlocked
+             */
+            s->regs.rx_smb0.can_ctrl =
+                s->regs.mbs[last_not_free_to_receive_mbid].can_id;
+            flexcan_mb_move_in(s, buf, &s->regs.rx_smb0);
+            s->smb_target_mbidx = last_not_free_to_receive_mbid;
+            return FLEXCAN_RX_SEARCH_ACCEPT;
+        } else if (s->regs.mcr & FLEXCAN_MCR_IRMQ) {
+            flexcan_mb_move_in(s, buf,
+                               &s->regs.mbs[last_not_free_to_receive_mbid]);
+            flexcan_irq_iflag_set(s, last_not_free_to_receive_mbid);
+            return FLEXCAN_RX_SEARCH_ACCEPT;
+        }
+    }
+
+    return FLEXCAN_RX_SEARCH_RETRY;
+}
+
+ssize_t flexcan_receive(CanBusClientState *client, const qemu_can_frame *frames,
+                        size_t frames_cnt)
+{
+    FlexcanState *s = container_of(client, FlexcanState, bus_client);
+    trace_flexcan_receive(s, frames_cnt);
+
+    if (frames_cnt <= 0) {
+        g_autofree char *path = object_get_canonical_path(OBJECT(s));
+        qemu_log_mask(LOG_GUEST_ERROR, "%s: Error in the data received.\n",
+                      path);
+        return 0;
+    }
+
+    /* clear the SMB, as it would be overriden in hardware */
+    memset(&s->regs.rx_smb0, 0, sizeof(FlexcanRegsMessageBuffer));
+    s->smb_target_mbidx = FLEXCAN_SMB_EMPTY;
+
+    for (size_t i = 0; i < frames_cnt; i++) {
+        int r;
+        const qemu_can_frame *frame = &frames[i];
+        if (frame->can_id & QEMU_CAN_ERR_FLAG) {
+            /* todo: error frame handling */
+            continue;
+        } else if (frame->flags & QEMU_CAN_FRMF_TYPE_FD) {
+            /* CAN FD supported only in later FlexCAN version */
+            continue;

hw/net/can/flexcan.c line 1196

Quoted 10 lines
hw/net/can/flexcan.c new file +10 -0
+        }
+
+        /* todo: this order logic is not complete and needs further work */
+        if (s->regs.mcr & FLEXCAN_MCR_FEN &&
+            s->regs.ctrl2 & FLEXCAN_CTRL2_MRP) {
+            r = flexcan_mb_rx(s, frame);
+            if (r != FLEXCAN_RX_SEARCH_RETRY) {
+                continue;
+            }
+            flexcan_fifo_rx(s, frame);

Why not invert the condition and move the call inside the if statement, eliminating the "continue;"?

hw/net/can/flexcan.c line 1202

Quoted 6 lines
hw/net/can/flexcan.c new file +6 -0
+        } else if (s->regs.mcr & FLEXCAN_MCR_FEN) {
+            r = flexcan_fifo_rx(s, frame);
+            if (r != FLEXCAN_RX_SEARCH_RETRY) {
+                continue;
+            }
+            flexcan_mb_rx(s, frame);

Same here.

Quoted 60 lines
hw/net/can/flexcan.c new file +60 -0
+        } else {
+            flexcan_mb_rx(s, frame);
+        }
+    }
+
+    flexcan_irq_update(s);
+    return 1;
+}
+
+/* ========== I/O handling ========== */
+static void flexcan_reg_write(FlexcanState *s, hwaddr addr, uint32_t val)
+{
+    uint32_t write_mask = ((const uint32_t *)
+        &flexcan_regs_write_mask)[addr / 4];
+    uint32_t old_value = s->regs_raw[addr / 4];
+
+    /*
+     * 0 for bits that can "only be written in Freeze mode as it is blocked
+     * by hardware in other modes"
+     */
+    const uint32_t freeze_mask_mcr = 0xDF54CC80;
+    const uint32_t freeze_mask_ctrl1 = 0x0000E740;
+
+    switch (addr) {
+    case offsetof(FlexcanRegs, mcr):
+        if (!(s->regs.mcr & FLEXCAN_MCR_FRZ_ACK)) {
+            write_mask &= freeze_mask_mcr;
+        }
+        s->regs.mcr = (val & write_mask) | (old_value & ~write_mask);
+        flexcan_set_mcr(s, old_value);
+        break;
+    case offsetof(FlexcanRegs, ctrl):
+        if (!(s->regs.mcr & FLEXCAN_MCR_FRZ_ACK)) {
+            write_mask &= freeze_mask_ctrl1;
+        }
+        s->regs.ctrl = (val & write_mask) | (old_value & ~write_mask);
+        break;
+    case offsetof(FlexcanRegs, iflag1):
+        s->regs.iflag1 &= ~val;
+        if ((s->regs.mcr & FLEXCAN_MCR_FEN) &&
+            (val & FLEXCAN_IFLAG_RX_FIFO_AVAILABLE)) {
+            flexcan_fifo_pop(s);
+        }
+        break;
+    case offsetof(FlexcanRegs, iflag2):
+        s->regs.iflag2 &= ~val;
+        break;
+    case offsetof(FlexcanRegs, ctrl2):
+        QEMU_FALLTHROUGH;
+    case offsetof(FlexcanRegs, ecr):
+        QEMU_FALLTHROUGH;
+    case offsetof(FlexcanRegs, rxmgmask):
+        QEMU_FALLTHROUGH;
+    case offsetof(FlexcanRegs, rx14mask):
+        QEMU_FALLTHROUGH;
+    case offsetof(FlexcanRegs, rx15mask):
+        QEMU_FALLTHROUGH;
+    case offsetof(FlexcanRegs, rxfgmask):
+        /* these registers can only be written in freeze mode */
+        if (!(s->regs.mcr & FLEXCAN_MCR_FRZ_ACK)) {

hw/net/can/flexcan.c line 1272

Quoted 10 lines
hw/net/can/flexcan.c new file +10 -0
+            break;
+        }
+        QEMU_FALLTHROUGH;
+    default:
+        /* RXIMRn can only be written in freeze mode */
+        if (!(s->regs.mcr & FLEXCAN_MCR_FRZ_ACK) &&
+            addr >= offsetof(FlexcanRegs, rximr) &&
+            addr < offsetof(FlexcanRegs, _reserved5)) {
+            break;
+        }

I think it should be possible to eliminate this if statement by adding another case before the default:
"case offsetof(FlexcanRegs, rximr[0]) ... offsetof(FlexcanRegs,
rximr[63]):"

Quoted 23 lines
hw/net/can/flexcan.c new file +23 -0
+
+        s->regs_raw[addr / 4] = (val & write_mask) | (old_value & ~write_mask);
+
+        if (addr >= offsetof(FlexcanRegs, mb) &&
+            addr < offsetof(FlexcanRegs, _reserved4)) {
+            /* access to mailbox */
+            int mbid = (addr - offsetof(FlexcanRegs, mb)) /
+                            sizeof(FlexcanRegsMessageBuffer);
+
+            if (s->locked_mbidx == mbid) {
+                flexcan_mb_unlock(s);
+            }
+
+            /* check for invalid writes into FIFO region */
+            if (s->regs.mcr & FLEXCAN_MCR_FEN && mbid < FLEXCAN_FIFO_DEPTH) {
+                g_autofree char *path = object_get_canonical_path(OBJECT(s));
+                qemu_log_mask(LOG_GUEST_ERROR,
+                              "%s: Invalid write to Rx-FIFO structure", path);
+                return;
+            }
+
+            /* run mailbox processing function on write to control word */
+            if ((addr & 0xF) == 0) {

hw/net/can/flexcan.c line 1305

Quoted 10 lines
hw/net/can/flexcan.c new file +10 -0
+                flexcan_mb_write(s, mbid);
+            }
+        }
+        break;
+    }
+
+    flexcan_irq_update(s);
+}
+
+void flexcan_mem_write(void *obj, hwaddr addr, uint64_t val, unsigned size)

The convention in QEMU is usually to name these void pointers "opaque". The name "obj" suggests a QOM Object * and is therefore midleading. Please rename to "opaque".

Same for the other flexcan_ops callbacks.

hw/net/can/flexcan.c line 1307

Quoted 2 lines
hw/net/can/flexcan.c new file +2 -0
+{
+    FlexcanState *s = CAN_FLEXCAN(obj);

C actually handles void pointers special, so you don't need to cast here.

Same for the other flexcan_ops callbacks.

Quoted 54 lines
hw/net/can/flexcan.c new file +54 -0
+    flexcan_trace_mem_op(s, addr, val, size, true);
+
+    if (addr < FLEXCAN_ADDR_SPC_END) {
+        flexcan_reg_write(s, addr, (uint32_t)val);
+    } else {
+        DPRINTF("warn: write outside of defined address space\n");
+    }
+}
+uint64_t flexcan_mem_read(void *obj, hwaddr addr, unsigned size)
+{
+    FlexcanState *s = CAN_FLEXCAN(obj);
+
+    if (addr < FLEXCAN_ADDR_SPC_END) {
+        uint32_t rv = s->regs_raw[addr >> 2];
+
+        if (addr >= offsetof(FlexcanRegs, mb) &&
+            addr < offsetof(FlexcanRegs, _reserved4)) {
+            /* reading from mailbox */
+            hwaddr offset = addr - offsetof(FlexcanRegs, mb);
+            int mbid = offset / sizeof(FlexcanRegsMessageBuffer);
+
+            if (addr % 16 == 0 && s->locked_mbidx != mbid) {
+                /* reading control word locks the mailbox */
+                flexcan_mb_unlock(s);
+                flexcan_mb_lock(s, mbid);
+                flexcan_irq_update(s);
+                rv = s->regs.mbs[mbid].can_ctrl & ~FLEXCAN_MB_CNT_NOT_SRV;
+            }
+        } else if (addr == offsetof(FlexcanRegs, timer)) {
+            flexcan_mb_unlock(s);
+            flexcan_irq_update(s);
+            rv = flexcan_get_timestamp(s, false);
+        }
+
+        flexcan_trace_mem_op(s, addr, rv, size, false);
+        return rv;
+    } else {
+        g_autofree char *path = object_get_canonical_path(OBJECT(s));
+        qemu_log_mask(LOG_GUEST_ERROR,
+                      "%s: Invalid write outside valid I/O space", path);
+
+        flexcan_trace_mem_op(s, addr, 0, size, false);
+        return 0;
+    }
+}
+bool flexcan_mem_accepts(void *obj, hwaddr addr,
+                                unsigned size, bool is_write,
+                                MemTxAttrs attrs)
+{
+    FlexcanState *s = CAN_FLEXCAN(obj);
+
+    if ((s->regs.ctrl2 & FLEXCAN_CTRL2_WRMFRZ) &&
+        (s->regs.mcr & FLEXCAN_MCR_FRZ_ACK)) {
+        /* unrestricted access to FlexCAN memory in freeze mode */

hw/net/can/flexcan.c line 1371

Quoted 10 lines
hw/net/can/flexcan.c new file +10 -0
+        return true;
+    } else if (attrs.user && (s->regs.mcr & FLEXCAN_MCR_SUPV)) {
+        goto denied;
+    } else if (is_write && attrs.user && addr < 4) {
+        /* illegal user write to MCR */
+        goto denied;
+    } else if (addr >= FLEXCAN_ADDR_SPC_END) {
+        /* illegal write to non-existent register */
+        goto denied;
+    }

The gotos can be avoided by turning the comments into guest error logs and returning early. Bonus points for avoiding else after return for readability reasons: https://clang.llvm.org/extra/clang-tidy/checks/readability/else-after-return.html

Quoted 19 lines
hw/net/can/flexcan.c new file +19 -0
+
+    return true;
+denied:
+    trace_flexcan_mem_accepts(s, addr, size, is_write, !attrs.user);
+    return false;
+}
+
+static CanBusClientInfo flexcan_bus_client_info = {
+    .can_receive = flexcan_can_receive,
+    .receive = flexcan_receive,
+};
+
+static int flexcan_connect_to_bus(FlexcanState *s, CanBusState *bus)
+{
+    s->bus_client.info = &flexcan_bus_client_info;
+
+    if (can_bus_insert_client(bus, &s->bus_client) < 0) {
+        return -1;
+    }

hw/net/can/flexcan.c line 1400

Quoted 10 lines
hw/net/can/flexcan.c new file +10 -0
+    return 0;
+}
+
+void flexcan_init(Object *obj)
+{
+    FlexcanState *s = CAN_FLEXCAN(obj);
+    SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
+
+    USE(s);
+    USE(sbd);

No unused variables, please. And no "USE" macros which basically circumvent compiler warnings that are there for a purpose.

To avoid an empty method, you could actually perform the initialization of s->iomem here instead of in the realize method. This would also prepare for adding flexcan 3 support (see below).

Quoted 7 lines
hw/net/can/flexcan.c new file +7 -0
+}
+
+static void flexcan_realize(DeviceState *dev, Error **errp)
+{
+    FlexcanState *s = CAN_FLEXCAN(dev);
+
+    if (s->canbus) {

hw/net/can/flexcan.c line 1417

Quoted 10 lines
hw/net/can/flexcan.c new file +10 -0
+        if (flexcan_connect_to_bus(s, s->canbus) < 0) {
+            g_autofree char *path = object_get_canonical_path(OBJECT(s));
+
+            error_setg(errp, "%s: flexcan_connect_to_bus"
+                        " failed.", path);
+            return;
+        }
+    }
+
+    flexcan_reset_local_state(s);

QEMU's reset logic will invoke this method automatically before starting the machine, rendering this call redundant. Please remove.

hw/net/can/flexcan.c line 1420

Quoted 3 lines
hw/net/can/flexcan.c new file +3 -0
+
+    memory_region_init_io(
+        &s->iomem, OBJECT(dev), &flexcan_ops, s, TYPE_CAN_FLEXCAN, 0x4000

Instead of passing 0x4000 (which likely contains SoC-specific padding) as the size, you could pass FLEXCAN_ADDR_SPC_END instead. This avoids quite a few error cases in the code since you can eliminate all bounds checks against FLEXCAN_ADDR_SPC_END. In fact, you could eliminate FLEXCAN_ADDR_SPC_END entirely and pass "offsetof(FlexcanRegs, _reserved6)" as the size directly. If you then initialize s->iomem in the init method, like suggested above, we could later add a flexcan3 init method with a different size, keeping code churn at a minimum. See my imx8mp-flexcan branch as a working example.

Quoted 316 lines
hw/net/can/flexcan.c new file +49 -0
+    );
+    sysbus_init_mmio(SYS_BUS_DEVICE(dev), &s->iomem);
+    sysbus_init_irq(SYS_BUS_DEVICE(SYS_BUS_DEVICE(dev)), &s->irq);
+}
+
+static const VMStateDescription vmstate_can = {
+    .name = TYPE_CAN_FLEXCAN,
+    .version_id = 1,
+    .minimum_version_id = 1,
+    .fields = (const VMStateField[]) {
+        VMSTATE_INT64(timer_start, FlexcanState),
+        VMSTATE_UINT32_ARRAY(regs_raw, FlexcanState, sizeof(FlexcanRegs) / 4),
+        VMSTATE_INT32(locked_mbidx, FlexcanState),
+        VMSTATE_INT32(smb_target_mbidx, FlexcanState),
+        VMSTATE_END_OF_LIST(),
+    },
+};
+
+static const Property flexcan_properties[] = {
+    DEFINE_PROP_LINK("canbus", FlexcanState, canbus, TYPE_CAN_BUS,
+                     CanBusState *),
+};
+
+static void flexcan_class_init(ObjectClass *klass, const void *data)
+{
+    DeviceClass *dc = DEVICE_CLASS(klass);
+    ResettableClass *rc = RESETTABLE_CLASS(klass);
+
+    rc->phases.enter = flexcan_reset_enter;
+    rc->phases.hold = flexcan_reset_hold;
+    dc->realize = &flexcan_realize;
+    device_class_set_props(dc, flexcan_properties);
+    dc->vmsd = &vmstate_can;
+    dc->desc = "i.MX FLEXCAN Controller";
+}
+
+static const TypeInfo flexcan_info = {
+    .name          = TYPE_CAN_FLEXCAN,
+    .parent        = TYPE_SYS_BUS_DEVICE,
+    .instance_size = sizeof(FlexcanState),
+    .class_init    = flexcan_class_init,
+    .instance_init = flexcan_init,
+};
+
+static void can_register_types(void)
+{
+    type_register_static(&flexcan_info);
+}
+type_init(can_register_types)
hw/net/can/flexcan_regs.h new file +188 -0
new file mode 100644
index 0000000000..8e976b2c9e
@@ -0,0 +1,188 @@
+/*
+ * Field bitmasks and register structs definitions for FlexCAN
+ *
+ * This implementation is based on the following datasheet:
+ * i.MX 6Dual/6Quad Applications Processor Reference Manual
+ * Document Number: IMX6DQRM, Rev. 6, 05/2020
+ *
+ * Copyright (c) 2025 Matyas Bobek <[email protected]>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+#include "qemu/bitops.h"
+
+#ifndef HW_CAN_FLEXCAN_REGS_H
+#define HW_CAN_FLEXCAN_REGS_H
+
+#define FLEXCAN_GENMASK(h, l) (((~(uint32_t)0) >> (31 - (h) + (l))) << (l))
+
+/* FLEXCAN module configuration register (CANMCR) bits */
+#define FLEXCAN_MCR_MDIS                BIT(31)
+#define FLEXCAN_MCR_FRZ                 BIT(30)
+#define FLEXCAN_MCR_FEN                 BIT(29)
+#define FLEXCAN_MCR_HALT                BIT(28)
+#define FLEXCAN_MCR_NOT_RDY             BIT(27)
+#define FLEXCAN_MCR_WAK_MSK             BIT(26)
+#define FLEXCAN_MCR_SOFTRST             BIT(25)
+#define FLEXCAN_MCR_FRZ_ACK             BIT(24)
+#define FLEXCAN_MCR_SUPV                BIT(23)
+#define FLEXCAN_MCR_SLF_WAK             BIT(22)
+#define FLEXCAN_MCR_WRN_EN              BIT(21)
+#define FLEXCAN_MCR_LPM_ACK             BIT(20)
+#define FLEXCAN_MCR_WAK_SRC             BIT(19)
+#define FLEXCAN_MCR_DOZE                BIT(18)
+#define FLEXCAN_MCR_SRX_DIS             BIT(17)
+#define FLEXCAN_MCR_IRMQ                BIT(16)
+#define FLEXCAN_MCR_LPRIO_EN            BIT(13)
+#define FLEXCAN_MCR_AEN                 BIT(12)
+#define FLEXCAN_MCR_FDEN                BIT(11)
+#define FLEXCAN_MCR_MAXMB(x)            ((x) & 0x7f)
+#define FLEXCAN_MCR_IDAM_A              (0x0 << 8)
+#define FLEXCAN_MCR_IDAM_B              (0x1 << 8)
+#define FLEXCAN_MCR_IDAM_C              (0x2 << 8)
+#define FLEXCAN_MCR_IDAM_D              (0x3 << 8)
+#define FLEXCAN_MCR_IDAM_MASK           (0x3 << 8)
+
+/* FLEXCAN control register (CANCTRL) bits */
+#define FLEXCAN_CTRL_PRESDIV(x)         (((x) & 0xFF) << 24)
+#define FLEXCAN_CTRL_PRESDIV_MASK       FLEXCAN_CTRL_PRESDIV(UINT32_MAX)
+#define FLEXCAN_CTRL_RJW(x)             (((x) & 0x03) << 22)
+#define FLEXCAN_CTRL_RJW_MASK           FLEXCAN_CTRL_RJW(UINT32_MAX)
+#define FLEXCAN_CTRL_PSEG1(x)           (((x) & 0x07) << 19)
+#define FLEXCAN_CTRL_PSEG1_MASK         FLEXCAN_CTRL_PSEG1(UINT32_MAX)
+#define FLEXCAN_CTRL_PSEG2(x)           (((x) & 0x07) << 16)
+#define FLEXCAN_CTRL_PSEG2_MASK         FLEXCAN_CTRL_PSEG2(UINT32_MAX)
+#define FLEXCAN_CTRL_BOFF_MSK           BIT(15)
+#define FLEXCAN_CTRL_ERR_MSK            BIT(14)
+#define FLEXCAN_CTRL_CLK_SRC            BIT(13)
+#define FLEXCAN_CTRL_LPB                BIT(12)
+#define FLEXCAN_CTRL_TWRN_MSK           BIT(11)
+#define FLEXCAN_CTRL_RWRN_MSK           BIT(10)
+#define FLEXCAN_CTRL_SMP                BIT(7)
+#define FLEXCAN_CTRL_BOFF_REC           BIT(6)
+#define FLEXCAN_CTRL_TSYN               BIT(5)
+#define FLEXCAN_CTRL_LBUF               BIT(4)
+#define FLEXCAN_CTRL_LOM                BIT(3)
+#define FLEXCAN_CTRL_PROPSEG(x)         ((x) & 0x07)
+#define FLEXCAN_CTRL_PROPSEG_MASK       FLEXCAN_CTRL_PROPSEG(UINT32_MAX)
+#define FLEXCAN_CTRL_ERR_BUS            (FLEXCAN_CTRL_ERR_MSK)
+#define FLEXCAN_CTRL_ERR_STATE \
+        (FLEXCAN_CTRL_TWRN_MSK | FLEXCAN_CTRL_RWRN_MSK | \
+         FLEXCAN_CTRL_BOFF_MSK)
+#define FLEXCAN_CTRL_ERR_ALL \
+        (FLEXCAN_CTRL_ERR_BUS | FLEXCAN_CTRL_ERR_STATE)
+
+/* FLEXCAN control register 2 (CTRL2) bits */
+#define FLEXCAN_CTRL2_ECRWRE            BIT(29)
+#define FLEXCAN_CTRL2_WRMFRZ            BIT(28)
+#define FLEXCAN_CTRL2_RFFN(x)           (((x) & 0x0f) << 24)
+#define FLEXCAN_CTRL2_TASD(x)           (((x) & 0x1f) << 19)
+#define FLEXCAN_CTRL2_MRP               BIT(18)
+#define FLEXCAN_CTRL2_RRS               BIT(17)
+#define FLEXCAN_CTRL2_EACEN             BIT(16)
+#define FLEXCAN_CTRL2_ISOCANFDEN        BIT(12)
+
+/* FLEXCAN memory error control register (MECR) bits */
+#define FLEXCAN_MECR_ECRWRDIS           BIT(31)
+#define FLEXCAN_MECR_HANCEI_MSK         BIT(19)
+#define FLEXCAN_MECR_FANCEI_MSK         BIT(18)
+#define FLEXCAN_MECR_CEI_MSK            BIT(16)
+#define FLEXCAN_MECR_HAERRIE            BIT(15)
+#define FLEXCAN_MECR_FAERRIE            BIT(14)
+#define FLEXCAN_MECR_EXTERRIE           BIT(13)
+#define FLEXCAN_MECR_RERRDIS            BIT(9)
+#define FLEXCAN_MECR_ECCDIS             BIT(8)
+#define FLEXCAN_MECR_NCEFAFRZ           BIT(7)
+
+/* FLEXCAN error and status register (ESR) bits */
+#define FLEXCAN_ESR_SYNCH               BIT(18)
+#define FLEXCAN_ESR_TWRN_INT            BIT(17)
+#define FLEXCAN_ESR_RWRN_INT            BIT(16)
+#define FLEXCAN_ESR_BIT1_ERR            BIT(15)
+#define FLEXCAN_ESR_BIT0_ERR            BIT(14)
+#define FLEXCAN_ESR_ACK_ERR             BIT(13)
+#define FLEXCAN_ESR_CRC_ERR             BIT(12)
+#define FLEXCAN_ESR_FRM_ERR             BIT(11)
+#define FLEXCAN_ESR_STF_ERR             BIT(10)
+#define FLEXCAN_ESR_TX_WRN              BIT(9)
+#define FLEXCAN_ESR_RX_WRN              BIT(8)
+#define FLEXCAN_ESR_IDLE                BIT(7)
+#define FLEXCAN_ESR_BOFF_INT            BIT(2)
+#define FLEXCAN_ESR_ERR_INT             BIT(1)
+#define FLEXCAN_ESR_WAK_INT             BIT(0)
+
+/* FLEXCAN Bit Timing register (CBT) bits */
+#define FLEXCAN_CBT_BTF                 BIT(31)
+#define FLEXCAN_CBT_EPRESDIV_MASK       FLEXCAN_GENMASK(30, 21)
+#define FLEXCAN_CBT_ERJW_MASK           FLEXCAN_GENMASK(20, 16)
+#define FLEXCAN_CBT_EPROPSEG_MASK       FLEXCAN_GENMASK(15, 10)
+#define FLEXCAN_CBT_EPSEG1_MASK         FLEXCAN_GENMASK(9, 5)
+#define FLEXCAN_CBT_EPSEG2_MASK         FLEXCAN_GENMASK(4, 0)
+
+/* FLEXCAN FD control register (FDCTRL) bits */
+#define FLEXCAN_FDCTRL_FDRATE           BIT(31)
+#define FLEXCAN_FDCTRL_MBDSR1           FLEXCAN_GENMASK(20, 19)
+#define FLEXCAN_FDCTRL_MBDSR0           FLEXCAN_GENMASK(17, 16)
+#define FLEXCAN_FDCTRL_MBDSR_8          0x0
+#define FLEXCAN_FDCTRL_MBDSR_12         0x1
+#define FLEXCAN_FDCTRL_MBDSR_32         0x2
+#define FLEXCAN_FDCTRL_MBDSR_64         0x3
+#define FLEXCAN_FDCTRL_TDCEN            BIT(15)
+#define FLEXCAN_FDCTRL_TDCFAIL          BIT(14)
+#define FLEXCAN_FDCTRL_TDCOFF           FLEXCAN_GENMASK(12, 8)
+#define FLEXCAN_FDCTRL_TDCVAL           FLEXCAN_GENMASK(5, 0)
+
+/* FLEXCAN FD Bit Timing register (FDCBT) bits */
+#define FLEXCAN_FDCBT_FPRESDIV_MASK     FLEXCAN_GENMASK(29, 20)
+#define FLEXCAN_FDCBT_FRJW_MASK         FLEXCAN_GENMASK(18, 16)
+#define FLEXCAN_FDCBT_FPROPSEG_MASK     FLEXCAN_GENMASK(14, 10)
+#define FLEXCAN_FDCBT_FPSEG1_MASK       FLEXCAN_GENMASK(7, 5)
+#define FLEXCAN_FDCBT_FPSEG2_MASK       FLEXCAN_GENMASK(2, 0)
+
+/* FLEXCAN CRC Register (CRCR) bits */
+#define FLEXCAN_CRCR_MBCRC_MASK         FLEXCAN_GENMASK(22, 16)
+#define FLEXCAN_CRCR_MBCRC(x)           (((x) & FLEXCAN_CRCR_MBCRC_MASK) << 16)
+#define FLEXCAN_CRCR_TXCRC_MASK         FLEXCAN_GENMASK(14, 0)
+#define FLEXCAN_CRCR_TXCRC(x)           ((x) & FLEXCAN_CRCR_TXCRC_MASK)
+
+/* FLEXCAN interrupt flag register (IFLAG) bits */
+/* Errata ERR005829 step7: Reserve first valid MB */
+#define I_FIFO_OVERFLOW  7
+#define I_FIFO_WARN      6
+#define I_FIFO_AVAILABLE 5
+
+#define FLEXCAN_TX_MB_RESERVED_RX_FIFO  8
+#define FLEXCAN_TX_MB_RESERVED_RX_MAILBOX       0
+#define FLEXCAN_RX_MB_RX_MAILBOX_FIRST  (FLEXCAN_TX_MB_RESERVED_RX_MAILBOX + 1)
+#define FLEXCAN_IFLAG_MB(x)             BIT_ULL(x)
+#define FLEXCAN_IFLAG_RX_FIFO_OVERFLOW  BIT(I_FIFO_OVERFLOW)
+#define FLEXCAN_IFLAG_RX_FIFO_WARN      BIT(I_FIFO_WARN)
+#define FLEXCAN_IFLAG_RX_FIFO_AVAILABLE BIT(I_FIFO_AVAILABLE)
+
+/* FLEXCAN message buffers */
+#define FLEXCAN_MB_CODE_RX_BUSY_BIT     (0x1 << 24)
+#define FLEXCAN_MB_CODE_RX_INACTIVE     (0x0 << 24)
+#define FLEXCAN_MB_CODE_RX_EMPTY        (0x4 << 24)
+#define FLEXCAN_MB_CODE_RX_FULL         (0x2 << 24)
+#define FLEXCAN_MB_CODE_RX_OVERRUN      (0x6 << 24)
+#define FLEXCAN_MB_CODE_RX_RANSWER      (0xa << 24)
+
+#define FLEXCAN_MB_CODE_TX_INACTIVE     (0x8 << 24)
+#define FLEXCAN_MB_CODE_TX_ABORT        (0x9 << 24)
+#define FLEXCAN_MB_CODE_TX_DATA         (0xc << 24)
+#define FLEXCAN_MB_CODE_TX_TANSWER      (0xe << 24)
+
+#define FLEXCAN_MB_CODE(x)              (((x) & 0xF) << 24)
+#define FLEXCAN_MB_CODE_MASK            FLEXCAN_MB_CODE(UINT32_MAX)
+
+#define FLEXCAN_MB_CNT_EDL              BIT(31)
+#define FLEXCAN_MB_CNT_BRS              BIT(30)
+#define FLEXCAN_MB_CNT_ESI              BIT(29)
+#define FLEXCAN_MB_CNT_SRR              BIT(22)
+#define FLEXCAN_MB_CNT_IDE              BIT(21)
+#define FLEXCAN_MB_CNT_RTR              BIT(20)
+#define FLEXCAN_MB_CNT_LENGTH(x)        (((x) & 0xF) << 16)
+#define FLEXCAN_MB_CNT_TIMESTAMP(x)     ((x) & 0xFFFF)
+#define FLEXCAN_MB_CNT_TIMESTAMP_MASK   FLEXCAN_MB_CNT_TIMESTAMP(UINT32_MAX)
+
+#endif
hw/net/can/meson.build +1 -0
index 7382344628..401afde2e4 100644
@@ -6,3 +6,4 @@ system_ss.add(when: 'CONFIG_CAN_CTUCANFD', if_true: files('ctucan_core.c'))
 system_ss.add(when: 'CONFIG_CAN_CTUCANFD_PCI', if_true: files('ctucan_pci.c'))
 system_ss.add(when: 'CONFIG_XLNX_ZYNQMP', if_true: files('xlnx-zynqmp-can.c'))
 system_ss.add(when: 'CONFIG_XLNX_VERSAL', if_true: files('xlnx-versal-canfd.c'))
+system_ss.add(when: 'CONFIG_CAN_FLEXCAN', if_true: files('flexcan.c'))
hw/net/can/trace-events +18 -0
index de64ac1b31..83f574e81c 100644
@@ -1,3 +1,21 @@
+# flexcan.c
+flexcan_irq_update(void *inst, uint32_t mb_irqs1, uint32_t mb_irqs2, int setting) "%p: irqs1 0x%08x irqs2 0x%08x request %i"
+flexcan_set_mcr(void* inst, const char *enabled, const char *freeze, const char *fifo, const char *rdy, const char *sync) "%p: %s %s %s %s %s"
+flexcan_mb_write(void *inst, int mbidx, const char *code, int is_mailbox, uint32_t ctrl, uint32_t id) "%p: mbidx %i code %s is_mailbox %i ctrl 0x%08x id 0x%08x"
+flexcan_mb_lock(void *inst, int mbidx, int had_rx_code) "%p: mbidx %i had_rx_code %i"
+flexcan_mb_unlock(void *inst, int mbidx, const char *pending_frame) "%p: mbidx %i%s"
+flexcan_fifo_pop(void *inst, int non_empty_before, int non_empty_after) "%p: non_empty before %i non_empty_after %i"
+flexcan_fifo_push(void *inst, int n_occupied) "%p: n_slots_occupied %i"
+flexcan_reset(void* inst) "%p: resetting"
+flexcan_mem_op(void *inst, const char *op, uint32_t v, int offset, const char *reg_name, int size) "%p: %s 0x%08x at offset %i register %s size %i"
+flexcan_mem_accepts(void *inst, int offset, int size, int is_write, int is_supv) "%p: denied access: offset %i size %i is_write %i is_supv %i"
+flexcan_get_timestamp(void *inst, int64_t time_elapsed_ms, uint32_t bitrate, uint64_t cycles, uint32_t shift, uint32_t timestamp) "%p: time_elapsed %" PRIi64 "ms bitrate %ub/s cycles %" PRIu64 " shift %u timestamp 0x%04x"
+flexcan_get_bitrate(void *inst, uint32_t pe_freq, uint32_t prediv, uint32_t s_freq, uint32_t tseg1, uint32_t tseg2, uint32_t quata_per_bit, uint32_t bitrate) "%p: pe_freq %uHz prescaler %u s_freq %uHz tseg1 %uq tseg2 %uq total %uq/b bitrate %ub/s"
+flexcan_timer_start(void *inst, uint32_t bitrate, uint32_t value) "%p: bitrate %ub/s value 0x%04x"
+flexcan_timer_stop(void *inst, uint32_t bitrate, uint32_t value) "%p: bitrate %ub/s value 0x%04x"
+flexcan_mb_rx_check_mb(void *inst, int mbidx, const char *code, int is_matched, int is_ftr, int is_serviced, int is_locked) "%p: checking mb %i code %s is_matched %i is_free_to_receive %i is_serviced %i is_locked %i"
+flexcan_receive(void *inst, size_t n_frames) "%p: received %zu frames"
+
 # xlnx-zynqmp-can.c

xlnx_can_update_irq(uint32_t isr, uint32_t ier, uint32_t irq) "ISR: 0x%08x IER: 0x%08x IRQ: 0x%08x" xlnx_can_reset(uint32_t val) "Resetting controller with value = 0x%08x"

include/hw/net/flexcan.h new file +32 -0
new file mode 100644
index 0000000000..eceb6a7920
@@ -0,0 +1,153 @@
+/*
+ * QEMU model of the NXP FLEXCAN device.
+ *
+ * Copyright (c) 2025 Matyas Bobek <[email protected]>
+ *
+ * Based on CTU CAN FD emulation implemented by Jan Charvat.
+ *
+ * This code is licensed under the GPL version 2 or later.  See
+ * the COPYING file in the top-level directory.
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#ifndef HW_CAN_FLEXCAN_H
+#define HW_CAN_FLEXCAN_H
+
+#include "hw/sysbus.h"
+#include "exec/hwaddr.h"
+#include "net/can_emu.h"
+#include "qom/object.h"
+#include "hw/misc/imx_ccm.h"
+
+#define FLEXCAN_FIFO_DEPTH 6
+#define FLEXCAN_MAILBOX_COUNT 64
+
+/* view of single message buffer registers */
+typedef struct FlexcanRegsMessageBuffer {
+    uint32_t can_ctrl;
+    uint32_t can_id;
+    uint32_t data[2];
+} FlexcanRegsMessageBuffer;
+

include/hw/net/flexcan.h line 42

Quoted 10 lines
include/hw/net/flexcan.h new file +10 -0
+/* RX FIFO view of message buffer registers */
+typedef struct FlexcanRegsRXFifo {
+    /* 6 message buffer deep queue, queue back first */
+    FlexcanRegsMessageBuffer mb_back;
+    FlexcanRegsMessageBuffer mbs_queue[5];
+
+    /* number of filter elements depends on ctrl2 | FLEXCAN_CTRL2_RFFN */
+    uint32_t                 filter_table_els[128];
+} FlexcanRegsRXFifo;
+

The following structure looks like being based on Linux. Does this affect the copyright of this file?

Shall we reuse constants like FLEXCAN_MAILBOX_COUNT and FLEXCAN_FIFO_DEPTH in the structure definition? Up to you.

Quoted 64 lines
include/hw/net/flexcan.h new file +64 -0
+/* Structure of the hardware registers */
+typedef struct FlexcanRegs {
+    uint32_t mcr;                /* 0x00 */
+    uint32_t ctrl;               /* 0x04 - not affected by soft reset */
+    uint32_t timer;              /* 0x08 */
+    uint32_t tcr;                /* 0x0C */
+    uint32_t rxmgmask;           /* 0x10 - not affected by soft reset */
+    uint32_t rx14mask;           /* 0x14 - not affected by soft reset */
+    uint32_t rx15mask;           /* 0x18 - not affected by soft reset */
+    uint32_t ecr;                /* 0x1C */
+    uint32_t esr;                /* 0x20 */
+    uint32_t imask2;             /* 0x24 */
+    uint32_t imask1;             /* 0x28 */
+    uint32_t iflag2;             /* 0x2C */
+    uint32_t iflag1;             /* 0x30 */
+    union {                      /* 0x34 */
+        uint32_t gfwr_mx28;      /* MX28, MX53 */
+        uint32_t ctrl2;          /* MX6, VF610 - not affected by soft reset */
+    };
+    uint32_t esr2;               /* 0x38 */
+    uint32_t imeur;              /* 0x3C, unused */
+    uint32_t lrfr;               /* 0x40, unused */
+    uint32_t crcr;               /* 0x44 */
+    uint32_t rxfgmask;           /* 0x48 */
+    uint32_t rxfir;              /* 0x4C - not affected by soft reset */
+    uint32_t cbt;                /* 0x50, unused - not affected by soft reset */
+    uint32_t _reserved2;         /* 0x54 */
+    uint32_t dbg1;               /* 0x58, unused */
+    uint32_t dbg2;               /* 0x5C, unused */
+    uint32_t _reserved3[8];      /* 0x60 */
+    union {                      /* 0x80 - not affected by soft reset */
+        uint32_t mb[256];
+        FlexcanRegsMessageBuffer mbs[64];
+        FlexcanRegsRXFifo fifo;
+    };
+    uint32_t _reserved4[256];    /* 0x480 */
+    uint32_t rximr[64];          /* 0x880 - not affected by soft reset */
+    uint32_t _reserved5[24];     /* 0x980 */
+    uint32_t gfwr_mx6;           /* 0x9E0 - MX6 */
+
+    /* the rest is unused except for SMB */
+    uint32_t _reserved6[39];     /* 0x9E4 */
+    uint32_t _rxfir[6];          /* 0xA80 */
+    uint32_t _reserved8[2];      /* 0xA98 */
+    uint32_t _rxmgmask;          /* 0xAA0 */
+    uint32_t _rxfgmask;          /* 0xAA4 */
+    uint32_t _rx14mask;          /* 0xAA8 */
+    uint32_t _rx15mask;          /* 0xAAC */
+    uint32_t tx_smb[4];          /* 0xAB0 */
+    union {                      /* 0xAC0, used for SMB emulation */
+        uint32_t rx_smb0_raw[4];
+        FlexcanRegsMessageBuffer rx_smb0;
+    };
+    uint32_t rx_smb1[4];         /* 0xAD0 */
+    uint32_t mecr;               /* 0xAE0 */
+    uint32_t erriar;             /* 0xAE4 */
+    uint32_t erridpr;            /* 0xAE8 */
+    uint32_t errippr;            /* 0xAEC */
+    uint32_t rerrar;             /* 0xAF0 */
+    uint32_t rerrdr;             /* 0xAF4 */
+    uint32_t rerrsynr;           /* 0xAF8 */
+    uint32_t errsr;              /* 0xAFC */
+    uint32_t _reserved7[64];     /* 0xB00 */
+    uint32_t fdctrl;             /* 0xC00 - not affected by soft reset */

include/hw/net/flexcan.h line 116

Quoted 10 lines
include/hw/net/flexcan.h new file +10 -0
+    uint32_t fdcbt;              /* 0xC04 - not affected by soft reset */
+    uint32_t fdcrc;              /* 0xC08 */
+    uint32_t _reserved9[199];    /* 0xC0C */
+    uint32_t tx_smb_fd[18];      /* 0xF28 */
+    uint32_t rx_smb0_fd[18];     /* 0xF70 */
+    uint32_t rx_smb1_fd[18];     /* 0xFB8 */
+} FlexcanRegs;
+
+typedef struct FlexcanState {
+    SysBusDevice        parent_obj;

Let's add a blank line here to visually separate the base class.

Quoted 11 lines
include/hw/net/flexcan.h new file +11 -0
+    MemoryRegion        iomem;
+    IMXCCMState        *ccm;
+    qemu_irq            irq;
+
+    CanBusState        *canbus;
+    CanBusClientState   bus_client;
+
+    union {
+        FlexcanRegs     regs;
+        uint32_t        regs_raw[sizeof(FlexcanRegs) / 4];
+    };

include/hw/net/flexcan.h line 137

Quoted 10 lines
include/hw/net/flexcan.h new file +10 -0
+    int64_t             timer_start;
+    uint64_t            last_rx_timer_cycles;
+    int32_t             locked_mbidx;
+    int32_t             smb_target_mbidx;
+    uint32_t            timer_freq;
+} FlexcanState;
+
+#define TYPE_CAN_FLEXCAN "flexcan"
+
+OBJECT_DECLARE_SIMPLE_TYPE(FlexcanState, CAN_FLEXCAN);

The folling symbols...

Quoted 4 lines
include/hw/net/flexcan.h new file +4 -0
+
+void flexcan_init(Object *obj);
+void flexcan_hardware_reset(FlexcanState *s);
+

include/hw/net/flexcan.h line 151

Quoted 10 lines
include/hw/net/flexcan.h new file +10 -0
+bool flexcan_can_receive(CanBusClientState *s);
+ssize_t flexcan_receive(CanBusClientState *s, const qemu_can_frame *frames,
+                        size_t frames_cnt);
+
+void flexcan_mem_write(void *obj, hwaddr addr, uint64_t val, unsigned size);
+uint64_t flexcan_mem_read(void *obj, hwaddr addr, unsigned size);
+bool flexcan_mem_accepts(void *obj, hwaddr addr, unsigned size, bool is_write,
+                         MemTxAttrs attrs);
+
+extern const VMStateDescription vmstate_flexcan;

... aren't used outside the .c file, so please keep them private. You may need to move the "flexcan_ops" definition in the .c file further down to avoid forward declarations.

Quite a lot of comments from my side, I hope you don'f feel intimidated! I'm looking forward to seeing FlexCAN support upstreamed. If you have any questions please feel free to ask.

Best regards,
Bernhard

Quoted 2 lines
include/hw/net/flexcan.h new file +2 -0
+
+#endif
#7 lore raw

Re: [PATCH v1 3/6] hw/net/can/flexcan: NXP FlexCAN core emulation

Am 2. Januar 2026 14:53:42 UTC schrieb Bernhard Beschow <[email protected]>:

Quoted 1,982 lines

Am 15. Dezember 2025 20:03:12 UTC schrieb "Matyáš Bobek" <[email protected]>:

Quoted 39 lines

This commit adds the FlexCAN2 emulator implementation and plugs it into Meson and Kconfig.
FlexCAN2 version can be found in i.MX6 SoCs and others.

More information about the implementation can be found in [1].

[1] http://dspace.cvut.cz/bitstream/handle/10467/122654/F3-BP-2025-Bobek-Matyas-BP_Bobek_FlexCAN_final_4.pdf

---

MAINTAINERS               |    1 +
hw/net/Kconfig            |    5 +
hw/net/can/flexcan.c      | 1469 +++++++++++++++++++++++++++++++++++++
hw/net/can/flexcan_regs.h |  188 +++++
hw/net/can/meson.build    |    1 +
hw/net/can/trace-events   |   18 +
include/hw/net/flexcan.h  |  153 ++++
7 files changed, 1835 insertions(+)

create mode 100644 hw/net/can/flexcan.c
create mode 100644 hw/net/can/flexcan_regs.h
create mode 100644 include/hw/net/flexcan.h

MAINTAINERS +0 -0
index 63e9ba521b..a0b152939b 100644
@@ -2858,6 +2858,7 @@ W: https://canbus.pages.fel.cvut.cz/

F: net/can/*
F: hw/net/can/*
F: include/net/can_*.h
+F: include/hw/net/flexcan.h
F: docs/system/devices/can.rst

OpenPIC interrupt controller

hw/net/Kconfig +0 -0
index 2b513d6895..160c311dcd 100644

hw/net/Kconfig config CAN_CTUCANFD_PCI · line 166

Quoted 7 lines
hw/net/Kconfig +4 -0
@@ -160,3 +160,8 @@ config CAN_CTUCANFD_PCI
    default y if PCI_DEVICES
    depends on PCI && CAN_CTUCANFD
    select CAN_BUS
+
+config CAN_FLEXCAN
+    bool
+    default y

I think the "default y" isn't needed since CONFIG_FSL_IMX6 will select it.

However, I need to add a "depends on IMX" to prevent linker errors. You might be able to reproduce this by enabling other architectures than arm-softmmu.

Quoted 169 lines
hw/net/Kconfig +1 -0
+    select CAN_BUS
hw/net/can/flexcan.c new file +162 -0
new file mode 100644
index 0000000000..1f50dd1d5b
@@ -0,0 +1,1469 @@
+/*
+ * QEMU model of the NXP FLEXCAN device.
+ *
+ * This implementation is based on the following reference manual:
+ * i.MX 6Dual/6Quad Applications Processor Reference Manual
+ * Document Number: IMX6DQRM, Rev. 6, 05/2020
+ *
+ * Copyright (c) 2025 Matyas Bobek <[email protected]>
+ *
+ * Based on CTU CAN FD emulation implemented by Jan Charvat.
+ *
+ * This code is licensed under the GPL version 2 or later.  See
+ * the COPYING file in the top-level directory.
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/log.h"
+#include "hw/sysbus.h"
+#include "qapi/error.h"
+#include "hw/irq.h"
+#include "migration/vmstate.h"
+#include "net/can_emu.h"
+#include "hw/qdev-properties.h"
+#include "trace.h"
+
+#include "hw/net/flexcan.h"
+#include "flexcan_regs.h"
+#include "qemu/timer.h"
+
+#define USE(var) (void)var;
+
+#define DEBUG_FLEXCAN 1
+#ifndef DEBUG_FLEXCAN
+#define DEBUG_FLEXCAN 0
+#endif
+
+/*
+ * Indicates MB w/ received frame has not been serviced yet
+ * This is an emulator-only flag in position of unused (reserved) bit
+ * of message buffer control register
+ */
+#define FLEXCAN_MB_CNT_NOT_SRV          BIT(23)
+/**
+ * if no MB is locked, FlexcanState.locked_mb
+ * is set to FLEXCAN_NO_MB_LOCKED
+ */
+#define FLEXCAN_NO_MB_LOCKED            -1
+/**
+ * if no frame is waiting in the SMB, FlexcanState.smb_target_mbid
+ * is set to FLEXCAN_SMB_EMPTY
+ */
+#define FLEXCAN_SMB_EMPTY               -1
+/**
+ * When the module is disabled or in freeze mode,
+ * the timer is not running. That is indicated by setting
+ * FlexcanState.timer_start to FLEXCAN_TIMER_STOPPED.
+ */
+#define FLEXCAN_TIMER_STOPPED           -1
+
+/**
+ * defines the end of the memory space of the implemented registers
+ *
+ * also prevents addressing memory after FlexcanRegs end
+ */
+#define FLEXCAN_ADDR_SPC_END offsetof(FlexcanRegs, _reserved6)
+QEMU_BUILD_BUG_ON(FLEXCAN_ADDR_SPC_END > sizeof(FlexcanRegs));
+
+/* These constants are returned by flexcan_fifo_rx() and flexcan_mb_rx(), */
+/* Retry the other receiving mechanism (ie. message bufer or mailbox). */
+#define FLEXCAN_RX_SEARCH_RETRY 0
+/* The frame was received and stored. */
+#define FLEXCAN_RX_SEARCH_ACCEPT 1
+/* The frame was filtered out and dropped. */
+#define FLEXCAN_RX_SEARCH_DROPPED 2
+
+/*
+ * These constants are returned by flexcan_mb_rx_check_mb().
+ * See flexcan_mb_rx_check_mb() kerneldoc for details.
+ */
+#define FLEXCAN_CHECK_MB_NIL 0
+#define FLEXCAN_CHECK_MB_MATCH 3
+#define FLEXCAN_CHECK_MB_MATCH_NON_FREE 1
+#define FLEXCAN_CHECK_MB_MATCH_LOCKED 5
+
+static const FlexcanRegs flexcan_regs_write_mask = {
+    .mcr = 0xF6EB337F,
+    .ctrl = 0xFFFFFFFF,
+    .timer = 0xFFFFFFFF,
+    .tcr = 0xFFFFFFFF,
+    .rxmgmask = 0xFFFFFFFF,
+    .rx14mask = 0xFFFFFFFF,
+    .rx15mask = 0xFFFFFFFF,
+    .ecr = 0xFFFFFFFF,
+    .esr = 0xFFFFFFFF,
+    .imask2 = 0xFFFFFFFF,
+    .imask1 = 0xFFFFFFFF,
+    .iflag2 = 0,
+    .iflag1 = 0,
+    .ctrl2 = 0xFFFFFFFF,
+    .esr2 = 0,
+    .imeur = 0,
+    .lrfr = 0,
+    .crcr = 0,
+    .rxfgmask = 0xFFFFFFFF,
+    .rxfir = 0,
+    .cbt = 0,
+    ._reserved2 = 0,
+    .dbg1 = 0,
+    .dbg2 = 0,
+    .mbs = { [0 ... 63] = {
+        .can_ctrl = 0xFFFFFFFF & ~FLEXCAN_MB_CNT_NOT_SRV,
+        .can_id = 0xFFFFFFFF,
+        .data = { 0xFFFFFFFF, 0xFFFFFFFF },
+    } },
+    ._reserved4 = {0},
+    .rximr = { [0 ... 63] = 0xFFFFFFFF },
+    ._reserved5 = {0},
+    .gfwr_mx6 = 0xFFFFFFFF,
+    ._reserved6 = {0},
+    ._reserved8 = {0},
+    .rx_smb0_raw = {0, 0, 0, 0},
+    .rx_smb1 = {0, 0, 0, 0},
+};
+static const FlexcanRegs flexcan_regs_reset_mask = {
+    .mcr = 0x80000000,
+    .ctrl = 0xFFFFFFFF,
+    .timer = 0,
+    .tcr = 0,
+    .rxmgmask = 0xFFFFFFFF,
+    .rx14mask = 0xFFFFFFFF,
+    .rx15mask = 0xFFFFFFFF,
+    .ecr = 0,
+    .esr = 0,
+    .imask2 = 0,
+    .imask1 = 0,
+    .iflag2 = 0,
+    .iflag1 = 0,
+    .ctrl2 = 0xFFFFFFFF,
+    .esr2 = 0,
+    .imeur = 0,
+    .lrfr = 0,
+    .crcr = 0,
+    .rxfgmask = 0xFFFFFFFF,
+    .rxfir = 0xFFFFFFFF,
+    .cbt = 0,
+    ._reserved2 = 0,
+    .dbg1 = 0,
+    .dbg2 = 0,
+    .mb = {0xFFFFFFFF},
+    ._reserved4 = {0},
+    .rximr = {0xFFFFFFFF},
+    ._reserved5 = {0},
+    .gfwr_mx6 = 0,
+    ._reserved6 = {0},
+    ._reserved8 = {0},
+    .rx_smb0_raw = {0, 0, 0, 0},
+    .rx_smb1 = {0, 0, 0, 0},
+};
+
+#if DEBUG_FLEXCAN

hw/net/can/flexcan.c line 172

Quoted 10 lines
hw/net/can/flexcan.c new file +10 -0
+
+#define DPRINTF(fmt, args...) \
+    fprintf(stderr, "(%p)[%s]%s: " fmt , (void *)s, TYPE_CAN_FLEXCAN, \
+            __func__, ##args);
+
+#else /* DEBUG_FLEXCAN */
+
+#define DPRINTF(fmt, args...) do { } while (0)
+
+#endif /* DEBUG_FLEXCAN */

Please no new DPRINTF. Use tracing and logging instead.

Quoted 89 lines
hw/net/can/flexcan.c new file +89 -0
+
+#define FLEXCAN_DBG_BUF_LEN 16
+
+static const char *flexcan_dbg_mb_code_strs[16] = {
+    "INACTIVE_RX",
+    "FULL",
+    "EMPTY",
+    "OVERRUN",
+    "INACTIVE_TX",
+    "RANSWER",
+    "DATA",
+    "TANSWER"
+};
+
+/**
+ * flexcan_dbg_mb_code() - Get the string representation of a mailbox code
+ * @mb_ctrl: The mailbox control register value
+ * @buf: The buffer to store the string representation
+ *
+ * Return: Either constant string or string formatted into @buf
+ */
+static const char *flexcan_dbg_mb_code(uint32_t mb_ctrl, char *buf)
+{
+    uint32_t code = mb_ctrl & FLEXCAN_MB_CODE_MASK;
+    uint32_t code_idx = code >> 24;
+    if (code == FLEXCAN_MB_CODE_TX_ABORT) {
+        return "ABORT";
+    }
+
+    const char *code_str = flexcan_dbg_mb_code_strs[code_idx >> 1];
+    if (code_idx & 1) {
+        g_snprintf(buf, FLEXCAN_DBG_BUF_LEN, "%s+BUSY", code_str);
+        return buf;
+    } else {
+        return code_str;
+    }
+}
+
+static const char *flexcan_dbg_reg_name_fixed(hwaddr addr)
+{
+    if (addr >= FLEXCAN_ADDR_SPC_END) {
+        return "OUT-OF-RANGE";
+    }
+
+    switch (addr) {
+    case offsetof(FlexcanRegs, mcr):
+        return "MCR";
+    case offsetof(FlexcanRegs, ctrl):
+        return "CTRL";
+    case offsetof(FlexcanRegs, timer):
+        return "TIMER";
+    case offsetof(FlexcanRegs, esr):
+        return "ESR";
+    case offsetof(FlexcanRegs, rxmgmask):
+        return "RXMGMASK";
+    case offsetof(FlexcanRegs, rx14mask):
+        return "RX14MASK";
+    case offsetof(FlexcanRegs, rx15mask):
+        return "RX15MASK";
+    case offsetof(FlexcanRegs, rxfgmask):
+        return "RXFGMASK";
+    case offsetof(FlexcanRegs, ecr):
+        return "ECR";
+    case offsetof(FlexcanRegs, ctrl2):
+        return "CTRL2";
+    case offsetof(FlexcanRegs, imask2):
+        return "IMASK2";
+    case offsetof(FlexcanRegs, imask1):
+        return "IMASK1";
+    case offsetof(FlexcanRegs, iflag2):
+        return "IFLAG2";
+    case offsetof(FlexcanRegs, iflag1):
+        return "IFLAG1";
+    }
+    return NULL;
+}
+
+static inline void flexcan_trace_mem_op(FlexcanState *s, hwaddr addr,
+                                        uint32_t value, int size, bool is_wr)
+{
+    if (trace_event_get_state_backends(TRACE_FLEXCAN_MEM_OP)) {
+        const char *reg_name = "unknown";
+        char reg_name_buf[FLEXCAN_DBG_BUF_LEN] = { 0 };
+        const char *reg_name_fixed = flexcan_dbg_reg_name_fixed(addr);
+        const char *op_string = is_wr ? "write" : "read";
+
+        if (reg_name_fixed) {
+            reg_name = reg_name_fixed;
+        } else if (addr >= 0x80 && addr < 0x480) {

hw/net/can/flexcan.c line 271

Quoted 10 lines
hw/net/can/flexcan.c new file +10 -0
+            int mbidx = (addr - 0x80) / 16;
+            g_snprintf(reg_name_buf, sizeof(reg_name_buf), "MB%i", mbidx);
+            reg_name = reg_name_buf;
+        } else if (addr >= 0x880 && addr < 0x9e0) {
+            int id = (addr - 0x880) / 4;
+            g_snprintf(reg_name_buf, sizeof(reg_name_buf), "RXIMR%i", id);
+            reg_name = reg_name_buf;
+        }
+
+        trace_flexcan_mem_op(s, op_string, value, addr, reg_name, size);

Better use "DEVICE(s)->canonical_path" instead of the "s" parameter which results in much more readable trace messages. This applies to all trace functions.

hw/net/can/flexcan.c line 278

Quoted 7 lines
hw/net/can/flexcan.c new file +7 -0
+    }
+}
+
+static const struct MemoryRegionOps flexcan_ops = {
+    .read = flexcan_mem_read,
+    .write = flexcan_mem_write,
+    .endianness = DEVICE_NATIVE_ENDIAN,

Please use DEVICE_LITTLE_ENDIAN for now since DEVICE_NATIVE_ENDIAN is deprecated, and checkpatch.pl actually flags it. While there may be big endian variants of this IP core, we don't need to worry about these at the moment since this device model already depends on the IMX clock module.

Quoted 92 lines
hw/net/can/flexcan.c new file +92 -0
+    .valid = {
+        .min_access_size = 1,
+        .max_access_size = 4,
+        .unaligned = true,
+        .accepts = flexcan_mem_accepts
+    },
+    .impl = {
+        .min_access_size = 4,
+        .max_access_size = 4,
+        .unaligned = false
+    },
+};
+
+static int flexcan_mb_rx(FlexcanState *s, const qemu_can_frame *frame);
+static void flexcan_mb_unlock(FlexcanState *s);
+
+/* ========== Mailbox Utils ========== */
+
+/**
+ * flexcan_mailbox_count() - Get number of enabled mailboxes
+ * @s: FlexCAN device pointer
+ *
+ * Count is based on MCR[MAXMB] field. Note that some of those mailboxes
+ * might be part of queue or queue ID filters or ordinary message buffers.
+ */
+static inline int flexcan_enabled_mailbox_count(const FlexcanState *s)
+{
+    return (s->regs.mcr & FLEXCAN_MCR_MAXMB(UINT32_MAX)) + 1;
+}
+
+/**
+ * flexcan_get_first_message_buffer() - Get pointer to first message buffer
+ * @s: FlexCAN device pointer
+ *
+ * In context of this function, message buffer means a mailbox which is not
+ * a queue element nor a queue filter. Note this function does not take
+ * MCR[MAXMB] into account, meaning that the returned mailbox
+ * might be disabled.
+ */
+static FlexcanRegsMessageBuffer *flexcan_get_first_message_buffer(
+                                 FlexcanState *s)
+{
+    if (s->regs.mcr & FLEXCAN_MCR_FEN) {
+        int rffn = (s->regs.ctrl2 & FLEXCAN_CTRL2_RFFN(UINT32_MAX)) >> 24;
+        return s->regs.mbs + 8 + 2 * rffn;
+    } else {
+        return s->regs.mbs;
+    }
+}
+
+/**
+ * flexcan_get_last_enabled_mailbox() - Get pointer to last enabled mailbox.
+ * @s: FlexCAN device pointer
+ *
+ * When used with flexcan_get_first_message_buffer(), all mailboxes *ptr in
+ * range `first_message_buffer() <= ptr <= last_enabled_mailbox` are valid
+ * message buffer mailboxes.
+ *
+ * Return: Last enabled mailbox in MCR[MAXMB] sense. The mailbox might be
+ * of any type.
+ */
+static inline FlexcanRegsMessageBuffer *flexcan_get_last_enabled_mailbox(
+                                 FlexcanState *s)
+{
+    return s->regs.mbs + flexcan_enabled_mailbox_count(s);
+}
+
+/**
+ * flexcan_get_first_filter_mailbox() - Get pointer to first queue filter.
+ * @s: FlexCAN device pointer
+ *
+ * This function does not check if FIFO is enabled.
+ *
+ * Return: Pointer to first queue filter element.
+ */
+static inline uint32_t *flexcan_get_first_filter_mailbox(FlexcanState *s)
+{
+    return (uint32_t *)(s->regs.mbs + 6);
+}
+
+/**
+ * flexcan_get_last_filter_mailbox() - Get pointer to last queue filter.
+ * @s: FlexCAN device pointer
+ *
+ * This function does not check if FIFO is enabled.
+ * All words in range [flexcan_get_first_filter_mailbox(),
+ * flexcan_get_last_filter_mailbox()] are queue filter elements, if queue
+ * is enabled.
+ *
+ * Return: Pointer to last queue filter element.
+ */
+static inline uint32_t *flexcan_get_last_filter_mailbox(FlexcanState *s)

hw/net/can/flexcan.c line 380

Quoted 10 lines
hw/net/can/flexcan.c new file +10 -0
+{
+    /* adding three to get pointer to the last word of the mailbox */
+    uint32_t *last_enabled_elem =
+        ((uint32_t *)flexcan_get_last_enabled_mailbox(s)) + 3;
+
+    int rffn = (s->regs.ctrl2 & FLEXCAN_CTRL2_RFFN(UINT32_MAX)) >> 24;
+    uint32_t *last_elem = (uint32_t *)(s->regs.mbs + 8 + 2 * rffn) - 1;
+
+    return MIN(last_elem, last_enabled_elem);
+}

The above two functions seem to be unused. Please remove.

Quoted 200 lines
hw/net/can/flexcan.c new file +200 -0
+
+/* ========== Free-running Timer ========== */
+static inline int64_t flexcan_get_time(void)
+{
+    return qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
+}
+
+/**
+ * flexcan_get_bitrate() - Calculate CAN bitrate (in Hz)
+ * @s: FlexCAN device pointer
+ *
+ * The bitrate is determined by FlexCAN configuration in CTRL1 register,
+ * and CCM co
+ */
+static uint32_t flexcan_get_bitrate(FlexcanState *s)
+{
+    uint32_t conf_presdiv = (s->regs.ctrl & FLEXCAN_CTRL_PRESDIV_MASK) >> 24;
+    uint32_t conf_pseg1 = (s->regs.ctrl & FLEXCAN_CTRL_PSEG1_MASK) >> 19;
+    uint32_t conf_pseg2 = (s->regs.ctrl & FLEXCAN_CTRL_PSEG2_MASK) >> 16;
+    uint32_t conf_propseg = s->regs.ctrl & FLEXCAN_CTRL_PROPSEG_MASK;
+
+    /* s_clock: CAN clock from CCM divivded by the prescaler */
+    assert(s->ccm);
+    uint32_t pe_freq = imx_ccm_get_clock_frequency(s->ccm, CLK_CAN);
+    uint32_t s_freq = pe_freq / (1 + conf_presdiv);
+
+    /* N of time quanta for segements */
+    uint32_t tseg1 = 2 + conf_pseg1 + conf_propseg;
+    uint32_t tseg2 = 1 + conf_pseg2;
+    uint32_t total_qpb = 1 + tseg1 + tseg2;
+
+    uint32_t bitrate = s_freq / total_qpb;
+
+    trace_flexcan_get_bitrate(s, pe_freq, 1 + conf_presdiv, s_freq, tseg1,
+                              tseg2, total_qpb, bitrate);
+    return bitrate;
+}
+
+/**
+ * int128_mul_6464() - Multiply two 64-bit integers into a 128-bit one
+ */
+static Int128 int128_muls_6464(int64_t ai, int64_t bi)
+{
+    uint64_t l, h;
+
+    muls64(&l, &h, ai, bi);
+    return int128_make128(l, h);
+}
+
+/**
+ * flexcan_get_timestamp() - Get current value of the 16-bit free-running timer
+ * @s: FlexCAN device pointer
+ * @mk_unique: if true, make the timestamp unique by incrementing it if needed
+ */
+static uint32_t flexcan_get_timestamp(FlexcanState *s, bool mk_unique)
+{
+    if (s->timer_start == FLEXCAN_TIMER_STOPPED) {
+        /* timer is not running, return last value */
+        trace_flexcan_get_timestamp(s, -1, 0, 0, 0, s->regs.timer);
+        return s->regs.timer;
+    }
+
+    int64_t current_time = flexcan_get_time();
+    int64_t elapsed_time_ns = current_time - s->timer_start;
+    int64_t elapsed_time_ms = elapsed_time_ns / 1000000;
+    if (elapsed_time_ns < 0) {
+        DPRINTF("timer overflow current_time=%li "
+                "timer_start=%li elapsed_time_ns=%li\n",
+                current_time, s->timer_start, elapsed_time_ns);
+        return 0xFFFF;
+    }
+
+    Int128 nanoseconds_in_second = int128_makes64(1000000000);
+    Int128 ncycles = int128_muls_6464(s->timer_freq, elapsed_time_ns);
+    Int128 cycles128 = int128_divs(ncycles, nanoseconds_in_second);
+    /* 64 bits hold for over 50k years at 10MHz */
+    uint64_t cycles = int128_getlo(cycles128);
+
+    uint32_t shift = 0;
+    if (mk_unique && cycles <= s->last_rx_timer_cycles) {
+        shift = 1;
+        cycles = s->last_rx_timer_cycles + shift;
+    }
+
+    s->last_rx_timer_cycles = cycles;
+    uint32_t rv = (uint32_t)cycles & 0xFFFF;
+
+    trace_flexcan_get_timestamp(s, elapsed_time_ms, s->timer_freq,
+                                cycles, shift, rv);
+    return rv;
+}
+
+/**
+ * flexcan_timer_start() - Start the free-running timer
+ * @s: FlexCAN device pointer
+ *
+ * This should be called when the module leaves freeze mode.
+ */
+static void flexcan_timer_start(FlexcanState *s)
+{
+    if (s->timer_start != FLEXCAN_TIMER_STOPPED) {
+        DPRINTF("module brought up, but timer is already running: "
+                "value=%" PRIu64 "\n", s->timer_start);
+    }
+    s->timer_freq = flexcan_get_bitrate(s);
+    s->timer_start = flexcan_get_time();
+    s->last_rx_timer_cycles = 0;
+
+    trace_flexcan_timer_start(s, s->timer_freq, s->regs.timer);
+}
+
+/**
+ * flexcan_timer_stop() - Stop the free-running timer
+ * @s: FlexCAN device pointer
+ *
+ * This should be called when the module enters freeze mode.
+ * Stores the current timestamp in the TIMER register.
+ */
+static void flexcan_timer_stop(FlexcanState *s)
+{
+    s->regs.timer = flexcan_get_timestamp(s, false);
+    s->timer_start = FLEXCAN_TIMER_STOPPED;
+
+    trace_flexcan_timer_stop(s, s->timer_freq, s->regs.timer);
+}
+
+/* ========== IRQ handling ========== */
+/**
+ * flexcan_irq_update() - Update qemu_irq line based on interrupt registers
+ * @s: FlexCAN device pointer
+ */
+static void flexcan_irq_update(FlexcanState *s)
+{
+    /* these are all interrupt sources from FlexCAN */
+    /* mailbox interrupt sources */
+    uint32_t mb_irqs1 = s->regs.iflag1 & s->regs.imask1;
+    uint32_t mb_irqs2 = s->regs.iflag2 & s->regs.imask2;
+
+    /**
+     * these interrupts aren't currently used and they can never be raised
+     *
+     * bool irq_wake_up = (s->regs.mcr & FLEXCAN_MCR_WAK_MSK) &&
+     *                    (s->regs.ecr & FLEXCAN_ESR_WAK_INT);
+     * bool irq_bus_off = (s->regs.ctrl & FLEXCAN_CTRL_BOFF_MSK) &&
+     *                    (s->regs.ecr & FLEXCAN_ESR_BOFF_INT);
+     * bool irq_error = (s->regs.ctrl & FLEXCAN_CTRL_ERR_MSK) &&
+     *                  (s->regs.ecr & FLEXCAN_ESR_ERR_INT);
+     * bool irq_tx_warn = (s->regs.ctrl & FLEXCAN_CTRL_TWRN_MSK) &&
+     *                    (s->regs.ecr & FLEXCAN_ESR_TWRN_INT);
+     * bool irq_rx_warn = (s->regs.ctrl & FLEXCAN_CTRL_RWRN_MSK) &&
+     *                    (s->regs.ecr & FLEXCAN_ESR_RWRN_INT);
+     */
+
+    int irq_setting = (mb_irqs1 | mb_irqs2) ? 1 : 0;
+    trace_flexcan_irq_update(s, mb_irqs1, mb_irqs2, irq_setting);
+
+    qemu_set_irq(s->irq, irq_setting);
+}
+
+/**
+ * flexcan_irq_iflag_set() - Set IFLAG bit corresponding to MB mbidx
+ * @s: FlexCAN device pointer
+ * @mbidx: mailbox index
+ */
+static void flexcan_irq_iflag_set(FlexcanState *s, int mbidx)
+{
+    if (mbidx < 32) {
+        s->regs.iflag1 |= BIT(mbidx);
+    } else {
+        s->regs.iflag2 |= BIT(mbidx - 32);
+    }
+}
+
+/**
+ * flexcan_irq_iflag_clear() - Clear IFLAG bit corresponding to MB mbidx
+ * @s: FlexCAN device pointer
+ * @mbidx: mailbox index
+ */
+static void flexcan_irq_iflag_clear(FlexcanState *s, int mbidx)
+{
+    if (mbidx < 32) {
+        s->regs.iflag1 &= ~BIT(mbidx);
+    } else {
+        s->regs.iflag2 &= ~BIT(mbidx - 32);
+    }
+}
+
+/* ========== RESET ========== */
+static void flexcan_reset_local_state(FlexcanState *s)
+{
+    uint32_t *reset_mask = (uint32_t *)&flexcan_regs_reset_mask;
+    for (int i = 0; i < (sizeof(FlexcanRegs) / 4); i++) {
+        s->regs_raw[i] &= reset_mask[i];
+    }
+
+    s->regs.mcr |= 0x5980000F;
+    s->locked_mbidx = FLEXCAN_NO_MB_LOCKED;
+    s->smb_target_mbidx = FLEXCAN_SMB_EMPTY;
+    s->timer_start = FLEXCAN_TIMER_STOPPED;
+

hw/net/can/flexcan.c line 590

Quoted 10 lines
hw/net/can/flexcan.c new file +10 -0
+    trace_flexcan_reset(s);
+}
+
+static void flexcan_soft_reset(FlexcanState *s)
+{
+    if (s->regs.mcr & FLEXCAN_MCR_LPM_ACK) {
+        g_autofree char *path = object_get_canonical_path(OBJECT(s));
+        qemu_log_mask(LOG_GUEST_ERROR,
+                      "%s: invalid soft reset request in low-power mode",
+                      path);

Here you can also use "DEVICE(s)->canonical_path", eliminating the dynamic allocation and cleanup logic entirely. Same for other cases.

Quoted 309 lines
hw/net/can/flexcan.c new file +309 -0
+    }
+
+    flexcan_reset_local_state(s);
+}
+
+static void flexcan_reset_enter(Object *obj, ResetType type)
+{
+    FlexcanState *s = CAN_FLEXCAN(obj);
+
+    memset(&s->regs, 0, sizeof(s->regs));
+    flexcan_reset_local_state(s);
+}
+
+static void flexcan_reset_hold(Object *obj, ResetType type)
+{
+    FlexcanState *s = CAN_FLEXCAN(obj);
+
+    flexcan_irq_update(s);
+}
+
+
+/* ========== Operation mode control ========== */
+/**
+ * flexcan_update_esr() - Update ESR based on mode and CAN bus connection state
+ * @s: FlexCAN device pointer
+ */
+static void flexcan_update_esr(FlexcanState *s)
+{
+    bool is_running = (s->regs.mcr & FLEXCAN_MCR_NOT_RDY) == 0;
+    /* potentially, there could be other influences on ESR[SYNCH] */
+
+    if (is_running && s->canbus) {
+        s->regs.esr |= FLEXCAN_ESR_SYNCH | FLEXCAN_ESR_IDLE;
+    } else {
+        s->regs.esr &= ~(FLEXCAN_ESR_SYNCH | FLEXCAN_ESR_IDLE);
+    }
+}
+
+/**
+ * flexcan_update_esr() - Process MCR write
+ * @s: FlexCAN device pointer
+ * @pv: previously set MCR value
+ *
+ * This function expects the new MCR value to be already written in s->regs.mcr.
+ */
+static void flexcan_set_mcr(FlexcanState *s, const uint32_t pv)
+{
+    uint32_t cv = s->regs.mcr;
+
+    /* -- module disable mode -- */
+    if (!(pv & FLEXCAN_MCR_MDIS) && (cv & FLEXCAN_MCR_MDIS)) {
+        /* transition to Module Disable mode */
+        cv |= FLEXCAN_MCR_LPM_ACK;
+    } else if ((pv & FLEXCAN_MCR_MDIS) && !(cv & FLEXCAN_MCR_MDIS)) {
+        /* transition from Module Disable mode */
+        cv &= ~FLEXCAN_MCR_LPM_ACK;
+    }
+
+    /* -- soft reset -- */
+    if (!(cv & FLEXCAN_MCR_LPM_ACK) && (cv & FLEXCAN_MCR_SOFTRST)) {
+        flexcan_soft_reset(s);
+        cv = s->regs.mcr;
+    }
+
+    /* -- freeze mode -- */
+    if (!(cv & FLEXCAN_MCR_LPM_ACK) &&
+        (cv & FLEXCAN_MCR_FRZ) &&
+        (cv & FLEXCAN_MCR_HALT)) {
+        cv |= FLEXCAN_MCR_FRZ_ACK;
+    } else {
+        cv &= ~FLEXCAN_MCR_FRZ_ACK;
+    }
+
+    /* -- fifo mode -- */
+    if (
+        ((pv & FLEXCAN_MCR_FEN) && !(cv & FLEXCAN_MCR_FEN)) ||
+        (!(pv & FLEXCAN_MCR_FEN) && (cv & FLEXCAN_MCR_FEN))
+    ) {
+        /* clear iflags used by fifo */
+        s->regs.iflag1 &= ~(
+            FLEXCAN_IFLAG_RX_FIFO_AVAILABLE |
+            FLEXCAN_IFLAG_RX_FIFO_OVERFLOW |
+            FLEXCAN_IFLAG_RX_FIFO_WARN
+        );
+    }
+    if (!(pv & FLEXCAN_MCR_FEN) && (cv & FLEXCAN_MCR_FEN)) {
+        /* zero out fifo region, we rely on zeroed can_ctrl for empty slots */
+        memset(s->regs.mbs, 0,
+               FLEXCAN_FIFO_DEPTH * sizeof(FlexcanRegsMessageBuffer));
+    }
+
+    /*
+     * assert NOT_RDY bit if in disable,
+     * stop (not implemented) or freeze mode
+     */
+    if ((cv & FLEXCAN_MCR_LPM_ACK) || (cv & FLEXCAN_MCR_FRZ_ACK)) {
+        cv |= FLEXCAN_MCR_NOT_RDY;
+    } else {
+        cv &= ~FLEXCAN_MCR_NOT_RDY;
+    }
+
+    if ((pv & FLEXCAN_MCR_NOT_RDY) && !(cv & FLEXCAN_MCR_NOT_RDY)) {
+        /* module went up, start the timer */
+        flexcan_timer_start(s);
+    } else if (!(pv & FLEXCAN_MCR_NOT_RDY) && (cv & FLEXCAN_MCR_NOT_RDY)) {
+        /* module went down, store the current timer value */
+        flexcan_timer_stop(s);
+    }
+
+    s->regs.mcr = cv;
+    flexcan_update_esr(s);
+    trace_flexcan_set_mcr(
+        s,
+        cv & FLEXCAN_MCR_LPM_ACK ? "DISABLED" : "ENABLED",
+        (cv & FLEXCAN_MCR_FRZ_ACK || cv & FLEXCAN_MCR_LPM_ACK) ?
+            "FROZEN" : "RUNNING",
+        cv & FLEXCAN_MCR_FEN ? "FIFO" : "MAILBOX",
+        cv & FLEXCAN_MCR_NOT_RDY ? "NOT_RDY" : "RDY",
+        s->regs.esr & FLEXCAN_ESR_SYNCH ? "SYNC" : "NOSYNC"
+    );
+}
+
+/* ========== TX ========== */
+static void flexcan_transmit(FlexcanState *s, int mbidx)
+{
+    FlexcanRegsMessageBuffer *mb = &s->regs.mbs[mbidx];
+    qemu_can_frame frame = {
+        .flags = 0,
+    };
+
+    if ((s->regs.ctrl & FLEXCAN_CTRL_LOM) ||
+        (s->regs.mcr & FLEXCAN_MCR_NOT_RDY)) {
+        /* no transmiting in listen-only, freeze or low-power mode */
+        return;
+    }
+
+    if (mb->can_ctrl & FLEXCAN_MB_CNT_IDE) {
+        /* 29b ID stored in bits [0, 29) */
+        uint32_t id = mb->can_id & 0x1FFFFFFF;
+        frame.can_id = id | QEMU_CAN_EFF_FLAG;
+    } else {
+        /* 11b ID stored in bits [18, 29) */
+        uint32_t id = (mb->can_id & (0x7FF << 18)) >> 18;
+        frame.can_id = id;
+    }
+
+    frame.can_dlc = (mb->can_ctrl & (0xF << 16)) >> 16;
+
+    uint32_t *frame_data = (uint32_t *)&frame.data;
+    for (int i = 0; i < 2; i++) {
+        stl_be_p(&frame_data[i], mb->data[i]);
+    }
+
+    if (!(s->regs.mcr & FLEXCAN_MCR_SRX_DIS)) {
+        /* self-reception */
+        flexcan_mb_rx(s, &frame);
+    }
+    if (!(s->regs.ctrl & FLEXCAN_CTRL_LPB)) {
+        /* send to bus if not in loopback mode */
+        if (s->canbus) {
+            can_bus_client_send(&s->bus_client, &frame, 1);
+        } else {
+            /* todo: raise error (no ack) */
+        }
+    }
+
+    uint32_t timestamp = flexcan_get_timestamp(s, true);
+    mb->can_ctrl &= ~(FLEXCAN_MB_CODE_MASK | FLEXCAN_MB_CNT_TIMESTAMP_MASK);
+    mb->can_ctrl |= FLEXCAN_MB_CODE_TX_INACTIVE |
+                    FLEXCAN_MB_CNT_TIMESTAMP(timestamp);
+
+    /* todo: compute the CRC */
+    s->regs.crcr = FLEXCAN_CRCR_TXCRC(0) | FLEXCAN_CRCR_MBCRC(mbidx);
+
+    flexcan_irq_iflag_set(s, mbidx);
+}
+
+static void flexcan_mb_write(FlexcanState *s, int mbid)
+{
+    FlexcanRegsMessageBuffer *mb = &s->regs.mbs[mbid];
+
+    bool is_mailbox = (mb <= flexcan_get_last_enabled_mailbox(s)) &&
+                     (mb >= flexcan_get_first_message_buffer(s));
+
+    if (trace_event_get_state_backends(TRACE_FLEXCAN_MB_WRITE)) {
+        char code_str_buf[FLEXCAN_DBG_BUF_LEN] = { 0 };
+        const char *code_str = flexcan_dbg_mb_code(mb->can_ctrl, code_str_buf);
+        trace_flexcan_mb_write(s, mbid, code_str, is_mailbox, mb->can_ctrl,
+                               mb->can_id);
+    }
+
+    if (!is_mailbox) {
+        /**
+         * Disabled mailbox or mailbox in region of queue filters
+         * was updated. Either way there is nothing to do.
+         */
+        return;
+    }
+
+    /* any write to message buffer clears the not_serviced flag */
+    mb->can_ctrl &= ~FLEXCAN_MB_CNT_NOT_SRV;
+
+    /**
+     * todo: search for active tx mbs on transition from freeze/disable mode
+     */
+    switch (mb->can_ctrl & FLEXCAN_MB_CODE_MASK) {
+    case FLEXCAN_MB_CODE_TX_INACTIVE:
+        QEMU_FALLTHROUGH;
+    case FLEXCAN_MB_CODE_RX_INACTIVE:
+        QEMU_FALLTHROUGH;
+    case FLEXCAN_MB_CODE_RX_EMPTY:
+        QEMU_FALLTHROUGH;
+    case FLEXCAN_MB_CODE_RX_FULL:
+        QEMU_FALLTHROUGH;
+    case FLEXCAN_MB_CODE_RX_RANSWER:
+        break;
+
+    case FLEXCAN_MB_CODE_TX_DATA:
+        flexcan_transmit(s, mbid);
+        break;
+    case FLEXCAN_MB_CODE_TX_ABORT:
+        /*
+         * as transmission is instant, it can never be aborted
+         * we need to set CODE in C/S back to the previous code
+         */
+        mb->can_ctrl &= ~FLEXCAN_MB_CODE(1);
+        break;
+    case FLEXCAN_MB_CODE_TX_TANSWER:
+        break;
+    default:
+        /* prevent setting the busy bit */
+        mb->can_ctrl &= ~FLEXCAN_MB_CODE_RX_BUSY_BIT;
+        break;
+  }
+
+}
+
+/* ========== RX ========== */
+static void flexcan_mb_move_in(FlexcanState *s, const qemu_can_frame *frame,
+                               FlexcanRegsMessageBuffer *target_mb)
+{
+    memset(target_mb, 0, sizeof(FlexcanRegsMessageBuffer));
+
+    uint32_t frame_len = frame->can_dlc;
+    if (frame_len > 8) {
+        frame_len = 8;
+    }
+    uint32_t *frame_data = (uint32_t *)&frame->data;
+    for (int i = 0; i < 2; i++) {
+        target_mb->data[i] = ldl_be_p(&frame_data[i]);
+    }
+
+    int timestamp = flexcan_get_timestamp(s, true);
+    uint32_t new_code = 0;
+
+    switch (target_mb->can_ctrl & FLEXCAN_MB_CODE_MASK) {
+    case FLEXCAN_MB_CODE_RX_FULL:
+    case FLEXCAN_MB_CODE_RX_OVERRUN:
+        if (target_mb->can_ctrl & FLEXCAN_MB_CNT_NOT_SRV) {
+            new_code = FLEXCAN_MB_CODE_RX_OVERRUN;
+        } else {
+            new_code = FLEXCAN_MB_CODE_RX_FULL;
+        }
+        break;
+    case FLEXCAN_MB_CODE_RX_RANSWER:
+        assert(s->regs.ctrl2 & FLEXCAN_CTRL2_RRS);
+        new_code = FLEXCAN_MB_CODE_TX_TANSWER;
+        break;
+    default:
+        new_code = FLEXCAN_MB_CODE_RX_FULL;
+    }
+
+    target_mb->can_ctrl = new_code
+        | FLEXCAN_MB_CNT_TIMESTAMP(timestamp)
+        | FLEXCAN_MB_CNT_LENGTH(frame_len)
+        | FLEXCAN_MB_CNT_NOT_SRV
+        | FLEXCAN_MB_CNT_SRR; /* always set for received frames */
+    if (frame->can_id & QEMU_CAN_RTR_FLAG) {
+        target_mb->can_ctrl |= FLEXCAN_MB_CNT_RTR;
+    }
+
+    if (frame->can_id & QEMU_CAN_EFF_FLAG) {
+        target_mb->can_ctrl |= FLEXCAN_MB_CNT_IDE;
+        target_mb->can_id |= frame->can_id & QEMU_CAN_EFF_MASK;
+    } else {
+        target_mb->can_id |= (frame->can_id & QEMU_CAN_SFF_MASK) << 18;
+    }
+}
+static void flexcan_mb_lock(FlexcanState *s, int mbidx)
+{
+    FlexcanRegsMessageBuffer *mb = &s->regs.mbs[mbidx];
+    if ((mb > flexcan_get_last_enabled_mailbox(s)) ||
+        (mb < flexcan_get_first_message_buffer(s))) {
+        return;
+    }
+    switch (mb->can_ctrl & FLEXCAN_MB_CODE_MASK) {
+    case FLEXCAN_MB_CODE_RX_FULL:
+        QEMU_FALLTHROUGH;
+    case FLEXCAN_MB_CODE_RX_OVERRUN:
+        QEMU_FALLTHROUGH;
+    case FLEXCAN_MB_CODE_RX_RANSWER:
+        /* continue */
+        trace_flexcan_mb_lock(s, mbidx, 1);
+        break;
+    default:
+        trace_flexcan_mb_lock(s, mbidx, 0);
+        return;
+    }
+

hw/net/can/flexcan.c line 909

Quoted 10 lines
hw/net/can/flexcan.c new file +10 -0
+    s->locked_mbidx = mbidx;
+}
+
+static void flexcan_mb_unlock(FlexcanState *s)
+{
+    if (s->locked_mbidx == FLEXCAN_NO_MB_LOCKED) {
+        return;
+    }
+
+    int locked_mbidx = s->locked_mbidx;

All variable declarations should be at the top of a block. I'm not sure why but this is a common request, see e.g. https://lore.kernel.org/qemu-devel/[email protected]/ .

Quoted 277 lines
hw/net/can/flexcan.c new file +277 -0
+    assert(locked_mbidx >= 0 && locked_mbidx < FLEXCAN_MAILBOX_COUNT);
+    FlexcanRegsMessageBuffer *locked_mb = &s->regs.mbs[locked_mbidx];
+    s->locked_mbidx = FLEXCAN_NO_MB_LOCKED;
+
+    if (locked_mb >= flexcan_get_first_message_buffer(s) &&
+        locked_mb <= flexcan_get_last_enabled_mailbox(s)
+    ) {
+        /* mark the message buffer as serviced */
+        locked_mb->can_ctrl &= ~FLEXCAN_MB_CNT_NOT_SRV;
+    }
+
+    /* try move in from SMB */
+    bool has_pending_frame = locked_mbidx == s->smb_target_mbidx;
+    trace_flexcan_mb_unlock(s, locked_mbidx,
+                            has_pending_frame ? " PENDING FRAME IN SMB" : "");
+
+    /* todo: in low-power modes, this should be postponed until exit */
+    if (has_pending_frame) {
+        FlexcanRegsMessageBuffer *target_mb = &s->regs.mbs[locked_mbidx];
+        memcpy(target_mb, &s->regs.rx_smb0, sizeof(FlexcanRegsMessageBuffer));
+
+        memset(&s->regs.rx_smb0, 0, sizeof(FlexcanRegsMessageBuffer));
+        s->locked_mbidx = FLEXCAN_SMB_EMPTY;
+
+        flexcan_irq_iflag_set(s, locked_mbidx);
+    }
+}
+
+bool flexcan_can_receive(CanBusClientState *client)
+{
+    FlexcanState *s = container_of(client, FlexcanState, bus_client);
+    return !(s->regs.mcr & FLEXCAN_MCR_NOT_RDY);
+}
+
+/* --------- RX FIFO ---------- */
+
+/**
+ * flexcan_fifo_pop() - Pop message from FIFO and update IRQs
+ * @s: FlexCAN device pointer
+ *
+ * Does not require the queue to be non-empty.
+ */
+static void flexcan_fifo_pop(FlexcanState *s)
+{
+    if (s->regs.fifo.mb_back.can_ctrl != 0) {
+        /* move queue elements forward */
+        memmove(&s->regs.fifo.mb_back, &s->regs.fifo.mbs_queue[0],
+                sizeof(s->regs.fifo.mbs_queue));
+
+        /* clear the first-in slot */
+        memset(&s->regs.mbs[FLEXCAN_FIFO_DEPTH - 1], 0,
+               sizeof(FlexcanRegsMessageBuffer));
+
+        trace_flexcan_fifo_pop(s, 1, s->regs.fifo.mb_back.can_ctrl != 0);
+    } else {
+        trace_flexcan_fifo_pop(s, 0, 0);
+    }
+
+    if (s->regs.fifo.mb_back.can_ctrl != 0) {
+        flexcan_irq_iflag_set(s, I_FIFO_AVAILABLE);
+    } else {
+        flexcan_irq_iflag_clear(s, I_FIFO_AVAILABLE);
+    }
+}
+
+/**
+ * flexcan_fifo_find_free_slot() - Find the first free slot in the FIFO
+ * @s: FlexCAN device pointer
+ *
+ * Return: Pointer to the first free slot in the FIFO,
+ *         or NULL if the queue is full.
+ */
+static FlexcanRegsMessageBuffer *flexcan_fifo_find_free_slot(FlexcanState *s)
+{
+    for (int i = 0; i < FLEXCAN_FIFO_DEPTH; i++) {
+        FlexcanRegsMessageBuffer *mb = &s->regs.mbs[i];
+        if (mb->can_ctrl == 0) {
+            return mb;
+        }
+    }
+    return NULL;
+}
+
+/**
+ * flexcan_fifo_push() - Update FIFO IRQs after frame move-in
+ * @s: FlexCAN device pointer
+ * @slot: Target FIFO slot
+ *
+ * The usage is as follows:
+ * 1. Get free slot pointer using flexcan_fifo_find_free_slot()
+ * 2. Move the frame in if not NULL
+ * 3. Call flexcan_fifo_push() regardless of the NULL pointer
+ */
+static void flexcan_fifo_push(FlexcanState *s, FlexcanRegsMessageBuffer *slot)
+{
+    if (slot) {
+        int n_occupied = slot - s->regs.mbs;
+        if (n_occupied == 4) { /* 4 means the 5th slot was filled in */
+            /*
+             * fifo occupancy increased from 4 to 5,
+             * raising FIFO_WARN interrupt
+             */
+            flexcan_irq_iflag_set(s, I_FIFO_WARN);
+        }
+        flexcan_irq_iflag_set(s, I_FIFO_AVAILABLE);
+
+        trace_flexcan_fifo_push(s, n_occupied);
+    } else {
+        flexcan_irq_iflag_set(s, I_FIFO_OVERFLOW);
+
+        trace_flexcan_fifo_push(s, -1);
+    }
+}
+
+static int flexcan_fifo_rx(FlexcanState *s, const qemu_can_frame *buf)
+{
+    /* todo: filtering. return FLEXCAN_FIFO_RX_RETRY if filtered out */
+    if ((s->regs.mcr & FLEXCAN_MCR_IDAM_MASK) == FLEXCAN_MCR_IDAM_D) {
+        /* all frames rejected */
+        return FLEXCAN_RX_SEARCH_RETRY;
+    }
+
+    /* push message to queue if not full */
+    FlexcanRegsMessageBuffer *slot = flexcan_fifo_find_free_slot(s);
+    if (slot) {
+        flexcan_mb_move_in(s, buf, slot);
+    }
+    flexcan_fifo_push(s, slot);
+
+    return slot ? FLEXCAN_RX_SEARCH_ACCEPT : FLEXCAN_RX_SEARCH_DROPPED;
+}
+
+/* --------- RX message buffer ---------- */
+
+/**
+ * flexcan_mb_rx_check_mb() - Check if a message buffer matches a received frame
+ * @s: FlexCAN device pointer
+ * @buf: Frame to be received from CAN subsystem
+ * @mbid: Target mailbox index. The mailbox must be a valid message buffer.
+ *
+ * Return: FLEXCAN_CHECK_MB_NIL if the message buffer does not match.
+ *         FLEXCAN_CHECK_MB_MATCH if the message buffer matches the received
+ *                                frame and is free-to-receive,
+ *         FLEXCAN_CHECK_MB_MATCH_LOCKED if the message buffer matches,
+ *                                       but is locked,
+ *         FLEXCAN_CHECK_MB_MATCH_NON_FREE if the message buffer matches,
+ *                                         but is not free-to-receive
+ *                                         for some other reason.
+ */
+static int flexcan_mb_rx_check_mb(FlexcanState *s, const qemu_can_frame *buf,
+                                  int mbid)
+{
+    FlexcanRegsMessageBuffer *mb = &s->regs.mbs[mbid];
+    const bool is_rtr = !!(buf->can_id & QEMU_CAN_RTR_FLAG);
+    const bool is_serviced = !(mb->can_ctrl & FLEXCAN_MB_CNT_NOT_SRV);
+    const bool is_locked = s->locked_mbidx == mbid;
+
+    bool is_free_to_receive = false;
+    bool is_matched = false;
+
+    switch (mb->can_ctrl & FLEXCAN_MB_CODE_MASK) {
+    case FLEXCAN_MB_CODE_RX_RANSWER:
+        if (is_rtr && !(s->regs.ctrl2 & FLEXCAN_CTRL2_RRS)) {
+            /* todo: do the actual matching/filtering and RTR answer */
+            is_matched = true;
+        }
+        break;
+    case FLEXCAN_MB_CODE_RX_FULL:
+        QEMU_FALLTHROUGH;
+    case FLEXCAN_MB_CODE_RX_OVERRUN:
+        is_free_to_receive = is_serviced;
+        /* todo: do the actual matching/filtering */
+        is_matched = true;
+        break;
+    case FLEXCAN_MB_CODE_RX_EMPTY:
+        is_free_to_receive = true;
+        /* todo: do the actual matching/filtering */
+        is_matched = true;
+        break;
+    default:
+    break;
+    }
+
+    if (trace_event_get_state_backends(TRACE_FLEXCAN_MB_RX_CHECK_MB)) {
+        char code_str_buf[FLEXCAN_DBG_BUF_LEN] = { 0 };
+        const char *code_str = flexcan_dbg_mb_code(mb->can_ctrl, code_str_buf);
+        trace_flexcan_mb_rx_check_mb(s, mbid, code_str, is_matched,
+                                     is_free_to_receive, is_serviced,
+                                     is_locked);
+    }
+
+    if (is_matched && is_free_to_receive && !is_locked) {
+        return FLEXCAN_CHECK_MB_MATCH;
+    } else if (is_matched && !is_locked) {
+        return FLEXCAN_CHECK_MB_MATCH_NON_FREE;
+    } else if (is_matched) {
+        return FLEXCAN_CHECK_MB_MATCH_LOCKED;
+    } else {
+        return FLEXCAN_CHECK_MB_NIL;
+    }
+}
+static int flexcan_mb_rx(FlexcanState *s, const qemu_can_frame *buf)
+{
+    int last_not_free_to_receive_mbid = -1;
+    bool last_not_free_to_receive_locked = false;
+
+    FlexcanRegsMessageBuffer *first_mb = flexcan_get_first_message_buffer(s);
+    FlexcanRegsMessageBuffer *last_mb = flexcan_get_last_enabled_mailbox(s);
+    for (FlexcanRegsMessageBuffer *mb = first_mb;
+         mb <= last_mb; mb++) {
+        int mbid = mb - s->regs.mbs;
+        int r = flexcan_mb_rx_check_mb(s, buf, mbid);
+        if (r == FLEXCAN_CHECK_MB_MATCH) {
+            flexcan_mb_move_in(s, buf, mb);
+            flexcan_irq_iflag_set(s, mbid);
+            return FLEXCAN_RX_SEARCH_ACCEPT;
+        } else if (r == FLEXCAN_CHECK_MB_MATCH_NON_FREE) {
+            last_not_free_to_receive_mbid = mbid;
+            last_not_free_to_receive_locked = false;
+        } else if (r == FLEXCAN_CHECK_MB_MATCH_LOCKED) {
+            /*
+             * message buffer is locked,
+             * we can move in the message after it's unlocked
+             */
+            last_not_free_to_receive_mbid = mbid;
+            last_not_free_to_receive_locked = true;
+        }
+    }
+
+    if (last_not_free_to_receive_mbid >= -1) {
+        if (last_not_free_to_receive_locked) {
+            /*
+             * copy to temporary mailbox (SMB)
+             * it will be moved in when the mailbox is unlocked
+             */
+            s->regs.rx_smb0.can_ctrl =
+                s->regs.mbs[last_not_free_to_receive_mbid].can_id;
+            flexcan_mb_move_in(s, buf, &s->regs.rx_smb0);
+            s->smb_target_mbidx = last_not_free_to_receive_mbid;
+            return FLEXCAN_RX_SEARCH_ACCEPT;
+        } else if (s->regs.mcr & FLEXCAN_MCR_IRMQ) {
+            flexcan_mb_move_in(s, buf,
+                               &s->regs.mbs[last_not_free_to_receive_mbid]);
+            flexcan_irq_iflag_set(s, last_not_free_to_receive_mbid);
+            return FLEXCAN_RX_SEARCH_ACCEPT;
+        }
+    }
+
+    return FLEXCAN_RX_SEARCH_RETRY;
+}
+
+ssize_t flexcan_receive(CanBusClientState *client, const qemu_can_frame *frames,
+                        size_t frames_cnt)
+{
+    FlexcanState *s = container_of(client, FlexcanState, bus_client);
+    trace_flexcan_receive(s, frames_cnt);
+
+    if (frames_cnt <= 0) {
+        g_autofree char *path = object_get_canonical_path(OBJECT(s));
+        qemu_log_mask(LOG_GUEST_ERROR, "%s: Error in the data received.\n",
+                      path);
+        return 0;
+    }
+
+    /* clear the SMB, as it would be overriden in hardware */
+    memset(&s->regs.rx_smb0, 0, sizeof(FlexcanRegsMessageBuffer));
+    s->smb_target_mbidx = FLEXCAN_SMB_EMPTY;
+
+    for (size_t i = 0; i < frames_cnt; i++) {
+        int r;
+        const qemu_can_frame *frame = &frames[i];
+        if (frame->can_id & QEMU_CAN_ERR_FLAG) {
+            /* todo: error frame handling */
+            continue;
+        } else if (frame->flags & QEMU_CAN_FRMF_TYPE_FD) {
+            /* CAN FD supported only in later FlexCAN version */
+            continue;

hw/net/can/flexcan.c line 1196

Quoted 10 lines
hw/net/can/flexcan.c new file +10 -0
+        }
+
+        /* todo: this order logic is not complete and needs further work */
+        if (s->regs.mcr & FLEXCAN_MCR_FEN &&
+            s->regs.ctrl2 & FLEXCAN_CTRL2_MRP) {
+            r = flexcan_mb_rx(s, frame);
+            if (r != FLEXCAN_RX_SEARCH_RETRY) {
+                continue;
+            }
+            flexcan_fifo_rx(s, frame);

Why not invert the condition and move the call inside the if statement, eliminating the "continue;"?

hw/net/can/flexcan.c line 1202

Quoted 6 lines
hw/net/can/flexcan.c new file +6 -0
+        } else if (s->regs.mcr & FLEXCAN_MCR_FEN) {
+            r = flexcan_fifo_rx(s, frame);
+            if (r != FLEXCAN_RX_SEARCH_RETRY) {
+                continue;
+            }
+            flexcan_mb_rx(s, frame);

Same here.

Quoted 60 lines
hw/net/can/flexcan.c new file +60 -0
+        } else {
+            flexcan_mb_rx(s, frame);
+        }
+    }
+
+    flexcan_irq_update(s);
+    return 1;
+}
+
+/* ========== I/O handling ========== */
+static void flexcan_reg_write(FlexcanState *s, hwaddr addr, uint32_t val)
+{
+    uint32_t write_mask = ((const uint32_t *)
+        &flexcan_regs_write_mask)[addr / 4];
+    uint32_t old_value = s->regs_raw[addr / 4];
+
+    /*
+     * 0 for bits that can "only be written in Freeze mode as it is blocked
+     * by hardware in other modes"
+     */
+    const uint32_t freeze_mask_mcr = 0xDF54CC80;
+    const uint32_t freeze_mask_ctrl1 = 0x0000E740;
+
+    switch (addr) {
+    case offsetof(FlexcanRegs, mcr):
+        if (!(s->regs.mcr & FLEXCAN_MCR_FRZ_ACK)) {
+            write_mask &= freeze_mask_mcr;
+        }
+        s->regs.mcr = (val & write_mask) | (old_value & ~write_mask);
+        flexcan_set_mcr(s, old_value);
+        break;
+    case offsetof(FlexcanRegs, ctrl):
+        if (!(s->regs.mcr & FLEXCAN_MCR_FRZ_ACK)) {
+            write_mask &= freeze_mask_ctrl1;
+        }
+        s->regs.ctrl = (val & write_mask) | (old_value & ~write_mask);
+        break;
+    case offsetof(FlexcanRegs, iflag1):
+        s->regs.iflag1 &= ~val;
+        if ((s->regs.mcr & FLEXCAN_MCR_FEN) &&
+            (val & FLEXCAN_IFLAG_RX_FIFO_AVAILABLE)) {
+            flexcan_fifo_pop(s);
+        }
+        break;
+    case offsetof(FlexcanRegs, iflag2):
+        s->regs.iflag2 &= ~val;
+        break;
+    case offsetof(FlexcanRegs, ctrl2):
+        QEMU_FALLTHROUGH;
+    case offsetof(FlexcanRegs, ecr):
+        QEMU_FALLTHROUGH;
+    case offsetof(FlexcanRegs, rxmgmask):
+        QEMU_FALLTHROUGH;
+    case offsetof(FlexcanRegs, rx14mask):
+        QEMU_FALLTHROUGH;
+    case offsetof(FlexcanRegs, rx15mask):
+        QEMU_FALLTHROUGH;
+    case offsetof(FlexcanRegs, rxfgmask):
+        /* these registers can only be written in freeze mode */
+        if (!(s->regs.mcr & FLEXCAN_MCR_FRZ_ACK)) {

hw/net/can/flexcan.c line 1272

Quoted 10 lines
hw/net/can/flexcan.c new file +10 -0
+            break;
+        }
+        QEMU_FALLTHROUGH;
+    default:
+        /* RXIMRn can only be written in freeze mode */
+        if (!(s->regs.mcr & FLEXCAN_MCR_FRZ_ACK) &&
+            addr >= offsetof(FlexcanRegs, rximr) &&
+            addr < offsetof(FlexcanRegs, _reserved5)) {
+            break;
+        }

I think it should be possible to eliminate this if statement by adding another case before the default:
"case offsetof(FlexcanRegs, rximr[0]) ... offsetof(FlexcanRegs, rximr[63]):"

Quoted 23 lines
hw/net/can/flexcan.c new file +23 -0
+
+        s->regs_raw[addr / 4] = (val & write_mask) | (old_value & ~write_mask);
+
+        if (addr >= offsetof(FlexcanRegs, mb) &&
+            addr < offsetof(FlexcanRegs, _reserved4)) {
+            /* access to mailbox */
+            int mbid = (addr - offsetof(FlexcanRegs, mb)) /
+                            sizeof(FlexcanRegsMessageBuffer);
+
+            if (s->locked_mbidx == mbid) {
+                flexcan_mb_unlock(s);
+            }
+
+            /* check for invalid writes into FIFO region */
+            if (s->regs.mcr & FLEXCAN_MCR_FEN && mbid < FLEXCAN_FIFO_DEPTH) {
+                g_autofree char *path = object_get_canonical_path(OBJECT(s));
+                qemu_log_mask(LOG_GUEST_ERROR,
+                              "%s: Invalid write to Rx-FIFO structure", path);
+                return;
+            }
+
+            /* run mailbox processing function on write to control word */
+            if ((addr & 0xF) == 0) {

hw/net/can/flexcan.c line 1305

Quoted 10 lines
hw/net/can/flexcan.c new file +10 -0
+                flexcan_mb_write(s, mbid);
+            }
+        }
+        break;
+    }
+
+    flexcan_irq_update(s);
+}
+
+void flexcan_mem_write(void *obj, hwaddr addr, uint64_t val, unsigned size)

The convention in QEMU is usually to name these void pointers "opaque". The name "obj" suggests a QOM Object * and is therefore midleading. Please rename to "opaque".

Same for the other flexcan_ops callbacks.

hw/net/can/flexcan.c line 1307

Quoted 2 lines
hw/net/can/flexcan.c new file +2 -0
+{
+    FlexcanState *s = CAN_FLEXCAN(obj);

C actually handles void pointers special, so you don't need to cast here.

Same for the other flexcan_ops callbacks.

Quoted 54 lines
hw/net/can/flexcan.c new file +54 -0
+    flexcan_trace_mem_op(s, addr, val, size, true);
+
+    if (addr < FLEXCAN_ADDR_SPC_END) {
+        flexcan_reg_write(s, addr, (uint32_t)val);
+    } else {
+        DPRINTF("warn: write outside of defined address space\n");
+    }
+}
+uint64_t flexcan_mem_read(void *obj, hwaddr addr, unsigned size)
+{
+    FlexcanState *s = CAN_FLEXCAN(obj);
+
+    if (addr < FLEXCAN_ADDR_SPC_END) {
+        uint32_t rv = s->regs_raw[addr >> 2];
+
+        if (addr >= offsetof(FlexcanRegs, mb) &&
+            addr < offsetof(FlexcanRegs, _reserved4)) {
+            /* reading from mailbox */
+            hwaddr offset = addr - offsetof(FlexcanRegs, mb);
+            int mbid = offset / sizeof(FlexcanRegsMessageBuffer);
+
+            if (addr % 16 == 0 && s->locked_mbidx != mbid) {
+                /* reading control word locks the mailbox */
+                flexcan_mb_unlock(s);
+                flexcan_mb_lock(s, mbid);
+                flexcan_irq_update(s);
+                rv = s->regs.mbs[mbid].can_ctrl & ~FLEXCAN_MB_CNT_NOT_SRV;
+            }
+        } else if (addr == offsetof(FlexcanRegs, timer)) {
+            flexcan_mb_unlock(s);
+            flexcan_irq_update(s);
+            rv = flexcan_get_timestamp(s, false);
+        }
+
+        flexcan_trace_mem_op(s, addr, rv, size, false);
+        return rv;
+    } else {
+        g_autofree char *path = object_get_canonical_path(OBJECT(s));
+        qemu_log_mask(LOG_GUEST_ERROR,
+                      "%s: Invalid write outside valid I/O space", path);
+
+        flexcan_trace_mem_op(s, addr, 0, size, false);
+        return 0;
+    }
+}
+bool flexcan_mem_accepts(void *obj, hwaddr addr,
+                                unsigned size, bool is_write,
+                                MemTxAttrs attrs)
+{
+    FlexcanState *s = CAN_FLEXCAN(obj);
+
+    if ((s->regs.ctrl2 & FLEXCAN_CTRL2_WRMFRZ) &&
+        (s->regs.mcr & FLEXCAN_MCR_FRZ_ACK)) {
+        /* unrestricted access to FlexCAN memory in freeze mode */

hw/net/can/flexcan.c line 1371

Quoted 10 lines
hw/net/can/flexcan.c new file +10 -0
+        return true;
+    } else if (attrs.user && (s->regs.mcr & FLEXCAN_MCR_SUPV)) {
+        goto denied;
+    } else if (is_write && attrs.user && addr < 4) {
+        /* illegal user write to MCR */
+        goto denied;
+    } else if (addr >= FLEXCAN_ADDR_SPC_END) {
+        /* illegal write to non-existent register */
+        goto denied;
+    }

The gotos can be avoided by turning the comments into guest error logs and returning early. Bonus points for avoiding else after return for readability reasons: https://clang.llvm.org/extra/clang-tidy/checks/readability/else-after-return.html

Quoted 19 lines
hw/net/can/flexcan.c new file +19 -0
+
+    return true;
+denied:
+    trace_flexcan_mem_accepts(s, addr, size, is_write, !attrs.user);
+    return false;
+}
+
+static CanBusClientInfo flexcan_bus_client_info = {
+    .can_receive = flexcan_can_receive,
+    .receive = flexcan_receive,
+};
+
+static int flexcan_connect_to_bus(FlexcanState *s, CanBusState *bus)
+{
+    s->bus_client.info = &flexcan_bus_client_info;
+
+    if (can_bus_insert_client(bus, &s->bus_client) < 0) {
+        return -1;
+    }

hw/net/can/flexcan.c line 1400

Quoted 10 lines
hw/net/can/flexcan.c new file +10 -0
+    return 0;
+}
+
+void flexcan_init(Object *obj)
+{
+    FlexcanState *s = CAN_FLEXCAN(obj);
+    SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
+
+    USE(s);
+    USE(sbd);

No unused variables, please. And no "USE" macros which basically circumvent compiler warnings that are there for a purpose.

To avoid an empty method, you could actually perform the initialization of s->iomem here instead of in the realize method. This would also prepare for adding flexcan 3 support (see below).

Quoted 7 lines
hw/net/can/flexcan.c new file +7 -0
+}
+
+static void flexcan_realize(DeviceState *dev, Error **errp)
+{
+    FlexcanState *s = CAN_FLEXCAN(dev);
+
+    if (s->canbus) {

hw/net/can/flexcan.c line 1417

Quoted 10 lines
hw/net/can/flexcan.c new file +10 -0
+        if (flexcan_connect_to_bus(s, s->canbus) < 0) {
+            g_autofree char *path = object_get_canonical_path(OBJECT(s));
+
+            error_setg(errp, "%s: flexcan_connect_to_bus"
+                        " failed.", path);
+            return;
+        }
+    }
+
+    flexcan_reset_local_state(s);

QEMU's reset logic will invoke this method automatically before starting the machine, rendering this call redundant. Please remove.

hw/net/can/flexcan.c line 1420

Quoted 3 lines
hw/net/can/flexcan.c new file +3 -0
+
+    memory_region_init_io(
+        &s->iomem, OBJECT(dev), &flexcan_ops, s, TYPE_CAN_FLEXCAN, 0x4000

Instead of passing 0x4000 (which likely contains SoC-specific padding) as the size, you could pass FLEXCAN_ADDR_SPC_END instead. This avoids quite a few error cases in the code since you can eliminate all bounds checks against FLEXCAN_ADDR_SPC_END. In fact, you could eliminate FLEXCAN_ADDR_SPC_END entirely and pass "offsetof(FlexcanRegs, _reserved6)" as the size directly. If you then initialize s->iomem in the init method, like suggested above, we could later add a flexcan3 init method with a different size, keeping code churn at a minimum. See my imx8mp-flexcan branch as a working example.

Quoted 316 lines
hw/net/can/flexcan.c new file +49 -0
+    );
+    sysbus_init_mmio(SYS_BUS_DEVICE(dev), &s->iomem);
+    sysbus_init_irq(SYS_BUS_DEVICE(SYS_BUS_DEVICE(dev)), &s->irq);
+}
+
+static const VMStateDescription vmstate_can = {
+    .name = TYPE_CAN_FLEXCAN,
+    .version_id = 1,
+    .minimum_version_id = 1,
+    .fields = (const VMStateField[]) {
+        VMSTATE_INT64(timer_start, FlexcanState),
+        VMSTATE_UINT32_ARRAY(regs_raw, FlexcanState, sizeof(FlexcanRegs) / 4),
+        VMSTATE_INT32(locked_mbidx, FlexcanState),
+        VMSTATE_INT32(smb_target_mbidx, FlexcanState),
+        VMSTATE_END_OF_LIST(),
+    },
+};
+
+static const Property flexcan_properties[] = {
+    DEFINE_PROP_LINK("canbus", FlexcanState, canbus, TYPE_CAN_BUS,
+                     CanBusState *),
+};
+
+static void flexcan_class_init(ObjectClass *klass, const void *data)
+{
+    DeviceClass *dc = DEVICE_CLASS(klass);
+    ResettableClass *rc = RESETTABLE_CLASS(klass);
+
+    rc->phases.enter = flexcan_reset_enter;
+    rc->phases.hold = flexcan_reset_hold;
+    dc->realize = &flexcan_realize;
+    device_class_set_props(dc, flexcan_properties);
+    dc->vmsd = &vmstate_can;
+    dc->desc = "i.MX FLEXCAN Controller";
+}
+
+static const TypeInfo flexcan_info = {
+    .name          = TYPE_CAN_FLEXCAN,
+    .parent        = TYPE_SYS_BUS_DEVICE,
+    .instance_size = sizeof(FlexcanState),
+    .class_init    = flexcan_class_init,
+    .instance_init = flexcan_init,
+};
+
+static void can_register_types(void)
+{
+    type_register_static(&flexcan_info);
+}
+type_init(can_register_types)
hw/net/can/flexcan_regs.h new file +188 -0
new file mode 100644
index 0000000000..8e976b2c9e
@@ -0,0 +1,188 @@
+/*
+ * Field bitmasks and register structs definitions for FlexCAN
+ *
+ * This implementation is based on the following datasheet:
+ * i.MX 6Dual/6Quad Applications Processor Reference Manual
+ * Document Number: IMX6DQRM, Rev. 6, 05/2020
+ *
+ * Copyright (c) 2025 Matyas Bobek <[email protected]>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+#include "qemu/bitops.h"
+
+#ifndef HW_CAN_FLEXCAN_REGS_H
+#define HW_CAN_FLEXCAN_REGS_H
+
+#define FLEXCAN_GENMASK(h, l) (((~(uint32_t)0) >> (31 - (h) + (l))) << (l))
+
+/* FLEXCAN module configuration register (CANMCR) bits */
+#define FLEXCAN_MCR_MDIS                BIT(31)
+#define FLEXCAN_MCR_FRZ                 BIT(30)
+#define FLEXCAN_MCR_FEN                 BIT(29)
+#define FLEXCAN_MCR_HALT                BIT(28)
+#define FLEXCAN_MCR_NOT_RDY             BIT(27)
+#define FLEXCAN_MCR_WAK_MSK             BIT(26)
+#define FLEXCAN_MCR_SOFTRST             BIT(25)
+#define FLEXCAN_MCR_FRZ_ACK             BIT(24)
+#define FLEXCAN_MCR_SUPV                BIT(23)
+#define FLEXCAN_MCR_SLF_WAK             BIT(22)
+#define FLEXCAN_MCR_WRN_EN              BIT(21)
+#define FLEXCAN_MCR_LPM_ACK             BIT(20)
+#define FLEXCAN_MCR_WAK_SRC             BIT(19)
+#define FLEXCAN_MCR_DOZE                BIT(18)
+#define FLEXCAN_MCR_SRX_DIS             BIT(17)
+#define FLEXCAN_MCR_IRMQ                BIT(16)
+#define FLEXCAN_MCR_LPRIO_EN            BIT(13)
+#define FLEXCAN_MCR_AEN                 BIT(12)
+#define FLEXCAN_MCR_FDEN                BIT(11)
+#define FLEXCAN_MCR_MAXMB(x)            ((x) & 0x7f)
+#define FLEXCAN_MCR_IDAM_A              (0x0 << 8)
+#define FLEXCAN_MCR_IDAM_B              (0x1 << 8)
+#define FLEXCAN_MCR_IDAM_C              (0x2 << 8)
+#define FLEXCAN_MCR_IDAM_D              (0x3 << 8)
+#define FLEXCAN_MCR_IDAM_MASK           (0x3 << 8)
+
+/* FLEXCAN control register (CANCTRL) bits */
+#define FLEXCAN_CTRL_PRESDIV(x)         (((x) & 0xFF) << 24)
+#define FLEXCAN_CTRL_PRESDIV_MASK       FLEXCAN_CTRL_PRESDIV(UINT32_MAX)
+#define FLEXCAN_CTRL_RJW(x)             (((x) & 0x03) << 22)
+#define FLEXCAN_CTRL_RJW_MASK           FLEXCAN_CTRL_RJW(UINT32_MAX)
+#define FLEXCAN_CTRL_PSEG1(x)           (((x) & 0x07) << 19)
+#define FLEXCAN_CTRL_PSEG1_MASK         FLEXCAN_CTRL_PSEG1(UINT32_MAX)
+#define FLEXCAN_CTRL_PSEG2(x)           (((x) & 0x07) << 16)
+#define FLEXCAN_CTRL_PSEG2_MASK         FLEXCAN_CTRL_PSEG2(UINT32_MAX)
+#define FLEXCAN_CTRL_BOFF_MSK           BIT(15)
+#define FLEXCAN_CTRL_ERR_MSK            BIT(14)
+#define FLEXCAN_CTRL_CLK_SRC            BIT(13)
+#define FLEXCAN_CTRL_LPB                BIT(12)
+#define FLEXCAN_CTRL_TWRN_MSK           BIT(11)
+#define FLEXCAN_CTRL_RWRN_MSK           BIT(10)
+#define FLEXCAN_CTRL_SMP                BIT(7)
+#define FLEXCAN_CTRL_BOFF_REC           BIT(6)
+#define FLEXCAN_CTRL_TSYN               BIT(5)
+#define FLEXCAN_CTRL_LBUF               BIT(4)
+#define FLEXCAN_CTRL_LOM                BIT(3)
+#define FLEXCAN_CTRL_PROPSEG(x)         ((x) & 0x07)
+#define FLEXCAN_CTRL_PROPSEG_MASK       FLEXCAN_CTRL_PROPSEG(UINT32_MAX)
+#define FLEXCAN_CTRL_ERR_BUS            (FLEXCAN_CTRL_ERR_MSK)
+#define FLEXCAN_CTRL_ERR_STATE \
+        (FLEXCAN_CTRL_TWRN_MSK | FLEXCAN_CTRL_RWRN_MSK | \
+         FLEXCAN_CTRL_BOFF_MSK)
+#define FLEXCAN_CTRL_ERR_ALL \
+        (FLEXCAN_CTRL_ERR_BUS | FLEXCAN_CTRL_ERR_STATE)
+
+/* FLEXCAN control register 2 (CTRL2) bits */
+#define FLEXCAN_CTRL2_ECRWRE            BIT(29)
+#define FLEXCAN_CTRL2_WRMFRZ            BIT(28)
+#define FLEXCAN_CTRL2_RFFN(x)           (((x) & 0x0f) << 24)
+#define FLEXCAN_CTRL2_TASD(x)           (((x) & 0x1f) << 19)
+#define FLEXCAN_CTRL2_MRP               BIT(18)
+#define FLEXCAN_CTRL2_RRS               BIT(17)
+#define FLEXCAN_CTRL2_EACEN             BIT(16)
+#define FLEXCAN_CTRL2_ISOCANFDEN        BIT(12)
+
+/* FLEXCAN memory error control register (MECR) bits */
+#define FLEXCAN_MECR_ECRWRDIS           BIT(31)
+#define FLEXCAN_MECR_HANCEI_MSK         BIT(19)
+#define FLEXCAN_MECR_FANCEI_MSK         BIT(18)
+#define FLEXCAN_MECR_CEI_MSK            BIT(16)
+#define FLEXCAN_MECR_HAERRIE            BIT(15)
+#define FLEXCAN_MECR_FAERRIE            BIT(14)
+#define FLEXCAN_MECR_EXTERRIE           BIT(13)
+#define FLEXCAN_MECR_RERRDIS            BIT(9)
+#define FLEXCAN_MECR_ECCDIS             BIT(8)
+#define FLEXCAN_MECR_NCEFAFRZ           BIT(7)
+
+/* FLEXCAN error and status register (ESR) bits */
+#define FLEXCAN_ESR_SYNCH               BIT(18)
+#define FLEXCAN_ESR_TWRN_INT            BIT(17)
+#define FLEXCAN_ESR_RWRN_INT            BIT(16)
+#define FLEXCAN_ESR_BIT1_ERR            BIT(15)
+#define FLEXCAN_ESR_BIT0_ERR            BIT(14)
+#define FLEXCAN_ESR_ACK_ERR             BIT(13)
+#define FLEXCAN_ESR_CRC_ERR             BIT(12)
+#define FLEXCAN_ESR_FRM_ERR             BIT(11)
+#define FLEXCAN_ESR_STF_ERR             BIT(10)
+#define FLEXCAN_ESR_TX_WRN              BIT(9)
+#define FLEXCAN_ESR_RX_WRN              BIT(8)
+#define FLEXCAN_ESR_IDLE                BIT(7)
+#define FLEXCAN_ESR_BOFF_INT            BIT(2)
+#define FLEXCAN_ESR_ERR_INT             BIT(1)
+#define FLEXCAN_ESR_WAK_INT             BIT(0)
+
+/* FLEXCAN Bit Timing register (CBT) bits */
+#define FLEXCAN_CBT_BTF                 BIT(31)
+#define FLEXCAN_CBT_EPRESDIV_MASK       FLEXCAN_GENMASK(30, 21)
+#define FLEXCAN_CBT_ERJW_MASK           FLEXCAN_GENMASK(20, 16)
+#define FLEXCAN_CBT_EPROPSEG_MASK       FLEXCAN_GENMASK(15, 10)
+#define FLEXCAN_CBT_EPSEG1_MASK         FLEXCAN_GENMASK(9, 5)
+#define FLEXCAN_CBT_EPSEG2_MASK         FLEXCAN_GENMASK(4, 0)
+
+/* FLEXCAN FD control register (FDCTRL) bits */
+#define FLEXCAN_FDCTRL_FDRATE           BIT(31)
+#define FLEXCAN_FDCTRL_MBDSR1           FLEXCAN_GENMASK(20, 19)
+#define FLEXCAN_FDCTRL_MBDSR0           FLEXCAN_GENMASK(17, 16)
+#define FLEXCAN_FDCTRL_MBDSR_8          0x0
+#define FLEXCAN_FDCTRL_MBDSR_12         0x1
+#define FLEXCAN_FDCTRL_MBDSR_32         0x2
+#define FLEXCAN_FDCTRL_MBDSR_64         0x3
+#define FLEXCAN_FDCTRL_TDCEN            BIT(15)
+#define FLEXCAN_FDCTRL_TDCFAIL          BIT(14)
+#define FLEXCAN_FDCTRL_TDCOFF           FLEXCAN_GENMASK(12, 8)
+#define FLEXCAN_FDCTRL_TDCVAL           FLEXCAN_GENMASK(5, 0)
+
+/* FLEXCAN FD Bit Timing register (FDCBT) bits */
+#define FLEXCAN_FDCBT_FPRESDIV_MASK     FLEXCAN_GENMASK(29, 20)
+#define FLEXCAN_FDCBT_FRJW_MASK         FLEXCAN_GENMASK(18, 16)
+#define FLEXCAN_FDCBT_FPROPSEG_MASK     FLEXCAN_GENMASK(14, 10)
+#define FLEXCAN_FDCBT_FPSEG1_MASK       FLEXCAN_GENMASK(7, 5)
+#define FLEXCAN_FDCBT_FPSEG2_MASK       FLEXCAN_GENMASK(2, 0)
+
+/* FLEXCAN CRC Register (CRCR) bits */
+#define FLEXCAN_CRCR_MBCRC_MASK         FLEXCAN_GENMASK(22, 16)
+#define FLEXCAN_CRCR_MBCRC(x)           (((x) & FLEXCAN_CRCR_MBCRC_MASK) << 16)
+#define FLEXCAN_CRCR_TXCRC_MASK         FLEXCAN_GENMASK(14, 0)
+#define FLEXCAN_CRCR_TXCRC(x)           ((x) & FLEXCAN_CRCR_TXCRC_MASK)
+
+/* FLEXCAN interrupt flag register (IFLAG) bits */
+/* Errata ERR005829 step7: Reserve first valid MB */
+#define I_FIFO_OVERFLOW  7
+#define I_FIFO_WARN      6
+#define I_FIFO_AVAILABLE 5
+
+#define FLEXCAN_TX_MB_RESERVED_RX_FIFO  8
+#define FLEXCAN_TX_MB_RESERVED_RX_MAILBOX       0
+#define FLEXCAN_RX_MB_RX_MAILBOX_FIRST  (FLEXCAN_TX_MB_RESERVED_RX_MAILBOX + 1)
+#define FLEXCAN_IFLAG_MB(x)             BIT_ULL(x)
+#define FLEXCAN_IFLAG_RX_FIFO_OVERFLOW  BIT(I_FIFO_OVERFLOW)
+#define FLEXCAN_IFLAG_RX_FIFO_WARN      BIT(I_FIFO_WARN)
+#define FLEXCAN_IFLAG_RX_FIFO_AVAILABLE BIT(I_FIFO_AVAILABLE)
+
+/* FLEXCAN message buffers */
+#define FLEXCAN_MB_CODE_RX_BUSY_BIT     (0x1 << 24)
+#define FLEXCAN_MB_CODE_RX_INACTIVE     (0x0 << 24)
+#define FLEXCAN_MB_CODE_RX_EMPTY        (0x4 << 24)
+#define FLEXCAN_MB_CODE_RX_FULL         (0x2 << 24)
+#define FLEXCAN_MB_CODE_RX_OVERRUN      (0x6 << 24)
+#define FLEXCAN_MB_CODE_RX_RANSWER      (0xa << 24)
+
+#define FLEXCAN_MB_CODE_TX_INACTIVE     (0x8 << 24)
+#define FLEXCAN_MB_CODE_TX_ABORT        (0x9 << 24)
+#define FLEXCAN_MB_CODE_TX_DATA         (0xc << 24)
+#define FLEXCAN_MB_CODE_TX_TANSWER      (0xe << 24)
+
+#define FLEXCAN_MB_CODE(x)              (((x) & 0xF) << 24)
+#define FLEXCAN_MB_CODE_MASK            FLEXCAN_MB_CODE(UINT32_MAX)
+
+#define FLEXCAN_MB_CNT_EDL              BIT(31)
+#define FLEXCAN_MB_CNT_BRS              BIT(30)
+#define FLEXCAN_MB_CNT_ESI              BIT(29)
+#define FLEXCAN_MB_CNT_SRR              BIT(22)
+#define FLEXCAN_MB_CNT_IDE              BIT(21)
+#define FLEXCAN_MB_CNT_RTR              BIT(20)
+#define FLEXCAN_MB_CNT_LENGTH(x)        (((x) & 0xF) << 16)
+#define FLEXCAN_MB_CNT_TIMESTAMP(x)     ((x) & 0xFFFF)
+#define FLEXCAN_MB_CNT_TIMESTAMP_MASK   FLEXCAN_MB_CNT_TIMESTAMP(UINT32_MAX)
+
+#endif
hw/net/can/meson.build +1 -0
index 7382344628..401afde2e4 100644
@@ -6,3 +6,4 @@ system_ss.add(when: 'CONFIG_CAN_CTUCANFD', if_true: files('ctucan_core.c'))
 system_ss.add(when: 'CONFIG_CAN_CTUCANFD_PCI', if_true: files('ctucan_pci.c'))
 system_ss.add(when: 'CONFIG_XLNX_ZYNQMP', if_true: files('xlnx-zynqmp-can.c'))
 system_ss.add(when: 'CONFIG_XLNX_VERSAL', if_true: files('xlnx-versal-canfd.c'))
+system_ss.add(when: 'CONFIG_CAN_FLEXCAN', if_true: files('flexcan.c'))
hw/net/can/trace-events +18 -0
index de64ac1b31..83f574e81c 100644
@@ -1,3 +1,21 @@
+# flexcan.c
+flexcan_irq_update(void *inst, uint32_t mb_irqs1, uint32_t mb_irqs2, int setting) "%p: irqs1 0x%08x irqs2 0x%08x request %i"
+flexcan_set_mcr(void* inst, const char *enabled, const char *freeze, const char *fifo, const char *rdy, const char *sync) "%p: %s %s %s %s %s"
+flexcan_mb_write(void *inst, int mbidx, const char *code, int is_mailbox, uint32_t ctrl, uint32_t id) "%p: mbidx %i code %s is_mailbox %i ctrl 0x%08x id 0x%08x"
+flexcan_mb_lock(void *inst, int mbidx, int had_rx_code) "%p: mbidx %i had_rx_code %i"
+flexcan_mb_unlock(void *inst, int mbidx, const char *pending_frame) "%p: mbidx %i%s"
+flexcan_fifo_pop(void *inst, int non_empty_before, int non_empty_after) "%p: non_empty before %i non_empty_after %i"
+flexcan_fifo_push(void *inst, int n_occupied) "%p: n_slots_occupied %i"
+flexcan_reset(void* inst) "%p: resetting"
+flexcan_mem_op(void *inst, const char *op, uint32_t v, int offset, const char *reg_name, int size) "%p: %s 0x%08x at offset %i register %s size %i"
+flexcan_mem_accepts(void *inst, int offset, int size, int is_write, int is_supv) "%p: denied access: offset %i size %i is_write %i is_supv %i"
+flexcan_get_timestamp(void *inst, int64_t time_elapsed_ms, uint32_t bitrate, uint64_t cycles, uint32_t shift, uint32_t timestamp) "%p: time_elapsed %" PRIi64 "ms bitrate %ub/s cycles %" PRIu64 " shift %u timestamp 0x%04x"
+flexcan_get_bitrate(void *inst, uint32_t pe_freq, uint32_t prediv, uint32_t s_freq, uint32_t tseg1, uint32_t tseg2, uint32_t quata_per_bit, uint32_t bitrate) "%p: pe_freq %uHz prescaler %u s_freq %uHz tseg1 %uq tseg2 %uq total %uq/b bitrate %ub/s"
+flexcan_timer_start(void *inst, uint32_t bitrate, uint32_t value) "%p: bitrate %ub/s value 0x%04x"
+flexcan_timer_stop(void *inst, uint32_t bitrate, uint32_t value) "%p: bitrate %ub/s value 0x%04x"
+flexcan_mb_rx_check_mb(void *inst, int mbidx, const char *code, int is_matched, int is_ftr, int is_serviced, int is_locked) "%p: checking mb %i code %s is_matched %i is_free_to_receive %i is_serviced %i is_locked %i"
+flexcan_receive(void *inst, size_t n_frames) "%p: received %zu frames"
+
 # xlnx-zynqmp-can.c

xlnx_can_update_irq(uint32_t isr, uint32_t ier, uint32_t irq) "ISR: 0x%08x IER: 0x%08x IRQ: 0x%08x" xlnx_can_reset(uint32_t val) "Resetting controller with value = 0x%08x"

include/hw/net/flexcan.h new file +32 -0
new file mode 100644
index 0000000000..eceb6a7920
@@ -0,0 +1,153 @@
+/*
+ * QEMU model of the NXP FLEXCAN device.
+ *
+ * Copyright (c) 2025 Matyas Bobek <[email protected]>
+ *
+ * Based on CTU CAN FD emulation implemented by Jan Charvat.
+ *
+ * This code is licensed under the GPL version 2 or later.  See
+ * the COPYING file in the top-level directory.
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#ifndef HW_CAN_FLEXCAN_H
+#define HW_CAN_FLEXCAN_H
+
+#include "hw/sysbus.h"
+#include "exec/hwaddr.h"
+#include "net/can_emu.h"
+#include "qom/object.h"
+#include "hw/misc/imx_ccm.h"
+
+#define FLEXCAN_FIFO_DEPTH 6
+#define FLEXCAN_MAILBOX_COUNT 64
+
+/* view of single message buffer registers */
+typedef struct FlexcanRegsMessageBuffer {
+    uint32_t can_ctrl;
+    uint32_t can_id;
+    uint32_t data[2];
+} FlexcanRegsMessageBuffer;
+

include/hw/net/flexcan.h line 42

Quoted 10 lines
include/hw/net/flexcan.h new file +10 -0
+/* RX FIFO view of message buffer registers */
+typedef struct FlexcanRegsRXFifo {
+    /* 6 message buffer deep queue, queue back first */
+    FlexcanRegsMessageBuffer mb_back;
+    FlexcanRegsMessageBuffer mbs_queue[5];
+
+    /* number of filter elements depends on ctrl2 | FLEXCAN_CTRL2_RFFN */
+    uint32_t                 filter_table_els[128];
+} FlexcanRegsRXFifo;
+

The following structure looks like being based on Linux. Does this affect the copyright of this file?

Shall we reuse constants like FLEXCAN_MAILBOX_COUNT and FLEXCAN_FIFO_DEPTH in the structure definition? Up to you.

Quoted 64 lines
include/hw/net/flexcan.h new file +64 -0
+/* Structure of the hardware registers */
+typedef struct FlexcanRegs {
+    uint32_t mcr;                /* 0x00 */
+    uint32_t ctrl;               /* 0x04 - not affected by soft reset */
+    uint32_t timer;              /* 0x08 */
+    uint32_t tcr;                /* 0x0C */
+    uint32_t rxmgmask;           /* 0x10 - not affected by soft reset */
+    uint32_t rx14mask;           /* 0x14 - not affected by soft reset */
+    uint32_t rx15mask;           /* 0x18 - not affected by soft reset */
+    uint32_t ecr;                /* 0x1C */
+    uint32_t esr;                /* 0x20 */
+    uint32_t imask2;             /* 0x24 */
+    uint32_t imask1;             /* 0x28 */
+    uint32_t iflag2;             /* 0x2C */
+    uint32_t iflag1;             /* 0x30 */
+    union {                      /* 0x34 */
+        uint32_t gfwr_mx28;      /* MX28, MX53 */
+        uint32_t ctrl2;          /* MX6, VF610 - not affected by soft reset */
+    };
+    uint32_t esr2;               /* 0x38 */
+    uint32_t imeur;              /* 0x3C, unused */
+    uint32_t lrfr;               /* 0x40, unused */
+    uint32_t crcr;               /* 0x44 */
+    uint32_t rxfgmask;           /* 0x48 */
+    uint32_t rxfir;              /* 0x4C - not affected by soft reset */
+    uint32_t cbt;                /* 0x50, unused - not affected by soft reset */
+    uint32_t _reserved2;         /* 0x54 */
+    uint32_t dbg1;               /* 0x58, unused */
+    uint32_t dbg2;               /* 0x5C, unused */
+    uint32_t _reserved3[8];      /* 0x60 */
+    union {                      /* 0x80 - not affected by soft reset */
+        uint32_t mb[256];
+        FlexcanRegsMessageBuffer mbs[64];
+        FlexcanRegsRXFifo fifo;
+    };
+    uint32_t _reserved4[256];    /* 0x480 */
+    uint32_t rximr[64];          /* 0x880 - not affected by soft reset */
+    uint32_t _reserved5[24];     /* 0x980 */
+    uint32_t gfwr_mx6;           /* 0x9E0 - MX6 */
+
+    /* the rest is unused except for SMB */
+    uint32_t _reserved6[39];     /* 0x9E4 */
+    uint32_t _rxfir[6];          /* 0xA80 */
+    uint32_t _reserved8[2];      /* 0xA98 */
+    uint32_t _rxmgmask;          /* 0xAA0 */
+    uint32_t _rxfgmask;          /* 0xAA4 */
+    uint32_t _rx14mask;          /* 0xAA8 */
+    uint32_t _rx15mask;          /* 0xAAC */
+    uint32_t tx_smb[4];          /* 0xAB0 */
+    union {                      /* 0xAC0, used for SMB emulation */
+        uint32_t rx_smb0_raw[4];
+        FlexcanRegsMessageBuffer rx_smb0;
+    };
+    uint32_t rx_smb1[4];         /* 0xAD0 */
+    uint32_t mecr;               /* 0xAE0 */
+    uint32_t erriar;             /* 0xAE4 */
+    uint32_t erridpr;            /* 0xAE8 */
+    uint32_t errippr;            /* 0xAEC */
+    uint32_t rerrar;             /* 0xAF0 */
+    uint32_t rerrdr;             /* 0xAF4 */
+    uint32_t rerrsynr;           /* 0xAF8 */
+    uint32_t errsr;              /* 0xAFC */
+    uint32_t _reserved7[64];     /* 0xB00 */
+    uint32_t fdctrl;             /* 0xC00 - not affected by soft reset */

include/hw/net/flexcan.h line 116

Quoted 10 lines
include/hw/net/flexcan.h new file +10 -0
+    uint32_t fdcbt;              /* 0xC04 - not affected by soft reset */
+    uint32_t fdcrc;              /* 0xC08 */
+    uint32_t _reserved9[199];    /* 0xC0C */
+    uint32_t tx_smb_fd[18];      /* 0xF28 */
+    uint32_t rx_smb0_fd[18];     /* 0xF70 */
+    uint32_t rx_smb1_fd[18];     /* 0xFB8 */
+} FlexcanRegs;
+
+typedef struct FlexcanState {
+    SysBusDevice        parent_obj;

Let's add a blank line here to visually separate the base class.

Quoted 11 lines
include/hw/net/flexcan.h new file +11 -0
+    MemoryRegion        iomem;
+    IMXCCMState        *ccm;
+    qemu_irq            irq;
+
+    CanBusState        *canbus;
+    CanBusClientState   bus_client;
+
+    union {
+        FlexcanRegs     regs;
+        uint32_t        regs_raw[sizeof(FlexcanRegs) / 4];
+    };

include/hw/net/flexcan.h line 137

Quoted 10 lines
include/hw/net/flexcan.h new file +10 -0
+    int64_t             timer_start;
+    uint64_t            last_rx_timer_cycles;
+    int32_t             locked_mbidx;
+    int32_t             smb_target_mbidx;
+    uint32_t            timer_freq;
+} FlexcanState;
+
+#define TYPE_CAN_FLEXCAN "flexcan"
+
+OBJECT_DECLARE_SIMPLE_TYPE(FlexcanState, CAN_FLEXCAN);

The folling symbols...

Quoted 4 lines
include/hw/net/flexcan.h new file +4 -0
+
+void flexcan_init(Object *obj);
+void flexcan_hardware_reset(FlexcanState *s);
+

include/hw/net/flexcan.h line 151

Quoted 10 lines
include/hw/net/flexcan.h new file +10 -0
+bool flexcan_can_receive(CanBusClientState *s);
+ssize_t flexcan_receive(CanBusClientState *s, const qemu_can_frame *frames,
+                        size_t frames_cnt);
+
+void flexcan_mem_write(void *obj, hwaddr addr, uint64_t val, unsigned size);
+uint64_t flexcan_mem_read(void *obj, hwaddr addr, unsigned size);
+bool flexcan_mem_accepts(void *obj, hwaddr addr, unsigned size, bool is_write,
+                         MemTxAttrs attrs);
+
+extern const VMStateDescription vmstate_flexcan;

... aren't used outside the .c file, so please keep them private. You may need to move the "flexcan_ops" definition in the .c file further down to avoid forward declarations.

Quoted 1 line

Quite a lot of comments from my side, I hope you don'f feel intimidated! I'm looking forward to seeing FlexCAN support upstreamed. If you have any questions please feel free to ask.

Ping

Any help needed?

Best regards,
Bernhard

Quoted 6 lines

Best regards,
Bernhard

Quoted 2 lines
include/hw/net/flexcan.h new file +2 -0
+
+#endif
#8 lore raw

Re: [PATCH v1 3/6] hw/net/can/flexcan: NXP FlexCAN core emulation

Dear Bernhard,

Quoted 2 lines · On Friday 16 of January 2026 10:53:24 Bernhard Beschow wrote:

Am 2. Januar 2026 14:53:42 UTC schrieb Bernhard Beschow <[email protected]>:

Quoted 1 line

Am 15. Dezember 2025 20:03:12 UTC schrieb "Matyáš Bobek"

<[email protected]>:

Quoted 12 lines
Quoted 12 lines
Quoted 12 lines

This commit adds the FlexCAN2 emulator implementation and plugs it into Meson and Kconfig.
FlexCAN2 version can be found in i.MX6 SoCs and others.

More information about the implementation can be found in [1].

[1]
http://dspace.cvut.cz/bitstream/handle/10467/122654/F3-BP-2025-Bobek-Mat
yas-BP_Bobek_FlexCAN_final_4.pdf

---

...

Quoted 10 lines
Quoted 3 lines

Quite a lot of comments from my side, I hope you don'f feel intimidated! I'm looking forward to seeing FlexCAN support upstreamed. If you have any questions please feel free to ask.

Ping

Any help needed?

Best regards,
Bernhard

thanks for thorough review of the patches and suggestions. We have discussed feedback with Matyas Bobek and he plans to follow suggestions. But the courses final project reviews week and then the exam period have started at our university after Christmas and he needs to submit more projects and prepare and pass exams, so this is the priority for now. He would inform you himself soon.

The another topic, there will be Linux Plumbers Conference and Open Source Summit in Prague in October 2026.
So if there is interrest there is option to meet and even to negotiate some room for some collocated meeting at the university. For me, embedded systems emulation in QEMU, CAN, CAN FD, motion control, realtime and RISC-V in education and real missions are of my interrest.
We can even try to submit something for conference when CfP is open.

Best wishes,

Pavel

Pavel Pisa
phone: +420 603531357
e-mail: [email protected]
Department of Control Engineering FEE CVUT Karlovo namesti 13, 121 35, Prague 2
university: http://control.fel.cvut.cz/
personal: http://cmp.felk.cvut.cz/~pisa
social: https://social.kernel.org/ppisa
projects: https://www.openhub.net/accounts/ppisa
CAN related:http://canbus.pages.fel.cvut.cz/ RISC-V education: https://comparch.edu.cvut.cz/
Open Technologies Research Education and Exchange Services
https://gitlab.fel.cvut.cz/otrees/org/-/wikis/home

#9 lore raw

PATCH 4/6 hw/arm: Plug FlexCAN into FSL_IMX6 and Sabrelite

In reply to Matyáš Bobek

FlexcanState is added to struct FslIMX6State like other peripherals.

Add two new machine properties to Sabrelite machine for linking the embedded FlexCAN instances to QEMU CAN buses by name. No other machine uses FslIMX6State.

---
 hw/arm/Kconfig            |  1 +
 hw/arm/fsl-imx6.c         | 29 +++++++++++++++++++++++++++++
 hw/arm/sabrelite.c        | 18 +++++++++++++++++-
 include/hw/arm/fsl-imx6.h |  7 +++++++
 4 files changed, 54 insertions(+), 1 deletion(-)
hw/arm/Kconfig +1 -0
index 7877506384..76aa7fee57 100644
@@ -530,6 +530,7 @@ config FSL_IMX6
     select IMX_FEC
     select IMX_I2C
     select IMX_USBPHY
+    select CAN_FLEXCAN
     select WDT_IMX2
     select PL310  # cache controller
     select PCI_EXPRESS_DESIGNWARE
hw/arm/fsl-imx6.c +29 -0
index f3a60022d8..19656ba571 100644
@@ -97,6 +97,10 @@ static void fsl_imx6_init(Object *obj)
         snprintf(name, NAME_SIZE, "spi%d", i + 1);
         object_initialize_child(obj, name, &s->spi[i], TYPE_IMX_SPI);
     }
+    for (i = 0; i < FSL_IMX6_NUM_CANS; i++) {
+        snprintf(name, NAME_SIZE, "flexcan%d", i + 1);
+        object_initialize_child(obj, name, &s->can[i], TYPE_CAN_FLEXCAN);
+    }
     for (i = 0; i < FSL_IMX6_NUM_WDTS; i++) {
         snprintf(name, NAME_SIZE, "wdt%d", i);
         object_initialize_child(obj, name, &s->wdt[i], TYPE_IMX2_WDT);
@@ -379,6 +383,27 @@ static void fsl_imx6_realize(DeviceState *dev, Error **errp)
                            qdev_get_gpio_in(gic, spi_table[i].irq));
     }
 
+    /* Initialize all FLEXCANs */
+    for (i = 0; i < FSL_IMX6_NUM_CANS; i++) {
+        static const struct {
+            hwaddr addr;
+            unsigned int irq;
+        } flexcan_table[FSL_IMX6_NUM_CANS] = {
+            { FSL_IMX6_CAN1_ADDR, FSL_IMX6_FLEXCAN1_IRQ },
+            { FSL_IMX6_CAN2_ADDR, FSL_IMX6_FLEXCAN2_IRQ },
+        };
+
+        s->can[i].ccm = IMX_CCM(&s->ccm);
+        object_property_set_link(OBJECT(&s->can[i]), "canbus",
+                                 OBJECT(s->canbus[i]), &error_abort);
+
+        sysbus_realize(SYS_BUS_DEVICE(&s->can[i]), &error_abort);
+
+        sysbus_mmio_map(SYS_BUS_DEVICE(&s->can[i]), 0, flexcan_table[i].addr);
+        sysbus_connect_irq(SYS_BUS_DEVICE(&s->can[i]), 0,
+                           qdev_get_gpio_in(gic, flexcan_table[i].irq));
+    }
+
     object_property_set_uint(OBJECT(&s->eth), "phy-num", s->phy_num,
                              &error_abort);
     qemu_configure_nic_device(DEVICE(&s->eth), true, NULL);
@@ -482,6 +507,10 @@ static void fsl_imx6_realize(DeviceState *dev, Error **errp)
 
 static const Property fsl_imx6_properties[] = {
     DEFINE_PROP_UINT32("fec-phy-num", FslIMX6State, phy_num, 0),
+    DEFINE_PROP_LINK("canbus0", FslIMX6State, canbus[0], TYPE_CAN_BUS,
+                     CanBusState *),
+    DEFINE_PROP_LINK("canbus1", FslIMX6State, canbus[1], TYPE_CAN_BUS,
+                     CanBusState *),
 };
 
 static void fsl_imx6_class_init(ObjectClass *oc, const void *data)
hw/arm/sabrelite.c +17 -1
index 29418af190..4bb8fe80d5 100644
@@ -23,6 +23,7 @@
 typedef struct SabreliteMachineState {
     MachineState parent_obj;
     FslIMX6State soc;
+    CanBusState *canbus[FSL_IMX6_NUM_CANS];
 
     struct arm_boot_info binfo;
 } Sabrelite;
@@ -65,6 +66,13 @@ static void sabrelite_init(MachineState *machine)
     /* Ethernet PHY address is 6 */
     object_property_set_int(OBJECT(&s->soc), "fec-phy-num", 6, &error_fatal);
 
+    for (int i = 0; i < FSL_IMX6_NUM_CANS; i++) {
+        g_autofree char *bus_name = g_strdup_printf("canbus%d", i);
+
+        object_property_set_link(OBJECT(&s->soc), bus_name,
+                                 OBJECT(s->canbus[i]), &error_fatal);
+    }
+
     qdev_realize(DEVICE(&s->soc), NULL, &error_fatal);
 
     memory_region_add_subregion(get_system_memory(), FSL_IMX6_MMDC_ADDR,
@@ -118,7 +126,15 @@ static void sabrelite_machine_instance_init(Object *obj)
 {
     Sabrelite *s = SABRELITE_MACHINE(obj);
 
-    (void)s;
+    object_property_add_link(obj, "canbus0", TYPE_CAN_BUS,
+                             (Object **)&s->canbus[0],
+                             object_property_allow_set_link,
+                             0);
+
+    object_property_add_link(obj, "canbus1", TYPE_CAN_BUS,
+                             (Object **)&s->canbus[1],
+                             object_property_allow_set_link,
+                             0);
 }
 
 static void sabrelite_machine_class_init(ObjectClass *oc, const void *data)
include/hw/arm/fsl-imx6.h +7 -0
index 124bbd478f..8edbcebe46 100644
@@ -30,11 +30,13 @@
 #include "hw/sd/sdhci.h"
 #include "hw/ssi/imx_spi.h"
 #include "hw/net/imx_fec.h"
+#include "hw/net/flexcan.h"
 #include "hw/usb/chipidea.h"
 #include "hw/usb/imx-usb-phy.h"
 #include "hw/pci-host/designware.h"
 #include "hw/or-irq.h"
 #include "system/memory.h"
+#include "net/can_emu.h"
 #include "cpu.h"
 #include "qom/object.h"
 
@@ -51,6 +53,7 @@ OBJECT_DECLARE_SIMPLE_TYPE(FslIMX6State, FSL_IMX6)
 #define FSL_IMX6_NUM_WDTS 2
 #define FSL_IMX6_NUM_USB_PHYS 2
 #define FSL_IMX6_NUM_USBS 4
+#define FSL_IMX6_NUM_CANS 2
 
 struct FslIMX6State {
     /*< private >*/
@@ -73,6 +76,7 @@ struct FslIMX6State {
     IMXUSBPHYState     usbphy[FSL_IMX6_NUM_USB_PHYS];
     ChipideaState      usb[FSL_IMX6_NUM_USBS];
     IMXFECState        eth;
+    FlexcanState       can[FSL_IMX6_NUM_CANS];
     DesignwarePCIEHost pcie;
     OrIRQState         pcie4_msi_irq;
     MemoryRegion       rom;
@@ -80,6 +84,9 @@ struct FslIMX6State {
     MemoryRegion       ocram;
     MemoryRegion       ocram_alias;
     uint32_t           phy_num;
+
+    /* CAN bus. */
+    CanBusState       *canbus[FSL_IMX6_NUM_CANS];
 };
 
 
Signature · 1 line
2.52.0
Reviewed-by on v1 4/6
#10 lore raw

Re: [PATCH v1 4/6] hw/arm: Plug FlexCAN into FSL_IMX6 and Sabrelite

Am 15. Dezember 2025 20:03:13 UTC schrieb "Matyáš Bobek" <[email protected]>:

Quoted 158 lines

FlexcanState is added to struct FslIMX6State like other peripherals.

Add two new machine properties to Sabrelite machine for linking the embedded FlexCAN instances to QEMU CAN buses by name. No other machine uses FslIMX6State.

---

hw/arm/Kconfig            |  1 +
hw/arm/fsl-imx6.c         | 29 +++++++++++++++++++++++++++++
hw/arm/sabrelite.c        | 18 +++++++++++++++++-
include/hw/arm/fsl-imx6.h |  7 +++++++
4 files changed, 54 insertions(+), 1 deletion(-)
hw/arm/Kconfig +1 -0
index 7877506384..76aa7fee57 100644
@@ -530,6 +530,7 @@ config FSL_IMX6
    select IMX_FEC
    select IMX_I2C
    select IMX_USBPHY
+    select CAN_FLEXCAN
    select WDT_IMX2
    select PL310  # cache controller
    select PCI_EXPRESS_DESIGNWARE
hw/arm/fsl-imx6.c +29 -0
index f3a60022d8..19656ba571 100644
@@ -97,6 +97,10 @@ static void fsl_imx6_init(Object *obj)
        snprintf(name, NAME_SIZE, "spi%d", i + 1);
        object_initialize_child(obj, name, &s->spi[i], TYPE_IMX_SPI);
    }
+    for (i = 0; i < FSL_IMX6_NUM_CANS; i++) {
+        snprintf(name, NAME_SIZE, "flexcan%d", i + 1);
+        object_initialize_child(obj, name, &s->can[i], TYPE_CAN_FLEXCAN);
+    }
    for (i = 0; i < FSL_IMX6_NUM_WDTS; i++) {
        snprintf(name, NAME_SIZE, "wdt%d", i);
        object_initialize_child(obj, name, &s->wdt[i], TYPE_IMX2_WDT);
@@ -379,6 +383,27 @@ static void fsl_imx6_realize(DeviceState *dev, Error **errp)
                           qdev_get_gpio_in(gic, spi_table[i].irq));
    }
 
+    /* Initialize all FLEXCANs */
+    for (i = 0; i < FSL_IMX6_NUM_CANS; i++) {
+        static const struct {
+            hwaddr addr;
+            unsigned int irq;
+        } flexcan_table[FSL_IMX6_NUM_CANS] = {
+            { FSL_IMX6_CAN1_ADDR, FSL_IMX6_FLEXCAN1_IRQ },
+            { FSL_IMX6_CAN2_ADDR, FSL_IMX6_FLEXCAN2_IRQ },
+        };
+
+        s->can[i].ccm = IMX_CCM(&s->ccm);
+        object_property_set_link(OBJECT(&s->can[i]), "canbus",
+                                 OBJECT(s->canbus[i]), &error_abort);
+
+        sysbus_realize(SYS_BUS_DEVICE(&s->can[i]), &error_abort);
+
+        sysbus_mmio_map(SYS_BUS_DEVICE(&s->can[i]), 0, flexcan_table[i].addr);
+        sysbus_connect_irq(SYS_BUS_DEVICE(&s->can[i]), 0,
+                           qdev_get_gpio_in(gic, flexcan_table[i].irq));
+    }
+
    object_property_set_uint(OBJECT(&s->eth), "phy-num", s->phy_num,
                             &error_abort);
    qemu_configure_nic_device(DEVICE(&s->eth), true, NULL);
@@ -482,6 +507,10 @@ static void fsl_imx6_realize(DeviceState *dev, Error **errp)
 
 static const Property fsl_imx6_properties[] = {
    DEFINE_PROP_UINT32("fec-phy-num", FslIMX6State, phy_num, 0),
+    DEFINE_PROP_LINK("canbus0", FslIMX6State, canbus[0], TYPE_CAN_BUS,
+                     CanBusState *),
+    DEFINE_PROP_LINK("canbus1", FslIMX6State, canbus[1], TYPE_CAN_BUS,
+                     CanBusState *),
 };
 
 static void fsl_imx6_class_init(ObjectClass *oc, const void *data)
hw/arm/sabrelite.c +17 -1
index 29418af190..4bb8fe80d5 100644
@@ -23,6 +23,7 @@
 typedef struct SabreliteMachineState {
    MachineState parent_obj;
    FslIMX6State soc;
+    CanBusState *canbus[FSL_IMX6_NUM_CANS];
 
    struct arm_boot_info binfo;
 } Sabrelite;
@@ -65,6 +66,13 @@ static void sabrelite_init(MachineState *machine)
    /* Ethernet PHY address is 6 */
    object_property_set_int(OBJECT(&s->soc), "fec-phy-num", 6, &error_fatal);
 
+    for (int i = 0; i < FSL_IMX6_NUM_CANS; i++) {
+        g_autofree char *bus_name = g_strdup_printf("canbus%d", i);
+
+        object_property_set_link(OBJECT(&s->soc), bus_name,
+                                 OBJECT(s->canbus[i]), &error_fatal);
+    }
+
    qdev_realize(DEVICE(&s->soc), NULL, &error_fatal);
 
    memory_region_add_subregion(get_system_memory(), FSL_IMX6_MMDC_ADDR,
@@ -118,7 +126,15 @@ static void sabrelite_machine_instance_init(Object *obj)
 {
    Sabrelite *s = SABRELITE_MACHINE(obj);
 
-    (void)s;
+    object_property_add_link(obj, "canbus0", TYPE_CAN_BUS,
+                             (Object **)&s->canbus[0],
+                             object_property_allow_set_link,
+                             0);
+
+    object_property_add_link(obj, "canbus1", TYPE_CAN_BUS,
+                             (Object **)&s->canbus[1],
+                             object_property_allow_set_link,
+                             0);
 }
 
 static void sabrelite_machine_class_init(ObjectClass *oc, const void *data)
include/hw/arm/fsl-imx6.h +4 -0
index 124bbd478f..8edbcebe46 100644
@@ -30,11 +30,13 @@
 #include "hw/sd/sdhci.h"
 #include "hw/ssi/imx_spi.h"
 #include "hw/net/imx_fec.h"
+#include "hw/net/flexcan.h"
 #include "hw/usb/chipidea.h"
 #include "hw/usb/imx-usb-phy.h"
 #include "hw/pci-host/designware.h"
 #include "hw/or-irq.h"
 #include "system/memory.h"
+#include "net/can_emu.h"
 #include "cpu.h"
 #include "qom/object.h"
 
@@ -51,6 +53,7 @@ OBJECT_DECLARE_SIMPLE_TYPE(FslIMX6State, FSL_IMX6)
 #define FSL_IMX6_NUM_WDTS 2
 #define FSL_IMX6_NUM_USB_PHYS 2
 #define FSL_IMX6_NUM_USBS 4
+#define FSL_IMX6_NUM_CANS 2
 
 struct FslIMX6State {
    /*< private >*/
@@ -73,6 +76,7 @@ struct FslIMX6State {
    IMXUSBPHYState     usbphy[FSL_IMX6_NUM_USB_PHYS];
    ChipideaState      usb[FSL_IMX6_NUM_USBS];
    IMXFECState        eth;
+    FlexcanState       can[FSL_IMX6_NUM_CANS];
    DesignwarePCIEHost pcie;
    OrIRQState         pcie4_msi_irq;
    MemoryRegion       rom;

include/hw/arm/fsl-imx6.h struct FslIMX6State { · line 88

Quoted 5 lines
include/hw/arm/fsl-imx6.h +2 -0
@@ -80,6 +84,9 @@ struct FslIMX6State {
    MemoryRegion       ocram;
    MemoryRegion       ocram_alias;
    uint32_t           phy_num;
+
+    /* CAN bus. */

I'd remove the comment since it merely states the obvious. Not a big deal though, so:

Quoted 4 lines
include/hw/arm/fsl-imx6.h +1 -0
+    CanBusState       *canbus[FSL_IMX6_NUM_CANS];
 };
 
 
#11 lore raw

PATCH 5/6 tests: Add qtests for FlexCAN

In reply to Matyáš Bobek

The tests do not test all of the FlexCAN emulator functionality. There are some duplicate tests for features that were buggy in development.

---
 MAINTAINERS                |   1 +
 tests/qtest/flexcan-test.c | 411 +++++++++++++++++++++++++++++++++++++
 tests/qtest/meson.build    |   1 +
 3 files changed, 413 insertions(+)
 create mode 100644 tests/qtest/flexcan-test.c
MAINTAINERS +1 -0
index a0b152939b..2b7fe904c4 100644
@@ -2859,6 +2859,7 @@ F: net/can/*
 F: hw/net/can/*
 F: include/net/can_*.h
 F: include/hw/net/flexcan.h
+F: tests/qtest/flexcan-test.c
 F: docs/system/devices/can.rst
 
 OpenPIC interrupt controller
tests/qtest/flexcan-test.c new file +411 -0

411 lines not on this page · raw message

tests/qtest/meson.build +1 -0
index 669d07c06b..c081075161 100644
@@ -246,6 +246,7 @@ qtests_arm = \
   (config_all_devices.has_key('CONFIG_MICROBIT') ? ['microbit-test'] : []) + \
   (config_all_devices.has_key('CONFIG_STM32L4X5_SOC') ? qtests_stm32l4x5 : []) + \
   (config_all_devices.has_key('CONFIG_FSI_APB2OPB_ASPEED') ? ['aspeed_fsi-test'] : []) + \
+  (config_all_devices.has_key('CONFIG_CAN_FLEXCAN') ? ['flexcan-test'] : []) + \
   (config_all_devices.has_key('CONFIG_STM32L4X5_SOC') and
    config_all_devices.has_key('CONFIG_DM163')? ['dm163-test'] : []) + \
   ['arm-cpu-features',
Signature · 1 line
2.52.0
#12 lore raw

Re: [PATCH v1 5/6] tests: Add qtests for FlexCAN

Am 15. Dezember 2025 20:03:14 UTC schrieb "Matyáš Bobek" <[email protected]>:

Quoted 3 lines

The tests do not test all of the FlexCAN emulator functionality. There are some duplicate tests for features that were buggy in development.

What exactly are "duplicate tests" and how did this help?

Quoted 40 lines

---

MAINTAINERS                |   1 +
tests/qtest/flexcan-test.c | 411 +++++++++++++++++++++++++++++++++++++
tests/qtest/meson.build    |   1 +
3 files changed, 413 insertions(+)

create mode 100644 tests/qtest/flexcan-test.c

MAINTAINERS +0 -0
index a0b152939b..2b7fe904c4 100644
@@ -2859,6 +2859,7 @@ F: net/can/*

F: hw/net/can/*
F: include/net/can_*.h
F: include/hw/net/flexcan.h
+F: tests/qtest/flexcan-test.c
F: docs/system/devices/can.rst

OpenPIC interrupt controller

tests/qtest/flexcan-test.c new file +13 -0
new file mode 100644
index 0000000000..8f3eb5eb8f
@@ -0,0 +1,411 @@
+/*
+ * QTests for FlexCAN CAN controller device model
+ *
+ * Copyright (c) 2025 Matyas Bobek <[email protected]>
+ *
+ * This code is licensed under the GPL version 2 or later.  See
+ * the COPYING file in the top-level directory.
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "libqtest-single.h"

tests/qtest/flexcan-test.c line 23

Quoted 10 lines
tests/qtest/flexcan-test.c new file +10 -0
+
+#include "hw/net/flexcan.h"
+#include "hw/net/can/flexcan_regs.h"
+
+#define FSL_IMX6_CAN2_ADDR 0x02094000
+#define FSL_IMX6_CAN2_SIZE 0x4000
+#define FSL_IMX6_CAN1_ADDR 0x02090000
+#define FSL_IMX6_CAN1_SIZE 0x4000
+
+#define FC_QEMU_ARGS "-nographic -M sabrelite --trace flexcan* " \

Please remove the tracing since it causes excessive debug output (remember that this test will be run in CI).

Quoted 12 lines
tests/qtest/flexcan-test.c new file +12 -0
+                     "-object can-bus,id=qcan0 " \
+                     "-machine canbus0=qcan0 -machine canbus1=qcan0"
+
+/* used for masking out unused/reserved bits */
+#define FC_MB_CNT_USED_MASK (~0xF080FFFFu)
+
+#define FCREG(BASE_ADDR, REG) ((BASE_ADDR) + offsetof(FlexcanRegs, REG))
+#define FCMB(BASE_ADDR, MB_IDX, WORD_IDX) (FCREG(BASE_ADDR, mbs) + \
+    0x10 * (MB_IDX) + 4 * (WORD_IDX))
+
+typedef struct FcTestFrame {
+    uint32_t id;

tests/qtest/flexcan-test.c line 45

Quoted 10 lines
tests/qtest/flexcan-test.c new file +10 -0
+    uint32_t data[2];
+    uint8_t len;
+    bool ide;
+    bool rtr;
+
+    /* rx only */
+    bool expect_overrun;
+} FcTestFrame;
+
+const FcTestFrame fc_test_frame_1 = {

Can be static.

tests/qtest/flexcan-test.c line 53

Quoted 8 lines
tests/qtest/flexcan-test.c new file +8 -0
+    .id = 0x5AF,
+    .len = 8,
+    .data = {
+        0x01020304,
+        0x0A0B0C0D
+    },
+    .ide = false
+};

Please ensure a blank line between functions and other definitions (more cases below).

tests/qtest/flexcan-test.c line 54

Quoted 1 line
tests/qtest/flexcan-test.c new file +1 -0
+const FcTestFrame fc_test_frame_1_ide = {

Can be static.

Best regards,
Bernhard

Quoted 369 lines
tests/qtest/flexcan-test.c new file +357 -0
+    .id = 0x105AF5AF,
+    .len = 8,
+    .data = {
+        0x01020304,
+        0x0A0B0C0D
+    },
+    .ide = true
+};
+
+static void fc_reset(hwaddr ba, uint32_t mcr_flags)
+{
+    /* disable */
+    writel(FCREG(ba, mcr), 0xD890000F);
+
+    /* enable in freeze mode */
+    writel(FCREG(ba, mcr), 0x5980000F);
+
+    /* soft reset */
+    writel(FCREG(ba, mcr), 0x5980000F | FLEXCAN_MCR_SOFTRST);
+
+    g_assert_cmpuint(readl(FCREG(ba, mcr)), ==, 0x5980000F);
+    g_assert_cmpuint(readl(FCREG(ba, ctrl)), ==, 0);
+    g_assert_cmpuint(readl(FCREG(ba, ctrl2)), ==, 0);
+
+    writel(FCREG(ba, mcr), (0x5980000F & ~FLEXCAN_MCR_HALT) | mcr_flags);
+    writel(FCREG(ba, ctrl2), FLEXCAN_CTRL2_RRS);
+
+    /* initialize all mailboxes as rx inactive */
+    for (int i = 0; i < FLEXCAN_MAILBOX_COUNT; i++) {
+        writel(FCMB(ba, i, 0), FLEXCAN_MB_CODE_RX_INACTIVE);
+        writel(FCMB(ba, i, 1), 0);
+        writel(FCMB(ba, i, 2), 0);
+        writel(FCMB(ba, i, 3), 0);
+    }
+}
+
+static uint64_t fc_get_irqs(hwaddr ba)
+{
+    return (uint64_t)readl(FCREG(ba, iflag1)) |
+        ((uint64_t)readl(FCREG(ba, iflag2)) << 32);
+}
+static void fc_clear_irq(hwaddr ba, int idx)
+{
+    if (idx >= 32) {
+        writel(FCREG(ba, iflag2), (uint32_t)1 << (idx - 32));
+    } else {
+        writel(FCREG(ba, iflag1), (uint32_t)1 << idx);
+    }
+
+    g_assert_cmpuint(fc_get_irqs(ba) & ((uint64_t)1 << idx), ==, 0);
+}
+static void fc_setup_rx_mb(hwaddr ba, int mbidx)
+{
+    writel(FCMB(ba, mbidx, 0), FLEXCAN_MB_CODE_RX_EMPTY);
+    writel(FCMB(ba, mbidx, 1), 0);
+    /* the data value should be ignored for RX mb */
+    writel(FCMB(ba, mbidx, 2), 0);
+    writel(FCMB(ba, mbidx, 3), 0);
+
+    g_assert_cmpuint(readl(FCMB(ba, mbidx, 0)), ==, FLEXCAN_MB_CODE_RX_EMPTY);
+}
+static void fc_tx(hwaddr ba, int mbidx, const FcTestFrame *frame)
+{
+    g_assert_cmpuint(frame->len, <=, 8);
+
+    writel(FCMB(ba, mbidx, 0), FLEXCAN_MB_CODE_TX_INACTIVE);
+    uint32_t id = frame->ide ? frame->id : frame->id << 18;
+    writel(FCMB(ba, mbidx, 1), id);
+    writel(FCMB(ba, mbidx, 2), frame->data[0]);
+    writel(FCMB(ba, mbidx, 3), frame->data[1]);
+
+    uint32_t ctrl = FLEXCAN_MB_CODE_TX_DATA | FLEXCAN_MB_CNT_LENGTH(frame->len);
+    if (frame->ide) {
+        ctrl |= FLEXCAN_MB_CNT_IDE | FLEXCAN_MB_CNT_SRR;
+    }
+    if (frame->rtr) {
+        ctrl |= FLEXCAN_MB_CNT_RTR;
+    }
+    writel(FCMB(ba, mbidx, 0), ctrl);
+
+    /* check frame was transmitted */
+    g_assert_cmpuint(fc_get_irqs(ba) & ((uint64_t)1 << mbidx),
+                     !=, 0);
+
+    uint32_t xpectd_ctrl = (ctrl & ~FLEXCAN_MB_CODE_MASK) |
+        FLEXCAN_MB_CODE_TX_INACTIVE;
+    g_assert_cmpuint(readl(FCMB(ba, mbidx, 0)) & FC_MB_CNT_USED_MASK, ==,
+                     xpectd_ctrl);
+    /* other fields should stay unchanged */
+    g_assert_cmpuint(readl(FCMB(ba, mbidx, 1)), ==, id);
+    g_assert_cmpuint(readl(FCMB(ba, mbidx, 2)), ==, frame->data[0]);
+    g_assert_cmpuint(readl(FCMB(ba, mbidx, 3)), ==, frame->data[1]);
+}
+static void fc_rx_check(hwaddr ba, int mbidx, const FcTestFrame *frame)
+{
+    uint32_t xpectd_ctrl = frame->expect_overrun ? FLEXCAN_MB_CODE_RX_OVERRUN
+                             : FLEXCAN_MB_CODE_RX_FULL;
+    xpectd_ctrl |= FLEXCAN_MB_CNT_LENGTH(frame->len) | FLEXCAN_MB_CNT_SRR;
+    if (frame->ide) {
+        xpectd_ctrl |= FLEXCAN_MB_CNT_IDE;
+    }
+    if (frame->rtr) {
+        xpectd_ctrl |= FLEXCAN_MB_CNT_RTR;
+    }
+
+    uint32_t xpectd_id = frame->ide ? frame->id : frame->id << 18;
+
+    uint32_t ctrl = readl(FCMB(ba, mbidx, 0)) & FC_MB_CNT_USED_MASK;
+    if ((ctrl & FLEXCAN_MB_CODE_MASK) == FLEXCAN_MB_CODE_RX_EMPTY) {
+        fprintf(stderr, "expected frame (id=0x%X) not received\n", frame->id);
+    }
+
+    g_assert_cmpuint(ctrl, ==, xpectd_ctrl);
+    g_assert_cmpuint(readl(FCMB(ba, mbidx, 1)), ==, xpectd_id);
+    g_assert_cmpuint(readl(FCMB(ba, mbidx, 2)), ==, frame->data[0]);
+    g_assert_cmpuint(readl(FCMB(ba, mbidx, 3)), ==, frame->data[1]);
+}
+static void fc_check_empty_multi(hwaddr ba, int idx_count, int mbidxs[])
+{
+    for (int i = 0; i < FLEXCAN_MAILBOX_COUNT; i++) {
+        int ctrl = readl(FCMB(ba, i, 0));
+        for (int j = 0; j < idx_count; j++) {
+            if (i == mbidxs[j]) {
+                goto non_empty;
+            }
+        }
+
+        if (!(ctrl == FLEXCAN_MB_CODE_RX_EMPTY ||
+              ctrl == FLEXCAN_MB_CODE_RX_INACTIVE)) {
+            g_assert_cmpuint(ctrl, ==, FLEXCAN_MB_CODE_RX_INACTIVE);
+        }
+        g_assert_cmpuint(readl(FCMB(ba, i, 1)), ==, 0);
+        g_assert_cmpuint(readl(FCMB(ba, i, 2)), ==, 0);
+        g_assert_cmpuint(readl(FCMB(ba, i, 3)), ==, 0);
+        continue;
+
+        non_empty:
+        g_assert_cmpuint(
+            ctrl & FLEXCAN_MB_CODE_MASK, !=,
+            FLEXCAN_MB_CODE_RX_INACTIVE
+        );
+    }
+}
+static void fc_check_empty(hwaddr ba, int mbidx)
+{
+    fc_check_empty_multi(ba, 1, &mbidx);
+}
+
+static void flexcan_test_linux_probe_impl(hwaddr fba)
+{
+    /* -- test Linux driver-like probe sequence -- */
+    /* disable */
+    writel(FCREG(fba, mcr), 0xD890000F);
+    g_assert_cmpuint(readl(FCREG(fba, mcr)), ==, 0xD890000F);
+    g_assert_cmpuint(readl(FCREG(fba, ctrl)), ==, 0);
+
+    /* set bit in reserved field we do not implement (CTRL_CLK_SRC) */
+    writel(FCREG(fba, ctrl), 0x00002000);
+    g_assert_cmpuint(readl(FCREG(fba, mcr)), ==, 0xD890000F);
+
+    /* enable in freeze mode */
+    writel(FCREG(fba, mcr), 0x5980000F);
+    g_assert_cmpuint(readl(FCREG(fba, mcr)), ==, 0x5980000F);
+
+    /* enable Rx-FIFO */
+    writel(FCREG(fba, mcr), 0x7980000F);
+    g_assert_cmpuint(readl(FCREG(fba, mcr)), ==, 0x7980000F);
+    g_assert_cmpuint(readl(FCREG(fba, ecr)), ==, 0);
+
+    /* disable */
+    writel(FCREG(fba, mcr), 0xF890000F);
+    g_assert_cmpuint(readl(FCREG(fba, mcr)), ==, 0xF890000F);
+}
+
+static void flexcan_test_freeze_disable_interaction_impl(hwaddr fba)
+{
+    /* -- test normal <=> freeze <=> disable transitions -- */
+
+    /* leave freeze in disabled, FRZ_ACK should stay cleared */
+    writel(FCREG(fba, mcr), 0xF890000F); /* disable */
+    g_assert_cmpuint(readl(FCREG(fba, mcr)), ==, 0xF890000F);
+    writel(FCREG(fba, mcr), 0xB890000F);  /* by clearing FRZ */
+    g_assert_cmpuint(readl(FCREG(fba, mcr)), ==, 0xB890000F);
+
+    writel(FCREG(fba, mcr), 0xF890000F); /* disable */
+    g_assert_cmpuint(readl(FCREG(fba, mcr)), ==, 0xF890000F);
+    writel(FCREG(fba, mcr), 0xE890000F);  /* by clearing HALT */
+    g_assert_cmpuint(readl(FCREG(fba, mcr)), ==, 0xE890000F);
+
+    writel(FCREG(fba, mcr), 0xF890000F); /* disable */
+    g_assert_cmpuint(readl(FCREG(fba, mcr)), ==, 0xF890000F);
+    writel(FCREG(fba, mcr), 0xA890000F);  /* by clearing both */
+    g_assert_cmpuint(readl(FCREG(fba, mcr)), ==, 0xA890000F);
+
+    /* enter and leave freeze */
+    writel(FCREG(fba, mcr), 0x7980000F); /* enable in freeze mode */
+    g_assert_cmpuint(readl(FCREG(fba, mcr)), ==, 0x7980000F);
+    writel(FCREG(fba, mcr), 0x3980000F); /* leave by clearing FRZ */
+    g_assert_cmpuint(readl(FCREG(fba, mcr)), ==, 0x3080000F);
+
+    writel(FCREG(fba, mcr), 0x7980000F); /* enable in freeze mode */
+    g_assert_cmpuint(readl(FCREG(fba, mcr)), ==, 0x7980000F);
+    writel(FCREG(fba, mcr), 0x6980000F); /* leave by clearing HALT */
+    g_assert_cmpuint(readl(FCREG(fba, mcr)), ==, 0x6080000F);
+}
+
+static void flexcan_test_mailbox_io_impl(hwaddr ba_tx, hwaddr ba_rx)
+{
+    /* -- test correct handling of mailbox IO -- */
+    const int test_1_mbidx = 0;
+    fc_reset(ba_tx,
+             FLEXCAN_MCR_SRX_DIS | FLEXCAN_MCR_MAXMB(FLEXCAN_MAILBOX_COUNT));
+    fc_reset(ba_rx,
+             FLEXCAN_MCR_SRX_DIS | FLEXCAN_MCR_MAXMB(FLEXCAN_MAILBOX_COUNT));
+
+    fc_setup_rx_mb(ba_rx, test_1_mbidx);
+    fc_tx(ba_tx, test_1_mbidx, &fc_test_frame_1_ide);
+    g_assert_cmpuint(fc_get_irqs(ba_rx), ==, 1 << test_1_mbidx);
+    fc_rx_check(ba_rx, test_1_mbidx, &fc_test_frame_1_ide);
+    readl(FCREG(ba_rx, timer)); /* reset lock */
+
+    writel(FCMB(ba_rx, test_1_mbidx, 0), 0);
+    g_assert_cmpuint(readl(FCMB(ba_rx, test_1_mbidx, 0)), ==, 0);
+    writel(FCMB(ba_rx, test_1_mbidx, 1), 0x99AABBCC);
+    g_assert_cmpuint(readl(FCMB(ba_rx, test_1_mbidx, 1)), ==, 0x99AABBCC);
+}
+
+static void flexcan_test_dual_transmit_receive_impl(hwaddr ba_tx, hwaddr ba_rx)
+{
+    /* -- test TX and RX between the two FlexCAN instances -- */
+    const int test_1_mbidx = 50;
+    const int test_rounds = 50;
+
+    /* self-receive enabled on tx FC */
+    fc_reset(ba_tx,
+             FLEXCAN_MCR_MAXMB(FLEXCAN_MAILBOX_COUNT));
+    fc_reset(ba_rx,
+             FLEXCAN_MCR_SRX_DIS | FLEXCAN_MCR_MAXMB(FLEXCAN_MAILBOX_COUNT));
+
+    /* tests self-receive on tx and reception on rx */
+    fc_setup_rx_mb(ba_rx, test_1_mbidx);
+    fc_check_empty(ba_rx, test_1_mbidx);
+    fc_setup_rx_mb(ba_tx, test_1_mbidx + 1);
+    fc_check_empty(ba_tx, test_1_mbidx + 1);
+    g_assert_cmpuint(fc_get_irqs(ba_rx), ==, 0);
+    g_assert_cmpuint(fc_get_irqs(ba_tx), ==, 0);
+
+    fc_tx(ba_tx, test_1_mbidx, &fc_test_frame_1);
+    fc_clear_irq(ba_tx, test_1_mbidx);
+
+    fc_rx_check(ba_rx, test_1_mbidx, &fc_test_frame_1);
+    fc_check_empty(ba_rx, test_1_mbidx);
+    fc_rx_check(ba_tx, test_1_mbidx + 1, &fc_test_frame_1);
+    int tx_non_empty_mbidxs[] = {test_1_mbidx, test_1_mbidx + 1};
+
+    fc_check_empty_multi(ba_tx, 2, tx_non_empty_mbidxs);
+    fc_clear_irq(ba_rx, test_1_mbidx);
+    fc_clear_irq(ba_tx, test_1_mbidx + 1);
+    readl(FCREG(ba_rx, timer)); /* reset lock */
+
+    for (int ridx = 0; ridx < test_rounds; ridx++) {
+        /* test extended IDs sent to all mailboxes */
+        for (int i = 0; i < FLEXCAN_MAILBOX_COUNT; i++) {
+            fc_setup_rx_mb(ba_rx, i);
+        }
+        fc_check_empty_multi(ba_rx, 0, NULL);
+        g_assert_cmpuint(fc_get_irqs(ba_rx), ==, 0);
+        g_assert_cmpuint(fc_get_irqs(ba_tx), ==, 0);
+
+        for (int i = 0; i < FLEXCAN_MAILBOX_COUNT; i++) {
+            fc_tx(ba_tx, i, &fc_test_frame_1_ide);
+        }
+        g_assert_cmpuint(fc_get_irqs(ba_rx), ==, UINT64_MAX);
+        g_assert_cmpuint(fc_get_irqs(ba_tx), ==, UINT64_MAX);
+        for (int i = 0; i < FLEXCAN_MAILBOX_COUNT; i++) {
+            fc_rx_check(ba_rx, i, &fc_test_frame_1_ide);
+        }
+
+        /* reset interrupts */
+        writel(FCREG(ba_rx, iflag1), UINT32_MAX);
+        writel(FCREG(ba_rx, iflag2), UINT32_MAX);
+        writel(FCREG(ba_tx, iflag1), UINT32_MAX);
+        writel(FCREG(ba_tx, iflag2), UINT32_MAX);
+        g_assert_cmpuint(fc_get_irqs(ba_rx), ==, 0);
+        g_assert_cmpuint(fc_get_irqs(ba_tx), ==, 0);
+    }
+}
+
+static void flexcan_test_tx_abort_impl(hwaddr ba)
+{
+    /* -- test the TX abort feature -- */
+    fc_reset(ba,
+             FLEXCAN_MCR_SRX_DIS | FLEXCAN_MCR_MAXMB(FLEXCAN_MAILBOX_COUNT));
+
+
+    for (int mbidx = 0; mbidx < FLEXCAN_MAILBOX_COUNT; mbidx++) {
+        fc_tx(ba, mbidx, &fc_test_frame_1);
+
+        writel(FCMB(ba, mbidx, 0), FLEXCAN_MB_CODE_TX_ABORT);
+        g_assert_cmpuint(readl(FCMB(ba, mbidx, 0)), ==,
+                         FLEXCAN_MB_CODE_TX_INACTIVE);
+    }
+}
+
+static void flexcan_test_freeze_disable_interaction(void)
+{
+    qtest_start(FC_QEMU_ARGS);
+    flexcan_test_freeze_disable_interaction_impl(FSL_IMX6_CAN1_ADDR);
+    flexcan_test_freeze_disable_interaction_impl(FSL_IMX6_CAN2_ADDR);
+    qtest_end();
+}
+static void flexcan_test_linux_probe(void)
+{
+    qtest_start(FC_QEMU_ARGS);
+    flexcan_test_linux_probe_impl(FSL_IMX6_CAN1_ADDR);
+    flexcan_test_linux_probe_impl(FSL_IMX6_CAN2_ADDR);
+    qtest_end();
+}
+static void flexcan_test_dual_transmit_receive(void)
+{
+    qtest_start(FC_QEMU_ARGS);
+    flexcan_test_dual_transmit_receive_impl(FSL_IMX6_CAN1_ADDR,
+                                            FSL_IMX6_CAN2_ADDR);
+    flexcan_test_dual_transmit_receive_impl(FSL_IMX6_CAN2_ADDR,
+                                            FSL_IMX6_CAN1_ADDR);
+    qtest_end();
+}
+static void flexcan_test_tx_abort(void)
+{
+    qtest_start(FC_QEMU_ARGS);
+    flexcan_test_tx_abort_impl(FSL_IMX6_CAN1_ADDR);
+    flexcan_test_tx_abort_impl(FSL_IMX6_CAN2_ADDR);
+    qtest_end();
+}
+static void flexcan_test_mailbox_io(void)
+{
+    qtest_start(FC_QEMU_ARGS);
+    flexcan_test_mailbox_io_impl(FSL_IMX6_CAN1_ADDR, FSL_IMX6_CAN2_ADDR);
+    flexcan_test_mailbox_io_impl(FSL_IMX6_CAN2_ADDR, FSL_IMX6_CAN1_ADDR);
+    qtest_end();
+}
+
+int main(int argc, char **argv)
+{
+    g_test_init(&argc, &argv, NULL);
+
+    qtest_add_func("flexcan/test_linux_probe", flexcan_test_linux_probe);
+    qtest_add_func("flexcan/test_freeze_disable_interaction",
+                   flexcan_test_freeze_disable_interaction);
+    qtest_add_func("flexcan/test_dual_transmit_receive",
+                   flexcan_test_dual_transmit_receive);
+    qtest_add_func("flexcan/test_tx_abort",
+                   flexcan_test_tx_abort);
+    qtest_add_func("flexcan/test_mailbox_io", flexcan_test_mailbox_io);
+
+    return g_test_run();
+}
tests/qtest/meson.build +1 -0
index 669d07c06b..c081075161 100644
@@ -246,6 +246,7 @@ qtests_arm = \
  (config_all_devices.has_key('CONFIG_MICROBIT') ? ['microbit-test'] : []) + \
  (config_all_devices.has_key('CONFIG_STM32L4X5_SOC') ? qtests_stm32l4x5 : []) + \
  (config_all_devices.has_key('CONFIG_FSI_APB2OPB_ASPEED') ? ['aspeed_fsi-test'] : []) + \
+  (config_all_devices.has_key('CONFIG_CAN_FLEXCAN') ? ['flexcan-test'] : []) + \
  (config_all_devices.has_key('CONFIG_STM32L4X5_SOC') and
   config_all_devices.has_key('CONFIG_DM163')? ['dm163-test'] : []) + \
  ['arm-cpu-features',
#13 lore raw

PATCH 6/6 docs/arm/sabrelite: Mention FlexCAN support

In reply to Matyáš Bobek

Also added example command line usage of the Sabrelite board with FlexCAN controllers.

---
 docs/system/arm/sabrelite.rst |  1 +
 docs/system/devices/can.rst   | 20 ++++++++++++++++++++
 2 files changed, 21 insertions(+)
docs/system/arm/sabrelite.rst +1 -0
index 4ccb0560af..d3a3c01dd6 100644
@@ -24,6 +24,7 @@ The SABRE Lite machine supports the following devices:
  * 4 SDHC storage controllers
  * 4 USB 2.0 host controllers
  * 5 ECSPI controllers
+ * 2 FlexCAN CAN controllers
  * 1 SST 25VF016B flash
 
 Please note above list is a complete superset the QEMU SABRE Lite machine can
docs/system/devices/can.rst +20 -0
index 09121836fd..5f21c01550 100644
@@ -173,6 +173,26 @@ The test can also be run the other way around, generating messages in the
 guest system and capturing them in the host system. Other combinations are
 also possible.
 
+Examples on how to use CAN emulation for FlexCAN on SabreLite board
+-------------------------------------------------------------------
+FlexCANs are connected to QEMU CAN buses by passing the bus IDs as machine properties:
+* property ``canbus0`` for connecting ``flexcan1``
+* property ``canbus1`` for connecting ``flexcan2``
+Note that upstream Linux SabreLite DTs have only a single FlexCAN (``flexcan1``) enabled.
+
+An example command to run QEMU emulating a Sabrelite development board
+with both FlexCANs connected to a single QEMU CAN bus (called ``qcan0``),
+bridged to host system ``can0`` interface::
+
+  qemu-system-arm -M sabrelite -smp 4 -m 1G \
+    -object can-bus,id=qcan0 \
+    -machine canbus0=qcan0 -machine canbus1=qcan0 \
+    -object can-host-socketcan,if=can0,canbus=qcan0,id=qcan0-socketcan \
+    -kernel ... -dtb ... -initrd ...
+
+Note that in the Linux guest, bitrate for the FlexCAN device is ignored,
+but needs to be set via the ``ip`` command.
+
 Links to other resources
 ------------------------
 
Signature · 1 line
2.52.0
Reviewed-by on v1 6/6
#14 lore raw

Re: [PATCH v1 6/6] docs/arm/sabrelite: Mention FlexCAN support

Am 15. Dezember 2025 20:03:15 UTC schrieb "Matyáš Bobek" <[email protected]>:

Quoted 29 lines

Also added example command line usage of the Sabrelite board with FlexCAN controllers.

---
docs/system/arm/sabrelite.rst | 1 +
docs/system/devices/can.rst | 20 ++++++++++++++++++++

2 files changed, 21 insertions(+)
docs/system/arm/sabrelite.rst +1 -0
index 4ccb0560af..d3a3c01dd6 100644
@@ -24,6 +24,7 @@ The SABRE Lite machine supports the following devices:
 * 4 SDHC storage controllers
 * 4 USB 2.0 host controllers
 * 5 ECSPI controllers
+ * 2 FlexCAN CAN controllers
 * 1 SST 25VF016B flash
 

Please note above list is a complete superset the QEMU SABRE Lite machine can

docs/system/devices/can.rst +0 -0
index 09121836fd..5f21c01550 100644
@@ -173,6 +173,26 @@ The test can also be run the other way around, generating messages in the

guest system and capturing them in the host system. Other combinations are also possible.

docs/system/devices/can.rst The test can also be run the other way around, generating messages in the · line 178

Quoted 3 lines
docs/system/devices/can.rst +3 -0
+Examples on how to use CAN emulation for FlexCAN on SabreLite board
+-------------------------------------------------------------------
+FlexCANs are connected to QEMU CAN buses by passing the bus IDs as machine properties:

Blank line needed here...

docs/system/devices/can.rst The test can also be run the other way around, generating messages in the · line 180

Quoted 2 lines
docs/system/devices/can.rst +2 -0
+* property ``canbus0`` for connecting ``flexcan1``
+* property ``canbus1`` for connecting ``flexcan2``

... and here to turn this into a list of bullet points.

I'd merge this patch into "[PATCH v1 4/6] hw/arm: Plug FlexCAN into FSL_IMX6 and Sabrelite" but this is totally up to you.

With the bullet points fixed:

Quoted 18 lines
docs/system/devices/can.rst +15 -0
+Note that upstream Linux SabreLite DTs have only a single FlexCAN (``flexcan1``) enabled.
+
+An example command to run QEMU emulating a Sabrelite development board
+with both FlexCANs connected to a single QEMU CAN bus (called ``qcan0``),
+bridged to host system ``can0`` interface::
+
+  qemu-system-arm -M sabrelite -smp 4 -m 1G \
+    -object can-bus,id=qcan0 \
+    -machine canbus0=qcan0 -machine canbus1=qcan0 \
+    -object can-host-socketcan,if=can0,canbus=qcan0,id=qcan0-socketcan \
+    -kernel ... -dtb ... -initrd ...
+
+Note that in the Linux guest, bitrate for the FlexCAN device is ignored,
+but needs to be set via the ``ip`` command.
+

Links to other resources
------------------------

#15 lore raw
In reply to Matyáš Bobek

Dear Matyas Bobek and Bernhard Beschow,

Quoted 3 lines · On Monday 15 of December 2025 21:03:09 Matyáš Bobek wrote:

This series adds emulation of the FlexCAN CAN controller, version 2, found in NXP i.MX6 series SoCs. The controller is integrated into fsl-imx6 and the Sabrelite ARM board.

First, thanks a lot to Matyas Bobek for finding the courage and sending the FlexCAN series, finally, after keeping it updated log time on his flexcan-series-XXX branches at his personal QEMU development repository

https://gitlab.fel.cvut.cz/bobekmat/qemu-flexcan/-/branches

Bernhard Beschow, thanks for expressing interrest in the project. Your intererst helped Matyas Bobek to collect courage to send. I have found that you have invested a lot in the CAN on your imx8mp-flexcan and can-cleanup branches, thanks again

https://github.com/shentok/qemu/branches

It is shame that long delay in sending of patches has lead to some divergence of the effort.

I have gone through your changes and would be happy if the effort can be joined and integrated into mainline.

I would prefer to help Matyas Bobek's series to be updated in state that it passes review and then the FlexCAN can be moved forward by you and others.

In long term, the extension to support FlexCAN3 would be very usesfull. But that that is work for other thesis, GSoC and or company funded project or developers. I would be happy to provide my knowledge as the time allows or look for students, etc.

As for the series and your (Bernhard Beschow) changes:

I have seen that you suggest some reordering of some functions in the hw/net/can/flexcan.c file. If you think that it is the better, more readable order for QEMU developers, I would suggest and have plea to Matyas Bobek to proceed with reorder. Same with tracepoints and debug prints which could be updated into state that your or others follow-up patches would not cause massive code movement which complicates tracking and reading of the changes.

On the other hand, I have some arguments against unification of memory FlexCAN access function and inlining register accesses into them. We have already discused with Matyas Bobek that for FlexCAN3 and other future changes it would worth to separate registers from memory part etc. So I would kept this separation. Making as much as possible of the core opaque for its external use is right from my view point on the other hand.

As for the CAN core changes, again there are some which I see as good moves, some cleanup of long term unused original structures which have been planned for another integration into QEMU before simplification to pass review etc. On the other hand, I would keep client state without const and with destructors etc. Again, actual code is somehow usable in its actual form, but from the long term perspective, I see the need for back-pressure propagation, emulation of the highest priority message (the lowest ID) exchange the first, etc. and I have some plans for that. I do not think that CAN emulation is and will be some real performance bottleneck in QEMU use for embedded systems development so I would like to keep there space for future more precise emulation.

Same with reset on the chip core level, I think that its redundant call does not cause any performance problems, but I would be happy if the controller codes are reusable for wide scenarios. I have written and used LinCAN with such controllers at time of ISA bus on PC. I would be happy if we have mechanism how to map them on SoCs with FPGAs. Unfortunately this valid and very usesfull feature

(
for example for our RTEMS effort on Zynq
https://docs.rtems.org/docs/main/bsp-howto/can.html
and ideally even on PolarFire (as time allows) where even NuttX can be tested and CI run
https://github.com/apache/nuttx/tree/master/drivers/can
)

hit concrete wall in May, without any suggestion how to make that needed use of QEMU for CI acceptable.

But our SJA1000 code is already used by Espressif in their QEMU fork
https://github.com/espressif/qemu/tree/esp-develop/hw/net/can
so there is more proven use out of PCI based cards. CTU CAN FD is used mostly on FPGAs but here are MCUs with it so again, even if the usability of mainline QEMU for FPGA development would stay blocked, there are standard, hopefully non problematic, regular machine code initiated uses of the CAN controllers which are part of QEMU.

So I would be happy if we can thought about that wider use to check that it would not be more problematic in future if some code is optimized.

On the other hand, it is right that even in esp32_twai.c case, the integration is based on RESETTABLE_CLASS and esp32_twai_reset() calls can_sja_hardware_reset() explicitly. So can_sja_hardware_reset() during can_sja_init() is not strictly necessary.

Back to sabrelite FlexCAN support series. In general I agree with the patch series and I have consulted and reviewed it multiple times. So it can be considered to be approved by me that it is functionally OK as well as it respects copyright requirements etc. I add my

As for individual formatting and may be some debug prints, I would allow it to go in in its actual form and then reduce these latter that we have state with more, may it be even abundant, debug in mainline recorded. But I expect that there could be more request for style and details from more experienced QEMU developers.

There is one unresolved patch check report about
DEVICE_NATIVE_ENDIAN

+static const struct MemoryRegionOps flexcan_ops = {

+    .read = flexcan_mem_read,
+    .write = flexcan_mem_write,
+    .endianness = DEVICE_NATIVE_ENDIAN,
+    .valid = {
+        .min_access_size = 1,
+        .max_access_size = 4,
+        .unaligned = true,
+        .accepts = flexcan_mem_accepts
+    },
+    .impl = {
+        .min_access_size = 4,
+        .max_access_size = 4,
+        .unaligned = false

+ },
+};

But I do not know what I can suggest there. The device is infernally accessed by 32-bits words and should be mapped in native format because same core is used on little-endian and may it be even bi-endian ARMs[*1] and for sure on big-endian PowerPCs. We believe that native endianness with host is the best option in this case. Extending .valid.max_access_size to 8 is right and probably require for 64-bit targets as I understand from your patches.

[*1] as I have used bi-endian ARMs they mapped peripherals often native way on 32-bit entities. So again, fixed DEVICE_BIG_ENDIAN or DEVICE_LITTLE_ENDIAN is incorrect in such case.

Best wishes,

Pavel

Pavel Pisa
phone: +420 603531357
e-mail: [email protected]
Department of Control Engineering FEE CVUT Karlovo namesti 13, 121 35, Prague 2
university: http://control.fel.cvut.cz/
personal: http://cmp.felk.cvut.cz/~pisa
social: https://social.kernel.org/ppisa
projects: https://www.openhub.net/accounts/ppisa
CAN related:http://canbus.pages.fel.cvut.cz/ RISC-V education: https://comparch.edu.cvut.cz/
Open Technologies Research Education and Exchange Services
https://gitlab.fel.cvut.cz/otrees/org/-/wikis/home

#16 lore raw

Am 16. Dezember 2025 00:44:54 UTC schrieb Pavel Pisa <[email protected]>:

Quoted 12 lines

Dear Matyas Bobek and Bernhard Beschow,

Quoted 3 lines · On Monday 15 of December 2025 21:03:09 Matyáš Bobek wrote:

This series adds emulation of the FlexCAN CAN controller, version 2, found in NXP i.MX6 series SoCs. The controller is integrated into fsl-imx6 and the Sabrelite ARM board.

First, thanks a lot to Matyas Bobek for finding the courage and sending the FlexCAN series, finally, after keeping it updated log time on his flexcan-series-XXX branches at his personal QEMU development repository

Quoted 4 lines

https://gitlab.fel.cvut.cz/bobekmat/qemu-flexcan/-/branches

Bernhard Beschow, thanks for expressing interrest in the project. Your intererst helped Matyas Bobek to collect courage to send.

Nice! Yeah, contributing to an OSS project can be intimidating at first. But seeing one's work accepted is encuraging which is the real beauty of OSS.

Quoted 15 lines

I have found that you have invested a lot in the CAN on your imx8mp-flexcan and can-cleanup branches, thanks again

https://github.com/shentok/qemu/branches

It is shame that long delay in sending of patches has lead to some divergence of the effort.

I have gone through your changes and would be happy if the effort can be joined and integrated into mainline.

I would prefer to help Matyas Bobek's series to be updated in state that it passes review and then the FlexCAN can be moved forward by you and others.

Quoted 5 lines

In long term, the extension to support FlexCAN3 would be very usesfull. But that that is work for other thesis, GSoC and or company funded project or developers. I would be happy to provide my knowledge as the time allows or look for students, etc.

Well, the imx8mp-flexcan branch is a moving target and currently exists as a proof of concept. Once this series is merged, I would like to integrate this series' FlexCAN functionality into imx8mp, even if the real controller has more features. So this branch just allows me to look into the future which helps my review.

Quoted 2 lines

As for the series and your (Bernhard Beschow) changes:

Let's keep the discussion on the list. While there is some truth to my branches I'd prefer to stick to the review process and only discuss topics raised here. I'll provide my feedback as soon as I find time for it.

Best regards,
Bernhard

Quoted 140 lines

I have seen that you suggest some reordering of some functions in the hw/net/can/flexcan.c file. If you think that it is the better, more readable order for QEMU developers, I would suggest and have plea to Matyas Bobek to proceed with reorder. Same with tracepoints and debug prints which could be updated into state that your or others follow-up patches would not cause massive code movement which complicates tracking and reading of the changes.

On the other hand, I have some arguments against unification of memory FlexCAN access function and inlining register accesses into them. We have already discused with Matyas Bobek that for FlexCAN3 and other future changes it would worth to separate registers from memory part etc. So I would kept this separation. Making as much as possible of the core opaque for its external use is right from my view point on the other hand.

As for the CAN core changes, again there are some which I see as good moves, some cleanup of long term unused original structures which have been planned for another integration into QEMU before simplification to pass review etc. On the other hand, I would keep client state without const and with destructors etc. Again, actual code is somehow usable in its actual form, but from the long term perspective, I see the need for back-pressure propagation, emulation of the highest priority message (the lowest ID) exchange the first, etc. and I have some plans for that. I do not think that CAN emulation is and will be some real performance bottleneck in QEMU use for embedded systems development so I would like to keep there space for future more precise emulation.

Same with reset on the chip core level, I think that its redundant call does not cause any performance problems, but I would be happy if the controller codes are reusable for wide scenarios. I have written and used LinCAN with such controllers at time of ISA bus on PC. I would be happy if we have mechanism how to map them on SoCs with FPGAs. Unfortunately this valid and very usesfull feature

(
for example for our RTEMS effort on Zynq
https://docs.rtems.org/docs/main/bsp-howto/can.html
and ideally even on PolarFire (as time allows) where even NuttX can be tested and CI run
https://github.com/apache/nuttx/tree/master/drivers/can
)

hit concrete wall in May, without any suggestion how to make that needed use of QEMU for CI acceptable.

But our SJA1000 code is already used by Espressif in their QEMU fork
https://github.com/espressif/qemu/tree/esp-develop/hw/net/can
so there is more proven use out of PCI based cards. CTU CAN FD is used mostly on FPGAs but here are MCUs with it so again, even if the usability of mainline QEMU for FPGA development would stay blocked, there are standard, hopefully non problematic, regular machine code initiated uses of the CAN controllers which are part of QEMU.

So I would be happy if we can thought about that wider use to check that it would not be more problematic in future if some code is optimized.

On the other hand, it is right that even in esp32_twai.c case, the integration is based on RESETTABLE_CLASS and esp32_twai_reset() calls can_sja_hardware_reset() explicitly. So can_sja_hardware_reset() during can_sja_init() is not strictly necessary.

Back to sabrelite FlexCAN support series. In general I agree with the patch series and I have consulted and reviewed it multiple times. So it can be considered to be approved by me that it is functionally OK as well as it respects copyright requirements etc. I add my

As for individual formatting and may be some debug prints, I would allow it to go in in its actual form and then reduce these latter that we have state with more, may it be even abundant, debug in mainline recorded. But I expect that there could be more request for style and details from more experienced QEMU developers.

There is one unresolved patch check report about
DEVICE_NATIVE_ENDIAN

(quoted diff) +16 -0
+static const struct MemoryRegionOps flexcan_ops = {
+    .read = flexcan_mem_read,
+    .write = flexcan_mem_write,
+    .endianness = DEVICE_NATIVE_ENDIAN,
+    .valid = {
+        .min_access_size = 1,
+        .max_access_size = 4,
+        .unaligned = true,
+        .accepts = flexcan_mem_accepts
+    },
+    .impl = {
+        .min_access_size = 4,
+        .max_access_size = 4,
+        .unaligned = false
+    },
+};

But I do not know what I can suggest there. The device is infernally accessed by 32-bits words and should be mapped in native format because same core is used on little-endian and may it be even bi-endian ARMs[*1] and for sure on big-endian PowerPCs. We believe that native endianness with host is the best option in this case. Extending .valid.max_access_size to 8 is right and probably require for 64-bit targets as I understand from your patches.

[*1] as I have used bi-endian ARMs they mapped peripherals often native way on 32-bit entities. So again, fixed DEVICE_BIG_ENDIAN or DEVICE_LITTLE_ENDIAN is incorrect in such case.

Best wishes,

Pavel

Pavel Pisa
phone: +420 603531357
e-mail: [email protected]
Department of Control Engineering FEE CVUT
Karlovo namesti 13, 121 35, Prague 2
university: http://control.fel.cvut.cz/

   personal:   http://cmp.felk.cvut.cz/~pisa
   social:     https://social.kernel.org/ppisa
   projects:   https://www.openhub.net/accounts/ppisa

CAN related:http://canbus.pages.fel.cvut.cz/ RISC-V education: https://comparch.edu.cvut.cz/
Open Technologies Research Education and Exchange Services
https://gitlab.fel.cvut.cz/otrees/org/-/wikis/home