{"id":2231479,"url":"http://patchwork.ozlabs.org/api/patches/2231479/?format=json","web_url":"http://patchwork.ozlabs.org/project/qemu-devel/patch/6d65c7c296fe5c7e4305cbcca9208babbee106a6.1777571962.git.matyas.bobek@gmail.com/","project":{"id":14,"url":"http://patchwork.ozlabs.org/api/projects/14/?format=json","name":"QEMU Development","link_name":"qemu-devel","list_id":"qemu-devel.nongnu.org","list_email":"qemu-devel@nongnu.org","web_url":"","scm_url":"","webscm_url":"","list_archive_url":"","list_archive_url_format":"","commit_url_format":""},"msgid":"<6d65c7c296fe5c7e4305cbcca9208babbee106a6.1777571962.git.matyas.bobek@gmail.com>","list_archive_url":null,"date":"2026-04-30T18:26:06","name":"[v3,4/7] hw/net/can/flexcan: NXP FlexCAN core emulation","commit_ref":null,"pull_url":null,"state":"new","archived":false,"hash":"f3237a5ba0cfe761f2e67ecb305d5308e51e2df9","submitter":{"id":92267,"url":"http://patchwork.ozlabs.org/api/people/92267/?format=json","name":"Matyáš Bobek","email":"matyas.bobek@gmail.com"},"delegate":null,"mbox":"http://patchwork.ozlabs.org/project/qemu-devel/patch/6d65c7c296fe5c7e4305cbcca9208babbee106a6.1777571962.git.matyas.bobek@gmail.com/mbox/","series":[{"id":502356,"url":"http://patchwork.ozlabs.org/api/series/502356/?format=json","web_url":"http://patchwork.ozlabs.org/project/qemu-devel/list/?series=502356","date":"2026-04-30T18:26:02","name":"hw/arm/sabrelite: Add FlexCAN support","version":3,"mbox":"http://patchwork.ozlabs.org/series/502356/mbox/"}],"comments":"http://patchwork.ozlabs.org/api/patches/2231479/comments/","check":"pending","checks":"http://patchwork.ozlabs.org/api/patches/2231479/checks/","tags":{},"related":[],"headers":{"Return-Path":"<qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org>","X-Original-To":"incoming@patchwork.ozlabs.org","Delivered-To":"patchwork-incoming@legolas.ozlabs.org","Authentication-Results":["legolas.ozlabs.org;\n\tdkim=pass (2048-bit key;\n unprotected) header.d=gmail.com header.i=@gmail.com header.a=rsa-sha256\n header.s=20251104 header.b=DgHB2Phz;\n\tdkim-atps=neutral","legolas.ozlabs.org;\n spf=pass (sender SPF authorized) smtp.mailfrom=nongnu.org\n (client-ip=209.51.188.17; helo=lists1p.gnu.org;\n envelope-from=qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org;\n receiver=patchwork.ozlabs.org)"],"Received":["from lists1p.gnu.org (lists1p.gnu.org [209.51.188.17])\n\t(using TLSv1.2 with cipher ECDHE-ECDSA-AES256-GCM-SHA384 (256/256 bits))\n\t(No client certificate requested)\n\tby legolas.ozlabs.org (Postfix) with ESMTPS id 4g62hx1cJFz1yK1\n\tfor <incoming@patchwork.ozlabs.org>; Fri, 01 May 2026 04:27:43 +1000 (AEST)","from localhost ([::1] helo=lists1p.gnu.org)\n\tby lists1p.gnu.org with esmtp (Exim 4.90_1)\n\t(envelope-from <qemu-devel-bounces@nongnu.org>)\n\tid 1wIW6X-0006kw-UR; Thu, 30 Apr 2026 14:27:18 -0400","from eggs.gnu.org ([2001:470:142:3::10])\n by lists1p.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256)\n (Exim 4.90_1) (envelope-from <matyas.bobek@gmail.com>)\n id 1wIW61-0006Xb-Sz\n for qemu-devel@nongnu.org; Thu, 30 Apr 2026 14:26:50 -0400","from mail-wr1-x42e.google.com ([2a00:1450:4864:20::42e])\n by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128)\n (Exim 4.90_1) (envelope-from <matyas.bobek@gmail.com>)\n id 1wIW5i-0005Ue-3W\n for qemu-devel@nongnu.org; Thu, 30 Apr 2026 14:26:33 -0400","by mail-wr1-x42e.google.com with SMTP id\n ffacd0b85a97d-44261378651so1557950f8f.0\n for <qemu-devel@nongnu.org>; Thu, 30 Apr 2026 11:26:24 -0700 (PDT)","from acidburn.pod.cvut.cz (acidburn.pod.cvut.cz. [147.32.90.2])\n by smtp.gmail.com with ESMTPSA id\n ffacd0b85a97d-447b76e5c00sm14674925f8f.25.2026.04.30.11.26.21\n (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256);\n Thu, 30 Apr 2026 11:26:22 -0700 (PDT)"],"DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/relaxed;\n d=gmail.com; s=20251104; t=1777573583; x=1778178383; darn=nongnu.org;\n h=content-transfer-encoding:mime-version:references:in-reply-to\n :message-id:date:subject:cc:to:from:from:to:cc:subject:date\n :message-id:reply-to;\n bh=PBdg4YQJjucaXluY8oTcVoBiEDn5/59abAbeWnL88T0=;\n b=DgHB2PhzX4vVD33zXST2MXE/CAw5UBiTn11u1obPdlS6VCt0I65hTJrk5O3L8PXftu\n BPvZaKFcZOa7amJkd3y6Oh2bU32ar3a48/bMZmb5fa2EaSBzWkeTeDRYSB+AC5sC13GH\n l0C6gG5Dk2DVrsiCG5YRB8ipk6lpTfEcxjB4aI4GrUvilNLZaNSS7uusGhyeNng6w4Zn\n Vzm0wBAQzf86wZ1i5yk4VxnPjsN+7hVv2EpOMWtUz+c3y+/DN4EshTkwdZ1ihkQUdlxw\n wydIEmsAulO4CYLmCTngt74dg/NhNfc5lmvzOM0WhaGIxN67NGlxPfnqbjgcMwczCUEo\n EmQw==","X-Google-DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/relaxed;\n d=1e100.net; s=20251104; t=1777573583; x=1778178383;\n h=content-transfer-encoding:mime-version:references:in-reply-to\n :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from\n :to:cc:subject:date:message-id:reply-to;\n bh=PBdg4YQJjucaXluY8oTcVoBiEDn5/59abAbeWnL88T0=;\n b=hhTQWg5Em/5gUx4n/Gmuwo4aD6LR+cDazQoKVZsbq7pdB8tTqGdnCKBJqW5meMF/Zk\n VPoER0a6Pd9xOz2CuaPQFnK7ramIbOrPBtqUtHyg87M3bgOsCOGxyvWAjxXAtEgs8inF\n r1a9/MC53NPS2aNfVMOYFlh586pyoP+zB6t1BLlimdtFx3r2IvkBfa3jpHUXOngcugW5\n wwQ4Y2XMAyFi678h7GcmJr8fvGcT5g9OielcJUIoXHdmhCbheBYjhhhpH4nXDIZnd0M3\n UiSKen/D8PZmsIeLEBwblE9viJiB/P8geLl0eyW9r24LlASL7xTxFfI7/wjldg1GEL5+\n ohRw==","X-Gm-Message-State":"AOJu0Yw33H0wamFKbqJNJLwAng1FT/rI56I+ouA0A2zVrLIf00OieeiX\n 3vyXKfabzhQTTjgn4cYa/75+nTPYBHU6nvOHtgcIonly4as2ty3iOkZR0l4A2yZ6","X-Gm-Gg":"AeBDievg/E4Lamh71gjSEZ5D5/cuFBtvt4AOmb1IQ8cKpCargbMVmRyQexit0HdLu4B\n gbuPr0g0Dwq/XwrYqUCQ/gLWfQYxKOjE9pIKdt6qPMs7H2+/aNus9rdYZ6d/kFQnaYRqi8ien7c\n xxxfl7uVLrauT5Dy6Hx4lJoSxLSg08dFtFI70WClB5P3BNcvMGxz4Y7zOZ1QmM8s2jQH8nuNqho\n a9UW0vOmrYs0pCm7+UZxedoj74hw0d3JONyqW1hkatYeRhypij63nwndPrdwLthImylTeulECDQ\n MasQPGyron1Go3fD54U2pJ9DjGY4Go72xDHpDkYyIyKGlL6+76OCw8yxlPkoe3c+5lgnC8wAC53\n gJsctJplGRHwAToH/ao1fFpLz73ozbCkJue/E4dz7K6/nah7JceTW4z7fHa5HXIAcs2Bdef+4I9\n T091PKhG7UbUXFJqE7UOpXpijQMjUcmnomHgKKOGqsz8xTaFDNi9najH5FCvB28D3d","X-Received":"by 2002:a5d:5a4c:0:b0:446:e2ba:45b with SMTP id\n ffacd0b85a97d-4494e5f1c7dmr4292779f8f.11.1777573583112;\n Thu, 30 Apr 2026 11:26:23 -0700 (PDT)","From":"=?utf-8?b?TWF0ecOhxaEgQm9iZWs=?= <matyas.bobek@gmail.com>","To":"qemu-devel@nongnu.org, Matyas Bobek <bobekmat@fel.cvut.cz>,\n Pavel Pisa <pisa@fel.cvut.cz>, Bernhard Beschow <shentey@gmail.com>","Cc":"qemu-arm@nongnu.org, Marc Kleine-Budde <mkl@pengutronix.de>,\n Oliver Hartkopp <socketcan@hartkopp.net>,\n Nikita Ostrenkov <n.ostrenkov@gmail.com>,\n Peter Maydell <peter.maydell@linaro.org>,\n =?utf-8?b?TWF0ecOhxaEgQm9iZWs=?= <matyas.bobek@gmail.com>","Subject":"[PATCH v3 4/7] hw/net/can/flexcan: NXP FlexCAN core emulation","Date":"Thu, 30 Apr 2026 20:26:06 +0200","Message-ID":"\n <6d65c7c296fe5c7e4305cbcca9208babbee106a6.1777571962.git.matyas.bobek@gmail.com>","X-Mailer":"git-send-email 2.53.0","In-Reply-To":"<cover.1777571962.git.matyas.bobek@gmail.com>","References":"<cover.1777571962.git.matyas.bobek@gmail.com>","MIME-Version":"1.0","Content-Type":"text/plain; charset=UTF-8","Content-Transfer-Encoding":"8bit","Received-SPF":"pass client-ip=2a00:1450:4864:20::42e;\n envelope-from=matyas.bobek@gmail.com; helo=mail-wr1-x42e.google.com","X-Spam_score_int":"-10","X-Spam_score":"-1.1","X-Spam_bar":"-","X-Spam_report":"(-1.1 / 5.0 requ) BAYES_00=-1.9, DKIM_SIGNED=0.1,\n DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1, FORGED_GMAIL_RCVD=1,\n FREEMAIL_FROM=0.001, RCVD_IN_DNSWL_NONE=-0.0001, SPF_HELO_NONE=0.001,\n SPF_PASS=-0.001 autolearn=no autolearn_force=no","X-Spam_action":"no action","X-BeenThere":"qemu-devel@nongnu.org","X-Mailman-Version":"2.1.29","Precedence":"list","List-Id":"qemu development <qemu-devel.nongnu.org>","List-Unsubscribe":"<https://lists.nongnu.org/mailman/options/qemu-devel>,\n <mailto:qemu-devel-request@nongnu.org?subject=unsubscribe>","List-Archive":"<https://lists.nongnu.org/archive/html/qemu-devel>","List-Post":"<mailto:qemu-devel@nongnu.org>","List-Help":"<mailto:qemu-devel-request@nongnu.org?subject=help>","List-Subscribe":"<https://lists.nongnu.org/mailman/listinfo/qemu-devel>,\n <mailto:qemu-devel-request@nongnu.org?subject=subscribe>","Errors-To":"qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org","Sender":"qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org"},"content":"Added the FlexCAN2 emulator implementation core, with\nCAN_FLEXCAN Kconfig flag and MAINTAINERS entry.\n\nFlexCAN2 version can be found in i.MX6 SoCs and others.\n\nMore information about the implementation can be found in [1].\n\n[1] http://dspace.cvut.cz/bitstream/handle/10467/122654/F3-BP-2025-Bobek-Matyas-BP_Bobek_FlexCAN_final_4.pdf\n\nSigned-off-by: Matyáš Bobek <matyas.bobek@gmail.com>\nSigned-off-by: Pavel Pisa <pisa@fel.cvut.cz>\nTested-by: Pavel Pisa <pisa@fel.cvut.cz>\nReviewed-by: Bernhard Beschow <shentey@gmail.com>\nReviewed-by: Pavel Pisa <pisa@fel.cvut.cz>\n---\n MAINTAINERS               |    8 +\n hw/net/Kconfig            |    5 +\n hw/net/can/flexcan.c      | 1395 +++++++++++++++++++++++++++++++++++++\n hw/net/can/flexcan_regs.h |  193 +++++\n hw/net/can/meson.build    |    1 +\n hw/net/can/trace-events   |   18 +\n include/hw/net/flexcan.h  |  139 ++++\n 7 files changed, 1759 insertions(+)\n create mode 100644 hw/net/can/flexcan.c\n create mode 100644 hw/net/can/flexcan_regs.h\n create mode 100644 include/hw/net/flexcan.h","diff":"diff --git a/MAINTAINERS b/MAINTAINERS\nindex f109e46172..59a3614a58 100644\n--- a/MAINTAINERS\n+++ b/MAINTAINERS\n@@ -2091,6 +2091,14 @@ F: hw/net/can/xlnx-*\n F: include/hw/net/xlnx-*\n F: tests/qtest/xlnx-can*-test*\n \n+FlexCAN\n+M: Matyas Bobek <matyas.bobek@gmail.com>\n+M: Pavel Pisa <pisa@cmp.felk.cvut.cz>\n+S: Maintained\n+F: hw/net/can/flexcan.c\n+F: hw/net/can/flexcan_regs.h\n+F: include/hw/net/flexcan.h\n+\n EDU\n M: Jiri Slaby <jslaby@suse.cz>\n S: Maintained\ndiff --git a/hw/net/Kconfig b/hw/net/Kconfig\nindex f9a1dfb80d..b56a173eed 100644\n--- a/hw/net/Kconfig\n+++ b/hw/net/Kconfig\n@@ -157,3 +157,8 @@ config CAN_CTUCANFD_PCI\n     default y if PCI_DEVICES\n     depends on PCI && CAN_CTUCANFD\n     select CAN_BUS\n+\n+config CAN_FLEXCAN\n+    bool\n+    depends on IMX\n+    select CAN_BUS\ndiff --git a/hw/net/can/flexcan.c b/hw/net/can/flexcan.c\nnew file mode 100644\nindex 0000000000..61a59e6086\n--- /dev/null\n+++ b/hw/net/can/flexcan.c\n@@ -0,0 +1,1395 @@\n+/*\n+ * QEMU model of the NXP FLEXCAN device.\n+ *\n+ * This implementation is based on the following reference manual:\n+ * i.MX 6Dual/6Quad Applications Processor Reference Manual\n+ * Document Number: IMX6DQRM, Rev. 6, 05/2020\n+ *\n+ * Copyright (c) 2025 Matyas Bobek <matyas.bobek@gmail.com>\n+ *\n+ * Based on CTU CAN FD emulation implemented by Jan Charvat.\n+ *\n+ * SPDX-License-Identifier: GPL-2.0-or-later\n+ */\n+\n+#include \"qemu/osdep.h\"\n+#include \"qemu/log.h\"\n+#include \"hw/core/sysbus.h\"\n+#include \"qapi/error.h\"\n+#include \"hw/core/irq.h\"\n+#include \"migration/vmstate.h\"\n+#include \"net/can_emu.h\"\n+#include \"hw/core/qdev-properties.h\"\n+#include \"trace.h\"\n+\n+#include \"hw/net/flexcan.h\"\n+#include \"flexcan_regs.h\"\n+#include \"qemu/timer.h\"\n+\n+/*\n+ * Indicates MB w/ received frame has not been serviced yet\n+ * This is an emulator-only flag in position of unused (reserved) bit\n+ * of message buffer control register\n+ */\n+#define FLEXCAN_MB_CNT_NOT_SRV          BIT(23)\n+/**\n+ * if no MB is locked, FlexcanState.locked_mb\n+ * is set to FLEXCAN_NO_MB_LOCKED\n+ */\n+#define FLEXCAN_NO_MB_LOCKED            -1\n+/**\n+ * if no frame is waiting in the SMB, FlexcanState.smb_target_mbid\n+ * is set to FLEXCAN_SMB_EMPTY\n+ */\n+#define FLEXCAN_SMB_EMPTY               -1\n+/**\n+ * When the module is disabled or in freeze mode,\n+ * the timer is not running. That is indicated by setting\n+ * FlexcanState.timer_start to FLEXCAN_TIMER_STOPPED.\n+ */\n+#define FLEXCAN_TIMER_STOPPED           -1\n+\n+/* These constants are returned by flexcan_fifo_rx() and flexcan_mb_rx(), */\n+enum FlexcanRx {\n+/* Retry the other receiving mechanism (ie. message bufer or mailbox). */\n+    FLEXCAN_RX_SEARCH_RETRY,\n+/* The frame was received and stored. */\n+    FLEXCAN_RX_SEARCH_ACCEPT,\n+/* The frame was filtered out and dropped. */\n+    FLEXCAN_RX_SEARCH_DROPPED,\n+};\n+\n+/*\n+ * These constants are returned by flexcan_mb_rx_check_mb().\n+ * See flexcan_mb_rx_check_mb() kerneldoc for details.\n+ */\n+enum FlexcanCheck {\n+    FLEXCAN_CHECK_MB_NIL = 0,\n+    FLEXCAN_CHECK_MB_MATCH = 3,\n+    FLEXCAN_CHECK_MB_MATCH_NON_FREE = 1,\n+    FLEXCAN_CHECK_MB_MATCH_LOCKED = 5,\n+};\n+\n+static const FlexcanRegs flexcan_regs_write_mask = {\n+    .mcr = 0xF6EB337F,\n+    .ctrl = 0xFFFFFFFF,\n+    .timer = 0xFFFFFFFF,\n+    .tcr = 0xFFFFFFFF,\n+    .rxmgmask = 0xFFFFFFFF,\n+    .rx14mask = 0xFFFFFFFF,\n+    .rx15mask = 0xFFFFFFFF,\n+    .ecr = 0xFFFFFFFF,\n+    .esr = 0xFFFFFFFF,\n+    .imask2 = 0xFFFFFFFF,\n+    .imask1 = 0xFFFFFFFF,\n+    .iflag2 = 0,\n+    .iflag1 = 0,\n+    .ctrl2 = 0xFFFFFFFF,\n+    .esr2 = 0,\n+    .imeur = 0,\n+    .lrfr = 0,\n+    .crcr = 0,\n+    .rxfgmask = 0xFFFFFFFF,\n+    .rxfir = 0,\n+    .cbt = 0,\n+    ._reserved2 = 0,\n+    .dbg1 = 0,\n+    .dbg2 = 0,\n+    .mbs = { [0 ... 63] = {\n+        .can_ctrl = 0xFFFFFFFF & ~FLEXCAN_MB_CNT_NOT_SRV,\n+        .can_id = 0xFFFFFFFF,\n+        .data = { 0xFFFFFFFF, 0xFFFFFFFF },\n+    } },\n+    ._reserved4 = {0},\n+    .rximr = { [0 ... 63] = 0xFFFFFFFF },\n+    ._reserved5 = {0},\n+    .gfwr_mx6 = 0xFFFFFFFF,\n+    ._reserved6 = {0},\n+    ._reserved8 = {0},\n+    .rx_smb0_raw = {0, 0, 0, 0},\n+    .rx_smb1 = {0, 0, 0, 0},\n+};\n+static const FlexcanRegs flexcan_regs_reset_mask = {\n+    .mcr = 0x80000000,\n+    .ctrl = 0xFFFFFFFF,\n+    .timer = 0,\n+    .tcr = 0,\n+    .rxmgmask = 0xFFFFFFFF,\n+    .rx14mask = 0xFFFFFFFF,\n+    .rx15mask = 0xFFFFFFFF,\n+    .ecr = 0,\n+    .esr = 0,\n+    .imask2 = 0,\n+    .imask1 = 0,\n+    .iflag2 = 0,\n+    .iflag1 = 0,\n+    .ctrl2 = 0xFFFFFFFF,\n+    .esr2 = 0,\n+    .imeur = 0,\n+    .lrfr = 0,\n+    .crcr = 0,\n+    .rxfgmask = 0xFFFFFFFF,\n+    .rxfir = 0xFFFFFFFF,\n+    .cbt = 0,\n+    ._reserved2 = 0,\n+    .dbg1 = 0,\n+    .dbg2 = 0,\n+    .mb = {0xFFFFFFFF},\n+    ._reserved4 = {0},\n+    .rximr = {0xFFFFFFFF},\n+    ._reserved5 = {0},\n+    .gfwr_mx6 = 0,\n+    ._reserved6 = {0},\n+    ._reserved8 = {0},\n+    .rx_smb0_raw = {0, 0, 0, 0},\n+    .rx_smb1 = {0, 0, 0, 0},\n+};\n+\n+/* length of buffer used to format register names in trace output */\n+#define FLEXCAN_DBG_BUF_LEN 16\n+\n+/**\n+ * flexcan_dbg_mb_code_strs - Readable names for CODE field codes\n+ *\n+ * Readable names for possible values of CODE field in message buffer\n+ * control word.\n+ */\n+static const char *flexcan_dbg_mb_code_strs[16] = {\n+    \"INACTIVE_RX\",\n+    \"FULL\",\n+    \"EMPTY\",\n+    \"OVERRUN\",\n+    \"INACTIVE_TX\",\n+    \"RANSWER\",\n+    \"DATA\",\n+    \"TANSWER\"\n+};\n+\n+/**\n+ * flexcan_dbg_mb_code() - Get the string representation of a mailbox code\n+ * @mb_ctrl: The mailbox control register value\n+ * @buf: The buffer to store the string representation\n+ *\n+ * Return: Either constant string or string formatted into @buf\n+ */\n+static const char *flexcan_dbg_mb_code(uint32_t mb_ctrl, char *buf)\n+{\n+    uint32_t code = mb_ctrl & FLEXCAN_MB_CODE_MASK;\n+    uint32_t code_idx = code >> 24;\n+    if (code == FLEXCAN_MB_CODE_TX_ABORT) {\n+        return \"ABORT\";\n+    } else {\n+        const char *code_str = flexcan_dbg_mb_code_strs[code_idx >> 1];\n+        if (code_idx & 1) {\n+            g_snprintf(buf, FLEXCAN_DBG_BUF_LEN, \"%s+BUSY\", code_str);\n+            return buf;\n+        }\n+\n+        return code_str;\n+    }\n+}\n+\n+static const char *flexcan_dbg_reg_name_fixed(hwaddr addr)\n+{\n+    switch (addr) {\n+    case offsetof(FlexcanRegs, mcr):\n+        return \"MCR\";\n+    case offsetof(FlexcanRegs, ctrl):\n+        return \"CTRL\";\n+    case offsetof(FlexcanRegs, timer):\n+        return \"TIMER\";\n+    case offsetof(FlexcanRegs, esr):\n+        return \"ESR\";\n+    case offsetof(FlexcanRegs, rxmgmask):\n+        return \"RXMGMASK\";\n+    case offsetof(FlexcanRegs, rx14mask):\n+        return \"RX14MASK\";\n+    case offsetof(FlexcanRegs, rx15mask):\n+        return \"RX15MASK\";\n+    case offsetof(FlexcanRegs, rxfgmask):\n+        return \"RXFGMASK\";\n+    case offsetof(FlexcanRegs, ecr):\n+        return \"ECR\";\n+    case offsetof(FlexcanRegs, ctrl2):\n+        return \"CTRL2\";\n+    case offsetof(FlexcanRegs, imask2):\n+        return \"IMASK2\";\n+    case offsetof(FlexcanRegs, imask1):\n+        return \"IMASK1\";\n+    case offsetof(FlexcanRegs, iflag2):\n+        return \"IFLAG2\";\n+    case offsetof(FlexcanRegs, iflag1):\n+        return \"IFLAG1\";\n+    }\n+    return NULL;\n+}\n+\n+static inline void flexcan_trace_mem_op(FlexcanState *s, hwaddr addr,\n+                                        uint32_t value, int size, bool is_wr)\n+{\n+    if (trace_event_get_state_backends(TRACE_FLEXCAN_MEM_OP)) {\n+        const char *reg_name = \"unknown\";\n+        char reg_name_buf[FLEXCAN_DBG_BUF_LEN] = { 0 };\n+        const char *reg_name_fixed = flexcan_dbg_reg_name_fixed(addr);\n+        const char *op_string = is_wr ? \"write\" : \"read\";\n+\n+        if (reg_name_fixed) {\n+            reg_name = reg_name_fixed;\n+        } else if (addr >= 0x80 && addr < 0x480) {\n+            int mbidx = (addr - 0x80) / 16;\n+            g_snprintf(reg_name_buf, sizeof(reg_name_buf), \"MB%i\", mbidx);\n+            reg_name = reg_name_buf;\n+        } else if (addr >= 0x880 && addr < 0x9e0) {\n+            int id = (addr - 0x880) / 4;\n+            g_snprintf(reg_name_buf, sizeof(reg_name_buf), \"RXIMR%i\", id);\n+            reg_name = reg_name_buf;\n+        }\n+\n+        trace_flexcan_mem_op(DEVICE(s)->canonical_path, op_string, value, addr,\n+                             reg_name, size);\n+    }\n+}\n+\n+static enum FlexcanRx flexcan_mb_rx(FlexcanState *s,\n+                                    const qemu_can_frame *frame);\n+static void flexcan_mb_unlock(FlexcanState *s);\n+\n+/* ========== Mailbox Utils ========== */\n+\n+/**\n+ * flexcan_mailbox_count() - Get number of enabled mailboxes\n+ * @s: FlexCAN device pointer\n+ *\n+ * Count is based on MCR[MAXMB] field. Note that some of those mailboxes\n+ * might be part of queue or queue ID filters or ordinary message buffers.\n+ */\n+static inline int flexcan_enabled_mailbox_count(const FlexcanState *s)\n+{\n+    return (s->regs.mcr & FLEXCAN_MCR_MAXMB(UINT32_MAX)) + 1;\n+}\n+\n+/**\n+ * flexcan_get_first_message_buffer() - Get pointer to first message buffer\n+ * @s: FlexCAN device pointer\n+ *\n+ * In context of this function, message buffer means a mailbox which is not\n+ * a queue element nor a queue filter. Note this function does not take\n+ * MCR[MAXMB] into account, meaning that the returned mailbox\n+ * might be disabled.\n+ */\n+static FlexcanRegsMessageBuffer *flexcan_get_first_message_buffer(\n+                                 FlexcanState *s)\n+{\n+    if (s->regs.mcr & FLEXCAN_MCR_FEN) {\n+        int rffn = (s->regs.ctrl2 & FLEXCAN_CTRL2_RFFN(UINT32_MAX)) >> 24;\n+        return s->regs.mbs + 8 + 2 * rffn;\n+    }\n+\n+    return s->regs.mbs;\n+}\n+\n+/**\n+ * flexcan_get_last_enabled_mailbox() - Get pointer to last enabled mailbox.\n+ * @s: FlexCAN device pointer\n+ *\n+ * When used with flexcan_get_first_message_buffer(), all mailboxes *ptr in\n+ * range `first_message_buffer() <= ptr <= last_enabled_mailbox` are valid\n+ * message buffer mailboxes.\n+ *\n+ * Return: Last enabled mailbox in MCR[MAXMB] sense. The mailbox might be\n+ * of any type.\n+ */\n+static inline FlexcanRegsMessageBuffer *flexcan_get_last_enabled_mailbox(\n+                                 FlexcanState *s)\n+{\n+    return s->regs.mbs + flexcan_enabled_mailbox_count(s);\n+}\n+\n+/* ========== Free-running Timer ========== */\n+static inline int64_t flexcan_get_time(void)\n+{\n+    return qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);\n+}\n+\n+/**\n+ * flexcan_get_bitrate() - Calculate CAN bitrate (in Hz)\n+ * @s: FlexCAN device pointer\n+ *\n+ * The bitrate is determined by FlexCAN configuration in CTRL1 register,\n+ * and CCM co\n+ */\n+static uint32_t flexcan_get_bitrate(FlexcanState *s)\n+{\n+    uint32_t conf_presdiv = (s->regs.ctrl & FLEXCAN_CTRL_PRESDIV_MASK) >> 24;\n+    uint32_t conf_pseg1 = (s->regs.ctrl & FLEXCAN_CTRL_PSEG1_MASK) >> 19;\n+    uint32_t conf_pseg2 = (s->regs.ctrl & FLEXCAN_CTRL_PSEG2_MASK) >> 16;\n+    uint32_t conf_propseg = s->regs.ctrl & FLEXCAN_CTRL_PROPSEG_MASK;\n+\n+    /* N of time quanta for segments */\n+    uint32_t tseg1 = 2 + conf_pseg1 + conf_propseg;\n+    uint32_t tseg2 = 1 + conf_pseg2;\n+    uint32_t total_qpb = 1 + tseg1 + tseg2;\n+\n+    uint32_t pe_freq, s_freq, bitrate;\n+\n+    assert(s->ccm);\n+\n+    /* s_freq: CAN clock from CCM divided by the prescaler */\n+    pe_freq = imx_ccm_get_clock_frequency(s->ccm, CLK_CAN);\n+    s_freq = pe_freq / (1 + conf_presdiv);\n+    bitrate = s_freq / total_qpb;\n+\n+    trace_flexcan_get_bitrate(DEVICE(s)->canonical_path, pe_freq,\n+                              1 + conf_presdiv, s_freq, tseg1, tseg2, total_qpb,\n+                              bitrate);\n+    return bitrate;\n+}\n+\n+/**\n+ * int128_mul_6464() - Multiply two 64-bit integers into a 128-bit one\n+ */\n+static Int128 int128_muls_6464(int64_t ai, int64_t bi)\n+{\n+    uint64_t l, h;\n+\n+    muls64(&l, &h, ai, bi);\n+    return int128_make128(l, h);\n+}\n+\n+/**\n+ * flexcan_get_timestamp() - Get current value of the 16-bit free-running timer\n+ * @s: FlexCAN device pointer\n+ * @mk_unique: if true, make the timestamp unique by incrementing it if needed\n+ */\n+static uint32_t flexcan_get_timestamp(FlexcanState *s, bool mk_unique)\n+{\n+    const Int128 nanoseconds_in_second = int128_makes64((int64_t)1e9);\n+    Int128 ncycles, cycles128;\n+    int64_t current_time, elapsed_time_ns;\n+    uint64_t cycles;\n+    uint32_t rv, shift = 0;\n+\n+    if (s->timer_start == FLEXCAN_TIMER_STOPPED) {\n+        /* timer is not running, return last value */\n+        trace_flexcan_get_timestamp(DEVICE(s)->canonical_path, -1, 0, 0, 0,\n+                                    s->regs.timer);\n+        return s->regs.timer;\n+    }\n+\n+    current_time = flexcan_get_time();\n+    elapsed_time_ns = current_time - s->timer_start;\n+    if (elapsed_time_ns < 0) {\n+        trace_flexcan_timer_overflow(DEVICE(s)->canonical_path, current_time,\n+                                     s->timer_start, elapsed_time_ns);\n+        return 0xFFFF;\n+    }\n+\n+    ncycles = int128_muls_6464(s->timer_freq, elapsed_time_ns);\n+    cycles128 = int128_divs(ncycles, nanoseconds_in_second);\n+    /* 64 bits hold for over 50k years at 10MHz */\n+    cycles = int128_getlo(cycles128);\n+\n+    if (mk_unique && cycles <= s->last_rx_timer_cycles) {\n+        shift = 1;\n+        cycles = s->last_rx_timer_cycles + shift;\n+    }\n+\n+    s->last_rx_timer_cycles = cycles;\n+    rv = (uint32_t)cycles & 0xFFFF;\n+\n+    trace_flexcan_get_timestamp(DEVICE(s)->canonical_path,\n+                                elapsed_time_ns / (uint32_t)1e6,\n+                                s->timer_freq, cycles, shift, rv);\n+    return rv;\n+}\n+\n+/**\n+ * flexcan_timer_start() - Start the free-running timer\n+ * @s: FlexCAN device pointer\n+ *\n+ * This should be called when the module leaves freeze mode.\n+ */\n+static void flexcan_timer_start(FlexcanState *s)\n+{\n+    s->timer_freq = flexcan_get_bitrate(s);\n+    s->timer_start = flexcan_get_time();\n+    s->last_rx_timer_cycles = 0;\n+\n+    trace_flexcan_timer_start(DEVICE(s)->canonical_path, s->timer_freq,\n+                              s->regs.timer);\n+}\n+\n+/**\n+ * flexcan_timer_stop() - Stop the free-running timer\n+ * @s: FlexCAN device pointer\n+ *\n+ * This should be called when the module enters freeze mode.\n+ * Stores the current timestamp in the TIMER register.\n+ */\n+static void flexcan_timer_stop(FlexcanState *s)\n+{\n+    s->regs.timer = flexcan_get_timestamp(s, false);\n+    s->timer_start = FLEXCAN_TIMER_STOPPED;\n+\n+    trace_flexcan_timer_stop(DEVICE(s)->canonical_path, s->timer_freq,\n+                             s->regs.timer);\n+}\n+\n+/* ========== IRQ handling ========== */\n+/**\n+ * flexcan_irq_update() - Update qemu_irq line based on interrupt registers\n+ * @s: FlexCAN device pointer\n+ */\n+static void flexcan_irq_update(FlexcanState *s)\n+{\n+    uint32_t mb_irqs[2];\n+    int irq_pending;\n+    /* these are all interrupt sources from FlexCAN */\n+    /* mailbox interrupt sources */\n+    mb_irqs[0] = s->regs.iflag1 & s->regs.imask1;\n+    mb_irqs[1] = s->regs.iflag2 & s->regs.imask2;\n+\n+    /**\n+     * these interrupts aren't currently used and they can never be raised\n+     *\n+     * bool irq_wake_up = (s->regs.mcr & FLEXCAN_MCR_WAK_MSK) &&\n+     *                    (s->regs.ecr & FLEXCAN_ESR_WAK_INT);\n+     * bool irq_bus_off = (s->regs.ctrl & FLEXCAN_CTRL_BOFF_MSK) &&\n+     *                    (s->regs.ecr & FLEXCAN_ESR_BOFF_INT);\n+     * bool irq_error = (s->regs.ctrl & FLEXCAN_CTRL_ERR_MSK) &&\n+     *                  (s->regs.ecr & FLEXCAN_ESR_ERR_INT);\n+     * bool irq_tx_warn = (s->regs.ctrl & FLEXCAN_CTRL_TWRN_MSK) &&\n+     *                    (s->regs.ecr & FLEXCAN_ESR_TWRN_INT);\n+     * bool irq_rx_warn = (s->regs.ctrl & FLEXCAN_CTRL_RWRN_MSK) &&\n+     *                    (s->regs.ecr & FLEXCAN_ESR_RWRN_INT);\n+     */\n+\n+    irq_pending = (mb_irqs[0] || mb_irqs[1]) ? 1 : 0;\n+    trace_flexcan_irq_update(DEVICE(s)->canonical_path, mb_irqs[0], mb_irqs[1],\n+                             irq_pending);\n+\n+    qemu_set_irq(s->irq, irq_pending);\n+}\n+\n+/**\n+ * flexcan_irq_iflag_set() - Set IFLAG bit corresponding to MB mbidx\n+ * @s: FlexCAN device pointer\n+ * @mbidx: mailbox index\n+ */\n+static void flexcan_irq_iflag_set(FlexcanState *s, int mbidx)\n+{\n+    if (mbidx < 32) {\n+        s->regs.iflag1 |= BIT(mbidx);\n+    } else {\n+        s->regs.iflag2 |= BIT(mbidx - 32);\n+    }\n+}\n+\n+/**\n+ * flexcan_irq_iflag_clear() - Clear IFLAG bit corresponding to MB mbidx\n+ * @s: FlexCAN device pointer\n+ * @mbidx: mailbox index\n+ */\n+static void flexcan_irq_iflag_clear(FlexcanState *s, int mbidx)\n+{\n+    if (mbidx < 32) {\n+        s->regs.iflag1 &= ~BIT(mbidx);\n+    } else {\n+        s->regs.iflag2 &= ~BIT(mbidx - 32);\n+    }\n+}\n+\n+/* ========== RESET ========== */\n+static void flexcan_reset_local_state(FlexcanState *s)\n+{\n+    uint32_t *reset_mask = (uint32_t *)&flexcan_regs_reset_mask;\n+    for (int i = 0; i < (sizeof(FlexcanRegs) / 4); i++) {\n+        s->regs_raw[i] &= reset_mask[i];\n+    }\n+\n+    s->regs.mcr |= 0x5980000F;\n+    s->locked_mbidx = FLEXCAN_NO_MB_LOCKED;\n+    s->smb_target_mbidx = FLEXCAN_SMB_EMPTY;\n+    s->timer_start = FLEXCAN_TIMER_STOPPED;\n+\n+    trace_flexcan_reset(DEVICE(s)->canonical_path);\n+}\n+\n+static void flexcan_reset_enter(Object *obj, ResetType type)\n+{\n+    FlexcanState *s = CAN_FLEXCAN(obj);\n+\n+    memset(&s->regs, 0, sizeof(s->regs));\n+    flexcan_reset_local_state(s);\n+}\n+\n+static void flexcan_reset_hold(Object *obj, ResetType type)\n+{\n+    FlexcanState *s = CAN_FLEXCAN(obj);\n+\n+    flexcan_irq_update(s);\n+}\n+\n+\n+/* ========== Operation mode control ========== */\n+/**\n+ * flexcan_update_esr() - Update ESR based on mode and CAN bus connection state\n+ * @s: FlexCAN device pointer\n+ */\n+static void flexcan_update_esr(FlexcanState *s)\n+{\n+    bool is_running = (s->regs.mcr & FLEXCAN_MCR_NOT_RDY) == 0;\n+    /* potentially, there could be other influences on ESR[SYNCH] */\n+\n+    if (is_running && s->canbus) {\n+        s->regs.esr |= FLEXCAN_ESR_SYNCH | FLEXCAN_ESR_IDLE;\n+    } else {\n+        s->regs.esr &= ~(FLEXCAN_ESR_SYNCH | FLEXCAN_ESR_IDLE);\n+    }\n+}\n+\n+/**\n+ * flexcan_update_esr() - Process MCR write\n+ * @s: FlexCAN device pointer\n+ * @pv: previously set MCR value\n+ *\n+ * This function expects the new MCR value to be already written in s->regs.mcr.\n+ */\n+static void flexcan_set_mcr(FlexcanState *s, const uint32_t pv)\n+{\n+    uint32_t cv = s->regs.mcr;\n+\n+    /* -- module disable mode -- */\n+    if (!(pv & FLEXCAN_MCR_MDIS) && (cv & FLEXCAN_MCR_MDIS)) {\n+        /* transition to Module Disable mode */\n+        cv |= FLEXCAN_MCR_LPM_ACK;\n+    } else if ((pv & FLEXCAN_MCR_MDIS) && !(cv & FLEXCAN_MCR_MDIS)) {\n+        /* transition from Module Disable mode */\n+        cv &= ~FLEXCAN_MCR_LPM_ACK;\n+    }\n+\n+    /* -- soft reset -- */\n+    if (!(cv & FLEXCAN_MCR_LPM_ACK) && (cv & FLEXCAN_MCR_SOFTRST)) {\n+        if (s->regs.mcr & FLEXCAN_MCR_LPM_ACK) {\n+            qemu_log_mask(LOG_GUEST_ERROR,\n+                          \"%s: invalid soft reset request in low-power mode\",\n+                          DEVICE(s)->canonical_path);\n+        }\n+\n+        flexcan_reset_local_state(s);\n+        cv = s->regs.mcr;\n+    }\n+\n+    /* -- freeze mode -- */\n+    if (!(cv & FLEXCAN_MCR_LPM_ACK) &&\n+        (cv & FLEXCAN_MCR_FRZ) &&\n+        (cv & FLEXCAN_MCR_HALT)) {\n+        cv |= FLEXCAN_MCR_FRZ_ACK;\n+    } else {\n+        cv &= ~FLEXCAN_MCR_FRZ_ACK;\n+    }\n+\n+    /* -- fifo mode -- */\n+    if (\n+        ((pv & FLEXCAN_MCR_FEN) && !(cv & FLEXCAN_MCR_FEN)) ||\n+        (!(pv & FLEXCAN_MCR_FEN) && (cv & FLEXCAN_MCR_FEN))\n+    ) {\n+        /* clear iflags used by fifo */\n+        s->regs.iflag1 &= ~(\n+            FLEXCAN_IFLAG_RX_FIFO_AVAILABLE |\n+            FLEXCAN_IFLAG_RX_FIFO_OVERFLOW |\n+            FLEXCAN_IFLAG_RX_FIFO_WARN\n+        );\n+    }\n+    if (!(pv & FLEXCAN_MCR_FEN) && (cv & FLEXCAN_MCR_FEN)) {\n+        /* zero out fifo region, we rely on zeroed can_ctrl for empty slots */\n+        memset(s->regs.mbs, 0,\n+               FLEXCAN_FIFO_DEPTH * sizeof(FlexcanRegsMessageBuffer));\n+    }\n+\n+    /*\n+     * assert NOT_RDY bit if in disable,\n+     * stop (not implemented) or freeze mode\n+     */\n+    if ((cv & FLEXCAN_MCR_LPM_ACK) || (cv & FLEXCAN_MCR_FRZ_ACK)) {\n+        cv |= FLEXCAN_MCR_NOT_RDY;\n+    } else {\n+        cv &= ~FLEXCAN_MCR_NOT_RDY;\n+    }\n+\n+    if ((pv & FLEXCAN_MCR_NOT_RDY) && !(cv & FLEXCAN_MCR_NOT_RDY)) {\n+        /* module went up, start the timer */\n+        flexcan_timer_start(s);\n+    } else if (!(pv & FLEXCAN_MCR_NOT_RDY) && (cv & FLEXCAN_MCR_NOT_RDY)) {\n+        /* module went down, store the current timer value */\n+        flexcan_timer_stop(s);\n+    }\n+\n+    s->regs.mcr = cv;\n+    flexcan_update_esr(s);\n+    trace_flexcan_set_mcr(\n+        DEVICE(s)->canonical_path,\n+        cv & FLEXCAN_MCR_LPM_ACK ? \"DISABLED\" : \"ENABLED\",\n+        (cv & FLEXCAN_MCR_FRZ_ACK || cv & FLEXCAN_MCR_LPM_ACK) ?\n+            \"FROZEN\" : \"RUNNING\",\n+        cv & FLEXCAN_MCR_FEN ? \"FIFO\" : \"MAILBOX\",\n+        cv & FLEXCAN_MCR_NOT_RDY ? \"NOT_RDY\" : \"RDY\",\n+        s->regs.esr & FLEXCAN_ESR_SYNCH ? \"SYNC\" : \"NOSYNC\"\n+    );\n+}\n+\n+/* ========== TX ========== */\n+static void flexcan_transmit(FlexcanState *s, int mbidx)\n+{\n+    FlexcanRegsMessageBuffer *mb = &s->regs.mbs[mbidx];\n+    qemu_can_frame frame = {\n+        .flags = 0,\n+    };\n+    uint32_t *frame_data = (uint32_t *)&frame.data;\n+    uint32_t timestamp = flexcan_get_timestamp(s, true);\n+\n+    if ((s->regs.ctrl & FLEXCAN_CTRL_LOM) ||\n+        (s->regs.mcr & FLEXCAN_MCR_NOT_RDY)) {\n+        /* no transmiting in listen-only, freeze or low-power mode */\n+        return;\n+    }\n+\n+    if (mb->can_ctrl & FLEXCAN_MB_CNT_IDE) {\n+        /* 29b ID stored in bits [0, 29) */\n+        uint32_t id = mb->can_id & 0x1FFFFFFF;\n+        frame.can_id = id | QEMU_CAN_EFF_FLAG;\n+    } else {\n+        /* 11b ID stored in bits [18, 29) */\n+        uint32_t id = (mb->can_id & (0x7FF << 18)) >> 18;\n+        frame.can_id = id;\n+    }\n+\n+    frame.can_dlc = (mb->can_ctrl & (0xF << 16)) >> 16;\n+\n+    for (int i = 0; i < 2; i++) {\n+        stl_be_p(&frame_data[i], mb->data[i]);\n+    }\n+\n+    if (!(s->regs.mcr & FLEXCAN_MCR_SRX_DIS)) {\n+        /* self-reception */\n+        flexcan_mb_rx(s, &frame);\n+    }\n+    if (!(s->regs.ctrl & FLEXCAN_CTRL_LPB)) {\n+        /* send to bus if not in loopback mode */\n+        if (s->canbus) {\n+            can_bus_client_send(&s->bus_client, &frame, 1);\n+        } else {\n+            /* todo: raise error (no ack) */\n+        }\n+    }\n+\n+    mb->can_ctrl &= ~(FLEXCAN_MB_CODE_MASK | FLEXCAN_MB_CNT_TIMESTAMP_MASK);\n+    mb->can_ctrl |= FLEXCAN_MB_CODE_TX_INACTIVE |\n+                    FLEXCAN_MB_CNT_TIMESTAMP(timestamp);\n+\n+    /* todo: compute the CRC */\n+    s->regs.crcr = FLEXCAN_CRCR_TXCRC(0) | FLEXCAN_CRCR_MBCRC(mbidx);\n+\n+    flexcan_irq_iflag_set(s, mbidx);\n+}\n+\n+static void flexcan_mb_write(FlexcanState *s, int mbid)\n+{\n+    FlexcanRegsMessageBuffer *mb = &s->regs.mbs[mbid];\n+\n+    bool is_mailbox = (mb <= flexcan_get_last_enabled_mailbox(s)) &&\n+                     (mb >= flexcan_get_first_message_buffer(s));\n+\n+    if (trace_event_get_state_backends(TRACE_FLEXCAN_MB_WRITE)) {\n+        char code_str_buf[FLEXCAN_DBG_BUF_LEN] = { 0 };\n+        const char *code_str = flexcan_dbg_mb_code(mb->can_ctrl, code_str_buf);\n+        trace_flexcan_mb_write(DEVICE(s)->canonical_path, mbid, code_str,\n+                               is_mailbox, mb->can_ctrl, mb->can_id);\n+    }\n+\n+    if (!is_mailbox) {\n+        /**\n+         * Disabled mailbox or mailbox in region of queue filters\n+         * was updated. Either way there is nothing to do.\n+         */\n+        return;\n+    }\n+\n+    /* any write to message buffer clears the not_serviced flag */\n+    mb->can_ctrl &= ~FLEXCAN_MB_CNT_NOT_SRV;\n+\n+    /**\n+     * todo: search for active tx mbs on transition from freeze/disable mode\n+     */\n+    switch (mb->can_ctrl & FLEXCAN_MB_CODE_MASK) {\n+    case FLEXCAN_MB_CODE_TX_INACTIVE:\n+        QEMU_FALLTHROUGH;\n+    case FLEXCAN_MB_CODE_RX_INACTIVE:\n+        QEMU_FALLTHROUGH;\n+    case FLEXCAN_MB_CODE_RX_EMPTY:\n+        QEMU_FALLTHROUGH;\n+    case FLEXCAN_MB_CODE_RX_FULL:\n+        QEMU_FALLTHROUGH;\n+    case FLEXCAN_MB_CODE_RX_RANSWER:\n+        break;\n+\n+    case FLEXCAN_MB_CODE_TX_DATA:\n+        flexcan_transmit(s, mbid);\n+        break;\n+    case FLEXCAN_MB_CODE_TX_ABORT:\n+        /*\n+         * as transmission is instant, it can never be aborted\n+         * we need to set CODE in C/S back to the previous code\n+         */\n+        mb->can_ctrl &= ~FLEXCAN_MB_CODE(1);\n+        break;\n+    case FLEXCAN_MB_CODE_TX_TANSWER:\n+        break;\n+    default:\n+        /* prevent setting the busy bit */\n+        mb->can_ctrl &= ~FLEXCAN_MB_CODE_RX_BUSY_BIT;\n+        break;\n+  }\n+\n+}\n+\n+/* ========== RX ========== */\n+static void flexcan_mb_move_in(FlexcanState *s, const qemu_can_frame *frame,\n+                               FlexcanRegsMessageBuffer *target_mb)\n+{\n+    uint32_t frame_len = frame->can_dlc;\n+    uint32_t *frame_data = (uint32_t *)&frame->data;\n+    int timestamp = flexcan_get_timestamp(s, true);\n+    uint32_t new_code = 0;\n+\n+    memset(target_mb, 0, sizeof(FlexcanRegsMessageBuffer));\n+\n+    if (frame_len > 8) {\n+        frame_len = 8;\n+    }\n+    for (int i = 0; i < 2; i++) {\n+        target_mb->data[i] = ldl_be_p(&frame_data[i]);\n+    }\n+\n+    switch (target_mb->can_ctrl & FLEXCAN_MB_CODE_MASK) {\n+    case FLEXCAN_MB_CODE_RX_FULL:\n+    case FLEXCAN_MB_CODE_RX_OVERRUN:\n+        if (target_mb->can_ctrl & FLEXCAN_MB_CNT_NOT_SRV) {\n+            new_code = FLEXCAN_MB_CODE_RX_OVERRUN;\n+        } else {\n+            new_code = FLEXCAN_MB_CODE_RX_FULL;\n+        }\n+        break;\n+    case FLEXCAN_MB_CODE_RX_RANSWER:\n+        assert(s->regs.ctrl2 & FLEXCAN_CTRL2_RRS);\n+        new_code = FLEXCAN_MB_CODE_TX_TANSWER;\n+        break;\n+    default:\n+        new_code = FLEXCAN_MB_CODE_RX_FULL;\n+    }\n+\n+    target_mb->can_ctrl = new_code\n+        | FLEXCAN_MB_CNT_TIMESTAMP(timestamp)\n+        | FLEXCAN_MB_CNT_LENGTH(frame_len)\n+        | FLEXCAN_MB_CNT_NOT_SRV\n+        | FLEXCAN_MB_CNT_SRR; /* always set for received frames */\n+    if (frame->can_id & QEMU_CAN_RTR_FLAG) {\n+        target_mb->can_ctrl |= FLEXCAN_MB_CNT_RTR;\n+    }\n+\n+    if (frame->can_id & QEMU_CAN_EFF_FLAG) {\n+        target_mb->can_ctrl |= FLEXCAN_MB_CNT_IDE;\n+        target_mb->can_id |= frame->can_id & QEMU_CAN_EFF_MASK;\n+    } else {\n+        target_mb->can_id |= (frame->can_id & QEMU_CAN_SFF_MASK) << 18;\n+    }\n+}\n+static void flexcan_mb_lock(FlexcanState *s, int mbidx)\n+{\n+    FlexcanRegsMessageBuffer *mb = &s->regs.mbs[mbidx];\n+    if ((mb > flexcan_get_last_enabled_mailbox(s)) ||\n+        (mb < flexcan_get_first_message_buffer(s))) {\n+        return;\n+    }\n+    switch (mb->can_ctrl & FLEXCAN_MB_CODE_MASK) {\n+    case FLEXCAN_MB_CODE_RX_FULL:\n+        QEMU_FALLTHROUGH;\n+    case FLEXCAN_MB_CODE_RX_OVERRUN:\n+        QEMU_FALLTHROUGH;\n+    case FLEXCAN_MB_CODE_RX_RANSWER:\n+        /* continue */\n+        trace_flexcan_mb_lock(DEVICE(s)->canonical_path, mbidx, 1);\n+        break;\n+    default:\n+        trace_flexcan_mb_lock(DEVICE(s)->canonical_path, mbidx, 0);\n+        return;\n+    }\n+\n+    s->locked_mbidx = mbidx;\n+}\n+\n+static void flexcan_mb_unlock(FlexcanState *s)\n+{\n+    int locked_mbidx = s->locked_mbidx;\n+    bool has_pending_frame = locked_mbidx == s->smb_target_mbidx;\n+\n+    if (s->locked_mbidx == FLEXCAN_NO_MB_LOCKED) {\n+        return;\n+    }\n+\n+    assert(locked_mbidx >= 0 && locked_mbidx < FLEXCAN_MAILBOX_COUNT);\n+    FlexcanRegsMessageBuffer *locked_mb = &s->regs.mbs[locked_mbidx];\n+    s->locked_mbidx = FLEXCAN_NO_MB_LOCKED;\n+\n+    if (locked_mb >= flexcan_get_first_message_buffer(s) &&\n+        locked_mb <= flexcan_get_last_enabled_mailbox(s)\n+    ) {\n+        /* mark the message buffer as serviced */\n+        locked_mb->can_ctrl &= ~FLEXCAN_MB_CNT_NOT_SRV;\n+    }\n+\n+    /* try move in from SMB */\n+    trace_flexcan_mb_unlock(DEVICE(s)->canonical_path, locked_mbidx,\n+                            has_pending_frame ? \" PENDING FRAME IN SMB\" : \"\");\n+\n+    /* todo: in low-power modes, this should be postponed until exit */\n+    if (has_pending_frame) {\n+        FlexcanRegsMessageBuffer *target_mb = &s->regs.mbs[locked_mbidx];\n+        memcpy(target_mb, &s->regs.rx_smb0, sizeof(FlexcanRegsMessageBuffer));\n+\n+        memset(&s->regs.rx_smb0, 0, sizeof(FlexcanRegsMessageBuffer));\n+        s->locked_mbidx = FLEXCAN_SMB_EMPTY;\n+\n+        flexcan_irq_iflag_set(s, locked_mbidx);\n+    }\n+}\n+\n+static bool flexcan_can_receive(CanBusClientState *client)\n+{\n+    FlexcanState *s = container_of(client, FlexcanState, bus_client);\n+    return !(s->regs.mcr & FLEXCAN_MCR_NOT_RDY);\n+}\n+\n+/* --------- RX FIFO ---------- */\n+\n+/**\n+ * flexcan_fifo_pop() - Pop message from FIFO and update IRQs\n+ * @s: FlexCAN device pointer\n+ *\n+ * Does not require the queue to be non-empty.\n+ */\n+static void flexcan_fifo_pop(FlexcanState *s)\n+{\n+    if (s->regs.fifo.mb_back.can_ctrl != 0) {\n+        /* move queue elements forward */\n+        memmove(&s->regs.fifo.mb_back, &s->regs.fifo.mbs_queue[0],\n+                sizeof(s->regs.fifo.mbs_queue));\n+\n+        /* clear the first-in slot */\n+        memset(&s->regs.mbs[FLEXCAN_FIFO_DEPTH - 1], 0,\n+               sizeof(FlexcanRegsMessageBuffer));\n+\n+        trace_flexcan_fifo_pop(DEVICE(s)->canonical_path, 1,\n+                               s->regs.fifo.mb_back.can_ctrl != 0);\n+    } else {\n+        trace_flexcan_fifo_pop(DEVICE(s)->canonical_path, 0, 0);\n+    }\n+\n+    if (s->regs.fifo.mb_back.can_ctrl != 0) {\n+        flexcan_irq_iflag_set(s, I_FIFO_AVAILABLE);\n+    } else {\n+        flexcan_irq_iflag_clear(s, I_FIFO_AVAILABLE);\n+    }\n+}\n+\n+/**\n+ * flexcan_fifo_find_free_slot() - Find the first free slot in the FIFO\n+ * @s: FlexCAN device pointer\n+ *\n+ * Return: Pointer to the first free slot in the FIFO,\n+ *         or NULL if the queue is full.\n+ */\n+static FlexcanRegsMessageBuffer *flexcan_fifo_find_free_slot(FlexcanState *s)\n+{\n+    for (int i = 0; i < FLEXCAN_FIFO_DEPTH; i++) {\n+        FlexcanRegsMessageBuffer *mb = &s->regs.mbs[i];\n+        if (mb->can_ctrl == 0) {\n+            return mb;\n+        }\n+    }\n+    return NULL;\n+}\n+\n+/**\n+ * flexcan_fifo_push() - Update FIFO IRQs after frame move-in\n+ * @s: FlexCAN device pointer\n+ * @slot: Target FIFO slot\n+ *\n+ * The usage is as follows:\n+ * 1. Get free slot pointer using flexcan_fifo_find_free_slot()\n+ * 2. Move the frame in if not NULL\n+ * 3. Call flexcan_fifo_push() regardless of the NULL pointer\n+ */\n+static void flexcan_fifo_push(FlexcanState *s, FlexcanRegsMessageBuffer *slot)\n+{\n+    if (slot) {\n+        int n_occupied = slot - s->regs.mbs;\n+        if (n_occupied == 4) { /* 4 means the 5th slot was filled in */\n+            /*\n+             * fifo occupancy increased from 4 to 5,\n+             * raising FIFO_WARN interrupt\n+             */\n+            flexcan_irq_iflag_set(s, I_FIFO_WARN);\n+        }\n+        flexcan_irq_iflag_set(s, I_FIFO_AVAILABLE);\n+\n+        trace_flexcan_fifo_push(DEVICE(s)->canonical_path, n_occupied);\n+    } else {\n+        flexcan_irq_iflag_set(s, I_FIFO_OVERFLOW);\n+\n+        trace_flexcan_fifo_push(DEVICE(s)->canonical_path, -1);\n+    }\n+}\n+\n+static enum FlexcanRx flexcan_fifo_rx(FlexcanState *s,\n+                                      const qemu_can_frame *buf)\n+{\n+    /* todo: filtering. return FLEXCAN_FIFO_RX_RETRY if filtered out */\n+    if ((s->regs.mcr & FLEXCAN_MCR_IDAM_MASK) == FLEXCAN_MCR_IDAM_D) {\n+        /* all frames rejected */\n+        return FLEXCAN_RX_SEARCH_RETRY;\n+    } else {\n+        /* push message to queue if not full */\n+        FlexcanRegsMessageBuffer *slot = flexcan_fifo_find_free_slot(s);\n+        if (slot) {\n+            flexcan_mb_move_in(s, buf, slot);\n+        }\n+        flexcan_fifo_push(s, slot);\n+\n+        return slot ? FLEXCAN_RX_SEARCH_ACCEPT : FLEXCAN_RX_SEARCH_DROPPED;\n+    }\n+}\n+\n+/* --------- RX message buffer ---------- */\n+\n+/**\n+ * flexcan_mb_rx_check_mb() - Check if a mb matches a received frame\n+ * @s: FlexCAN device pointer\n+ * @buf: Frame to be received from CAN subsystem\n+ * @mbid: Target mailbox index. The mailbox must be a valid message buffer.\n+ *\n+ * Return: FLEXCAN_CHECK_MB_NIL if the message buffer does not match.\n+ *         FLEXCAN_CHECK_MB_MATCH if the message buffer matches the received\n+ *                                frame and is free-to-receive,\n+ *         FLEXCAN_CHECK_MB_MATCH_LOCKED if the message buffer matches,\n+ *                                       but is locked,\n+ *         FLEXCAN_CHECK_MB_MATCH_NON_FREE if the message buffer matches,\n+ *                                         but is not free-to-receive\n+ *                                         for some other reason.\n+ */\n+static enum FlexcanCheck flexcan_mb_rx_check_mb(FlexcanState *s,\n+                                                const qemu_can_frame *buf,\n+                                                int mbid)\n+{\n+    FlexcanRegsMessageBuffer *mb = &s->regs.mbs[mbid];\n+    const bool is_rtr = !!(buf->can_id & QEMU_CAN_RTR_FLAG);\n+    const bool is_serviced = !(mb->can_ctrl & FLEXCAN_MB_CNT_NOT_SRV);\n+    const bool is_locked = s->locked_mbidx == mbid;\n+\n+    bool is_free_to_receive = false;\n+    bool is_matched = false;\n+\n+    switch (mb->can_ctrl & FLEXCAN_MB_CODE_MASK) {\n+    case FLEXCAN_MB_CODE_RX_RANSWER:\n+        if (is_rtr && !(s->regs.ctrl2 & FLEXCAN_CTRL2_RRS)) {\n+            /* todo: do the actual matching/filtering and RTR answer */\n+            is_matched = true;\n+        }\n+        break;\n+    case FLEXCAN_MB_CODE_RX_FULL:\n+        QEMU_FALLTHROUGH;\n+    case FLEXCAN_MB_CODE_RX_OVERRUN:\n+        is_free_to_receive = is_serviced;\n+        /* todo: do the actual matching/filtering */\n+        is_matched = true;\n+        break;\n+    case FLEXCAN_MB_CODE_RX_EMPTY:\n+        is_free_to_receive = true;\n+        /* todo: do the actual matching/filtering */\n+        is_matched = true;\n+        break;\n+    default:\n+    break;\n+    }\n+\n+    if (trace_event_get_state_backends(TRACE_FLEXCAN_MB_RX_CHECK_MB)) {\n+        char code_str_buf[FLEXCAN_DBG_BUF_LEN] = { 0 };\n+        const char *code_str = flexcan_dbg_mb_code(mb->can_ctrl, code_str_buf);\n+        trace_flexcan_mb_rx_check_mb(DEVICE(s)->canonical_path, mbid, code_str,\n+                                     is_matched, is_free_to_receive,\n+                                     is_serviced, is_locked);\n+    }\n+\n+    if (!is_matched) {\n+        return FLEXCAN_CHECK_MB_NIL;\n+    }\n+\n+    if (is_locked) {\n+        return FLEXCAN_CHECK_MB_MATCH_LOCKED;\n+    }\n+\n+    if (is_free_to_receive) {\n+        return FLEXCAN_CHECK_MB_MATCH;\n+    }\n+\n+    return FLEXCAN_CHECK_MB_MATCH_NON_FREE;\n+}\n+\n+static enum FlexcanRx flexcan_mb_rx(FlexcanState *s, const qemu_can_frame *buf)\n+{\n+    int last_not_free_to_receive_mbid = -1;\n+    bool last_not_free_to_receive_locked = false;\n+\n+    FlexcanRegsMessageBuffer *first_mb = flexcan_get_first_message_buffer(s);\n+    FlexcanRegsMessageBuffer *last_mb = flexcan_get_last_enabled_mailbox(s);\n+\n+    for (FlexcanRegsMessageBuffer *mb = first_mb;\n+         mb <= last_mb; mb++) {\n+        int mbid = mb - s->regs.mbs;\n+        enum FlexcanCheck r = flexcan_mb_rx_check_mb(s, buf, mbid);\n+        if (r == FLEXCAN_CHECK_MB_MATCH) {\n+            flexcan_mb_move_in(s, buf, mb);\n+            flexcan_irq_iflag_set(s, mbid);\n+            return FLEXCAN_RX_SEARCH_ACCEPT;\n+        }\n+\n+        if (r == FLEXCAN_CHECK_MB_MATCH_NON_FREE) {\n+            last_not_free_to_receive_mbid = mbid;\n+            last_not_free_to_receive_locked = false;\n+        } else if (r == FLEXCAN_CHECK_MB_MATCH_LOCKED) {\n+            /*\n+             * message buffer is locked,\n+             * we can move in the message after it's unlocked\n+             */\n+            last_not_free_to_receive_mbid = mbid;\n+            last_not_free_to_receive_locked = true;\n+        }\n+    }\n+\n+    if (last_not_free_to_receive_mbid >= -1) {\n+        if (last_not_free_to_receive_locked) {\n+            /*\n+             * copy to temporary mailbox (SMB)\n+             * it will be moved in when the mailbox is unlocked\n+             */\n+            s->regs.rx_smb0.can_ctrl =\n+                s->regs.mbs[last_not_free_to_receive_mbid].can_id;\n+            flexcan_mb_move_in(s, buf, &s->regs.rx_smb0);\n+            s->smb_target_mbidx = last_not_free_to_receive_mbid;\n+            return FLEXCAN_RX_SEARCH_ACCEPT;\n+        }\n+\n+        if (s->regs.mcr & FLEXCAN_MCR_IRMQ) {\n+            flexcan_mb_move_in(s, buf,\n+                               &s->regs.mbs[last_not_free_to_receive_mbid]);\n+            flexcan_irq_iflag_set(s, last_not_free_to_receive_mbid);\n+            return FLEXCAN_RX_SEARCH_ACCEPT;\n+        }\n+    }\n+\n+    return FLEXCAN_RX_SEARCH_RETRY;\n+}\n+\n+static ssize_t flexcan_receive(CanBusClientState *client,\n+                               const qemu_can_frame *frames, size_t frames_cnt)\n+{\n+    FlexcanState *s = container_of(client, FlexcanState, bus_client);\n+    trace_flexcan_receive(DEVICE(s)->canonical_path, frames_cnt);\n+\n+    if (frames_cnt == 0) {\n+        return 0;\n+    }\n+\n+    /* clear the SMB, as it would be overriden in hardware */\n+    memset(&s->regs.rx_smb0, 0, sizeof(FlexcanRegsMessageBuffer));\n+    s->smb_target_mbidx = FLEXCAN_SMB_EMPTY;\n+\n+    for (size_t i = 0; i < frames_cnt; i++) {\n+        int r;\n+        const qemu_can_frame *frame = &frames[i];\n+        if (frame->can_id & QEMU_CAN_ERR_FLAG) {\n+            /* todo: error frame handling */\n+            continue;\n+        }\n+        if (frame->flags & QEMU_CAN_FRMF_TYPE_FD) {\n+            /* CAN FD supported only in later FlexCAN version */\n+            continue;\n+        }\n+\n+        /* todo: this order logic is not complete and needs further work */\n+        if (s->regs.mcr & FLEXCAN_MCR_FEN &&\n+            s->regs.ctrl2 & FLEXCAN_CTRL2_MRP) {\n+            r = flexcan_mb_rx(s, frame);\n+            if (r == FLEXCAN_RX_SEARCH_RETRY) {\n+                flexcan_fifo_rx(s, frame);\n+            }\n+        } else if (s->regs.mcr & FLEXCAN_MCR_FEN) {\n+            r = flexcan_fifo_rx(s, frame);\n+            if (r == FLEXCAN_RX_SEARCH_RETRY) {\n+                flexcan_mb_rx(s, frame);\n+            }\n+        } else {\n+            flexcan_mb_rx(s, frame);\n+        }\n+    }\n+\n+    flexcan_irq_update(s);\n+    return 1;\n+}\n+\n+/* ========== I/O handling ========== */\n+static void flexcan_mem_write(void *opaque, hwaddr addr, uint64_t val,\n+                              unsigned size)\n+{\n+    FlexcanState *s = opaque;\n+    uint32_t write_mask = ((const uint32_t *)\n+        &flexcan_regs_write_mask)[addr / 4];\n+    uint32_t old_value = s->regs_raw[addr / 4];\n+\n+    /*\n+     * 0 for bits that can \"only be written in Freeze mode as it is blocked\n+     * by hardware in other modes\"\n+     */\n+    const uint32_t freeze_mask_mcr = 0xDF54CC80;\n+    const uint32_t freeze_mask_ctrl1 = 0x0000E740;\n+\n+    flexcan_trace_mem_op(s, addr, val, size, true);\n+    switch (addr) {\n+    case offsetof(FlexcanRegs, mcr):\n+        if (!(s->regs.mcr & FLEXCAN_MCR_FRZ_ACK)) {\n+            write_mask &= freeze_mask_mcr;\n+        }\n+        s->regs.mcr = (val & write_mask) | (old_value & ~write_mask);\n+        flexcan_set_mcr(s, old_value);\n+        break;\n+    case offsetof(FlexcanRegs, ctrl):\n+        if (!(s->regs.mcr & FLEXCAN_MCR_FRZ_ACK)) {\n+            write_mask &= freeze_mask_ctrl1;\n+        }\n+        s->regs.ctrl = (val & write_mask) | (old_value & ~write_mask);\n+        break;\n+    case offsetof(FlexcanRegs, iflag1):\n+        s->regs.iflag1 &= ~val;\n+        if ((s->regs.mcr & FLEXCAN_MCR_FEN) &&\n+            (val & FLEXCAN_IFLAG_RX_FIFO_AVAILABLE)) {\n+            flexcan_fifo_pop(s);\n+        }\n+        break;\n+    case offsetof(FlexcanRegs, iflag2):\n+        s->regs.iflag2 &= ~val;\n+        break;\n+    case offsetof(FlexcanRegs, ctrl2):\n+        QEMU_FALLTHROUGH;\n+    case offsetof(FlexcanRegs, ecr):\n+        QEMU_FALLTHROUGH;\n+    case offsetof(FlexcanRegs, rxmgmask):\n+        QEMU_FALLTHROUGH;\n+    case offsetof(FlexcanRegs, rx14mask):\n+        QEMU_FALLTHROUGH;\n+    case offsetof(FlexcanRegs, rx15mask):\n+        QEMU_FALLTHROUGH;\n+    case offsetof(FlexcanRegs, rxfgmask):\n+        QEMU_FALLTHROUGH;\n+    case offsetof(FlexcanRegs, rximr[0]) ... offsetof(FlexcanRegs, rximr[63]):\n+        /* these registers can only be written in freeze mode */\n+        if (!(s->regs.mcr & FLEXCAN_MCR_FRZ_ACK)) {\n+            break;\n+        }\n+        QEMU_FALLTHROUGH;\n+    default:\n+        s->regs_raw[addr / 4] = (val & write_mask) | (old_value & ~write_mask);\n+\n+        if (addr >= offsetof(FlexcanRegs, mb) &&\n+            addr < offsetof(FlexcanRegs, _reserved4)) {\n+            /* access to mailbox */\n+            int mbid = (addr - offsetof(FlexcanRegs, mb)) /\n+                            sizeof(FlexcanRegsMessageBuffer);\n+\n+            if (s->locked_mbidx == mbid) {\n+                flexcan_mb_unlock(s);\n+            }\n+\n+            /* check for invalid writes into FIFO region */\n+            if (s->regs.mcr & FLEXCAN_MCR_FEN && mbid < FLEXCAN_FIFO_DEPTH) {\n+                qemu_log_mask(LOG_GUEST_ERROR,\n+                              \"%s: Invalid write to Rx-FIFO structure\",\n+                              DEVICE(s)->canonical_path);\n+                return;\n+            }\n+\n+            /* run mailbox processing function on write to control word */\n+            if ((addr & 0xF) == 0) {\n+                flexcan_mb_write(s, mbid);\n+            }\n+        }\n+        break;\n+    }\n+\n+    flexcan_irq_update(s);\n+}\n+\n+static uint64_t flexcan_mem_read(void *opqaue, hwaddr addr, unsigned size)\n+{\n+    FlexcanState *s = opqaue;\n+    uint32_t rv = s->regs_raw[addr >> 2];\n+\n+    if (addr >= offsetof(FlexcanRegs, mb) &&\n+        addr < offsetof(FlexcanRegs, _reserved4)) {\n+        /* reading from mailbox */\n+        hwaddr offset = addr - offsetof(FlexcanRegs, mb);\n+        int mbid = offset / sizeof(FlexcanRegsMessageBuffer);\n+\n+        if (addr % 16 == 0 && s->locked_mbidx != mbid) {\n+            /* reading control word locks the mailbox */\n+            flexcan_mb_unlock(s);\n+            flexcan_mb_lock(s, mbid);\n+            flexcan_irq_update(s);\n+            rv = s->regs.mbs[mbid].can_ctrl & ~FLEXCAN_MB_CNT_NOT_SRV;\n+        }\n+    } else if (addr == offsetof(FlexcanRegs, timer)) {\n+        flexcan_mb_unlock(s);\n+        flexcan_irq_update(s);\n+        rv = flexcan_get_timestamp(s, false);\n+    }\n+\n+    flexcan_trace_mem_op(s, addr, rv, size, false);\n+    return rv;\n+}\n+\n+static bool flexcan_mem_accepts(void *opaque, hwaddr addr,\n+                                unsigned size, bool is_write,\n+                                MemTxAttrs attrs)\n+{\n+    FlexcanState *s = opaque;\n+\n+    if ((s->regs.ctrl2 & FLEXCAN_CTRL2_WRMFRZ) &&\n+        (s->regs.mcr & FLEXCAN_MCR_FRZ_ACK)) {\n+        /* unrestricted access to FlexCAN memory in freeze mode */\n+        return true;\n+    } else if (attrs.user && (s->regs.mcr & FLEXCAN_MCR_SUPV)) {\n+        qemu_log_mask(LOG_GUEST_ERROR,\n+                      \"%s: Invalid user-mode access to restricted register\",\n+                      DEVICE(s)->canonical_path);\n+        return false;\n+    } else if (attrs.user && is_write && addr < 4) {\n+        qemu_log_mask(LOG_GUEST_ERROR,\n+                      \"%s: Invalid user-mode access to MCR\",\n+                      DEVICE(s)->canonical_path);\n+        return false;\n+    }\n+\n+    return true;\n+}\n+\n+static const struct MemoryRegionOps flexcan_ops = {\n+    .read = flexcan_mem_read,\n+    .write = flexcan_mem_write,\n+    .endianness = DEVICE_LITTLE_ENDIAN,\n+    .valid = {\n+        .min_access_size = 1,\n+        .max_access_size = 4,\n+        .unaligned = true,\n+        .accepts = flexcan_mem_accepts\n+    },\n+    .impl = {\n+        .min_access_size = 4,\n+        .max_access_size = 4,\n+        .unaligned = false\n+    },\n+};\n+\n+static CanBusClientInfo flexcan_bus_client_info = {\n+    .can_receive = flexcan_can_receive,\n+    .receive = flexcan_receive,\n+};\n+\n+static int flexcan_connect_to_bus(FlexcanState *s, CanBusState *bus)\n+{\n+    s->bus_client.info = &flexcan_bus_client_info;\n+\n+    if (can_bus_insert_client(bus, &s->bus_client) < 0) {\n+        return -1;\n+    }\n+    return 0;\n+}\n+\n+static void flexcan_init(Object *obj)\n+{\n+    FlexcanState *s = CAN_FLEXCAN(obj);\n+\n+    memory_region_init_io(\n+        &s->iomem, obj, &flexcan_ops, s, TYPE_CAN_FLEXCAN,\n+        offsetof(FlexcanRegs, _reserved6)\n+    );\n+}\n+\n+static void flexcan_realize(DeviceState *dev, Error **errp)\n+{\n+    FlexcanState *s = CAN_FLEXCAN(dev);\n+\n+    if (s->canbus) {\n+        if (flexcan_connect_to_bus(s, s->canbus) < 0) {\n+            error_setg(errp, \"%s: flexcan_connect_to_bus failed\",\n+                       dev->canonical_path);\n+            return;\n+        }\n+    }\n+\n+    sysbus_init_mmio(SYS_BUS_DEVICE(dev), &s->iomem);\n+    sysbus_init_irq(SYS_BUS_DEVICE(SYS_BUS_DEVICE(dev)), &s->irq);\n+}\n+\n+static const VMStateDescription vmstate_can = {\n+    .name = TYPE_CAN_FLEXCAN,\n+    .version_id = 1,\n+    .minimum_version_id = 1,\n+    .fields = (const VMStateField[]) {\n+        VMSTATE_INT64(timer_start, FlexcanState),\n+        VMSTATE_UINT32_ARRAY(regs_raw, FlexcanState, sizeof(FlexcanRegs) / 4),\n+        VMSTATE_INT32(locked_mbidx, FlexcanState),\n+        VMSTATE_INT32(smb_target_mbidx, FlexcanState),\n+        VMSTATE_END_OF_LIST(),\n+    },\n+};\n+\n+static const Property flexcan_properties[] = {\n+    DEFINE_PROP_LINK(\"canbus\", FlexcanState, canbus, TYPE_CAN_BUS,\n+                     CanBusState *),\n+};\n+\n+static void flexcan_class_init(ObjectClass *klass, const void *data)\n+{\n+    DeviceClass *dc = DEVICE_CLASS(klass);\n+    ResettableClass *rc = RESETTABLE_CLASS(klass);\n+\n+    rc->phases.enter = flexcan_reset_enter;\n+    rc->phases.hold = flexcan_reset_hold;\n+    dc->realize = flexcan_realize;\n+    device_class_set_props(dc, flexcan_properties);\n+    dc->vmsd = &vmstate_can;\n+    dc->desc = \"i.MX FLEXCAN Controller\";\n+}\n+\n+static const TypeInfo flexcan_info = {\n+    .name          = TYPE_CAN_FLEXCAN,\n+    .parent        = TYPE_SYS_BUS_DEVICE,\n+    .instance_size = sizeof(FlexcanState),\n+    .class_init    = flexcan_class_init,\n+    .instance_init = flexcan_init,\n+};\n+\n+static void can_register_types(void)\n+{\n+    type_register_static(&flexcan_info);\n+}\n+type_init(can_register_types)\ndiff --git a/hw/net/can/flexcan_regs.h b/hw/net/can/flexcan_regs.h\nnew file mode 100644\nindex 0000000000..8dcf1047c6\n--- /dev/null\n+++ b/hw/net/can/flexcan_regs.h\n@@ -0,0 +1,193 @@\n+/*\n+ * Field bitmasks and register structs definitions for FlexCAN\n+ *\n+ * This implementation is based on the following datasheet:\n+ * i.MX 6Dual/6Quad Applications Processor Reference Manual\n+ * Document Number: IMX6DQRM, Rev. 6, 05/2020\n+ *\n+ * Copyright (c) 2025 Matyas Bobek <matyas.bobek@gmail.com>\n+ *\n+ * SPDX-License-Identifier: GPL-2.0-or-later\n+ */\n+#include \"qemu/bitops.h\"\n+\n+#ifndef HW_CAN_FLEXCAN_REGS_H\n+#define HW_CAN_FLEXCAN_REGS_H\n+\n+#define FLEXCAN_GENMASK(h, l) (((~(uint32_t)0) >> (31 - (h) + (l))) << (l))\n+\n+/**\n+ * These macros ware originally written for the Linux kernel\n+ * by Marc Kleine-Budde.\n+ */\n+\n+/* FLEXCAN module configuration register (CANMCR) bits */\n+#define FLEXCAN_MCR_MDIS                BIT(31)\n+#define FLEXCAN_MCR_FRZ                 BIT(30)\n+#define FLEXCAN_MCR_FEN                 BIT(29)\n+#define FLEXCAN_MCR_HALT                BIT(28)\n+#define FLEXCAN_MCR_NOT_RDY             BIT(27)\n+#define FLEXCAN_MCR_WAK_MSK             BIT(26)\n+#define FLEXCAN_MCR_SOFTRST             BIT(25)\n+#define FLEXCAN_MCR_FRZ_ACK             BIT(24)\n+#define FLEXCAN_MCR_SUPV                BIT(23)\n+#define FLEXCAN_MCR_SLF_WAK             BIT(22)\n+#define FLEXCAN_MCR_WRN_EN              BIT(21)\n+#define FLEXCAN_MCR_LPM_ACK             BIT(20)\n+#define FLEXCAN_MCR_WAK_SRC             BIT(19)\n+#define FLEXCAN_MCR_DOZE                BIT(18)\n+#define FLEXCAN_MCR_SRX_DIS             BIT(17)\n+#define FLEXCAN_MCR_IRMQ                BIT(16)\n+#define FLEXCAN_MCR_LPRIO_EN            BIT(13)\n+#define FLEXCAN_MCR_AEN                 BIT(12)\n+#define FLEXCAN_MCR_FDEN                BIT(11)\n+#define FLEXCAN_MCR_MAXMB(x)            ((x) & 0x7f)\n+#define FLEXCAN_MCR_IDAM_A              (0x0 << 8)\n+#define FLEXCAN_MCR_IDAM_B              (0x1 << 8)\n+#define FLEXCAN_MCR_IDAM_C              (0x2 << 8)\n+#define FLEXCAN_MCR_IDAM_D              (0x3 << 8)\n+#define FLEXCAN_MCR_IDAM_MASK           (0x3 << 8)\n+\n+/* FLEXCAN control register (CANCTRL) bits */\n+#define FLEXCAN_CTRL_PRESDIV(x)         (((x) & 0xFF) << 24)\n+#define FLEXCAN_CTRL_PRESDIV_MASK       FLEXCAN_CTRL_PRESDIV(UINT32_MAX)\n+#define FLEXCAN_CTRL_RJW(x)             (((x) & 0x03) << 22)\n+#define FLEXCAN_CTRL_RJW_MASK           FLEXCAN_CTRL_RJW(UINT32_MAX)\n+#define FLEXCAN_CTRL_PSEG1(x)           (((x) & 0x07) << 19)\n+#define FLEXCAN_CTRL_PSEG1_MASK         FLEXCAN_CTRL_PSEG1(UINT32_MAX)\n+#define FLEXCAN_CTRL_PSEG2(x)           (((x) & 0x07) << 16)\n+#define FLEXCAN_CTRL_PSEG2_MASK         FLEXCAN_CTRL_PSEG2(UINT32_MAX)\n+#define FLEXCAN_CTRL_BOFF_MSK           BIT(15)\n+#define FLEXCAN_CTRL_ERR_MSK            BIT(14)\n+#define FLEXCAN_CTRL_CLK_SRC            BIT(13)\n+#define FLEXCAN_CTRL_LPB                BIT(12)\n+#define FLEXCAN_CTRL_TWRN_MSK           BIT(11)\n+#define FLEXCAN_CTRL_RWRN_MSK           BIT(10)\n+#define FLEXCAN_CTRL_SMP                BIT(7)\n+#define FLEXCAN_CTRL_BOFF_REC           BIT(6)\n+#define FLEXCAN_CTRL_TSYN               BIT(5)\n+#define FLEXCAN_CTRL_LBUF               BIT(4)\n+#define FLEXCAN_CTRL_LOM                BIT(3)\n+#define FLEXCAN_CTRL_PROPSEG(x)         ((x) & 0x07)\n+#define FLEXCAN_CTRL_PROPSEG_MASK       FLEXCAN_CTRL_PROPSEG(UINT32_MAX)\n+#define FLEXCAN_CTRL_ERR_BUS            (FLEXCAN_CTRL_ERR_MSK)\n+#define FLEXCAN_CTRL_ERR_STATE \\\n+        (FLEXCAN_CTRL_TWRN_MSK | FLEXCAN_CTRL_RWRN_MSK | \\\n+         FLEXCAN_CTRL_BOFF_MSK)\n+#define FLEXCAN_CTRL_ERR_ALL \\\n+        (FLEXCAN_CTRL_ERR_BUS | FLEXCAN_CTRL_ERR_STATE)\n+\n+/* FLEXCAN control register 2 (CTRL2) bits */\n+#define FLEXCAN_CTRL2_ECRWRE            BIT(29)\n+#define FLEXCAN_CTRL2_WRMFRZ            BIT(28)\n+#define FLEXCAN_CTRL2_RFFN(x)           (((x) & 0x0f) << 24)\n+#define FLEXCAN_CTRL2_TASD(x)           (((x) & 0x1f) << 19)\n+#define FLEXCAN_CTRL2_MRP               BIT(18)\n+#define FLEXCAN_CTRL2_RRS               BIT(17)\n+#define FLEXCAN_CTRL2_EACEN             BIT(16)\n+#define FLEXCAN_CTRL2_ISOCANFDEN        BIT(12)\n+\n+/* FLEXCAN memory error control register (MECR) bits */\n+#define FLEXCAN_MECR_ECRWRDIS           BIT(31)\n+#define FLEXCAN_MECR_HANCEI_MSK         BIT(19)\n+#define FLEXCAN_MECR_FANCEI_MSK         BIT(18)\n+#define FLEXCAN_MECR_CEI_MSK            BIT(16)\n+#define FLEXCAN_MECR_HAERRIE            BIT(15)\n+#define FLEXCAN_MECR_FAERRIE            BIT(14)\n+#define FLEXCAN_MECR_EXTERRIE           BIT(13)\n+#define FLEXCAN_MECR_RERRDIS            BIT(9)\n+#define FLEXCAN_MECR_ECCDIS             BIT(8)\n+#define FLEXCAN_MECR_NCEFAFRZ           BIT(7)\n+\n+/* FLEXCAN error and status register (ESR) bits */\n+#define FLEXCAN_ESR_SYNCH               BIT(18)\n+#define FLEXCAN_ESR_TWRN_INT            BIT(17)\n+#define FLEXCAN_ESR_RWRN_INT            BIT(16)\n+#define FLEXCAN_ESR_BIT1_ERR            BIT(15)\n+#define FLEXCAN_ESR_BIT0_ERR            BIT(14)\n+#define FLEXCAN_ESR_ACK_ERR             BIT(13)\n+#define FLEXCAN_ESR_CRC_ERR             BIT(12)\n+#define FLEXCAN_ESR_FRM_ERR             BIT(11)\n+#define FLEXCAN_ESR_STF_ERR             BIT(10)\n+#define FLEXCAN_ESR_TX_WRN              BIT(9)\n+#define FLEXCAN_ESR_RX_WRN              BIT(8)\n+#define FLEXCAN_ESR_IDLE                BIT(7)\n+#define FLEXCAN_ESR_BOFF_INT            BIT(2)\n+#define FLEXCAN_ESR_ERR_INT             BIT(1)\n+#define FLEXCAN_ESR_WAK_INT             BIT(0)\n+\n+/* FLEXCAN Bit Timing register (CBT) bits */\n+#define FLEXCAN_CBT_BTF                 BIT(31)\n+#define FLEXCAN_CBT_EPRESDIV_MASK       FLEXCAN_GENMASK(30, 21)\n+#define FLEXCAN_CBT_ERJW_MASK           FLEXCAN_GENMASK(20, 16)\n+#define FLEXCAN_CBT_EPROPSEG_MASK       FLEXCAN_GENMASK(15, 10)\n+#define FLEXCAN_CBT_EPSEG1_MASK         FLEXCAN_GENMASK(9, 5)\n+#define FLEXCAN_CBT_EPSEG2_MASK         FLEXCAN_GENMASK(4, 0)\n+\n+/* FLEXCAN FD control register (FDCTRL) bits */\n+#define FLEXCAN_FDCTRL_FDRATE           BIT(31)\n+#define FLEXCAN_FDCTRL_MBDSR1           FLEXCAN_GENMASK(20, 19)\n+#define FLEXCAN_FDCTRL_MBDSR0           FLEXCAN_GENMASK(17, 16)\n+#define FLEXCAN_FDCTRL_MBDSR_8          0x0\n+#define FLEXCAN_FDCTRL_MBDSR_12         0x1\n+#define FLEXCAN_FDCTRL_MBDSR_32         0x2\n+#define FLEXCAN_FDCTRL_MBDSR_64         0x3\n+#define FLEXCAN_FDCTRL_TDCEN            BIT(15)\n+#define FLEXCAN_FDCTRL_TDCFAIL          BIT(14)\n+#define FLEXCAN_FDCTRL_TDCOFF           FLEXCAN_GENMASK(12, 8)\n+#define FLEXCAN_FDCTRL_TDCVAL           FLEXCAN_GENMASK(5, 0)\n+\n+/* FLEXCAN FD Bit Timing register (FDCBT) bits */\n+#define FLEXCAN_FDCBT_FPRESDIV_MASK     FLEXCAN_GENMASK(29, 20)\n+#define FLEXCAN_FDCBT_FRJW_MASK         FLEXCAN_GENMASK(18, 16)\n+#define FLEXCAN_FDCBT_FPROPSEG_MASK     FLEXCAN_GENMASK(14, 10)\n+#define FLEXCAN_FDCBT_FPSEG1_MASK       FLEXCAN_GENMASK(7, 5)\n+#define FLEXCAN_FDCBT_FPSEG2_MASK       FLEXCAN_GENMASK(2, 0)\n+\n+/* FLEXCAN CRC Register (CRCR) bits */\n+#define FLEXCAN_CRCR_MBCRC_MASK         FLEXCAN_GENMASK(22, 16)\n+#define FLEXCAN_CRCR_MBCRC(x)           (((x) & FLEXCAN_CRCR_MBCRC_MASK) << 16)\n+#define FLEXCAN_CRCR_TXCRC_MASK         FLEXCAN_GENMASK(14, 0)\n+#define FLEXCAN_CRCR_TXCRC(x)           ((x) & FLEXCAN_CRCR_TXCRC_MASK)\n+\n+/* FLEXCAN interrupt flag register (IFLAG) bits */\n+/* Errata ERR005829 step7: Reserve first valid MB */\n+#define I_FIFO_OVERFLOW  7\n+#define I_FIFO_WARN      6\n+#define I_FIFO_AVAILABLE 5\n+\n+#define FLEXCAN_TX_MB_RESERVED_RX_FIFO  8\n+#define FLEXCAN_TX_MB_RESERVED_RX_MAILBOX       0\n+#define FLEXCAN_RX_MB_RX_MAILBOX_FIRST  (FLEXCAN_TX_MB_RESERVED_RX_MAILBOX + 1)\n+#define FLEXCAN_IFLAG_MB(x)             BIT_ULL(x)\n+#define FLEXCAN_IFLAG_RX_FIFO_OVERFLOW  BIT(I_FIFO_OVERFLOW)\n+#define FLEXCAN_IFLAG_RX_FIFO_WARN      BIT(I_FIFO_WARN)\n+#define FLEXCAN_IFLAG_RX_FIFO_AVAILABLE BIT(I_FIFO_AVAILABLE)\n+\n+/* FLEXCAN message buffers */\n+#define FLEXCAN_MB_CODE_RX_BUSY_BIT     (0x1 << 24)\n+#define FLEXCAN_MB_CODE_RX_INACTIVE     (0x0 << 24)\n+#define FLEXCAN_MB_CODE_RX_EMPTY        (0x4 << 24)\n+#define FLEXCAN_MB_CODE_RX_FULL         (0x2 << 24)\n+#define FLEXCAN_MB_CODE_RX_OVERRUN      (0x6 << 24)\n+#define FLEXCAN_MB_CODE_RX_RANSWER      (0xa << 24)\n+\n+#define FLEXCAN_MB_CODE_TX_INACTIVE     (0x8 << 24)\n+#define FLEXCAN_MB_CODE_TX_ABORT        (0x9 << 24)\n+#define FLEXCAN_MB_CODE_TX_DATA         (0xc << 24)\n+#define FLEXCAN_MB_CODE_TX_TANSWER      (0xe << 24)\n+\n+#define FLEXCAN_MB_CODE(x)              (((x) & 0xF) << 24)\n+#define FLEXCAN_MB_CODE_MASK            FLEXCAN_MB_CODE(UINT32_MAX)\n+\n+#define FLEXCAN_MB_CNT_EDL              BIT(31)\n+#define FLEXCAN_MB_CNT_BRS              BIT(30)\n+#define FLEXCAN_MB_CNT_ESI              BIT(29)\n+#define FLEXCAN_MB_CNT_SRR              BIT(22)\n+#define FLEXCAN_MB_CNT_IDE              BIT(21)\n+#define FLEXCAN_MB_CNT_RTR              BIT(20)\n+#define FLEXCAN_MB_CNT_LENGTH(x)        (((x) & 0xF) << 16)\n+#define FLEXCAN_MB_CNT_TIMESTAMP(x)     ((x) & 0xFFFF)\n+#define FLEXCAN_MB_CNT_TIMESTAMP_MASK   FLEXCAN_MB_CNT_TIMESTAMP(UINT32_MAX)\n+\n+#endif\ndiff --git a/hw/net/can/meson.build b/hw/net/can/meson.build\nindex 7382344628..401afde2e4 100644\n--- a/hw/net/can/meson.build\n+++ b/hw/net/can/meson.build\n@@ -6,3 +6,4 @@ system_ss.add(when: 'CONFIG_CAN_CTUCANFD', if_true: files('ctucan_core.c'))\n system_ss.add(when: 'CONFIG_CAN_CTUCANFD_PCI', if_true: files('ctucan_pci.c'))\n system_ss.add(when: 'CONFIG_XLNX_ZYNQMP', if_true: files('xlnx-zynqmp-can.c'))\n system_ss.add(when: 'CONFIG_XLNX_VERSAL', if_true: files('xlnx-versal-canfd.c'))\n+system_ss.add(when: 'CONFIG_CAN_FLEXCAN', if_true: files('flexcan.c'))\ndiff --git a/hw/net/can/trace-events b/hw/net/can/trace-events\nindex de64ac1b31..7500f10d7a 100644\n--- a/hw/net/can/trace-events\n+++ b/hw/net/can/trace-events\n@@ -1,3 +1,21 @@\n+# flexcan.c\n+flexcan_irq_update(const char *inst, uint32_t mb_irqs1, uint32_t mb_irqs2, int setting) \"%s: irqs1 0x%08x irqs2 0x%08x request %i\"\n+flexcan_set_mcr(const char *inst, const char *enabled, const char *freeze, const char *fifo, const char *rdy, const char *sync) \"%s: %s %s %s %s %s\"\n+flexcan_mb_write(const char *inst, int mbidx, const char *code, int is_mailbox, uint32_t ctrl, uint32_t id) \"%s: mbidx %i code %s is_mailbox %i ctrl 0x%08x id 0x%08x\"\n+flexcan_mb_lock(const char *inst, int mbidx, int had_rx_code) \"%s: mbidx %i had_rx_code %i\"\n+flexcan_mb_unlock(const char *inst, int mbidx, const char *pending_frame) \"%s: mbidx %i%s\"\n+flexcan_fifo_pop(const char *inst, int non_empty_before, int non_empty_after) \"%s: non_empty before %i non_empty_after %i\"\n+flexcan_fifo_push(const char *inst, int n_occupied) \"%s: n_slots_occupied %i\"\n+flexcan_reset(const char *inst) \"%s: resetting\"\n+flexcan_mem_op(const char *inst, const char *op, uint32_t v, int offset, const char *reg_name, int size) \"%s: %s 0x%08x at offset %i register %s size %i\"\n+flexcan_get_timestamp(const char *inst, int64_t time_elapsed_ms, uint32_t bitrate, uint64_t cycles, uint32_t shift, uint32_t timestamp) \"%s: time_elapsed %\" PRIi64 \"ms bitrate %ub/s cycles %\" PRIu64 \" shift %u timestamp 0x%04x\"\n+flexcan_get_bitrate(const char *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) \"%s: pe_freq %uHz prescaler %u s_freq %uHz tseg1 %uq tseg2 %uq total %uq/b bitrate %ub/s\"\n+flexcan_timer_start(const char *inst, uint32_t bitrate, uint32_t value) \"%s: bitrate %ub/s value 0x%04x\"\n+flexcan_timer_stop(const char *inst, uint32_t bitrate, uint32_t value) \"%s: bitrate %ub/s value 0x%04x\"\n+flexcan_timer_overflow(const char *inst, int64_t current_time, int64_t timer_start, int64_t elapsed_ns) \"%s: current_time %\" PRIi64 \"timer_start %\" PRIi64 \"elapsed_ns %\" PRIi64\n+flexcan_mb_rx_check_mb(const char *inst, int mbidx, const char *code, int is_matched, int is_ftr, int is_serviced, int is_locked) \"%s: checking mb %i code %s is_matched %i is_free_to_receive %i is_serviced %i is_locked %i\"\n+flexcan_receive(const char *inst, size_t n_frames) \"%s: received %zu frames\"\n+\n # xlnx-zynqmp-can.c\n xlnx_can_update_irq(uint32_t isr, uint32_t ier, uint32_t irq) \"ISR: 0x%08x IER: 0x%08x IRQ: 0x%08x\"\n xlnx_can_reset(uint32_t val) \"Resetting controller with value = 0x%08x\"\ndiff --git a/include/hw/net/flexcan.h b/include/hw/net/flexcan.h\nnew file mode 100644\nindex 0000000000..18f4b29ca3\n--- /dev/null\n+++ b/include/hw/net/flexcan.h\n@@ -0,0 +1,139 @@\n+/*\n+ * QEMU model of the NXP FLEXCAN device.\n+ *\n+ * Copyright (c) 2025 Matyas Bobek <matyas.bobek@gmail.com>\n+ *\n+ * Based on CTU CAN FD emulation implemented by Jan Charvat.\n+ *\n+ * SPDX-License-Identifier: GPL-2.0-or-later\n+ */\n+\n+#ifndef HW_CAN_FLEXCAN_H\n+#define HW_CAN_FLEXCAN_H\n+\n+#include \"net/can_emu.h\"\n+#include \"qom/object.h\"\n+#include \"hw/misc/imx_ccm.h\"\n+\n+#define FLEXCAN_FIFO_DEPTH 6\n+#define FLEXCAN_MAILBOX_COUNT 64\n+\n+/* view of single message buffer registers */\n+typedef struct FlexcanRegsMessageBuffer {\n+    uint32_t can_ctrl;\n+    uint32_t can_id;\n+    uint32_t data[2];\n+} FlexcanRegsMessageBuffer;\n+\n+/* RX FIFO view of message buffer registers */\n+typedef struct FlexcanRegsRXFifo {\n+    /* 6 message buffer deep queue, queue back first */\n+    FlexcanRegsMessageBuffer mb_back;\n+    FlexcanRegsMessageBuffer mbs_queue[FLEXCAN_FIFO_DEPTH - 1];\n+\n+    /* number of filter elements active depends on ctrl2 | FLEXCAN_CTRL2_RFFN */\n+    uint32_t                 filter_table_els[128];\n+} FlexcanRegsRXFifo;\n+\n+/**\n+ * Structure of the hardware registers\n+ *\n+ * originally created for the Linux kernel by Marc Kleine-Budde\n+ */\n+typedef struct FlexcanRegs {\n+    uint32_t mcr;                /* 0x00 */\n+    uint32_t ctrl;               /* 0x04 - not affected by soft reset */\n+    uint32_t timer;              /* 0x08 */\n+    uint32_t tcr;                /* 0x0C */\n+    uint32_t rxmgmask;           /* 0x10 - not affected by soft reset */\n+    uint32_t rx14mask;           /* 0x14 - not affected by soft reset */\n+    uint32_t rx15mask;           /* 0x18 - not affected by soft reset */\n+    uint32_t ecr;                /* 0x1C */\n+    uint32_t esr;                /* 0x20 */\n+    uint32_t imask2;             /* 0x24 */\n+    uint32_t imask1;             /* 0x28 */\n+    uint32_t iflag2;             /* 0x2C */\n+    uint32_t iflag1;             /* 0x30 */\n+    union {                      /* 0x34 */\n+        uint32_t gfwr_mx28;      /* MX28, MX53 */\n+        uint32_t ctrl2;          /* MX6, VF610 - not affected by soft reset */\n+    };\n+    uint32_t esr2;               /* 0x38 */\n+    uint32_t imeur;              /* 0x3C, unused */\n+    uint32_t lrfr;               /* 0x40, unused */\n+    uint32_t crcr;               /* 0x44 */\n+    uint32_t rxfgmask;           /* 0x48 */\n+    uint32_t rxfir;              /* 0x4C - not affected by soft reset */\n+    uint32_t cbt;                /* 0x50, unused - not affected by soft reset */\n+    uint32_t _reserved2;         /* 0x54 */\n+    uint32_t dbg1;               /* 0x58, unused */\n+    uint32_t dbg2;               /* 0x5C, unused */\n+    uint32_t _reserved3[8];      /* 0x60 */\n+    union {                      /* 0x80 - not affected by soft reset */\n+        uint32_t mb[sizeof(FlexcanRegsMessageBuffer) * FLEXCAN_MAILBOX_COUNT];\n+        FlexcanRegsMessageBuffer mbs[FLEXCAN_MAILBOX_COUNT];\n+        FlexcanRegsRXFifo fifo;\n+    };\n+    uint32_t _reserved4[256];    /* 0x480 */\n+    uint32_t rximr[64];          /* 0x880 - not affected by soft reset */\n+    uint32_t _reserved5[24];     /* 0x980 */\n+    uint32_t gfwr_mx6;           /* 0x9E0 - MX6 */\n+\n+    /* the rest is unused except for SMB */\n+    uint32_t _reserved6[39];     /* 0x9E4 */\n+    uint32_t _rxfir[6];          /* 0xA80 */\n+    uint32_t _reserved8[2];      /* 0xA98 */\n+    uint32_t _rxmgmask;          /* 0xAA0 */\n+    uint32_t _rxfgmask;          /* 0xAA4 */\n+    uint32_t _rx14mask;          /* 0xAA8 */\n+    uint32_t _rx15mask;          /* 0xAAC */\n+    uint32_t tx_smb[4];          /* 0xAB0 */\n+    union {                      /* 0xAC0, used for SMB emulation */\n+        uint32_t rx_smb0_raw[4];\n+        FlexcanRegsMessageBuffer rx_smb0;\n+    };\n+    uint32_t rx_smb1[4];         /* 0xAD0 */\n+    uint32_t mecr;               /* 0xAE0 */\n+    uint32_t erriar;             /* 0xAE4 */\n+    uint32_t erridpr;            /* 0xAE8 */\n+    uint32_t errippr;            /* 0xAEC */\n+    uint32_t rerrar;             /* 0xAF0 */\n+    uint32_t rerrdr;             /* 0xAF4 */\n+    uint32_t rerrsynr;           /* 0xAF8 */\n+    uint32_t errsr;              /* 0xAFC */\n+    uint32_t _reserved7[64];     /* 0xB00 */\n+    uint32_t fdctrl;             /* 0xC00 - not affected by soft reset */\n+    uint32_t fdcbt;              /* 0xC04 - not affected by soft reset */\n+    uint32_t fdcrc;              /* 0xC08 */\n+    uint32_t _reserved9[199];    /* 0xC0C */\n+    uint32_t tx_smb_fd[18];      /* 0xF28 */\n+    uint32_t rx_smb0_fd[18];     /* 0xF70 */\n+    uint32_t rx_smb1_fd[18];     /* 0xFB8 */\n+} FlexcanRegs;\n+\n+typedef struct FlexcanState {\n+    SysBusDevice        parent_obj;\n+\n+    MemoryRegion        iomem;\n+    IMXCCMState        *ccm;\n+    qemu_irq            irq;\n+\n+    CanBusState        *canbus;\n+    CanBusClientState   bus_client;\n+\n+    union {\n+        FlexcanRegs     regs;\n+        uint32_t        regs_raw[sizeof(FlexcanRegs) / 4];\n+    };\n+    int64_t             timer_start;\n+    uint64_t            last_rx_timer_cycles;\n+    int32_t             locked_mbidx;\n+    int32_t             smb_target_mbidx;\n+    uint32_t            timer_freq;\n+} FlexcanState;\n+\n+#define TYPE_CAN_FLEXCAN \"flexcan\"\n+\n+OBJECT_DECLARE_SIMPLE_TYPE(FlexcanState, CAN_FLEXCAN);\n+\n+#endif\n","prefixes":["v3","4/7"]}