Patch Detail
get:
Show a patch.
patch:
Update a patch.
put:
Update a patch.
GET /api/patches/2230657/?format=api
{ "id": 2230657, "url": "http://patchwork.ozlabs.org/api/patches/2230657/?format=api", "web_url": "http://patchwork.ozlabs.org/project/qemu-devel/patch/20260429-pic32mk-board-support-v1-1-1dfac14f8e2d@voltumotor.com/", "project": { "id": 14, "url": "http://patchwork.ozlabs.org/api/projects/14/?format=api", "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": "<20260429-pic32mk-board-support-v1-1-1dfac14f8e2d@voltumotor.com>", "list_archive_url": null, "date": "2026-04-29T21:51:17", "name": "[RFC,1/3] hw/mips: add Microchip PIC32MK GPK/MCM board", "commit_ref": null, "pull_url": null, "state": "new", "archived": false, "hash": "70e357eebc8d3dc9b031690ea56583dac0f52f07", "submitter": { "id": 93284, "url": "http://patchwork.ozlabs.org/api/people/93284/?format=api", "name": "Ericson Joseph", "email": "ericsonjoseph@gmail.com" }, "delegate": null, "mbox": "http://patchwork.ozlabs.org/project/qemu-devel/patch/20260429-pic32mk-board-support-v1-1-1dfac14f8e2d@voltumotor.com/mbox/", "series": [ { "id": 502167, "url": "http://patchwork.ozlabs.org/api/series/502167/?format=api", "web_url": "http://patchwork.ozlabs.org/project/qemu-devel/list/?series=502167", "date": "2026-04-29T21:51:18", "name": "hw/mips: add Microchip PIC32MK GPK/MCM board emulation", "version": 1, "mbox": "http://patchwork.ozlabs.org/series/502167/mbox/" } ], "comments": "http://patchwork.ozlabs.org/api/patches/2230657/comments/", "check": "pending", "checks": "http://patchwork.ozlabs.org/api/patches/2230657/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=Et3bubPy;\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 4g5YbL1Zn9z1yHZ\n\tfor <incoming@patchwork.ozlabs.org>; Thu, 30 Apr 2026 09:36:14 +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 1wIEQZ-0004pp-8S; Wed, 29 Apr 2026 19:34:47 -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 <ericsonjoseph@gmail.com>)\n id 1wICoh-00010i-5l\n for qemu-devel@nongnu.org; Wed, 29 Apr 2026 17:51:35 -0400", "from mail-dy1-x132c.google.com ([2607:f8b0:4864:20::132c])\n by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128)\n (Exim 4.90_1) (envelope-from <ericsonjoseph@gmail.com>)\n id 1wICob-0004HY-Dq\n for qemu-devel@nongnu.org; Wed, 29 Apr 2026 17:51:34 -0400", "by mail-dy1-x132c.google.com with SMTP id\n 5a478bee46e88-2bdd40d3c61so397587eec.1\n for <qemu-devel@nongnu.org>; Wed, 29 Apr 2026 14:51:29 -0700 (PDT)", "from [127.0.1.1] ([2803:9800:9001:c4f0:5d16:7dac:ff8:d8ff])\n by smtp.gmail.com with ESMTPSA id\n 5a478bee46e88-2ed1bf6d315sm4353878eec.1.2026.04.29.14.51.22\n (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256);\n Wed, 29 Apr 2026 14:51:25 -0700 (PDT)" ], "DKIM-Signature": "v=1; a=rsa-sha256; c=relaxed/relaxed;\n d=gmail.com; s=20251104; t=1777499488; x=1778104288; darn=nongnu.org;\n h=cc:to:in-reply-to:references:message-id:content-transfer-encoding\n :mime-version:subject:date:from:from:to:cc:subject:date:message-id\n :reply-to; bh=UMLAZrBshk7lDmkr+EUehCw1mWT2Ux6/wmvWENJvKdo=;\n b=Et3bubPycNu71sUhI8J0aBr/Mt36qCRfENDBUxKQDk4SVJ09YL6F1rMuwmwKzP0W44\n r2pqEoj8b06euqMvgHNISJCKmwQcXErwfQkog8YeyeSp3JCi5EeY1CDNke0i9VZMkrNx\n +cPD+LhbtdmS+SYm4OJQ8eadt80Docj8p8ryv8LyP79+PNSftMKAkfErVTekLEqLVDx+\n KifDI7H/Qx8kjPIq/kG/R9niZIbLGO5Sx3S9+mYt79yRq0f3gRMLUoV9fI/GhChMvMSK\n R/K85BBTe3X+Tg+WELm3FTPcLrshrVEf68ZjLKFufHN2ee3mNT1+GZmTbYQgtL4cYI5s\n zBWQ==", "X-Google-DKIM-Signature": "v=1; a=rsa-sha256; c=relaxed/relaxed;\n d=1e100.net; s=20251104; t=1777499488; x=1778104288;\n h=cc:to:in-reply-to:references:message-id:content-transfer-encoding\n :mime-version:subject:date:from:x-gm-gg:x-gm-message-state:from:to\n :cc:subject:date:message-id:reply-to;\n bh=UMLAZrBshk7lDmkr+EUehCw1mWT2Ux6/wmvWENJvKdo=;\n b=kMeeFZA7RnrPgvE8CO5rsg8kcm8HGGJh24gW+KZRgpIDjvAW2Y+sT7AIotZJT4XCsp\n W96eG2a3sXdjNcbCfMJccfzTo5+O7dUVkVOjj4DcEK00h8RcKQxNJBzKCiPNBjHY1Wto\n HlL9g4r6Pmx2tn5yXfIpePvygbvfVrzfnGoBPKNWnyfcVxbVZkYC8Gf9K9likx5MtbiR\n 5yilm4TzqrYxHA87w5y1JS7B5U4mGLd7Z+Syxb/lFbSDyZHJ1yHUKNZSGJ/bM3FGddVP\n c6gFW+Naue8b9cmfnkV8V/JLVU5pZDBO5NUH/R1/9Y7X2pxtFzmc6nCwMy4UmYpSuU18\n RcCA==", "X-Gm-Message-State": "AOJu0Yy7fV5bRH+UOxCbaNvSBK9u7XFVsdEiMy8Zmcc76xut2C6oX2RM\n ysUMDdx5xXPN2meZIHpuazOpS1x8qWRhSOrtzvwI7HtE+ByDZKR4CCyt", "X-Gm-Gg": "AeBDieuBhEvwpBUGWOtEiQKXaCsj5D0l6VQ4mSUQRVmGoVy66bmvcw/OV4EZ0qmrhZi\n EiE5TX59DXSFgZ2Ttaos0pt67bbFDLWH70m/f5FQtPqeIQpdWau++rMlbrIojKxjYSDi7uMbLbj\n EqnHZ4N4J5iVM+Q+Iieva5SU5CqUITaG38FZiCvj+7uF4F6WBqXl0Xcn1fjr+OhBjgi7WXlMFag\n UZia6gyqnXqze+r5NP94ZzfX9l3h35/p11LAwnRLZ/QYGAR73hpGIDNLd9dYzSXE5NyMzPQVAgC\n gfEoha4OtaWMbzh55oKLW0GC0EuGihumhDdIFpICTHsOSV56K3+do9kFHA1WvLQg2uZDgcG3nvB\n vir3VUTv5I4jM5Wr50F1mwoDbNoASZij8QzYq0QnXk9tpbKgu2PrbkPk/JsRCNR4asIw1OrTzmG\n 2ngZgSZDxPFRwgyHpWrPuWjrRS1vniq/FzVAKWE0y9", "X-Received": "by 2002:a05:7301:3d11:b0:2d1:9b35:4ed3 with SMTP id\n 5a478bee46e88-2ed3e67f822mr46261eec.28.1777499486963;\n Wed, 29 Apr 2026 14:51:26 -0700 (PDT)", "From": "Ericson Joseph <ericsonjoseph@gmail.com>", "Date": "Wed, 29 Apr 2026 18:51:17 -0300", "Subject": "[PATCH RFC 1/3] hw/mips: add Microchip PIC32MK GPK/MCM board", "MIME-Version": "1.0", "Content-Type": "text/plain; charset=\"utf-8\"", "Content-Transfer-Encoding": "8bit", "Message-Id": "<20260429-pic32mk-board-support-v1-1-1dfac14f8e2d@voltumotor.com>", "References": "<20260429-pic32mk-board-support-v1-0-1dfac14f8e2d@voltumotor.com>", "In-Reply-To": "<20260429-pic32mk-board-support-v1-0-1dfac14f8e2d@voltumotor.com>", "To": "qemu-devel@nongnu.org", "Cc": "=?utf-8?q?Philippe_Mathieu-Daud=C3=A9?= <philmd@linaro.org>,\n Jiaxun Yang <jiaxun.yang@flygoat.com>, Paolo Bonzini <pbonzini@redhat.com>,\n Fabiano Rosas <farosas@suse.de>, Laurent Vivier <lvivier@redhat.com>,\n Pierrick Bouvier <pierrick.bouvier@oss.qualcomm.com>,\n Ericson Joseph <ericsonjoseph@gmail.com>", "X-Mailer": "b4 0.15.2", "Received-SPF": "pass client-ip=2607:f8b0:4864:20::132c;\n envelope-from=ericsonjoseph@gmail.com; helo=mail-dy1-x132c.google.com", "X-Spam_score_int": "-20", "X-Spam_score": "-2.1", "X-Spam_bar": "--", "X-Spam_report": "(-2.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, FREEMAIL_FROM=0.001,\n RCVD_IN_DNSWL_NONE=-0.0001, SPF_HELO_NONE=0.001,\n SPF_PASS=-0.001 autolearn=ham autolearn_force=no", "X-Spam_action": "no action", "X-Mailman-Approved-At": "Wed, 29 Apr 2026 19:34:45 -0400", "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": "Add emulation for the Microchip PIC32MK GPK/MCM family of 32-bit MIPS\nmicrocontrollers (DS60001519E). The family uses a MIPS32 microAptiv MCU\ncore running at up to 120 MHz with 256 KB SRAM and 1 MB program flash.\n\nThe following peripherals are modelled:\n\n - EVIC — 216-source interrupt controller with SET/CLR/INV registers,\n single-vector and multi-vector modes, and OFFx vector offsets\n - UART — 6 instances with TX/RX FIFOs and interrupt generation;\n UART1 wired to the first serial port\n - Timer — 9 instances using QEMU ptimer; Timer1 drives a 1 kHz tick\n - GPIO — 7 ports (A–G) with TRIS/LAT/PORT/ANSEL/CNPU/CNPD registers\n - SPI — 6 instances including master and slave modes\n - I2C — 4 instances (register-level stub, interrupt capable)\n - DMA — 8 channels with CELL/BLOCK/PATTERN transfer modes\n - CAN FD — 4 instances exposed via QEMU can-bus; SocketCAN accessible\n with -object can-bus,id=canbus<n>\n - USB — 2 Full-Speed OTG instances exposed as chardev PTY\n - ADC — ADCHS high-speed 12-bit ADC with 7 cores and FIFO\n - NVM — Flash controller with backed host-file support for persistence\n - DataEE — Software data EEPROM layer over program flash\n - OC — 16 Output Compare units\n - IC — 16 Input Capture units\n - CRU — Clock and Reset Unit (SYSCLK, PBCLK, RCON, RSWRST)\n - WDT — Watchdog timer (write-once WDTKEY protocol)\n - CFG — Configuration registers and SYSKEY unlock sequence\n\nAll unimplemented SFR ranges log LOG_UNIMP rather than silently ignoring\naccesses. The SET (+4), CLR (+8), and INV (+C) sub-register convention\nis implemented for all peripheral register banks.\n\nThe boot ROM at 0xBFC00000 contains a j 0xBFC40000 trampoline; firmware\nis loaded into Boot Flash 1 at physical 0x1FC40000 via -bios.\n\nReference: Microchip DS60001519E (publicly available from microchip.com)\n\nSigned-off-by: Ericson Joseph <ericsonjoseph@gmail.com>\n---\n MAINTAINERS | 9 +\n configs/targets/mipsel-softmmu.mak | 1 +\n hw/mips/Kconfig | 8 +\n hw/mips/meson.build | 21 +\n hw/mips/pic32mk.c | 815 ++++++++++++++++++++\n hw/mips/pic32mk_adchs.c | 583 ++++++++++++++\n hw/mips/pic32mk_canfd.c | 1143 ++++++++++++++++++++++++++++\n hw/mips/pic32mk_cfg.c | 247 ++++++\n hw/mips/pic32mk_cru.c | 375 +++++++++\n hw/mips/pic32mk_dataee.c | 463 +++++++++++\n hw/mips/pic32mk_dma.c | 255 +++++++\n hw/mips/pic32mk_evic.c | 399 ++++++++++\n hw/mips/pic32mk_gpio.c | 418 ++++++++++\n hw/mips/pic32mk_i2c.c | 184 +++++\n hw/mips/pic32mk_ic.c | 384 ++++++++++\n hw/mips/pic32mk_nvm.c | 572 ++++++++++++++\n hw/mips/pic32mk_oc.c | 293 +++++++\n hw/mips/pic32mk_spi.c | 532 +++++++++++++\n hw/mips/pic32mk_timer.c | 294 +++++++\n hw/mips/pic32mk_uart.c | 334 ++++++++\n hw/mips/pic32mk_usb.c | 1033 +++++++++++++++++++++++++\n hw/mips/pic32mk_wdt.c | 230 ++++++\n include/hw/mips/pic32mk.h | 952 +++++++++++++++++++++++\n include/hw/mips/pic32mk_adchs.h | 87 +++\n include/hw/mips/pic32mk_canfd.h | 210 +++++\n include/hw/mips/pic32mk_evic.h | 45 ++\n include/hw/mips/pic32mk_usb.h | 181 +++++\n tests/functional/mipsel/pic32mk_test_fw.S | 64 ++\n tests/functional/mipsel/pic32mk_test_fw.ld | 20 +\n tests/functional/mipsel/test_pic32mk.py | 92 +++\n tests/qtest/pic32mk-canfd-test.c | 411 ++++++++++\n tests/qtest/pic32mk-test.c | 457 +++++++++++\n 32 files changed, 11112 insertions(+)", "diff": "diff --git a/MAINTAINERS b/MAINTAINERS\nindex 49f9bce818..5321eea58d 100644\n--- a/MAINTAINERS\n+++ b/MAINTAINERS\n@@ -1506,6 +1506,15 @@ F: hw/mips/boston.c\n F: hw/pci-host/xilinx-pcie.c\n F: include/hw/pci-host/xilinx-pcie.h\n \n+Microchip PIC32MK GPK/MCM\n+M: Ericson Joseph <ericsonjoseph@gmail.com>\n+S: Maintained\n+F: hw/mips/pic32mk*.c\n+F: hw/mips/pic32mk*.h\n+F: include/hw/mips/pic32mk*.h\n+F: tests/functional/mipsel/test_pic32mk.py\n+F: tests/qtest/pic32mk*.c\n+\n OpenRISC Machines\n -----------------\n or1k-sim\ndiff --git a/configs/targets/mipsel-softmmu.mak b/configs/targets/mipsel-softmmu.mak\nindex b0fba8a9d0..b2cbb3222d 100644\n--- a/configs/targets/mipsel-softmmu.mak\n+++ b/configs/targets/mipsel-softmmu.mak\n@@ -1,3 +1,4 @@\n TARGET_ARCH=mips\n TARGET_LONG_BITS=32\n TARGET_NOT_USING_LEGACY_LDST_PHYS_API=y\n+CONFIG_PIC32MK=y\ndiff --git a/hw/mips/Kconfig b/hw/mips/Kconfig\nindex b59cb2f111..a9766519f9 100644\n--- a/hw/mips/Kconfig\n+++ b/hw/mips/Kconfig\n@@ -84,5 +84,13 @@ config MIPS_BOSTON\n select AHCI_ICH9\n select SERIAL_MM\n \n+config PIC32MK\n+ bool\n+ default y\n+ depends on MIPS && !TARGET_BIG_ENDIAN\n+ select UNIMP\n+ select PTIMER\n+ select CAN_BUS\n+\n config FW_CFG_MIPS\n bool\ndiff --git a/hw/mips/meson.build b/hw/mips/meson.build\nindex 390f0fd7f9..40364638a5 100644\n--- a/hw/mips/meson.build\n+++ b/hw/mips/meson.build\n@@ -12,4 +12,25 @@ mips_ss.add(when: 'CONFIG_FULOONG', if_true: files('fuloong2e.c'))\n mips_ss.add(when: 'CONFIG_MIPS_BOSTON', if_true: files('boston.c'))\n endif\n \n+mips_ss.add(when: 'CONFIG_PIC32MK', if_true: files(\n+ 'pic32mk.c',\n+ 'pic32mk_evic.c',\n+ 'pic32mk_uart.c',\n+ 'pic32mk_timer.c',\n+ 'pic32mk_gpio.c',\n+ 'pic32mk_spi.c',\n+ 'pic32mk_i2c.c',\n+ 'pic32mk_dma.c',\n+ 'pic32mk_canfd.c',\n+ 'pic32mk_usb.c',\n+ 'pic32mk_wdt.c',\n+ 'pic32mk_cru.c',\n+ 'pic32mk_cfg.c',\n+ 'pic32mk_adchs.c',\n+ 'pic32mk_dataee.c',\n+ 'pic32mk_nvm.c',\n+ 'pic32mk_oc.c',\n+ 'pic32mk_ic.c',\n+))\n+\n hw_arch += {'mips': mips_ss}\ndiff --git a/hw/mips/pic32mk.c b/hw/mips/pic32mk.c\nnew file mode 100644\nindex 0000000000..e3a8019507\n--- /dev/null\n+++ b/hw/mips/pic32mk.c\n@@ -0,0 +1,815 @@\n+/*\n+ * Microchip PIC32MK GPK/MCM with CAN FD — board emulation\n+ * Datasheet: DS60001519E\n+ *\n+ * Phase 2: CPU core, EVIC, UART×6, Timers×9, GPIO A-G, SPI×6, I2C×4, DMA×8\n+ *\n+ * Copyright (c) 2026 QEMU contributors\n+ * SPDX-License-Identifier: GPL-2.0-or-later\n+ */\n+\n+#include \"qemu/osdep.h\"\n+#include \"qemu/units.h\"\n+#include \"qemu/log.h\"\n+#include \"qemu/datadir.h\"\n+#include \"qapi/error.h\"\n+#include \"qemu/error-report.h\"\n+#include \"hw/core/boards.h\"\n+#include \"hw/core/loader.h\"\n+#include \"hw/core/clock.h\"\n+#include \"hw/core/qdev-properties.h\"\n+#include \"hw/mips/mips.h\"\n+#include \"hw/mips/pic32mk.h\"\n+#include \"hw/mips/pic32mk_evic.h\"\n+#include \"hw/mips/pic32mk_usb.h\"\n+#include \"system/address-spaces.h\"\n+#include \"system/system.h\"\n+#include \"net/can_emu.h\"\n+#include \"chardev/char.h\"\n+#include \"cpu.h\"\n+\n+/* Device type strings from our peripheral files */\n+#define TYPE_PIC32MK_UART \"pic32mk-uart\"\n+#define TYPE_PIC32MK_TIMER \"pic32mk-timer\"\n+#define TYPE_PIC32MK_GPIO \"pic32mk-gpio\"\n+#define TYPE_PIC32MK_SPI \"pic32mk-spi\"\n+#define TYPE_PIC32MK_I2C \"pic32mk-i2c\"\n+#define TYPE_PIC32MK_DMA \"pic32mk-dma\"\n+#define TYPE_PIC32MK_CANFD \"pic32mk-canfd\"\n+#define TYPE_PIC32MK_USB \"pic32mk-usb\"\n+#define TYPE_PIC32MK_WDT \"pic32mk-wdt\"\n+#define TYPE_PIC32MK_CRU \"pic32mk-cru\"\n+#define TYPE_PIC32MK_CFG \"pic32mk-cfg\"\n+#define TYPE_PIC32MK_ADCHS \"pic32mk-adchs\"\n+#define TYPE_PIC32MK_NVM \"pic32mk-nvm\"\n+#define TYPE_PIC32MK_DATAEE \"pic32mk-dataee\"\n+#define TYPE_PIC32MK_OC \"pic32mk-oc\"\n+#define TYPE_PIC32MK_IC \"pic32mk-ic\"\n+\n+/*\n+ * Board state.\n+ */\n+typedef struct {\n+ MIPSCPU *cpu;\n+ MemoryRegion boot_rom;\n+ MemoryRegion pflash;\n+ MemoryRegion bflash1;\n+ MemoryRegion bflash2;\n+ MemoryRegion sfr;\n+ MemoryRegion sfr_unimpl;\n+ MemoryRegion pps_stub; /* PPS input/output regs 0xBF801400-0xBF8017FF */\n+\n+ DeviceState *evic;\n+} PIC32MKState;\n+\n+/*\n+ * SFR catch-all stub — logs every unimplemented register access.\n+ * -----------------------------------------------------------------------\n+ */\n+\n+static uint64_t sfr_unimpl_read(void *opaque, hwaddr addr, unsigned size)\n+{\n+ qemu_log_mask(LOG_UNIMP,\n+ \"pic32mk: unimplemented SFR read @ 0x%08\" HWADDR_PRIx\n+ \" (size %u)\\n\",\n+ (hwaddr)(PIC32MK_SFR_BASE + addr), size);\n+ return 0;\n+}\n+\n+static void sfr_unimpl_write(void *opaque, hwaddr addr, uint64_t val,\n+ unsigned size)\n+{\n+ qemu_log_mask(LOG_UNIMP,\n+ \"pic32mk: unimplemented SFR write @ 0x%08\" HWADDR_PRIx\n+ \" = 0x%08\" PRIx64 \" (size %u)\\n\",\n+ (hwaddr)(PIC32MK_SFR_BASE + addr), val, size);\n+}\n+\n+static const MemoryRegionOps sfr_unimpl_ops = {\n+ .read = sfr_unimpl_read,\n+ .write = sfr_unimpl_write,\n+ .endianness = DEVICE_LITTLE_ENDIAN,\n+ .valid = {\n+ .min_access_size = 1,\n+ .max_access_size = 4,\n+ },\n+};\n+\n+/*\n+ * Silent stub — used for PPS and other write-only config registers that\n+ * need no emulation but should not generate unimplemented warnings.\n+ */\n+static uint64_t sfr_ignore_read(void *opaque, hwaddr addr, unsigned size)\n+{\n+ return 0;\n+}\n+static void sfr_ignore_write(void *opaque, hwaddr addr, uint64_t val,\n+ unsigned size)\n+{\n+ /* silently accept */\n+}\n+static const MemoryRegionOps sfr_ignore_ops = {\n+ .read = sfr_ignore_read,\n+ .write = sfr_ignore_write,\n+ .endianness = DEVICE_LITTLE_ENDIAN,\n+ .valid = {\n+ .min_access_size = 4,\n+ .max_access_size = 4,\n+ },\n+};\n+\n+\n+\n+/*\n+ * Helper: create a peripheral SysBusDevice, map its MMIO into the SFR\n+ * window at the given offset (overriding the catch-all at priority 1).\n+ * Returns the DeviceState for further property/IRQ wiring.\n+ * -----------------------------------------------------------------------\n+ */\n+\n+static DeviceState *sfr_device_create(MemoryRegion *sfr, const char *type,\n+ hwaddr sfr_offset, Error **errp)\n+{\n+ DeviceState *dev = qdev_new(type);\n+ if (!sysbus_realize_and_unref(SYS_BUS_DEVICE(dev), errp)) {\n+ return NULL;\n+ }\n+\n+ MemoryRegion *mr = sysbus_mmio_get_region(SYS_BUS_DEVICE(dev), 0);\n+ memory_region_add_subregion_overlap(sfr, sfr_offset, mr, 1);\n+ return dev;\n+}\n+\n+/*\n+ * Memory map initialisation\n+ * -----------------------------------------------------------------------\n+ */\n+\n+static void pic32mk_memory_init(PIC32MKState *s, MachineState *machine)\n+{\n+ MemoryRegion *sys_mem = get_system_memory();\n+\n+ /* 256 KB SRAM */\n+ memory_region_add_subregion(sys_mem, PIC32MK_RAM_BASE, machine->ram);\n+\n+ /* 1 MB Program Flash — RAM-backed so NVM controller can write */\n+ memory_region_init_ram(&s->pflash, NULL, \"pic32mk.pflash\",\n+ PIC32MK_PFLASH_SIZE, &error_fatal);\n+ memory_region_add_subregion(sys_mem, PIC32MK_PFLASH_BASE, &s->pflash);\n+\n+ /* Boot Flash 1 — firmware loaded here via -bios */\n+ memory_region_init_rom(&s->bflash1, NULL, \"pic32mk.bflash1\",\n+ PIC32MK_BFLASH1_SIZE, &error_fatal);\n+ memory_region_add_subregion(sys_mem, PIC32MK_BFLASH1_BASE, &s->bflash1);\n+\n+ /* Boot Flash 2 */\n+ memory_region_init_rom(&s->bflash2, NULL, \"pic32mk.bflash2\",\n+ PIC32MK_BFLASH2_SIZE, &error_fatal);\n+ memory_region_add_subregion(sys_mem, PIC32MK_BFLASH2_BASE, &s->bflash2);\n+\n+ /*\n+ * Boot vector ROM — physical 0x1FC00000 to 0x1FC3FFFF.\n+ * Contains a two-instruction trampoline:\n+ * j 0xBFC40000 (Boot Flash 1, KSEG1)\n+ * nop (branch delay slot)\n+ *\n+ * j-encoding when PC = 0xBFC00000:\n+ * instr_index = (0xBFC40000 >> 2) & 0x3FFFFFF = 0x03F10000\n+ * word = (2 << 26) | 0x03F10000 = 0x0BF10000\n+ */\n+ memory_region_init_rom(&s->boot_rom, NULL, \"pic32mk.boot-rom\",\n+ PIC32MK_BOOTVEC_SIZE, &error_fatal);\n+ memory_region_add_subregion(sys_mem, PIC32MK_BOOTVEC_BASE, &s->boot_rom);\n+ {\n+ uint32_t *p = memory_region_get_ram_ptr(&s->boot_rom);\n+ p[0] = 0x0BF10000; /* j 0xBFC40000 */\n+ p[1] = 0x00000000; /* nop (delay slot) */\n+ }\n+\n+ /* SFR window: 1 MB container */\n+ memory_region_init(&s->sfr, NULL, \"pic32mk.sfr\", PIC32MK_SFR_SIZE);\n+ memory_region_add_subregion(sys_mem, PIC32MK_SFR_BASE, &s->sfr);\n+\n+ /* Catch-all at priority 0 */\n+ memory_region_init_io(&s->sfr_unimpl, NULL, &sfr_unimpl_ops, s,\n+ \"pic32mk.sfr-unimpl\", PIC32MK_SFR_SIZE);\n+ memory_region_add_subregion_overlap(&s->sfr, 0, &s->sfr_unimpl, 0);\n+\n+ /* PPS (Peripheral Pin Select) 0xBF801400–0xBF8017FF — silent stub */\n+ memory_region_init_io(&s->pps_stub, NULL, &sfr_ignore_ops, NULL,\n+ \"pic32mk.pps\", PIC32MK_PPS_SIZE);\n+ memory_region_add_subregion_overlap(&s->sfr, PIC32MK_PPS_OFFSET,\n+ &s->pps_stub, 1);\n+}\n+\n+/*\n+ * CPU initialisation\n+ * -----------------------------------------------------------------------\n+ */\n+\n+static void pic32mk_cpu_init(PIC32MKState *s, MachineState *machine)\n+{\n+ Clock *cpuclk;\n+\n+ cpuclk = clock_new(OBJECT(machine), \"cpu-refclk\");\n+ clock_set_hz(cpuclk, PIC32MK_CPU_HZ);\n+\n+ s->cpu = mips_cpu_create_with_clock(machine->cpu_type, cpuclk, false);\n+ if (!s->cpu) {\n+ error_report(\"pic32mk: failed to create CPU '%s'\", machine->cpu_type);\n+ exit(1);\n+ }\n+\n+ /*\n+ * Allocate the 8 CPU interrupt lines (env->irq[0..7]).\n+ * Must be called before wiring EVIC to CPU pins.\n+ */\n+ cpu_mips_irq_init_cpu(s->cpu);\n+ cpu_mips_clock_init(s->cpu);\n+}\n+\n+/*\n+ * EVIC initialisation — create device, map MMIO, wire CPU pins\n+ * -----------------------------------------------------------------------\n+ */\n+\n+static void pic32mk_evic_init(PIC32MKState *s)\n+{\n+ DeviceState *evic = qdev_new(TYPE_PIC32MK_EVIC);\n+ sysbus_realize_and_unref(SYS_BUS_DEVICE(evic), &error_fatal);\n+\n+ MemoryRegion *evic_mr = sysbus_mmio_get_region(SYS_BUS_DEVICE(evic), 0);\n+ memory_region_add_subregion_overlap(&s->sfr, PIC32MK_EVIC_OFFSET,\n+ evic_mr, 1);\n+ s->evic = evic;\n+\n+ /*\n+ * Wire EVIC output pins to CPU interrupt inputs.\n+ * The EVIC device stores these handles and calls qemu_set_irq()\n+ * when a pending+enabled interrupt is found at a given priority level.\n+ */\n+ PIC32MKEVICState *evic_s = PIC32MK_EVIC(evic);\n+ CPUMIPSState *env = &s->cpu->env;\n+ for (int i = 0; i < 8; i++) {\n+ evic_s->cpu_irq[i] = env->irq[i];\n+ }\n+\n+ /*\n+ * Intercept the CP0 Core Timer's interrupt output (normally fires on\n+ * env->irq[7]) and redirect it through EVIC source 0 (CT = Core Timer).\n+ *\n+ * This makes CP0 timer interrupts subject to IEC0[0] (CTIE) and\n+ * IPC0 priority configuration, as on real hardware.\n+ *\n+ * cp0_timer.c calls: qemu_irq_raise(env->irq[IPTI & 7])\n+ * With IPTI=7 (M14K default, CP0_IntCtl = 0xe0000000), that is irq[7].\n+ * We replace irq[7] with the EVIC's irq_in[PIC32MK_IRQ_CT].\n+ */\n+ env->irq[7] = qdev_get_gpio_in(evic, PIC32MK_IRQ_CT);\n+\n+ /*\n+ * Intercept CP0 Software Interrupts 0 and 1 (env->irq[0..1]) and\n+ * redirect them through the EVIC (sources CS0=1, CS1=2).\n+ *\n+ * On real PIC32MK hardware, writing Cause.IP0 triggers EVIC source\n+ * \"Core Software Interrupt 0\" (CS0) at whatever priority is configured\n+ * in IPC0. The EVIC then delivers the interrupt through the normal\n+ * priority comparison, which prevents same-priority nesting.\n+ *\n+ * In QEMU VEIC mode the pending-vs-status comparison is\n+ * (Cause & 0xFF00) > (Status & 0xFF00)\n+ * If IP0 (bit 8) is left in Cause while the tick ISR sets IPL=1\n+ * (bit 10), 0x0500 > 0x0400 causes an unwanted nested interrupt.\n+ *\n+ * The custom handler below:\n+ * 1) routes SW0/SW1 through the EVIC input lines, and\n+ * 2) clears the direct Cause.IP0/IP1 bit so the VEIC comparison\n+ * only sees the EVIC-asserted priority pin (bit 10+).\n+ */\n+ env->irq[0] = qdev_get_gpio_in(evic, PIC32MK_IRQ_CS0);\n+ env->irq[1] = qdev_get_gpio_in(evic, PIC32MK_IRQ_CS1);\n+\n+ /*\n+ * Store a reference to the CPU env in the EVIC state so the\n+ * evic_set_irq handler can clear the direct Cause.IP bits for\n+ * software interrupt sources routed through the EVIC.\n+ */\n+ evic_s->cpu = s->cpu;\n+}\n+\n+/*\n+ * Peripheral initialisation helpers\n+ * -----------------------------------------------------------------------\n+ */\n+\n+/*\n+ * Create a UART instance, attach a chardev, map into the SFR window,\n+ * and connect its RX/TX/error IRQ outputs to the EVIC input lines.\n+ */\n+static void pic32mk_uart_create(PIC32MKState *s, int index,\n+ hwaddr sfr_offset,\n+ int irq_rx, int irq_tx, int irq_err,\n+ Chardev *chr)\n+{\n+ DeviceState *dev = qdev_new(TYPE_PIC32MK_UART);\n+ if (chr) {\n+ qdev_prop_set_chr(dev, \"chardev\", chr);\n+ }\n+ sysbus_realize_and_unref(SYS_BUS_DEVICE(dev), &error_fatal);\n+\n+ MemoryRegion *mr = sysbus_mmio_get_region(SYS_BUS_DEVICE(dev), 0);\n+ memory_region_add_subregion_overlap(&s->sfr, sfr_offset, mr, 1);\n+\n+ /* Connect UART IRQ outputs → EVIC inputs */\n+ sysbus_connect_irq(SYS_BUS_DEVICE(dev), 0,\n+ qdev_get_gpio_in(s->evic, irq_rx));\n+ sysbus_connect_irq(SYS_BUS_DEVICE(dev), 1,\n+ qdev_get_gpio_in(s->evic, irq_tx));\n+ sysbus_connect_irq(SYS_BUS_DEVICE(dev), 2,\n+ qdev_get_gpio_in(s->evic, irq_err));\n+}\n+\n+/*\n+ * Create a Timer instance, map into the SFR window, connect IRQ to EVIC.\n+ */\n+static void pic32mk_timer_create(PIC32MKState *s,\n+ hwaddr sfr_offset, int irq_src, bool type_a)\n+{\n+ DeviceState *dev = qdev_new(TYPE_PIC32MK_TIMER);\n+ qdev_prop_set_bit(dev, \"type-a\", type_a);\n+ sysbus_realize_and_unref(SYS_BUS_DEVICE(dev), &error_fatal);\n+ memory_region_add_subregion_overlap(&s->sfr, sfr_offset,\n+ sysbus_mmio_get_region(SYS_BUS_DEVICE(dev), 0), 1);\n+ sysbus_connect_irq(SYS_BUS_DEVICE(dev), 0,\n+ qdev_get_gpio_in(s->evic, irq_src));\n+}\n+\n+static void pic32mk_oc_create(PIC32MKState *s, int index,\n+ hwaddr sfr_offset, int irq_src)\n+{\n+ DeviceState *dev = qdev_new(TYPE_PIC32MK_OC);\n+ qdev_prop_set_uint8(dev, \"index\", (uint8_t)index);\n+ sysbus_realize_and_unref(SYS_BUS_DEVICE(dev), &error_fatal);\n+ memory_region_add_subregion_overlap(&s->sfr, sfr_offset,\n+ sysbus_mmio_get_region(SYS_BUS_DEVICE(dev), 0), 1);\n+ sysbus_connect_irq(SYS_BUS_DEVICE(dev), 0,\n+ qdev_get_gpio_in(s->evic, irq_src));\n+ /* Optional chardev for waveform event streaming */\n+ Chardev *chr = qemu_chr_find(\"oc-events\");\n+ if (chr) {\n+ pic32mk_oc_set_chardev(dev, chr);\n+ }\n+}\n+\n+/*\n+ * pic32mk_ic_create — instantiate one IC peripheral and map it into the SFR\n+ * window. irq_cap = capture IRQ index, irq_err = error IRQ index (Table 8-3).\n+ * The optional chardev \"ic-events\" is used to inject capture events from the\n+ * host; must be set before sysbus_realize_and_unref.\n+ */\n+static void pic32mk_ic_create(PIC32MKState *s, int index,\n+ hwaddr sfr_offset, int irq_cap, int irq_err)\n+{\n+ DeviceState *dev = qdev_new(TYPE_PIC32MK_IC);\n+ qdev_prop_set_uint8(dev, \"index\", (uint8_t)index);\n+ /*\n+ * Only IC1 owns the \"ic-events\" chardev; it dispatches to all instances\n+ * via a global routing table registered during realize.\n+ */\n+ if (index == 1) {\n+ Chardev *chr = qemu_chr_find(\"ic-events\");\n+ if (chr) {\n+ qdev_prop_set_chr(dev, \"chardev\", chr);\n+ }\n+ }\n+ sysbus_realize_and_unref(SYS_BUS_DEVICE(dev), &error_fatal);\n+ memory_region_add_subregion_overlap(&s->sfr, sfr_offset,\n+ sysbus_mmio_get_region(SYS_BUS_DEVICE(dev), 0), 1);\n+ sysbus_connect_irq(SYS_BUS_DEVICE(dev), 0,\n+ qdev_get_gpio_in(s->evic, irq_cap));\n+ sysbus_connect_irq(SYS_BUS_DEVICE(dev), 1,\n+ qdev_get_gpio_in(s->evic, irq_err));\n+}\n+\n+static const char * const pic32mk_gpio_port_names[PIC32MK_GPIO_NPORTS] = {\n+ \"gpio-portA\", \"gpio-portB\", \"gpio-portC\", \"gpio-portD\",\n+ \"gpio-portE\", \"gpio-portF\", \"gpio-portG\",\n+};\n+\n+/*\n+ * Create a GPIO port instance, map into the SFR window.\n+ * The CN interrupt output (sysbus IRQ 0) is wired to the EVIC.\n+ * The device is registered as a named child of the machine object so that\n+ * QOM paths are predictable: /machine/gpio-portA … /machine/gpio-portG.\n+ */\n+static void pic32mk_gpio_create(PIC32MKState *s, MachineState *machine,\n+ int port_idx, hwaddr sfr_offset, int cn_irq,\n+ Chardev *gpio_chr)\n+{\n+ /*\n+ * Create the device first, register it as a named child of the machine\n+ * BEFORE calling sysbus_realize_and_unref(). If the object already has\n+ * a parent when device_realize() runs it will NOT be placed under the\n+ * anonymous machine/unattached container, so object_property_add_child()\n+ * won't hit the \"!child->parent\" assertion.\n+ */\n+ DeviceState *dev = qdev_new(TYPE_PIC32MK_GPIO);\n+ qdev_prop_set_uint8(dev, \"port-index\", (uint8_t)port_idx);\n+ object_property_add_child(OBJECT(machine),\n+ pic32mk_gpio_port_names[port_idx], OBJECT(dev));\n+ if (!sysbus_realize_and_unref(SYS_BUS_DEVICE(dev), &error_fatal)) {\n+ return;\n+ }\n+\n+ /* Shared chardev for GPIO event streaming (all ports write to same chardev) */\n+ if (gpio_chr) {\n+ pic32mk_gpio_set_chardev(dev, gpio_chr);\n+ }\n+\n+ MemoryRegion *mr = sysbus_mmio_get_region(SYS_BUS_DEVICE(dev), 0);\n+ memory_region_add_subregion_overlap(&s->sfr, sfr_offset, mr, 1);\n+\n+ sysbus_connect_irq(SYS_BUS_DEVICE(dev), 0,\n+ qdev_get_gpio_in(s->evic, cn_irq));\n+}\n+\n+/*\n+ * Create a SPI instance, map into the SFR window.\n+ */\n+static void pic32mk_spi_create(PIC32MKState *s, int index, hwaddr sfr_offset,\n+ int irq_rx, int irq_tx, int irq_err)\n+{\n+ DeviceState *dev = qdev_new(TYPE_PIC32MK_SPI);\n+ char chr_name[16];\n+\n+ qdev_prop_set_uint8(dev, \"spi-index\", (uint8_t)index);\n+ snprintf(chr_name, sizeof(chr_name), \"spi%d\", index);\n+ Chardev *chr = qemu_chr_find(chr_name);\n+ if (chr) {\n+ qdev_prop_set_chr(dev, \"chardev\", chr);\n+ }\n+\n+ sysbus_realize_and_unref(SYS_BUS_DEVICE(dev), &error_fatal);\n+\n+ MemoryRegion *mr = sysbus_mmio_get_region(SYS_BUS_DEVICE(dev), 0);\n+ memory_region_add_subregion_overlap(&s->sfr, sfr_offset, mr, 1);\n+\n+ if (irq_rx >= 0) {\n+ sysbus_connect_irq(SYS_BUS_DEVICE(dev), 0,\n+ qdev_get_gpio_in(s->evic, irq_rx));\n+ }\n+ if (irq_tx >= 0) {\n+ sysbus_connect_irq(SYS_BUS_DEVICE(dev), 1,\n+ qdev_get_gpio_in(s->evic, irq_tx));\n+ }\n+ if (irq_err >= 0) {\n+ sysbus_connect_irq(SYS_BUS_DEVICE(dev), 2,\n+ qdev_get_gpio_in(s->evic, irq_err));\n+ }\n+}\n+\n+/*\n+ * Create an I2C instance, map into the SFR window.\n+ */\n+static void pic32mk_i2c_create(PIC32MKState *s, hwaddr sfr_offset)\n+{\n+ sfr_device_create(&s->sfr, TYPE_PIC32MK_I2C, sfr_offset, &error_fatal);\n+}\n+\n+/*\n+ * pic32mk_find_canbus — look up a can-bus object created with\n+ * -object can-bus,id=canbus<idx>\n+ * Returns NULL if no such object exists (CAN instance runs standalone).\n+ */\n+static CanBusState *pic32mk_find_canbus(int idx)\n+{\n+ char path[32];\n+ snprintf(path, sizeof(path), \"/objects/canbus%d\", idx);\n+ Object *obj = object_resolve_path_type(path, TYPE_CAN_BUS, NULL);\n+ return obj ? CAN_BUS(obj) : NULL;\n+}\n+\n+/*\n+ * Create a CAN FD instance:\n+ * - SFR region (mmio 0) mapped into the SFR window at sfr_offset\n+ * - Message RAM (mmio 1) mapped into system memory at msgram_phys_base\n+ * - Single IRQ wired to EVIC input irq_src\n+ * - canbus: optional virtual bus (NULL = standalone/loopback only)\n+ */\n+static void pic32mk_canfd_create(PIC32MKState *s,\n+ hwaddr sfr_offset,\n+ hwaddr msgram_phys_base,\n+ int irq_src,\n+ CanBusState *canbus,\n+ uint32_t instance_id)\n+{\n+ DeviceState *dev = qdev_new(TYPE_PIC32MK_CANFD);\n+ qdev_prop_set_uint32(dev, \"msg-ram-base\", (uint32_t)msgram_phys_base);\n+ qdev_prop_set_uint32(dev, \"instance-id\", instance_id);\n+ if (canbus) {\n+ object_property_set_link(OBJECT(dev), \"canbus\",\n+ OBJECT(canbus), &error_fatal);\n+ }\n+ sysbus_realize_and_unref(SYS_BUS_DEVICE(dev), &error_fatal);\n+\n+ /* SFR block overrides the catch-all at priority 1 */\n+ memory_region_add_subregion_overlap(&s->sfr, sfr_offset,\n+ sysbus_mmio_get_region(SYS_BUS_DEVICE(dev), 0), 1);\n+\n+ /* Message RAM mapped into the flat system address space */\n+ memory_region_add_subregion(get_system_memory(), msgram_phys_base,\n+ sysbus_mmio_get_region(SYS_BUS_DEVICE(dev), 1));\n+\n+ sysbus_connect_irq(SYS_BUS_DEVICE(dev), 0,\n+ qdev_get_gpio_in(s->evic, irq_src));\n+}\n+\n+/*\n+ * Create a USB OTG instance, map SFR into the SFR window, connect IRQ.\n+ */\n+static void pic32mk_usb_create(PIC32MKState *s,\n+ hwaddr sfr_offset, int irq_src,\n+ const char *chardev_id)\n+{\n+ DeviceState *dev = qdev_new(TYPE_PIC32MK_USB);\n+ if (chardev_id) {\n+ Chardev *chr = qemu_chr_find(chardev_id);\n+ if (chr) {\n+ qdev_prop_set_chr(dev, \"chardev\", chr);\n+ }\n+ }\n+ sysbus_realize_and_unref(SYS_BUS_DEVICE(dev), &error_fatal);\n+ memory_region_add_subregion_overlap(&s->sfr, sfr_offset,\n+ sysbus_mmio_get_region(SYS_BUS_DEVICE(dev), 0), 1);\n+ sysbus_connect_irq(SYS_BUS_DEVICE(dev), 0,\n+ qdev_get_gpio_in(s->evic, irq_src));\n+}\n+\n+/*\n+ * Firmware loading\n+ * -----------------------------------------------------------------------\n+ */\n+\n+static void pic32mk_load_firmware(MachineState *machine)\n+{\n+ if (!machine->firmware) {\n+ return;\n+ }\n+\n+ char *filename = qemu_find_file(QEMU_FILE_TYPE_BIOS, machine->firmware);\n+ if (!filename) {\n+ error_report(\"pic32mk: could not find firmware '%s'\",\n+ machine->firmware);\n+ exit(1);\n+ }\n+\n+ ssize_t bios_size = load_image_targphys(filename,\n+ PIC32MK_BFLASH1_BASE,\n+ PIC32MK_BFLASH1_SIZE,\n+ NULL);\n+ g_free(filename);\n+\n+ if (bios_size < 0) {\n+ error_report(\"pic32mk: could not load firmware '%s'\",\n+ machine->firmware);\n+ exit(1);\n+ }\n+}\n+\n+/*\n+ * Machine entry point\n+ * -----------------------------------------------------------------------\n+ */\n+\n+static void pic32mk_machine_init(MachineState *machine)\n+{\n+ PIC32MKState *s = g_new0(PIC32MKState, 1);\n+\n+ pic32mk_cpu_init(s, machine);\n+ pic32mk_memory_init(s, machine);\n+\n+ /* EVIC — must come before peripherals so we can wire IRQs */\n+ pic32mk_evic_init(s);\n+\n+ /* DMA — mapped into the EVIC's 4 KB page (DMA_OFFSET = EVIC+0x1000) */\n+ {\n+ DeviceState *dma = qdev_new(TYPE_PIC32MK_DMA);\n+ sysbus_realize_and_unref(SYS_BUS_DEVICE(dma), &error_fatal);\n+ MemoryRegion *mr = sysbus_mmio_get_region(SYS_BUS_DEVICE(dma), 0);\n+ memory_region_add_subregion_overlap(&s->sfr, PIC32MK_DMA_OFFSET, mr, 1);\n+ for (int i = 0; i < PIC32MK_DMA_NCHANNELS; i++) {\n+ sysbus_connect_irq(SYS_BUS_DEVICE(dma), i,\n+ qdev_get_gpio_in(s->evic,\n+ PIC32MK_IRQ_DMA0 + i));\n+ }\n+ }\n+\n+ /* UART1 on serial_hd(0), UART2–6 without chardev (stub only) */\n+ pic32mk_uart_create(s, 1, PIC32MK_UART1_OFFSET,\n+ PIC32MK_IRQ_U1RX, PIC32MK_IRQ_U1TX, PIC32MK_IRQ_U1E,\n+ serial_hd(0));\n+ pic32mk_uart_create(s, 2, PIC32MK_UART2_OFFSET,\n+ PIC32MK_IRQ_U2RX, PIC32MK_IRQ_U2TX, PIC32MK_IRQ_U2E,\n+ serial_hd(1));\n+ pic32mk_uart_create(s, 3, PIC32MK_UART3_OFFSET,\n+ PIC32MK_IRQ_U3RX, PIC32MK_IRQ_U3TX, PIC32MK_IRQ_U3E,\n+ serial_hd(2));\n+ pic32mk_uart_create(s, 4, PIC32MK_UART4_OFFSET,\n+ PIC32MK_IRQ_U4RX, PIC32MK_IRQ_U4TX, PIC32MK_IRQ_U4E,\n+ serial_hd(3));\n+ pic32mk_uart_create(s, 5, PIC32MK_UART5_OFFSET,\n+ PIC32MK_IRQ_U5RX, PIC32MK_IRQ_U5TX, PIC32MK_IRQ_U5E,\n+ serial_hd(4));\n+ pic32mk_uart_create(s, 6, PIC32MK_UART6_OFFSET,\n+ PIC32MK_IRQ_U6RX, PIC32MK_IRQ_U6TX, PIC32MK_IRQ_U6E,\n+ serial_hd(5));\n+\n+ /* Timers 1–9: Timer1 is Type A (2-bit TCKPS {1,8,64,256}); 2–9 are Type B/C */\n+ pic32mk_timer_create(s, PIC32MK_T1_OFFSET, PIC32MK_IRQ_T1, true);\n+ pic32mk_timer_create(s, PIC32MK_T2_OFFSET, PIC32MK_IRQ_T2, false);\n+ pic32mk_timer_create(s, PIC32MK_T3_OFFSET, PIC32MK_IRQ_T3, false);\n+ pic32mk_timer_create(s, PIC32MK_T4_OFFSET, PIC32MK_IRQ_T4, false);\n+ pic32mk_timer_create(s, PIC32MK_T5_OFFSET, PIC32MK_IRQ_T5, false);\n+ pic32mk_timer_create(s, PIC32MK_T6_OFFSET, PIC32MK_IRQ_T6, false);\n+ pic32mk_timer_create(s, PIC32MK_T7_OFFSET, PIC32MK_IRQ_T7, false);\n+ pic32mk_timer_create(s, PIC32MK_T8_OFFSET, PIC32MK_IRQ_T8, false);\n+ pic32mk_timer_create(s, PIC32MK_T9_OFFSET, PIC32MK_IRQ_T9, false);\n+\n+ /* Output Compare OC1–OC16 (diagnostic emulation) */\n+ pic32mk_oc_create(s, 1, PIC32MK_OC1_OFFSET, PIC32MK_IRQ_OC1);\n+ pic32mk_oc_create(s, 2, PIC32MK_OC2_OFFSET, PIC32MK_IRQ_OC2);\n+ pic32mk_oc_create(s, 3, PIC32MK_OC3_OFFSET, PIC32MK_IRQ_OC3);\n+ pic32mk_oc_create(s, 4, PIC32MK_OC4_OFFSET, PIC32MK_IRQ_OC4);\n+ pic32mk_oc_create(s, 5, PIC32MK_OC5_OFFSET, PIC32MK_IRQ_OC5);\n+ pic32mk_oc_create(s, 6, PIC32MK_OC6_OFFSET, PIC32MK_IRQ_OC6);\n+ pic32mk_oc_create(s, 7, PIC32MK_OC7_OFFSET, PIC32MK_IRQ_OC7);\n+ pic32mk_oc_create(s, 8, PIC32MK_OC8_OFFSET, PIC32MK_IRQ_OC8);\n+ pic32mk_oc_create(s, 9, PIC32MK_OC9_OFFSET, PIC32MK_IRQ_OC9);\n+ pic32mk_oc_create(s, 10, PIC32MK_OC10_OFFSET, PIC32MK_IRQ_OC10);\n+ pic32mk_oc_create(s, 11, PIC32MK_OC11_OFFSET, PIC32MK_IRQ_OC11);\n+ pic32mk_oc_create(s, 12, PIC32MK_OC12_OFFSET, PIC32MK_IRQ_OC12);\n+ pic32mk_oc_create(s, 13, PIC32MK_OC13_OFFSET, PIC32MK_IRQ_OC13);\n+ pic32mk_oc_create(s, 14, PIC32MK_OC14_OFFSET, PIC32MK_IRQ_OC14);\n+ pic32mk_oc_create(s, 15, PIC32MK_OC15_OFFSET, PIC32MK_IRQ_OC15);\n+ pic32mk_oc_create(s, 16, PIC32MK_OC16_OFFSET, PIC32MK_IRQ_OC16);\n+\n+ /* Input Capture IC1–IC16 (full register model with FIFO) */\n+ pic32mk_ic_create(s, 1, PIC32MK_IC1_OFFSET, PIC32MK_IRQ_IC1, PIC32MK_IRQ_IC1E);\n+ pic32mk_ic_create(s, 2, PIC32MK_IC2_OFFSET, PIC32MK_IRQ_IC2, PIC32MK_IRQ_IC2E);\n+ pic32mk_ic_create(s, 3, PIC32MK_IC3_OFFSET, PIC32MK_IRQ_IC3, PIC32MK_IRQ_IC3E);\n+ pic32mk_ic_create(s, 4, PIC32MK_IC4_OFFSET, PIC32MK_IRQ_IC4, PIC32MK_IRQ_IC4E);\n+ pic32mk_ic_create(s, 5, PIC32MK_IC5_OFFSET, PIC32MK_IRQ_IC5, PIC32MK_IRQ_IC5E);\n+ pic32mk_ic_create(s, 6, PIC32MK_IC6_OFFSET, PIC32MK_IRQ_IC6, PIC32MK_IRQ_IC6E);\n+ pic32mk_ic_create(s, 7, PIC32MK_IC7_OFFSET, PIC32MK_IRQ_IC7, PIC32MK_IRQ_IC7E);\n+ pic32mk_ic_create(s, 8, PIC32MK_IC8_OFFSET, PIC32MK_IRQ_IC8, PIC32MK_IRQ_IC8E);\n+ pic32mk_ic_create(s, 9, PIC32MK_IC9_OFFSET, PIC32MK_IRQ_IC9, PIC32MK_IRQ_IC9E);\n+ pic32mk_ic_create(s, 10, PIC32MK_IC10_OFFSET, PIC32MK_IRQ_IC10, PIC32MK_IRQ_IC10E);\n+ pic32mk_ic_create(s, 11, PIC32MK_IC11_OFFSET, PIC32MK_IRQ_IC11, PIC32MK_IRQ_IC11E);\n+ pic32mk_ic_create(s, 12, PIC32MK_IC12_OFFSET, PIC32MK_IRQ_IC12, PIC32MK_IRQ_IC12E);\n+ pic32mk_ic_create(s, 13, PIC32MK_IC13_OFFSET, PIC32MK_IRQ_IC13, PIC32MK_IRQ_IC13E);\n+ pic32mk_ic_create(s, 14, PIC32MK_IC14_OFFSET, PIC32MK_IRQ_IC14, PIC32MK_IRQ_IC14E);\n+ pic32mk_ic_create(s, 15, PIC32MK_IC15_OFFSET, PIC32MK_IRQ_IC15, PIC32MK_IRQ_IC15E);\n+ pic32mk_ic_create(s, 16, PIC32MK_IC16_OFFSET, PIC32MK_IRQ_IC16, PIC32MK_IRQ_IC16E);\n+\n+ /* GPIO ports A–G: CN interrupt vectors 44–50 (_CHANGE_NOTICE_x_VECTOR) */\n+ static const int cn_irqs[PIC32MK_GPIO_NPORTS] = {\n+ PIC32MK_IRQ_CNA, PIC32MK_IRQ_CNB, PIC32MK_IRQ_CNC, PIC32MK_IRQ_CND,\n+ PIC32MK_IRQ_CNE, PIC32MK_IRQ_CNF, PIC32MK_IRQ_CNG,\n+ };\n+ /* Optional: look up shared chardev \"gpio-events\" for GUI event streaming */\n+ Chardev *gpio_chr = qemu_chr_find(\"gpio-events\");\n+ for (int port = 0; port < PIC32MK_GPIO_NPORTS; port++) {\n+ pic32mk_gpio_create(s, machine, port,\n+ PIC32MK_GPIO_OFFSET\n+ + (hwaddr)port * PIC32MK_GPIO_PORT_SIZE,\n+ cn_irqs[port], gpio_chr);\n+ }\n+\n+ /* SPI 1–6 */\n+ pic32mk_spi_create(s, 1, PIC32MK_SPI1_OFFSET,\n+ PIC32MK_IRQ_SPI1_RX,\n+ PIC32MK_IRQ_SPI1_TX,\n+ PIC32MK_IRQ_SPI1_FAULT);\n+ pic32mk_spi_create(s, 2, PIC32MK_SPI2_OFFSET,\n+ PIC32MK_IRQ_SPI2_RX,\n+ PIC32MK_IRQ_SPI2_TX,\n+ PIC32MK_IRQ_SPI2_FAULT);\n+ pic32mk_spi_create(s, 3, PIC32MK_SPI3_OFFSET,\n+ PIC32MK_IRQ_SPI3_RX,\n+ PIC32MK_IRQ_SPI3_TX,\n+ PIC32MK_IRQ_SPI3_FAULT);\n+ pic32mk_spi_create(s, 4, PIC32MK_SPI4_OFFSET,\n+ PIC32MK_IRQ_SPI4_RX,\n+ PIC32MK_IRQ_SPI4_TX,\n+ PIC32MK_IRQ_SPI4_FAULT);\n+ pic32mk_spi_create(s, 5, PIC32MK_SPI5_OFFSET,\n+ PIC32MK_IRQ_SPI5_RX,\n+ PIC32MK_IRQ_SPI5_TX,\n+ PIC32MK_IRQ_SPI5_FAULT);\n+ pic32mk_spi_create(s, 6, PIC32MK_SPI6_OFFSET,\n+ PIC32MK_IRQ_SPI6_RX,\n+ PIC32MK_IRQ_SPI6_TX,\n+ PIC32MK_IRQ_SPI6_FAULT);\n+\n+ /* I2C 1–4 */\n+ pic32mk_i2c_create(s, PIC32MK_I2C1_OFFSET);\n+ pic32mk_i2c_create(s, PIC32MK_I2C2_OFFSET);\n+ pic32mk_i2c_create(s, PIC32MK_I2C3_OFFSET);\n+ pic32mk_i2c_create(s, PIC32MK_I2C4_OFFSET);\n+\n+ /* ADCHS — High-Speed ADC at 0xBF887000 */\n+ {\n+ DeviceState *adc = qdev_new(TYPE_PIC32MK_ADCHS);\n+ object_property_add_child(OBJECT(machine), \"adchs\", OBJECT(adc));\n+ sysbus_realize_and_unref(SYS_BUS_DEVICE(adc), &error_fatal);\n+ MemoryRegion *mr = sysbus_mmio_get_region(SYS_BUS_DEVICE(adc), 0);\n+ memory_region_add_subregion_overlap(&s->sfr, PIC32MK_ADC_OFFSET,\n+ mr, 1);\n+ /* IRQ 0 = EOS (101), IRQ 1 = main ADC (92) */\n+ sysbus_connect_irq(SYS_BUS_DEVICE(adc), 0,\n+ qdev_get_gpio_in(s->evic, PIC32MK_IRQ_ADC_EOS));\n+ sysbus_connect_irq(SYS_BUS_DEVICE(adc), 1,\n+ qdev_get_gpio_in(s->evic, PIC32MK_IRQ_ADC));\n+ }\n+\n+ /* CAN FD 1–4 */\n+ pic32mk_canfd_create(s, PIC32MK_CAN1_OFFSET,\n+ PIC32MK_CAN1_MSGRAM_BASE, PIC32MK_IRQ_CAN1,\n+ pic32mk_find_canbus(0), 0);\n+ pic32mk_canfd_create(s, PIC32MK_CAN2_OFFSET,\n+ PIC32MK_CAN2_MSGRAM_BASE, PIC32MK_IRQ_CAN2,\n+ pic32mk_find_canbus(1), 1);\n+ pic32mk_canfd_create(s, PIC32MK_CAN3_OFFSET,\n+ PIC32MK_CAN3_MSGRAM_BASE, PIC32MK_IRQ_CAN3,\n+ pic32mk_find_canbus(2), 2);\n+ pic32mk_canfd_create(s, PIC32MK_CAN4_OFFSET,\n+ PIC32MK_CAN4_MSGRAM_BASE, PIC32MK_IRQ_CAN4,\n+ pic32mk_find_canbus(3), 3);\n+\n+ /* USB OTG 1–2 (Phase 4A — register-file stub) */\n+ pic32mk_usb_create(s, PIC32MK_USB1_OFFSET, PIC32MK_IRQ_USB1, \"usbcdc\");\n+ pic32mk_usb_create(s, PIC32MK_USB2_OFFSET, PIC32MK_IRQ_USB2, NULL);\n+\n+ /* WDT — register-file stub; absorbs WDTCON reads/writes and clear-key */\n+ sfr_device_create(&s->sfr, TYPE_PIC32MK_WDT, PIC32MK_WDT_OFFSET,\n+ &error_fatal);\n+\n+ /* CFG / PMD / SYSKEY — register block at 0xBF800000 */\n+ sfr_device_create(&s->sfr, TYPE_PIC32MK_CFG, PIC32MK_CFG_OFFSET,\n+ &error_fatal);\n+\n+ /* CRU — Clock Reference Unit at 0xBF801200 (includes RCON/RSWRST) */\n+ sfr_device_create(&s->sfr, TYPE_PIC32MK_CRU, PIC32MK_CRU_OFFSET,\n+ &error_fatal);\n+\n+ /* NVM / Flash Controller at 0xBF800A00 */\n+ {\n+ DeviceState *nvm = qdev_new(TYPE_PIC32MK_NVM);\n+ object_property_set_link(OBJECT(nvm), \"pflash\",\n+ OBJECT(&s->pflash), &error_fatal);\n+ sysbus_realize_and_unref(SYS_BUS_DEVICE(nvm), &error_fatal);\n+ MemoryRegion *mr = sysbus_mmio_get_region(SYS_BUS_DEVICE(nvm), 0);\n+ memory_region_add_subregion_overlap(&s->sfr, PIC32MK_NVM_OFFSET,\n+ mr, 1);\n+ sysbus_connect_irq(SYS_BUS_DEVICE(nvm), 0,\n+ qdev_get_gpio_in(s->evic, PIC32MK_IRQ_FCE));\n+ }\n+\n+ /* Data EEPROM at 0xBF829000 — 4 KB, optionally backed by host file */\n+ {\n+ DeviceState *ee = qdev_new(TYPE_PIC32MK_DATAEE);\n+ /* Backing file: use -global pic32mk-dataee.filename=<path> */\n+ sysbus_realize_and_unref(SYS_BUS_DEVICE(ee), &error_fatal);\n+ MemoryRegion *mr = sysbus_mmio_get_region(SYS_BUS_DEVICE(ee), 0);\n+ memory_region_add_subregion_overlap(&s->sfr, PIC32MK_DATAEE_OFFSET,\n+ mr, 1);\n+ sysbus_connect_irq(SYS_BUS_DEVICE(ee), 0,\n+ qdev_get_gpio_in(s->evic, PIC32MK_IRQ_DATAEE));\n+ }\n+\n+ pic32mk_load_firmware(machine);\n+}\n+\n+/*\n+ * MachineClass registration\n+ * -----------------------------------------------------------------------\n+ */\n+\n+static void pic32mk_machine_class_init(MachineClass *mc)\n+{\n+ mc->desc = \"Microchip PIC32MK GPK/MCM with CAN FD\";\n+ mc->init = pic32mk_machine_init;\n+ mc->max_cpus = 1;\n+ /*\n+ * microAptiv: MIPS32r2 + FPU + DSP R2 + microMIPS + MCU ASE,\n+ * fixed-mapping MMU, VEIC — matches PIC32MK DS60001519E §3.\n+ */\n+ mc->default_cpu_type = MIPS_CPU_TYPE_NAME(\"microAptiv\");\n+ mc->default_ram_id = \"pic32mk.ram\";\n+ mc->default_ram_size = PIC32MK_RAM_SIZE;\n+ mc->no_parallel = 1;\n+ mc->no_floppy = 1;\n+ mc->no_cdrom = 1;\n+}\n+\n+DEFINE_MACHINE(\"pic32mk\", pic32mk_machine_class_init)\ndiff --git a/hw/mips/pic32mk_adchs.c b/hw/mips/pic32mk_adchs.c\nnew file mode 100644\nindex 0000000000..64d1079c02\n--- /dev/null\n+++ b/hw/mips/pic32mk_adchs.c\n@@ -0,0 +1,583 @@\n+/*\n+ * PIC32MK ADCHS — High-Speed ADC peripheral emulation\n+ * Datasheet: DS60001519E, §22\n+ *\n+ * Emulates the 12-bit pipelined SAR ADC found on PIC32MK GPK/MCM devices.\n+ * 46 data channels (0–27, 33–41, 45–53), 7 ADC modules (0–5, 7).\n+ *\n+ * Key features implemented:\n+ * - All SFR registers with SET/CLR/INV sub-register support\n+ * - Software-triggered conversions (GSWTRG, GLSWTRG, RQCNVRT)\n+ * - Instant conversion model (no timing delays)\n+ * - Host-injectable analog values via QOM properties (adc-ch0 … adc-ch53)\n+ * - End-of-Scan (EOS) + main ADC interrupt outputs to EVIC\n+ * - BGVRRDY and WKRDYx bits always report ready (emulation shortcut)\n+ *\n+ * Stubs (LOG_UNIMP): digital filters, digital comparators, DMA, early IRQ.\n+ *\n+ * Copyright (c) 2026 QEMU contributors\n+ * SPDX-License-Identifier: GPL-2.0-or-later\n+ */\n+\n+#include \"qemu/osdep.h\"\n+#include \"qemu/log.h\"\n+#include \"qemu/module.h\"\n+#include \"qapi/error.h\"\n+#include \"qapi/visitor.h\"\n+#include \"hw/core/sysbus.h\"\n+#include \"hw/core/irq.h\"\n+#include \"hw/core/qdev-properties.h\"\n+#include \"hw/mips/pic32mk.h\"\n+#include \"hw/mips/pic32mk_adchs.h\"\n+\n+/*\n+ * Channel validity table — true for channels present on PIC32MK1024MCM100\n+ * Channels: 0–27, 33–41, 45–53. Missing: 28–32, 42–44.\n+ * -----------------------------------------------------------------------\n+ */\n+\n+static bool adchs_channel_valid(unsigned ch)\n+{\n+ if (ch <= 27) {\n+ return true;\n+ }\n+ if (ch >= 33 && ch <= 41) {\n+ return true;\n+ }\n+ if (ch >= 45 && ch <= 53) {\n+ return true;\n+ }\n+ return false;\n+}\n+\n+/*\n+ * Map channel number to its data-ready IRQ number.\n+ * DATA0=106, DATA1=107, …, DATA27=133, (gap), DATA33=139, …, DATA41=147,\n+ * (gap), DATA45=151, …, DATA53=159.\n+ */\n+static int G_GNUC_UNUSED adchs_channel_irq(unsigned ch)\n+{\n+ if (ch <= 27) {\n+ return PIC32MK_IRQ_ADC_DATA0 + (int)ch;\n+ }\n+ if (ch >= 33 && ch <= 41) {\n+ return 139 + (int)(ch - 33);\n+ }\n+ if (ch >= 45 && ch <= 53) {\n+ return 151 + (int)(ch - 45);\n+ }\n+ return -1;\n+}\n+\n+/*\n+ * SET/CLR/INV helper (PIC32MK convention: +0=REG, +4=CLR, +8=SET, +C=INV)\n+ * -----------------------------------------------------------------------\n+ */\n+\n+static void apply_sci(uint32_t *reg, uint32_t val, int sub)\n+{\n+ switch (sub) {\n+ case 0:\n+ *reg = val;\n+ break;\n+ case 4:\n+ *reg &= ~val;\n+ break;\n+ case 8:\n+ *reg |= val;\n+ break;\n+ case 12:\n+ *reg ^= val;\n+ break;\n+ }\n+}\n+\n+/*\n+ * Conversion engine\n+ * -----------------------------------------------------------------------\n+ */\n+\n+/*\n+ * Perform conversion for a single channel: copy the host-injected analog\n+ * value into the data register, set the data-ready status bit, and fire\n+ * the per-channel IRQ if the global interrupt-enable bit is set.\n+ */\n+static void adchs_convert_channel(PIC32MKADCHSState *s, unsigned ch)\n+{\n+ if (ch >= PIC32MK_ADC_MAX_CH || !adchs_channel_valid(ch)) {\n+ return;\n+ }\n+\n+ /* Store 12-bit result in data register (upper bits zero) */\n+ s->adcdata[ch] = s->analog_input[ch] & 0xFFF;\n+\n+ /* Set data-ready status bit */\n+ if (ch < 32) {\n+ s->adcdstat[0] |= (1u << ch);\n+ } else {\n+ s->adcdstat[1] |= (1u << (ch - 32));\n+ }\n+\n+ /* Fire per-channel IRQ if ADCGIRQEN bit is set */\n+ bool girq_en;\n+ if (ch < 32) {\n+ girq_en = !!(s->adcgirqen[0] & (1u << ch));\n+ } else {\n+ girq_en = !!(s->adcgirqen[1] & (1u << (ch - 32)));\n+ }\n+\n+ if (girq_en) {\n+ /*\n+ * Per-channel data IRQs go directly to EVIC IFS bits.\n+ * We pulse the main ADC IRQ output for simplicity — the EVIC\n+ * will latch IFS[irq_num]. Individual data channel IRQ wiring\n+ * can be extended if firmware requires it.\n+ */\n+ qemu_irq_pulse(s->irq_main);\n+ }\n+}\n+\n+/*\n+ * Scan conversion: iterate over all channels enabled in ADCCSS1/2,\n+ * convert each, then signal End-of-Scan.\n+ */\n+static void adchs_scan_convert(PIC32MKADCHSState *s)\n+{\n+ /* Only convert if module is ON */\n+ if (!(s->adccon1 & PIC32MK_ADCCON1_ON)) {\n+ return;\n+ }\n+\n+ /* Scan CSS1 (channels 0–31) */\n+ for (int ch = 0; ch < 32; ch++) {\n+ if (s->adccss[0] & (1u << ch)) {\n+ adchs_convert_channel(s, ch);\n+ }\n+ }\n+\n+ /* Scan CSS2 (channels 32–53) */\n+ for (int ch = 0; ch < 22; ch++) {\n+ if (s->adccss[1] & (1u << ch)) {\n+ adchs_convert_channel(s, ch + 32);\n+ }\n+ }\n+\n+ /* Signal End-of-Scan */\n+ qemu_irq_pulse(s->irq_eos);\n+}\n+\n+/*\n+ * Register dispatch\n+ * -----------------------------------------------------------------------\n+ */\n+\n+/*\n+ * Map an offset (base, i.e. aligned to 0x10) to a register pointer.\n+ * Returns NULL for offsets that are read-only, data, or unimplemented.\n+ */\n+static uint32_t *adchs_find_reg(PIC32MKADCHSState *s, hwaddr base)\n+{\n+ switch (base) {\n+ case PIC32MK_ADCCON1:\n+ return &s->adccon1;\n+ case PIC32MK_ADCCON2:\n+ return &s->adccon2;\n+ case PIC32MK_ADCCON3:\n+ return &s->adccon3;\n+ case PIC32MK_ADCTRGMODE:\n+ return &s->adctrgmode;\n+\n+ case PIC32MK_ADCIMCON1:\n+ return &s->adcimcon[0];\n+ case PIC32MK_ADCIMCON2:\n+ return &s->adcimcon[1];\n+ case PIC32MK_ADCIMCON3:\n+ return &s->adcimcon[2];\n+ case PIC32MK_ADCIMCON4:\n+ return &s->adcimcon[3];\n+\n+ case PIC32MK_ADCGIRQEN1:\n+ return &s->adcgirqen[0];\n+ case PIC32MK_ADCGIRQEN2:\n+ return &s->adcgirqen[1];\n+\n+ case PIC32MK_ADCCSS1:\n+ return &s->adccss[0];\n+ case PIC32MK_ADCCSS2:\n+ return &s->adccss[1];\n+\n+ case PIC32MK_ADCDSTAT1:\n+ return &s->adcdstat[0];\n+ case PIC32MK_ADCDSTAT2:\n+ return &s->adcdstat[1];\n+\n+ case PIC32MK_ADCCMPEN1:\n+ return &s->adccmpen[0];\n+ case PIC32MK_ADCCMPEN2:\n+ return &s->adccmpen[1];\n+ case PIC32MK_ADCCMPEN3:\n+ return &s->adccmpen[2];\n+ case PIC32MK_ADCCMPEN4:\n+ return &s->adccmpen[3];\n+\n+ case PIC32MK_ADCCMP1:\n+ return &s->adccmp[0];\n+ case PIC32MK_ADCCMP2:\n+ return &s->adccmp[1];\n+ case PIC32MK_ADCCMP3:\n+ return &s->adccmp[2];\n+ case PIC32MK_ADCCMP4:\n+ return &s->adccmp[3];\n+\n+ case PIC32MK_ADCFLTR1:\n+ return &s->adcfltr[0];\n+ case PIC32MK_ADCFLTR2:\n+ return &s->adcfltr[1];\n+ case PIC32MK_ADCFLTR3:\n+ return &s->adcfltr[2];\n+ case PIC32MK_ADCFLTR4:\n+ return &s->adcfltr[3];\n+\n+ case PIC32MK_ADCTRG1:\n+ return &s->adctrg[0];\n+ case PIC32MK_ADCTRG2:\n+ return &s->adctrg[1];\n+ case PIC32MK_ADCTRG3:\n+ return &s->adctrg[2];\n+ case PIC32MK_ADCTRG4:\n+ return &s->adctrg[3];\n+ case PIC32MK_ADCTRG5:\n+ return &s->adctrg[4];\n+ case PIC32MK_ADCTRG6:\n+ return &s->adctrg[5];\n+ case PIC32MK_ADCTRG7:\n+ return &s->adctrg[6];\n+\n+ case PIC32MK_ADCCMPCON1:\n+ return &s->adccmpcon[0];\n+ case PIC32MK_ADCCMPCON2:\n+ return &s->adccmpcon[1];\n+ case PIC32MK_ADCCMPCON3:\n+ return &s->adccmpcon[2];\n+ case PIC32MK_ADCCMPCON4:\n+ return &s->adccmpcon[3];\n+\n+ case PIC32MK_ADCBASE:\n+ return &s->adcbase;\n+ case PIC32MK_ADCTRGSNS:\n+ return &s->adctrgsns;\n+\n+ case PIC32MK_ADC0TIME:\n+ return &s->adctime[0];\n+ case PIC32MK_ADC1TIME:\n+ return &s->adctime[1];\n+ case PIC32MK_ADC2TIME:\n+ return &s->adctime[2];\n+ case PIC32MK_ADC3TIME:\n+ return &s->adctime[3];\n+ case PIC32MK_ADC4TIME:\n+ return &s->adctime[4];\n+ case PIC32MK_ADC5TIME:\n+ return &s->adctime[5];\n+\n+ case PIC32MK_ADCEIEN1:\n+ return &s->adceien[0];\n+ case PIC32MK_ADCEIEN2:\n+ return &s->adceien[1];\n+ case PIC32MK_ADCEISTAT1:\n+ return &s->adceistat[0];\n+ case PIC32MK_ADCEISTAT2:\n+ return &s->adceistat[1];\n+\n+ case PIC32MK_ADCANCON:\n+ return &s->adcancon;\n+\n+ case PIC32MK_ADC0CFG:\n+ return &s->adccfg[0];\n+ case PIC32MK_ADC1CFG:\n+ return &s->adccfg[1];\n+ case PIC32MK_ADC2CFG:\n+ return &s->adccfg[2];\n+ case PIC32MK_ADC3CFG:\n+ return &s->adccfg[3];\n+ case PIC32MK_ADC4CFG:\n+ return &s->adccfg[4];\n+ case PIC32MK_ADC5CFG:\n+ return &s->adccfg[5];\n+ case PIC32MK_ADC6CFG:\n+ return &s->adccfg[6];\n+ case PIC32MK_ADC7CFG:\n+ return &s->adccfg[7];\n+\n+ case PIC32MK_ADCSYSCFG0:\n+ return &s->adcsyscfg[0];\n+ case PIC32MK_ADCSYSCFG1:\n+ return &s->adcsyscfg[1];\n+\n+ default:\n+ return NULL;\n+ }\n+}\n+\n+/*\n+ * MMIO read\n+ * -----------------------------------------------------------------------\n+ */\n+\n+static uint64_t adchs_read(void *opaque, hwaddr addr, unsigned size)\n+{\n+ PIC32MKADCHSState *s = opaque;\n+ hwaddr base = addr & ~(hwaddr)0xF;\n+\n+ /*\n+ * ADCCON2: always report reference voltage ready and no fault.\n+ * Firmware polls these bits during initialization.\n+ */\n+ if (base == PIC32MK_ADCCON2) {\n+ return (s->adccon2 | PIC32MK_ADCCON2_BGVRRDY)\n+ & ~PIC32MK_ADCCON2_REFFLT;\n+ }\n+\n+ /*\n+ * ADCANCON: mirror ANENx bits into WKRDYx positions.\n+ * Firmware enables ANENx then polls WKRDYx until ready.\n+ */\n+ if (base == PIC32MK_ADCANCON) {\n+ uint32_t anen = s->adcancon & 0xFFu;\n+ return (s->adcancon & ~0xFF00u) | (anen << 8);\n+ }\n+\n+ /* ADCDATA registers (0x600–0x950 range, stride 0x10) */\n+ if (base >= PIC32MK_ADCDATA_BASE &&\n+ base < PIC32MK_ADCDATA_BASE + PIC32MK_ADC_MAX_CH * PIC32MK_ADCDATA_STRIDE) {\n+ unsigned ch = (base - PIC32MK_ADCDATA_BASE) / PIC32MK_ADCDATA_STRIDE;\n+ if (ch < PIC32MK_ADC_MAX_CH && adchs_channel_valid(ch)) {\n+ /* Auto-clear data-ready status on read */\n+ if (ch < 32) {\n+ s->adcdstat[0] &= ~(1u << ch);\n+ } else {\n+ s->adcdstat[1] &= ~(1u << (ch - 32));\n+ }\n+ return s->adcdata[ch];\n+ }\n+ }\n+\n+ /* Standard register dispatch */\n+ uint32_t *reg = adchs_find_reg(s, base);\n+ if (reg) {\n+ return *reg;\n+ }\n+\n+ qemu_log_mask(LOG_UNIMP,\n+ \"pic32mk_adchs: unimplemented read @ 0x%04\" HWADDR_PRIx \"\\n\",\n+ addr);\n+ return 0;\n+}\n+\n+/*\n+ * MMIO write\n+ * -----------------------------------------------------------------------\n+ */\n+\n+static void adchs_write(void *opaque, hwaddr addr, uint64_t val, unsigned size)\n+{\n+ PIC32MKADCHSState *s = opaque;\n+ int sub = (int)(addr & 0xF);\n+ hwaddr base = addr & ~(hwaddr)0xF;\n+\n+ /* ADCDATA registers are read-only from firmware perspective */\n+ if (base >= PIC32MK_ADCDATA_BASE &&\n+ base < PIC32MK_ADCDATA_BASE + PIC32MK_ADC_MAX_CH * PIC32MK_ADCDATA_STRIDE) {\n+ qemu_log_mask(LOG_GUEST_ERROR,\n+ \"pic32mk_adchs: write to read-only ADCDATA @ 0x%04\"\n+ HWADDR_PRIx \"\\n\", addr);\n+ return;\n+ }\n+\n+ /* Special handling for ADCCON3 — trigger bits */\n+ if (base == PIC32MK_ADCCON3) {\n+ uint32_t old = s->adccon3;\n+ apply_sci(&s->adccon3, (uint32_t)val, sub);\n+\n+ /* GSWTRG: global software trigger → scan conversion */\n+ if ((s->adccon3 & PIC32MK_ADCCON3_GSWTRG) &&\n+ !(old & PIC32MK_ADCCON3_GSWTRG)) {\n+ adchs_scan_convert(s);\n+ /* GSWTRG is self-clearing */\n+ s->adccon3 &= ~PIC32MK_ADCCON3_GSWTRG;\n+ }\n+\n+ /* GLSWTRG: global level software trigger → also scan */\n+ if ((s->adccon3 & PIC32MK_ADCCON3_GLSWTRG) &&\n+ !(old & PIC32MK_ADCCON3_GLSWTRG)) {\n+ adchs_scan_convert(s);\n+ }\n+\n+ /* RQCNVRT: request single-channel conversion */\n+ if ((s->adccon3 & PIC32MK_ADCCON3_RQCNVRT) &&\n+ !(old & PIC32MK_ADCCON3_RQCNVRT)) {\n+ unsigned ch = s->adccon3 & PIC32MK_ADCCON3_ADINSEL_MASK;\n+ adchs_convert_channel(s, ch);\n+ /* RQCNVRT is self-clearing */\n+ s->adccon3 &= ~PIC32MK_ADCCON3_RQCNVRT;\n+ }\n+ return;\n+ }\n+\n+ /* Standard register dispatch */\n+ uint32_t *reg = adchs_find_reg(s, base);\n+ if (reg) {\n+ apply_sci(reg, (uint32_t)val, sub);\n+ return;\n+ }\n+\n+ qemu_log_mask(LOG_UNIMP,\n+ \"pic32mk_adchs: unimplemented write @ 0x%04\" HWADDR_PRIx\n+ \" = 0x%08\" PRIx64 \"\\n\",\n+ addr, val);\n+}\n+\n+static const MemoryRegionOps adchs_ops = {\n+ .read = adchs_read,\n+ .write = adchs_write,\n+ .endianness = DEVICE_LITTLE_ENDIAN,\n+ .valid = {\n+ .min_access_size = 4,\n+ .max_access_size = 4,\n+ },\n+};\n+\n+/*\n+ * QOM properties — host-side analog value injection\n+ * -----------------------------------------------------------------------\n+ */\n+\n+static void adchs_prop_ch_get(Object *obj, Visitor *v, const char *name,\n+ void *opaque, Error **errp)\n+{\n+ PIC32MKADCHSState *s = PIC32MK_ADCHS(obj);\n+ unsigned ch = (unsigned)(uintptr_t)opaque;\n+ int64_t val = s->analog_input[ch];\n+ visit_type_int(v, name, &val, errp);\n+}\n+\n+static void adchs_prop_ch_set(Object *obj, Visitor *v, const char *name,\n+ void *opaque, Error **errp)\n+{\n+ PIC32MKADCHSState *s = PIC32MK_ADCHS(obj);\n+ unsigned ch = (unsigned)(uintptr_t)opaque;\n+ int64_t val;\n+\n+ if (!visit_type_int(v, name, &val, errp)) {\n+ return;\n+ }\n+ if (val < 0 || val > 4095) {\n+ error_setg(errp, \"adc-ch%u value must be 0–4095 (12-bit)\", ch);\n+ return;\n+ }\n+ s->analog_input[ch] = (uint16_t)val;\n+ s->adcdata[ch] = (uint16_t)(val & 0xFFF);\n+}\n+\n+static void adchs_prop_data_get(Object *obj, Visitor *v, const char *name,\n+ void *opaque, Error **errp)\n+{\n+ PIC32MKADCHSState *s = PIC32MK_ADCHS(obj);\n+ unsigned ch = (unsigned)(uintptr_t)opaque;\n+ int64_t val = s->adcdata[ch];\n+ visit_type_int(v, name, &val, errp);\n+}\n+\n+/*\n+ * Device lifecycle\n+ * -----------------------------------------------------------------------\n+ */\n+\n+static void pic32mk_adchs_reset(DeviceState *dev)\n+{\n+ PIC32MKADCHSState *s = PIC32MK_ADCHS(dev);\n+\n+ s->adccon1 = 0;\n+ s->adccon2 = 0;\n+ s->adccon3 = 0;\n+ s->adctrgmode = 0;\n+\n+ memset(s->adcimcon, 0, sizeof(s->adcimcon));\n+ memset(s->adcgirqen, 0, sizeof(s->adcgirqen));\n+ memset(s->adccss, 0, sizeof(s->adccss));\n+ memset(s->adcdstat, 0, sizeof(s->adcdstat));\n+ memset(s->adccmpen, 0, sizeof(s->adccmpen));\n+ memset(s->adccmp, 0, sizeof(s->adccmp));\n+ memset(s->adccmpcon, 0, sizeof(s->adccmpcon));\n+ memset(s->adcfltr, 0, sizeof(s->adcfltr));\n+ memset(s->adctrg, 0, sizeof(s->adctrg));\n+ s->adctrgsns = 0;\n+ memset(s->adctime, 0, sizeof(s->adctime));\n+ memset(s->adceien, 0, sizeof(s->adceien));\n+ memset(s->adceistat, 0, sizeof(s->adceistat));\n+ s->adcancon = 0;\n+ s->adcbase = 0;\n+ memset(s->adccfg, 0, sizeof(s->adccfg));\n+ memset(s->adcsyscfg, 0, sizeof(s->adcsyscfg));\n+ memset(s->adcdata, 0, sizeof(s->adcdata));\n+ /* analog_input[] is NOT reset — host injections persist across resets */\n+}\n+\n+static void pic32mk_adchs_init(Object *obj)\n+{\n+ PIC32MKADCHSState *s = PIC32MK_ADCHS(obj);\n+\n+ memory_region_init_io(&s->mr, obj, &adchs_ops, s,\n+ TYPE_PIC32MK_ADCHS, PIC32MK_ADC_SIZE);\n+ sysbus_init_mmio(SYS_BUS_DEVICE(obj), &s->mr);\n+\n+ /* IRQ outputs: 0=EOS, 1=main */\n+ sysbus_init_irq(SYS_BUS_DEVICE(obj), &s->irq_eos);\n+ sysbus_init_irq(SYS_BUS_DEVICE(obj), &s->irq_main);\n+\n+ /*\n+ * QOM properties for host-side analog value injection.\n+ * adc-ch<N> — int r/w — set the 12-bit analog value for channel N\n+ * adc-data<N> — int r/o — read the last conversion result for channel N\n+ */\n+ for (unsigned ch = 0; ch < PIC32MK_ADC_MAX_CH; ch++) {\n+ if (!adchs_channel_valid(ch)) {\n+ continue;\n+ }\n+\n+ char name_ch[16], name_data[16];\n+ snprintf(name_ch, sizeof(name_ch), \"adc-ch%u\", ch);\n+ snprintf(name_data, sizeof(name_data), \"adc-data%u\", ch);\n+\n+ object_property_add(obj, name_ch, \"int\",\n+ adchs_prop_ch_get,\n+ adchs_prop_ch_set,\n+ NULL, (void *)(uintptr_t)ch);\n+\n+ object_property_add(obj, name_data, \"int\",\n+ adchs_prop_data_get,\n+ NULL, /* read-only */\n+ NULL, (void *)(uintptr_t)ch);\n+ }\n+}\n+\n+static void pic32mk_adchs_class_init(ObjectClass *klass, const void *data)\n+{\n+ DeviceClass *dc = DEVICE_CLASS(klass);\n+ device_class_set_legacy_reset(dc, pic32mk_adchs_reset);\n+}\n+\n+static const TypeInfo pic32mk_adchs_info = {\n+ .name = TYPE_PIC32MK_ADCHS,\n+ .parent = TYPE_SYS_BUS_DEVICE,\n+ .instance_size = sizeof(PIC32MKADCHSState),\n+ .instance_init = pic32mk_adchs_init,\n+ .class_init = pic32mk_adchs_class_init,\n+};\n+\n+static void pic32mk_adchs_register_types(void)\n+{\n+ type_register_static(&pic32mk_adchs_info);\n+}\n+\n+type_init(pic32mk_adchs_register_types)\ndiff --git a/hw/mips/pic32mk_canfd.c b/hw/mips/pic32mk_canfd.c\nnew file mode 100644\nindex 0000000000..eb2a89a2ce\n--- /dev/null\n+++ b/hw/mips/pic32mk_canfd.c\n@@ -0,0 +1,1143 @@\n+/*\n+ * Microchip PIC32MK CAN FD controller emulation\n+ * Based on PIC32MK GPK/MCM with CAN FD Family Datasheet (DS60001519E)\n+ * and Microchip CAN FD Controller Reference Manual (DS60001507).\n+ *\n+ * Implements:\n+ * - Two MemoryRegions per instance: SFR block + Message RAM\n+ * - Operating mode transitions (Config / Normal / Internal Loopback / others)\n+ * - TX Queue (TXQ) and up to 31 configurable FIFOs\n+ * - UINC pointer-advance protocol (head/tail management)\n+ * - Internal loopback: TX → acceptance filter → RX FIFO\n+ * - CiINT two-level interrupt aggregator → single EVIC IRQ line\n+ * - 32 acceptance filters with mask\n+ * - SET/CLR/INV register aliasing for key registers\n+ *\n+ * Not yet implemented:\n+ * - CiFIFOBA register (message RAM base set by firmware; needed for\n+ * Harmony3-generated drivers that call CFD1FIFOBA = KVA_TO_PA(buf))\n+ * - TEF (TX Event FIFO) population on TX complete\n+ * - CiTBC free-running counter (timestamp)\n+ *\n+ * Copyright (c) 2026 QEMU contributors\n+ * SPDX-License-Identifier: GPL-2.0-or-later\n+ */\n+\n+#include \"qemu/osdep.h\"\n+#include \"qemu/log.h\"\n+#include \"qemu/units.h\"\n+#include \"qapi/error.h\"\n+#include \"hw/core/qdev-properties.h\"\n+#include \"hw/core/sysbus.h\"\n+#include \"hw/core/irq.h\"\n+#include \"hw/mips/pic32mk.h\"\n+#include \"hw/mips/pic32mk_canfd.h\"\n+\n+/*\n+ * DLC / PLSIZE helpers\n+ * -----------------------------------------------------------------------\n+ */\n+\n+static const uint8_t dlc_to_bytes_fd[16] = {\n+ 0, 1, 2, 3, 4, 5, 6, 7, 8, 12, 16, 20, 24, 32, 48, 64\n+};\n+\n+static uint8_t canfd_dlc_bytes(uint8_t dlc, bool fdf)\n+{\n+ if (dlc > 8 && !fdf) {\n+ return 8;\n+ }\n+ return dlc_to_bytes_fd[dlc & 0xF];\n+}\n+\n+/* PLSIZE field → payload bytes */\n+static const uint8_t plsize_to_bytes[8] = { 8, 12, 16, 20, 24, 32, 48, 64 };\n+\n+static uint32_t canfd_obj_size(uint32_t fifocon)\n+{\n+ uint8_t plsize = (fifocon >> CANFD_FIFO_PLSIZE_SHIFT) & 0x7u;\n+ return 8u + plsize_to_bytes[plsize]; /* 2 header words + payload */\n+}\n+\n+/*\n+ * Message RAM layout helpers\n+ *\n+ * Layout (sequential from msg_ram_phys):\n+ * [TXQ region] if TXQEN\n+ * [TEF region] if STEF\n+ * [FIFO 1..31]\n+ *\n+ * For simplicity we pre-allocate the maximum per region and compute\n+ * offsets statically from the FIFO configuration registers.\n+ * -----------------------------------------------------------------------\n+ */\n+\n+#define CANFD_MAX_OBJ_SIZE 72u /* 8 header + 64 payload */\n+#define CANFD_MAX_DEPTH 32u /* FSIZE+1 max */\n+\n+static uint32_t canfd_txq_ram_base(PIC32MKCANFDState *s)\n+{\n+ return s->msg_ram_phys;\n+}\n+\n+static uint32_t canfd_txq_obj_size(PIC32MKCANFDState *s)\n+{\n+ return canfd_obj_size(s->txqcon);\n+}\n+\n+static uint32_t canfd_txq_depth(PIC32MKCANFDState *s)\n+{\n+ return ((s->txqcon >> CANFD_FIFO_FSIZE_SHIFT) & 0x1Fu) + 1u;\n+}\n+\n+/* Region base for FIFO n (1-based) within the message RAM buffer */\n+static uint32_t canfd_fifo_ram_base(PIC32MKCANFDState *s, int n)\n+{\n+ /* TXQ region */\n+ uint32_t off = 0;\n+ if (s->con & CANFD_CON_TXQEN) {\n+ off += canfd_txq_obj_size(s) * canfd_txq_depth(s);\n+ }\n+ /* TEF region (each TEF object = 8 bytes, no payload) */\n+ if (s->con & CANFD_CON_STEF) {\n+ uint32_t tef_depth = ((s->tefcon >> CANFD_FIFO_FSIZE_SHIFT) & 0x1Fu) + 1u;\n+ off += 8u * tef_depth;\n+ }\n+ /* FIFOs 1..n-1 */\n+ for (int i = 1; i < n; i++) {\n+ uint32_t depth = ((s->fifocon[i] >> CANFD_FIFO_FSIZE_SHIFT) & 0x1Fu) + 1u;\n+ off += canfd_obj_size(s->fifocon[i]) * depth;\n+ }\n+ return s->msg_ram_phys + off;\n+}\n+\n+static uint32_t canfd_fifo_slot_ua(PIC32MKCANFDState *s, int n, uint8_t slot)\n+{\n+ return canfd_fifo_ram_base(s, n) + (uint32_t)slot * canfd_obj_size(s->fifocon[n]);\n+}\n+\n+static uint32_t canfd_txq_slot_ua(PIC32MKCANFDState *s, uint8_t slot)\n+{\n+ return canfd_txq_ram_base(s) + (uint32_t)slot * canfd_txq_obj_size(s);\n+}\n+\n+/*\n+ * IRQ update — recomputes CiINT status bits and asserts/deasserts IRQ\n+ * -----------------------------------------------------------------------\n+ */\n+\n+static void canfd_update_irq(PIC32MKCANFDState *s)\n+{\n+ bool fire = false;\n+\n+ /* RXIF: any RX FIFO has data */\n+ if (s->cint & CANFD_INT_RXIE) {\n+ if (s->rxif) {\n+ s->cint |= CANFD_INT_RXIF;\n+ fire = true;\n+ } else {\n+ s->cint &= ~CANFD_INT_RXIF;\n+ }\n+ }\n+\n+ /* TXIF: any TX FIFO / TXQ completed */\n+ if (s->cint & CANFD_INT_TXIE) {\n+ if (s->txif) {\n+ s->cint |= CANFD_INT_TXIF;\n+ fire = true;\n+ } else {\n+ s->cint &= ~CANFD_INT_TXIF;\n+ }\n+ }\n+\n+ /* MODIF: mode changed and MODIE enabled */\n+ if ((s->cint & CANFD_INT_MODIE) && (s->cint & CANFD_INT_MODIF)) {\n+ fire = true;\n+ }\n+\n+ /* RXOVIF: any RX overflow and RXOVIE enabled */\n+ if ((s->cint & CANFD_INT_RXOVIE) && (s->cint & CANFD_INT_RXOVIF)) {\n+ fire = true;\n+ }\n+\n+ qemu_set_irq(s->irq, fire ? 1 : 0);\n+}\n+\n+/*\n+ * Acceptance filter matching\n+ * Returns destination FIFO index (1-based) or -1 if no match.\n+ * -----------------------------------------------------------------------\n+ */\n+\n+static int canfd_find_fifo(PIC32MKCANFDState *s, uint32_t id, bool xtd)\n+{\n+ for (int n = 0; n < 32; n++) {\n+ /* Each CiFLTCON register holds 4 filter bytes */\n+ uint8_t fltcon_byte = (s->fltcon[n / 4] >> ((n % 4) * 8)) & 0xFFu;\n+ if (!(fltcon_byte & 0x80u)) {\n+ continue; /* FLTEN = 0 */\n+ }\n+\n+ uint32_t obj = s->fltobj[n];\n+ uint32_t msk = s->mask[n];\n+\n+ /*\n+ * MIDE bit (bit 29 of CiMASKn): when set, the filter only matches\n+ * frames whose IDE bit equals FLTOBJ.IDE (bit 30).\n+ * When MIDE=0, the filter accepts both standard and extended frames.\n+ */\n+ if (msk & (1u << 29u)) {\n+ bool obj_xtd = (obj >> 30) & 1u;\n+ if (obj_xtd != xtd) {\n+ continue;\n+ }\n+ }\n+\n+ /* (frame_id XOR filter_id) AND mask == 0 means match */\n+ if ((id ^ (obj & 0x1FFFFFFFu)) & (msk & 0x1FFFFFFFu)) {\n+ continue;\n+ }\n+\n+ return fltcon_byte & 0x1Fu; /* destination FIFO */\n+ }\n+ return -1;\n+}\n+\n+/*\n+ * RX deliver — write an incoming frame into the matching RX FIFO\n+ * -----------------------------------------------------------------------\n+ */\n+\n+static void canfd_rx_deliver(PIC32MKCANFDState *s,\n+ uint32_t id, bool xtd, bool fdf,\n+ uint8_t dlc, const uint8_t *data, int len,\n+ int filter_hit)\n+{\n+ int dest = filter_hit;\n+ if (dest < 1 || dest > 31) {\n+ return;\n+ }\n+\n+ uint32_t depth = ((s->fifocon[dest] >> CANFD_FIFO_FSIZE_SHIFT) & 0x1Fu) + 1u;\n+ if (s->fifo_count[dest] >= (uint8_t)depth) {\n+ /* Overflow */\n+ s->rxovif |= (1u << dest);\n+ s->cint |= CANFD_INT_RXOVIF;\n+ canfd_update_irq(s);\n+ return;\n+ }\n+\n+ /* Write message object to tail slot */\n+ uint32_t ua = canfd_fifo_slot_ua(s, dest, s->fifo_tail[dest]);\n+ uint32_t ram_off = ua - s->msg_ram_phys;\n+ uint8_t *obj = s->msg_ram_buf + ram_off;\n+ uint32_t payload_cap = canfd_obj_size(s->fifocon[dest]) - 8u;\n+ uint8_t dlc_len = canfd_dlc_bytes(dlc, fdf);\n+ uint8_t copy_len = (uint8_t)MIN((uint32_t)MAX(len, 0), payload_cap);\n+ if (copy_len > dlc_len) {\n+ copy_len = dlc_len;\n+ }\n+\n+ /*\n+ * RX message object R0/R1 layout (DS60001507 Table 38-2):\n+ * R0[10:0] : SID[10:0] (or upper 11 of 29-bit EID)\n+ * R0[28:11] : EID[17:0] (lower 18 bits of 29-bit extended ID)\n+ * R0[30] : EXIDE (1 = extended frame) — matches CiFLTOBJ layout\n+ * R1[4] : IDE (1 = extended frame) — this is what plib_canfd checks\n+ * Note: TX T0 has the same SID/EID layout but IDE is in T1[4], not T0[30].\n+ */\n+ uint32_t r0;\n+ if (xtd) {\n+ /* 29-bit extended: SID = ID[28:18], EID = ID[17:0] */\n+ r0 = ((id >> 18) & 0x7FFu) /* SID → r0 bits [10:0] */\n+ | ((id & 0x3FFFFu) << 11) /* EID → r0 bits [28:11] */\n+ | (1u << 30u); /* IDE = 1 */\n+ } else {\n+ r0 = id & 0x7FFu; /* SID → r0 bits [10:0] */\n+ }\n+ *(uint32_t *)(obj + 0) = r0;\n+\n+ /* R1: DLC, flags, FILHIT, RXTS=0 */\n+ uint32_t r1 = (uint32_t)dlc\n+ | (xtd ? (1u << 4) : 0u)\n+ | (fdf ? (1u << 7) : 0u)\n+ | ((uint32_t)(filter_hit & 0x1F) << 11);\n+ *(uint32_t *)(obj + 4) = r1;\n+\n+ /*\n+ * RX message data area layout (DS60001507 Figure 3-2):\n+ * data[0..3] : RXMSGTS — 32-bit receive timestamp (always present when\n+ * RXTSEN=1 in FIFOCONn; firmware expects this field)\n+ * data[4..] : Payload bytes\n+ *\n+ * Harmony3 plib_canfd always reads payload from data[4] onwards, so we\n+ * must write a timestamp (even if 0) before the payload.\n+ */\n+ memset(obj + 8, 0, payload_cap); /* Clear timestamp + payload */\n+ *(uint32_t *)(obj + 8) = s->tbc; /* Write timestamp at data[0..3] */\n+ if (copy_len > 0 && data) {\n+ memcpy(obj + 12, data, copy_len); /* Payload starts at data[4] */\n+ }\n+\n+ /* Advance tail */\n+ s->fifo_tail[dest] = (s->fifo_tail[dest] + 1u) % (uint8_t)depth;\n+ s->fifo_count[dest]++;\n+\n+ /*\n+ * Set interrupt flags — TFNRFNIF signals data present.\n+ * Only assert RXIF if TFNRFNIE (per-FIFO RX interrupt enable) is set;\n+ * otherwise the frame waits silently until firmware arms reception\n+ * via CAN_MessageReceive() which re-enables TFNRFNIE.\n+ */\n+ s->fifosta[dest] |= CANFD_FIFOSTA_TFNRFNIF;\n+ if (s->fifocon[dest] & CANFD_FIFO_TFNRFNIE) {\n+ s->rxif |= (1u << dest);\n+ }\n+\n+ /*\n+ * Update UA: RX has no write slot; keep head slot for firmware read.\n+ */\n+ s->fifoua[dest] = canfd_fifo_slot_ua(s, dest, s->fifo_head[dest]);\n+\n+ /* CiVEC.ICODE — firmware RX ISR reads this to learn which FIFO fired */\n+ s->vec = (uint32_t)dest & 0x7Fu;\n+\n+ canfd_update_irq(s);\n+}\n+\n+/*\n+ * TX processing — triggered when TXREQ is set for a FIFO or TXQ\n+ * -----------------------------------------------------------------------\n+ */\n+\n+static void canfd_process_tx(PIC32MKCANFDState *s, int fifo)\n+{\n+ uint32_t ua;\n+ if (fifo == 0) {\n+ /* TXQ: transmit from head (oldest queued frame) */\n+ ua = canfd_txq_slot_ua(s, s->txq_head);\n+ } else {\n+ /* TX FIFO: transmit from head (oldest pending frame) */\n+ ua = canfd_fifo_slot_ua(s, fifo, s->fifo_head[fifo]);\n+ }\n+\n+ uint32_t ram_off = ua - s->msg_ram_phys;\n+ if (ram_off >= PIC32MK_CAN_MSGRAM_SIZE) {\n+ qemu_log_mask(LOG_GUEST_ERROR,\n+ \"pic32mk-canfd: TX UA 0x%08x out of message RAM\\n\", ua);\n+ return;\n+ }\n+\n+ const uint8_t *obj = s->msg_ram_buf + ram_off;\n+ uint32_t t0 = *(const uint32_t *)(obj + 0);\n+ uint32_t t1 = *(const uint32_t *)(obj + 4);\n+ uint8_t dlc = t1 & 0xFu;\n+ bool fdf = (t1 >> 7) & 1u;\n+ bool xtd = (t1 >> 4) & 1u; /* IDE in T1[4], not T0[30] per DS60001507 s3.1 */\n+ /* T0 ID layout: SID in bits[10:0], EID in bits[28:11] (same as R0/CiFLTOBJ) */\n+ uint32_t id = xtd ? (((t0 & 0x7FFu) << 18) | ((t0 >> 11) & 0x3FFFFu))\n+ : (t0 & 0x7FFu);\n+ int len = canfd_dlc_bytes(dlc, fdf);\n+ const uint8_t *data = obj + 8;\n+\n+ uint8_t opmod = (s->con >> CANFD_CON_OPMOD_SHIFT) & 0x7u;\n+ if (opmod == CANFD_OPMOD_INT_LOOP) {\n+ /* Internal loopback: deliver frame through acceptance filters */\n+ int dest = canfd_find_fifo(s, id, xtd);\n+ if (dest >= 1) {\n+ canfd_rx_deliver(s, id, xtd, fdf, dlc, data, len, dest);\n+ }\n+ } else {\n+ /* External TX: send frame to the virtual CAN bus */\n+ qemu_can_frame frame = {};\n+ frame.can_id = xtd ? (id | QEMU_CAN_EFF_FLAG) : id;\n+ frame.can_dlc = (uint8_t)len;\n+ frame.flags = fdf ? QEMU_CAN_FRMF_TYPE_FD : 0u;\n+ memcpy(frame.data, data, (size_t)len);\n+ if (s->canbus) {\n+ can_bus_client_send(&s->bus_client, &frame, 1);\n+ } else {\n+ qemu_log_mask(LOG_UNIMP,\n+ \"pic32mk-canfd[%u]: no canbus connected, \"\n+ \"dropping TX frame id=0x%08x\\n\", s->instance_id, id);\n+ }\n+ }\n+\n+ /* TX complete bookkeeping */\n+ if (fifo == 0) {\n+ /* TXQ: advance head (oldest pending frame consumed) */\n+ uint8_t depth = (uint8_t)canfd_txq_depth(s);\n+ s->txq_head = (s->txq_head + 1u) % depth;\n+ if (s->txq_count > 0) {\n+ s->txq_count--;\n+ }\n+ s->txqcon &= ~CANFD_FIFO_TXREQ;\n+ s->txqsta |= CANFD_TXQSTA_TXQNIF; /* slot available */\n+ s->txif |= 1u; /* bit 0 = TXQ */\n+ s->vec = 0u; /* ICODE=0 for TXQ */\n+ } else {\n+ uint32_t depth = ((s->fifocon[fifo] >> CANFD_FIFO_FSIZE_SHIFT) & 0x1Fu) + 1u;\n+ s->fifo_head[fifo] = (s->fifo_head[fifo] + 1u) % (uint8_t)depth;\n+ if (s->fifo_count[fifo] > 0) {\n+ s->fifo_count[fifo]--;\n+ }\n+ s->fifocon[fifo] &= ~CANFD_FIFO_TXREQ;\n+ s->fifosta[fifo] |= CANFD_FIFOSTA_TXATIF;\n+ /* TX slot freed — TX FIFO is no longer full */\n+ s->fifosta[fifo] |= CANFD_FIFOSTA_TFNRFNIF;\n+ s->txif |= (1u << fifo);\n+ s->vec = (uint32_t)fifo & 0x7Fu; /* ICODE = FIFO number */\n+ }\n+\n+ canfd_update_irq(s);\n+}\n+\n+/* Forward declaration — defined below canfd_receive() */\n+static void canfd_bus_buf_drain(PIC32MKCANFDState *s);\n+\n+/*\n+ * UINC — advance head (RX) or tail (TX) pointer, clear UINC bit\n+ * -----------------------------------------------------------------------\n+ */\n+\n+static void canfd_uinc_fifo(PIC32MKCANFDState *s, int n)\n+{\n+ bool is_tx = (s->fifocon[n] & CANFD_FIFO_TXEN) != 0u;\n+ uint32_t depth = ((s->fifocon[n] >> CANFD_FIFO_FSIZE_SHIFT) & 0x1Fu) + 1u;\n+\n+ if (is_tx) {\n+ /* Firmware finished writing a TX object — advance tail */\n+ s->fifo_tail[n] = (s->fifo_tail[n] + 1u) % (uint8_t)depth;\n+ s->fifo_count[n]++;\n+ s->fifoua[n] = canfd_fifo_slot_ua(s, n, s->fifo_tail[n]);\n+ /* TX FIFO full — clear \"not full\" flag so firmware won't overwrite */\n+ if (s->fifo_count[n] >= (uint8_t)depth) {\n+ s->fifosta[n] &= ~CANFD_FIFOSTA_TFNRFNIF;\n+ }\n+ } else {\n+ /* Firmware finished reading an RX object — advance head */\n+ if (s->fifo_count[n] > 0) {\n+ s->fifo_count[n]--;\n+ }\n+ s->fifo_head[n] = (s->fifo_head[n] + 1u) % (uint8_t)depth;\n+ s->fifoua[n] = canfd_fifo_slot_ua(s, n, s->fifo_head[n]);\n+\n+ if (s->fifo_count[n] == 0) {\n+ s->rxif &= ~(1u << n);\n+ s->fifosta[n] &= ~CANFD_FIFOSTA_TFNRFNIF;\n+ }\n+ canfd_update_irq(s);\n+ /*\n+ * Drain bus_buf after UINC: firmware just freed a FIFO slot, so the\n+ * next buffered frame can be delivered immediately. In polling mode\n+ * the firmware checks FIFOSTA.TFNRFNIF directly after UINC; delivering\n+ * here ensures that flag is set before the next poll. In interrupt\n+ * mode with TFNRFNIE enabled, canfd_rx_deliver will re-raise the IRQ\n+ * for the newly delivered frame.\n+ */\n+ canfd_bus_buf_drain(s);\n+ }\n+}\n+\n+static void canfd_uinc_txq(PIC32MKCANFDState *s)\n+{\n+ uint8_t depth = (uint8_t)canfd_txq_depth(s);\n+ /* Advance tail — firmware finished writing a TX object */\n+ s->txq_tail = (s->txq_tail + 1u) % depth;\n+ s->txq_count++;\n+ /* UA now points at the next empty write slot */\n+ s->txqua = canfd_txq_slot_ua(s, s->txq_tail);\n+}\n+\n+/*\n+ * FRESET — reset a FIFO to empty state\n+ * -----------------------------------------------------------------------\n+ */\n+\n+static void canfd_freset_fifo(PIC32MKCANFDState *s, int n)\n+{\n+ s->fifo_head[n] = 0;\n+ s->fifo_tail[n] = 0;\n+ s->fifo_count[n] = 0;\n+ s->rxif &= ~(1u << n);\n+ s->txif &= ~(1u << n);\n+ s->fifosta[n] = 0;\n+ /* TX FIFO: empty after reset means \"not full\" → TFNRFNIF = 1 */\n+ if (s->fifocon[n] & CANFD_FIFO_TXEN) {\n+ s->fifosta[n] |= CANFD_FIFOSTA_TFNRFNIF;\n+ }\n+ s->fifoua[n] = canfd_fifo_slot_ua(s, n, 0);\n+ canfd_update_irq(s);\n+}\n+\n+/*\n+ * Abort all pending TX (CiCON.ABAT)\n+ * -----------------------------------------------------------------------\n+ */\n+\n+static void canfd_abort_all_tx(PIC32MKCANFDState *s)\n+{\n+ /* TXQ */\n+ s->txqcon &= ~CANFD_FIFO_TXREQ;\n+ s->txqsta |= CANFD_TXQSTA_TXQNIF;\n+\n+ /* TX FIFOs */\n+ for (int i = 1; i < 32; i++) {\n+ if (s->fifocon[i] & CANFD_FIFO_TXEN) {\n+ s->fifocon[i] &= ~CANFD_FIFO_TXREQ;\n+ s->fifosta[i] |= CANFD_FIFOSTA_TXATIF;\n+ s->txif |= (1u << i);\n+ }\n+ }\n+ canfd_update_irq(s);\n+}\n+\n+/*\n+ * SocketCAN virtual bus callbacks\n+ *\n+ * When a can-bus object is linked (via the \"canbus\" QOM property), the\n+ * device behaves as a bus participant:\n+ * TX (non-loopback): canfd_process_tx() calls can_bus_client_send()\n+ * RX (from bus): canfd_receive() delivers frames through the\n+ * acceptance filter into an RX FIFO\n+ * -----------------------------------------------------------------------\n+ */\n+\n+static bool canfd_can_receive(CanBusClientState *client)\n+{\n+ PIC32MKCANFDState *s = container_of(client, PIC32MKCANFDState, bus_client);\n+ uint8_t opmod = (s->con >> CANFD_CON_OPMOD_SHIFT) & 0x7u;\n+ /*\n+ * Accept incoming frames in Normal, External Loopback, Listen-only,\n+ * and Restricted modes — not in Config or Internal Loopback mode.\n+ */\n+ return opmod == CANFD_OPMOD_NORMAL\n+ || opmod == CANFD_OPMOD_EXT_LOOP\n+ || opmod == CANFD_OPMOD_LISTEN\n+ || opmod == CANFD_OPMOD_RESTRICTED;\n+}\n+\n+/*\n+ * Drain the software bus buffer into hardware RX FIFOs.\n+ * Called after each RX UINC — the firmware just freed a FIFO slot so we can\n+ * deliver at most one buffered frame per UINC (matching real FIFO depth-1\n+ * behaviour). Stops when the head frame's target FIFO is still full.\n+ */\n+static void canfd_bus_buf_drain(PIC32MKCANFDState *s)\n+{\n+ while (s->bus_buf_count > 0) {\n+ int n = s->bus_buf_head;\n+ int dest = s->bus_buf_dest[n];\n+ uint32_t depth = ((s->fifocon[dest] >> CANFD_FIFO_FSIZE_SHIFT) & 0x1Fu) + 1u;\n+ if (s->fifo_count[dest] >= (uint8_t)depth) {\n+ break; /* target FIFO still full — wait for next UINC */\n+ }\n+ canfd_rx_deliver(s,\n+ s->bus_buf_id[n], s->bus_buf_xtd[n],\n+ s->bus_buf_fdf[n], s->bus_buf_dlc[n],\n+ s->bus_buf_data[n], s->bus_buf_len[n], dest);\n+ s->bus_buf_head = (s->bus_buf_head + 1) % 64;\n+ s->bus_buf_count--;\n+ }\n+}\n+\n+static ssize_t canfd_receive(CanBusClientState *client,\n+ const qemu_can_frame *frames, size_t frames_cnt)\n+{\n+ PIC32MKCANFDState *s = container_of(client, PIC32MKCANFDState, bus_client);\n+\n+ for (size_t i = 0; i < frames_cnt; i++) {\n+ const qemu_can_frame *f = &frames[i];\n+ bool xtd = (f->can_id & QEMU_CAN_EFF_FLAG) != 0;\n+ uint32_t id = xtd ? (f->can_id & QEMU_CAN_EFF_MASK)\n+ : (f->can_id & QEMU_CAN_SFF_MASK);\n+ bool fdf = (f->flags & QEMU_CAN_FRMF_TYPE_FD) != 0;\n+ uint8_t dlc = can_len2dlc(f->can_dlc);\n+ uint8_t rx_len = MIN((uint8_t)f->can_dlc, canfd_dlc_bytes(dlc, fdf));\n+ int dest = canfd_find_fifo(s, id, xtd);\n+\n+ /* Log received frame with full payload hex dump */\n+\n+ if (dest < 1) {\n+ continue; /* no filter match */\n+ }\n+\n+ uint32_t depth = ((s->fifocon[dest] >> CANFD_FIFO_FSIZE_SHIFT) & 0x1Fu) + 1u;\n+ if (s->fifo_count[dest] < (uint8_t)depth) {\n+ /* FIFO has space — deliver directly */\n+ canfd_rx_deliver(s, id, xtd, fdf, dlc, f->data, rx_len, dest);\n+ } else if (s->bus_buf_count < 64) {\n+ /* FIFO full — buffer for later delivery after UINC */\n+ int slot = s->bus_buf_tail;\n+ s->bus_buf_id[slot] = id;\n+ s->bus_buf_xtd[slot] = xtd;\n+ s->bus_buf_fdf[slot] = fdf;\n+ s->bus_buf_dlc[slot] = dlc;\n+ s->bus_buf_len[slot] = rx_len;\n+ s->bus_buf_dest[slot] = dest;\n+ memcpy(s->bus_buf_data[slot], f->data, rx_len);\n+ s->bus_buf_tail = (s->bus_buf_tail + 1) % 64;\n+ s->bus_buf_count++;\n+ } else {\n+ /* Software buffer also full — real bus overflow */\n+ s->rxovif |= (1u << dest);\n+ s->cint |= CANFD_INT_RXOVIF;\n+ canfd_update_irq(s);\n+ }\n+ }\n+ return (ssize_t)frames_cnt;\n+}\n+\n+static CanBusClientInfo canfd_bus_client_info = {\n+ .can_receive = canfd_can_receive,\n+ .receive = canfd_receive,\n+};\n+\n+/*\n+ * SFR MMIO read\n+ *\n+ * Each logical register occupies 0x10 bytes: +0=base, +4=SET, +8=CLR, +C=INV.\n+ * Reads always return the base register value regardless of sub-offset.\n+ * -----------------------------------------------------------------------\n+ */\n+\n+static uint64_t canfd_sfr_read(void *opaque, hwaddr offset, unsigned size)\n+{\n+ PIC32MKCANFDState *s = opaque;\n+ /* Strip SET/CLR/INV sub-offset: base is at (offset & ~0xCu) */\n+ hwaddr base_off = offset & ~0xCu;\n+\n+ switch (base_off) {\n+ case CANFD_CiCON:\n+ return s->con;\n+ case CANFD_CiNBTCFG:\n+ return s->nbtcfg;\n+ case CANFD_CiDBTCFG:\n+ return s->dbtcfg;\n+ case CANFD_CiTDC:\n+ return s->tdc;\n+ case CANFD_CiTBC:\n+ return s->tbc;\n+ case CANFD_CiTSCON:\n+ return s->tscon;\n+ case CANFD_CiVEC:\n+ return s->vec;\n+ case CANFD_CiINT:\n+ return s->cint;\n+ case CANFD_CiRXIF:\n+ return s->rxif;\n+ case CANFD_CiTXIF:\n+ return s->txif;\n+ case CANFD_CiRXOVIF:\n+ return s->rxovif;\n+ case CANFD_CiTXATIF:\n+ return 0;\n+ case CANFD_CiTXREQ:\n+ return s->txreq;\n+ case CANFD_CiTREC:\n+ return s->trec;\n+ case CANFD_CiTEFCON:\n+ return s->tefcon;\n+ case CANFD_CiTEFSTA:\n+ return s->tefsta;\n+ case CANFD_CiTEFUA:\n+ return s->tefua;\n+ case CANFD_CiFIFOBA:\n+ return 0;\n+ case CANFD_CiTXQCON:\n+ return s->txqcon;\n+ case CANFD_CiTXQSTA:\n+ return s->txqsta;\n+ case CANFD_CiTXQUA:\n+ return s->txqua;\n+ default:\n+ break;\n+ }\n+\n+ /*\n+ * FIFO registers: base 0x170, stride 0x30 per FIFO (n=1..31)\n+ * +0x00: CiFIFOCONn, +0x10: CiFIFOSTAn, +0x20: CiFIFOUAn\n+ */\n+ if (base_off >= CANFD_CiFIFOCON(1) && base_off <= CANFD_CiFIFOCON(31) + 0x20u) {\n+ int idx = (int)((base_off - 0x170u) / 0x30u) + 1;\n+ int sub = (int)((base_off - 0x170u) % 0x30u);\n+ if (idx >= 1 && idx <= 31) {\n+ if (sub == 0x00) {\n+ return s->fifocon[idx];\n+ }\n+ if (sub == 0x10) {\n+ return s->fifosta[idx];\n+ }\n+ if (sub == 0x20) {\n+ return s->fifoua[idx];\n+ }\n+ }\n+ }\n+\n+ /* Filter control: stride 0x10 per register (r=0..7) */\n+ if (base_off >= CANFD_CiFLTCON(0) && base_off <= CANFD_CiFLTCON(7)) {\n+ return s->fltcon[(base_off - 0x740u) / 0x10u];\n+ }\n+\n+ /* Filter obj/mask: stride 0x20 per pair (n=0..31) */\n+ if (base_off >= CANFD_CiFLTOBJ(0) && base_off <= CANFD_CiMASK(31)) {\n+ if (base_off >= 0x7C0u && base_off < 0x7C0u + 32u * 0x20u) {\n+ int fn = (int)((base_off - 0x7C0u) / 0x20u);\n+ return s->fltobj[fn];\n+ }\n+ if (base_off >= 0x7D0u && base_off < 0x7D0u + 32u * 0x20u) {\n+ int fn = (int)((base_off - 0x7D0u) / 0x20u);\n+ return s->mask[fn];\n+ }\n+ }\n+\n+ qemu_log_mask(LOG_UNIMP,\n+ \"pic32mk-canfd: unimplemented SFR read @ +0x%03\" HWADDR_PRIx \"\\n\",\n+ offset);\n+ return 0;\n+}\n+\n+/*\n+ * Apply a CLR/SET/INV write to a register value.\n+ * PIC32MK convention: sub=0 → plain write, +4 → CLR, +8 → SET, +0xC → INV\n+ * -----------------------------------------------------------------------\n+ */\n+static uint32_t apply_sci(uint32_t cur, uint32_t val, int sub)\n+{\n+ switch (sub) {\n+ case 0:\n+ return val;\n+ case 4:\n+ return cur & ~val;\n+ case 8:\n+ return cur | val;\n+ case 0xC:\n+ return cur ^ val;\n+ default:\n+ return cur;\n+ }\n+}\n+\n+/*\n+ * SFR MMIO write\n+ *\n+ * Each logical register occupies 0x10 bytes: +0=base, +4=SET, +8=CLR, +C=INV.\n+ * UINC, TXREQ, FRESET are pulse bits — handled then stripped from storage.\n+ * -----------------------------------------------------------------------\n+ */\n+\n+static void canfd_sfr_write(void *opaque, hwaddr offset, uint64_t val,\n+ unsigned size)\n+{\n+ PIC32MKCANFDState *s = opaque;\n+ uint32_t v32 = (uint32_t)val;\n+ hwaddr base_off = offset & ~0xCu;\n+ int sub = (int)(offset & 0xCu);\n+ uint8_t opmod = (s->con >> CANFD_CON_OPMOD_SHIFT) & 0x7u;\n+\n+ switch (base_off) {\n+\n+ /* ---- CiCON ---- */\n+ case CANFD_CiCON: {\n+ uint8_t old_opmod = opmod;\n+ uint32_t new_con = apply_sci(s->con, v32, sub);\n+\n+ uint8_t reqop = (new_con >> CANFD_CON_REQOP_SHIFT) & 0x7u;\n+ /* Reflect REQOP into OPMOD immediately; BUSY always 0 */\n+ new_con = (new_con & ~CANFD_CON_OPMOD_MASK)\n+ | ((uint32_t)reqop << CANFD_CON_OPMOD_SHIFT);\n+ s->con = new_con;\n+\n+ if (reqop != old_opmod) {\n+ s->cint |= CANFD_INT_MODIF;\n+ canfd_update_irq(s);\n+ }\n+\n+ /* Handle ABAT */\n+ if (new_con & CANFD_CON_ABAT) {\n+ canfd_abort_all_tx(s);\n+ s->con &= ~CANFD_CON_ABAT;\n+ }\n+\n+ /* Initialise TXQ UA when TXQEN becomes set */\n+ if ((new_con & CANFD_CON_TXQEN) && !(s->txqua)) {\n+ s->txqua = canfd_txq_slot_ua(s, s->txq_tail);\n+ }\n+ return;\n+ }\n+\n+ /* ---- Bit-time / TDC — only writable in Config mode ---- */\n+ case CANFD_CiNBTCFG:\n+ if (opmod == CANFD_OPMOD_CONFIG) {\n+ s->nbtcfg = apply_sci(s->nbtcfg, v32, sub);\n+ }\n+ return;\n+ case CANFD_CiDBTCFG:\n+ if (opmod == CANFD_OPMOD_CONFIG) {\n+ s->dbtcfg = apply_sci(s->dbtcfg, v32, sub);\n+ }\n+ return;\n+ case CANFD_CiTDC:\n+ if (opmod == CANFD_OPMOD_CONFIG) {\n+ s->tdc = apply_sci(s->tdc, v32, sub);\n+ }\n+ return;\n+ case CANFD_CiTSCON:\n+ s->tscon = apply_sci(s->tscon, v32, sub);\n+ return;\n+\n+ /* CiINT: enable bits [31:16] writable; status [15:0] cleared by fw */\n+ case CANFD_CiINT:\n+ s->cint = apply_sci(s->cint, v32, sub);\n+ canfd_update_irq(s);\n+ return;\n+\n+ /* ---- CiRXIF / CiTXIF / CiRXOVIF — firmware clears by writing 0 ---- */\n+ case CANFD_CiRXIF:\n+ s->rxif = apply_sci(s->rxif, v32, sub);\n+ canfd_update_irq(s);\n+ return;\n+ case CANFD_CiTXIF:\n+ s->txif = apply_sci(s->txif, v32, sub);\n+ canfd_update_irq(s);\n+ return;\n+ case CANFD_CiRXOVIF:\n+ s->rxovif = apply_sci(s->rxovif, v32, sub);\n+ return;\n+\n+ /* ---- CiTXREQ — writing 1 to a bit requests TX for that FIFO ---- */\n+ case CANFD_CiTXREQ: {\n+ uint32_t new_req = apply_sci(s->txreq, v32, sub);\n+ uint32_t newly = new_req & ~s->txreq;\n+ s->txreq = new_req;\n+ for (int i = 1; i < 32; i++) {\n+ if (newly & (1u << i)) {\n+ canfd_process_tx(s, i);\n+ }\n+ }\n+ return;\n+ }\n+\n+ /* ---- CiTXQCON ---- */\n+ case CANFD_CiTXQCON: {\n+ uint32_t new_con = apply_sci(s->txqcon, v32, sub);\n+ bool uinc = (new_con & CANFD_FIFO_UINC) != 0u;\n+ bool req = (new_con & CANFD_FIFO_TXREQ) != 0u;\n+ s->txqcon = new_con & ~(CANFD_FIFO_UINC | CANFD_FIFO_TXREQ\n+ | CANFD_FIFO_FRESET);\n+ /*\n+ * Clearing TXQEIE (bit 4) acknowledges TX completion — deassert TXQ\n+ * source in txif so TXIF is not re-asserted on the next irq update.\n+ */\n+ if (!(s->txqcon & (1u << 4u))) {\n+ s->txif &= ~1u;\n+ canfd_update_irq(s);\n+ }\n+ if (uinc) {\n+ canfd_uinc_txq(s);\n+ }\n+ if (req) {\n+ canfd_process_tx(s, 0);\n+ }\n+ return;\n+ }\n+\n+ /* ---- CiTXQSTA / CiTXQUA — hardware-managed, ignore firmware writes ---- */\n+ case CANFD_CiTXQSTA:\n+ case CANFD_CiTXQUA:\n+ return;\n+\n+ /* ---- TEF (stub) ---- */\n+ case CANFD_CiTEFCON:\n+ s->tefcon = apply_sci(s->tefcon, v32, sub);\n+ return;\n+ case CANFD_CiTEFSTA:\n+ s->tefsta = apply_sci(s->tefsta, v32, sub);\n+ return;\n+ case CANFD_CiTEFUA:\n+ return;\n+\n+ /* ---- CiFIFOBA — Phase 3C stub: log and ignore ---- */\n+ case CANFD_CiFIFOBA:\n+ if (sub == 0) {\n+ qemu_log_mask(LOG_UNIMP,\n+ \"pic32mk-canfd: CiFIFOBA write 0x%08x (Phase 3C stub, \"\n+ \"using fixed msg RAM base 0x%08x)\\n\",\n+ v32, s->msg_ram_phys);\n+ }\n+ return;\n+\n+ default:\n+ break;\n+ }\n+\n+ /*\n+ * ---- FIFO registers: base 0x170, stride 0x30 per FIFO (n=1..31)\n+ * +0x00: CiFIFOCONn, +0x10: CiFIFOSTAn, +0x20: CiFIFOUAn ----\n+ */\n+ if (base_off >= CANFD_CiFIFOCON(1) && base_off <= CANFD_CiFIFOCON(31) + 0x20u) {\n+ int idx = (int)((base_off - 0x170u) / 0x30u) + 1;\n+ int field = (int)((base_off - 0x170u) % 0x30u);\n+ if (idx < 1 || idx > 31) {\n+ return;\n+ }\n+\n+ if (field == 0x00) {\n+ /* CiFIFOCONn */\n+ uint32_t new_con = apply_sci(s->fifocon[idx], v32, sub);\n+\n+ if (new_con & CANFD_FIFO_FRESET) {\n+ s->fifocon[idx] = new_con & ~CANFD_FIFO_FRESET;\n+ canfd_freset_fifo(s, idx);\n+ return;\n+ }\n+\n+ bool uinc = (new_con & CANFD_FIFO_UINC) != 0u;\n+ bool txreq = (new_con & CANFD_FIFO_TXREQ) != 0u;\n+ s->fifocon[idx] = new_con & ~(CANFD_FIFO_UINC | CANFD_FIFO_TXREQ);\n+\n+ /*\n+ * First-time TX FIFO configuration (Harmony CAN1..4 init pattern):\n+ * firmware writes FIFOCONn with TXEN set; at this point the FIFO\n+ * is empty so it is \"not full\" and FIFOUA must point to slot 0.\n+ * Mirror the TXQ treatment: set TFNRFNIF=1 and initialise UA.\n+ */\n+ if ((s->fifocon[idx] & CANFD_FIFO_TXEN) && s->fifoua[idx] == 0) {\n+ s->fifosta[idx] |= CANFD_FIFOSTA_TFNRFNIF;\n+ s->fifoua[idx] = canfd_fifo_slot_ua(s, idx, 0);\n+ }\n+\n+ /*\n+ * Clearing TFERFFIE (bit 4) acknowledges TX completion for this\n+ * FIFO — deassert its txif source bit.\n+ */\n+ if (!(s->fifocon[idx] & (1u << 4u))) {\n+ s->txif &= ~(1u << idx);\n+ canfd_update_irq(s);\n+ }\n+ if (uinc) {\n+ canfd_uinc_fifo(s, idx);\n+ }\n+ if (txreq && (s->fifocon[idx] & CANFD_FIFO_TXEN)) {\n+ canfd_process_tx(s, idx);\n+ }\n+\n+ /*\n+ * For RX FIFOs, honour TFNRFNIE: if the firmware just toggled it,\n+ * update rxif to match the real hardware behaviour.\n+ * Enabling → IRQ rises (frame already in FIFO fires interrupt)\n+ * Disabling → IRQ falls (flow-control)\n+ */\n+ if (!(s->fifocon[idx] & CANFD_FIFO_TXEN)) {\n+ bool new_ie = (s->fifocon[idx] & CANFD_FIFO_TFNRFNIE) != 0;\n+ bool has_data = (s->fifosta[idx] & CANFD_FIFOSTA_TFNRFNIF) != 0;\n+ if (has_data && new_ie) {\n+ s->rxif |= (1u << idx);\n+ } else {\n+ s->rxif &= ~(1u << idx);\n+ }\n+ canfd_update_irq(s);\n+ }\n+\n+ } else if (field == 0x10) {\n+ /* CiFIFOSTAn — firmware can clear status flags */\n+ s->fifosta[idx] = apply_sci(s->fifosta[idx], v32, sub);\n+ if (!(s->fifosta[idx] & CANFD_FIFOSTA_TFNRFNIF)) {\n+ if (s->fifo_count[idx] == 0) {\n+ s->rxif &= ~(1u << idx);\n+ }\n+ }\n+ canfd_update_irq(s);\n+ }\n+ /* CiFIFOUA (+0x20) is hardware-owned, ignore writes */\n+ return;\n+ }\n+\n+ /* ---- Filter control: stride 0x10 per register (r=0..7) ---- */\n+ if (base_off >= CANFD_CiFLTCON(0) && base_off <= CANFD_CiFLTCON(7)) {\n+ int reg = (int)((base_off - 0x740u) / 0x10u);\n+ s->fltcon[reg] = apply_sci(s->fltcon[reg], v32, sub);\n+ return;\n+ }\n+\n+ /* ---- Filter object / mask — only writable in Config mode ---- */\n+ if (base_off >= CANFD_CiFLTOBJ(0) && base_off < 0x7D0u + 32u * 0x20u) {\n+ if (opmod != CANFD_OPMOD_CONFIG) {\n+ return;\n+ }\n+ if (base_off >= 0x7C0u && base_off < 0x7C0u + 32u * 0x20u) {\n+ int fn = (int)((base_off - 0x7C0u) / 0x20u);\n+ s->fltobj[fn] = apply_sci(s->fltobj[fn], v32, sub);\n+ } else if (base_off >= 0x7D0u) {\n+ int fn = (int)((base_off - 0x7D0u) / 0x20u);\n+ if (fn < 32) {\n+ s->mask[fn] = apply_sci(s->mask[fn], v32, sub);\n+ }\n+ }\n+ return;\n+ }\n+\n+ qemu_log_mask(LOG_UNIMP,\n+ \"pic32mk-canfd: unimplemented SFR write @ +0x%03\" HWADDR_PRIx\n+ \" = 0x%08x\\n\", offset, v32);\n+}\n+\n+static const MemoryRegionOps canfd_sfr_ops = {\n+ .read = canfd_sfr_read,\n+ .write = canfd_sfr_write,\n+ .endianness = DEVICE_LITTLE_ENDIAN,\n+ .valid = {\n+ .min_access_size = 1,\n+ .max_access_size = 4,\n+ },\n+};\n+\n+/*\n+ * Device lifecycle\n+ * -----------------------------------------------------------------------\n+ */\n+\n+static void pic32mk_canfd_reset(DeviceState *dev)\n+{\n+ PIC32MKCANFDState *s = PIC32MK_CANFD(dev);\n+\n+ s->con = CANFD_CON_RESET; /* OPMOD=100 (Config), REQOP=100 */\n+ s->nbtcfg = 0;\n+ s->dbtcfg = 0;\n+ s->tdc = 0;\n+ s->tbc = 0;\n+ s->tscon = 0;\n+ s->vec = 0;\n+ s->cint = 0;\n+ s->rxif = 0;\n+ s->txif = 0;\n+ s->rxovif = 0;\n+ s->txreq = 0;\n+ s->trec = 0;\n+ s->tefcon = 0;\n+ s->tefsta = 0;\n+ s->tefua = 0;\n+ s->txqcon = 0;\n+ s->txqsta = CANFD_TXQSTA_TXQNIF; /* TXQNIF=1 (bit 0) — slot available on reset */\n+ s->txq_head = 0;\n+ s->txq_tail = 0;\n+ s->txq_count = 0;\n+ s->txqua = 0; /* will be computed when TXQEN is set */\n+\n+ for (int i = 0; i < 32; i++) {\n+ s->fifocon[i] = 0;\n+ s->fifosta[i] = 0;\n+ s->fifoua[i] = 0;\n+ s->fifo_head[i] = 0;\n+ s->fifo_tail[i] = 0;\n+ s->fifo_count[i] = 0;\n+ s->fltobj[i] = 0;\n+ s->mask[i] = 0;\n+ }\n+ for (int i = 0; i < 8; i++) {\n+ s->fltcon[i] = 0;\n+ }\n+\n+ /* Clear message RAM */\n+ if (s->msg_ram_buf) {\n+ memset(s->msg_ram_buf, 0, PIC32MK_CAN_MSGRAM_SIZE);\n+ }\n+\n+ /* Reset software bus buffer */\n+ s->bus_buf_head = 0;\n+ s->bus_buf_tail = 0;\n+ s->bus_buf_count = 0;\n+\n+ qemu_irq_lower(s->irq);\n+}\n+\n+static void pic32mk_canfd_instance_init(Object *obj)\n+{\n+ PIC32MKCANFDState *s = PIC32MK_CANFD(obj);\n+ object_property_add_link(obj, \"canbus\", TYPE_CAN_BUS,\n+ (Object **)&s->canbus,\n+ qdev_prop_allow_set_link_before_realize,\n+ 0);\n+}\n+\n+static void pic32mk_canfd_realize(DeviceState *dev, Error **errp)\n+{\n+ PIC32MKCANFDState *s = PIC32MK_CANFD(dev);\n+\n+ /* SFR region (index 0) */\n+ memory_region_init_io(&s->sfr_mmio, OBJECT(s), &canfd_sfr_ops, s,\n+ \"pic32mk-canfd-sfr\", PIC32MK_CAN_SFR_SIZE);\n+ sysbus_init_mmio(SYS_BUS_DEVICE(dev), &s->sfr_mmio);\n+\n+ /* Message RAM backing store */\n+ s->msg_ram_buf = g_malloc0(PIC32MK_CAN_MSGRAM_SIZE);\n+\n+ /* Message RAM region (index 1) — RAM-ptr so CPU can read/write freely */\n+ memory_region_init_ram_ptr(&s->msg_ram, OBJECT(s),\n+ \"pic32mk-canfd-msgram\",\n+ PIC32MK_CAN_MSGRAM_SIZE,\n+ s->msg_ram_buf);\n+ sysbus_init_mmio(SYS_BUS_DEVICE(dev), &s->msg_ram);\n+\n+ sysbus_init_irq(SYS_BUS_DEVICE(dev), &s->irq);\n+\n+ /* Connect to virtual CAN bus if one was linked */\n+ s->bus_client.info = &canfd_bus_client_info;\n+ s->bus_client.fd_mode = true;\n+ if (s->canbus) {\n+ if (can_bus_insert_client(s->canbus, &s->bus_client) < 0) {\n+ error_setg(errp, \"pic32mk-canfd: can_bus_insert_client failed\");\n+ return;\n+ }\n+ }\n+}\n+\n+static void pic32mk_canfd_unrealize(DeviceState *dev)\n+{\n+ PIC32MKCANFDState *s = PIC32MK_CANFD(dev);\n+ can_bus_remove_client(&s->bus_client);\n+ g_free(s->msg_ram_buf);\n+ s->msg_ram_buf = NULL;\n+}\n+\n+/*\n+ * Properties\n+ * -----------------------------------------------------------------------\n+ */\n+\n+static const Property pic32mk_canfd_props[] = {\n+ DEFINE_PROP_UINT32(\"msg-ram-base\", PIC32MKCANFDState, msg_ram_phys, 0),\n+ DEFINE_PROP_UINT32(\"instance-id\", PIC32MKCANFDState, instance_id, 0),\n+};\n+\n+/*\n+ * Class / type registration\n+ * -----------------------------------------------------------------------\n+ */\n+\n+static void pic32mk_canfd_class_init(ObjectClass *klass, const void *data)\n+{\n+ DeviceClass *dc = DEVICE_CLASS(klass);\n+\n+ dc->realize = pic32mk_canfd_realize;\n+ dc->unrealize = pic32mk_canfd_unrealize;\n+ device_class_set_legacy_reset(dc, pic32mk_canfd_reset);\n+ device_class_set_props(dc, pic32mk_canfd_props);\n+ set_bit(DEVICE_CATEGORY_MISC, dc->categories);\n+ dc->desc = \"Microchip PIC32MK CAN FD controller\";\n+}\n+\n+static const TypeInfo pic32mk_canfd_info = {\n+ .name = TYPE_PIC32MK_CANFD,\n+ .parent = TYPE_SYS_BUS_DEVICE,\n+ .instance_size = sizeof(PIC32MKCANFDState),\n+ .instance_init = pic32mk_canfd_instance_init,\n+ .class_init = pic32mk_canfd_class_init,\n+};\n+\n+static void pic32mk_canfd_register_types(void)\n+{\n+ type_register_static(&pic32mk_canfd_info);\n+}\n+\n+type_init(pic32mk_canfd_register_types)\ndiff --git a/hw/mips/pic32mk_cfg.c b/hw/mips/pic32mk_cfg.c\nnew file mode 100644\nindex 0000000000..736f0e84fd\n--- /dev/null\n+++ b/hw/mips/pic32mk_cfg.c\n@@ -0,0 +1,247 @@\n+/*\n+ * Microchip PIC32MK — Configuration / PMD / SYSKEY registers\n+ * Datasheet: DS60001519E §6\n+ *\n+ * Models the CFG register block at 0xBF800000–0xBF80011F:\n+ * CFGCON, SYSKEY, PMD1–PMD7, CFGCON2.\n+ *\n+ * SYSKEY implements the unlock state machine:\n+ * Write 0x00000000 → LOCKED\n+ * Write 0xAA996655 → FIRST_KEY\n+ * Write 0x556699AA when FIRST_KEY → UNLOCKED\n+ * Any wrong write → LOCKED\n+ * SYSKEY always reads 0.\n+ *\n+ * PMD1–PMD7 writes are gated by CFGCON.PMDLOCK (bit 29).\n+ * When PMDLOCK=1, writes to PMDx are silently ignored.\n+ *\n+ * Copyright (c) 2026 QEMU contributors\n+ * SPDX-License-Identifier: GPL-2.0-or-later\n+ */\n+\n+#include \"qemu/osdep.h\"\n+#include \"qemu/log.h\"\n+#include \"qemu/module.h\"\n+#include \"hw/core/sysbus.h\"\n+#include \"hw/mips/pic32mk.h\"\n+\n+#define TYPE_PIC32MK_CFG \"pic32mk-cfg\"\n+OBJECT_DECLARE_SIMPLE_TYPE(PIC32MKCFGState, PIC32MK_CFG)\n+\n+/* SYSKEY unlock state machine */\n+enum {\n+ SYSKEY_LOCKED = 0,\n+ SYSKEY_FIRST_KEY,\n+ SYSKEY_UNLOCKED,\n+};\n+\n+typedef struct PIC32MKCFGState {\n+ SysBusDevice parent_obj;\n+ MemoryRegion mmio;\n+\n+ uint32_t cfgcon;\n+ uint32_t cfgcon2;\n+ uint32_t checon; /* Prefetch Cache Control */\n+ uint32_t pmd[PIC32MK_PMD_COUNT]; /* PMD1–PMD7 */\n+ uint8_t syskey_state; /* SYSKEY unlock FSM */\n+} PIC32MKCFGState;\n+\n+/* SET/CLR/INV helper */\n+static uint32_t apply_sci(uint32_t old, uint32_t val, unsigned sub)\n+{\n+ switch (sub) {\n+ case 0x0:\n+ return val;\n+ case 0x4:\n+ return old | val;\n+ case 0x8:\n+ return old & ~val;\n+ case 0xC:\n+ return old ^ val;\n+ default:\n+ return old;\n+ }\n+}\n+\n+/*\n+ * Read handler\n+ * -----------------------------------------------------------------------\n+ */\n+static uint64_t pic32mk_cfg_read(void *opaque, hwaddr addr, unsigned size)\n+{\n+ PIC32MKCFGState *s = PIC32MK_CFG(opaque);\n+ hwaddr base = addr & ~0xFu;\n+\n+ switch (base) {\n+ case PIC32MK_CFGCON:\n+ return s->cfgcon;\n+\n+ case PIC32MK_SYSKEY:\n+ /* SYSKEY always reads 0 */\n+ return 0;\n+\n+ case PIC32MK_PMD1:\n+ return s->pmd[0];\n+ case PIC32MK_PMD2:\n+ return s->pmd[1];\n+ case PIC32MK_PMD3:\n+ return s->pmd[2];\n+ case PIC32MK_PMD4:\n+ return s->pmd[3];\n+ case PIC32MK_PMD5:\n+ return s->pmd[4];\n+ case PIC32MK_PMD6:\n+ return s->pmd[5];\n+ case PIC32MK_PMD7:\n+ return s->pmd[6];\n+\n+ case PIC32MK_CFGCON2:\n+ return s->cfgcon2;\n+\n+ case PIC32MK_CHECON:\n+ return s->checon;\n+\n+ default:\n+ qemu_log_mask(LOG_UNIMP,\n+ \"pic32mk-cfg: unimplemented read @ 0x%03\" HWADDR_PRIx \"\\n\",\n+ addr);\n+ return 0;\n+ }\n+}\n+\n+/*\n+ * Write handler\n+ * -----------------------------------------------------------------------\n+ */\n+static void pic32mk_cfg_write(void *opaque, hwaddr addr, uint64_t val,\n+ unsigned size)\n+{\n+ PIC32MKCFGState *s = PIC32MK_CFG(opaque);\n+ hwaddr base = addr & ~0xFu;\n+ unsigned sub = addr & 0xCu;\n+ uint32_t v32 = (uint32_t)val;\n+\n+ /* Handle SYSKEY — no SET/CLR/INV, always direct write */\n+ if (base == PIC32MK_SYSKEY) {\n+ switch (s->syskey_state) {\n+ case SYSKEY_LOCKED:\n+ if (v32 == 0xAA996655u) {\n+ s->syskey_state = SYSKEY_FIRST_KEY;\n+ }\n+ break;\n+ case SYSKEY_FIRST_KEY:\n+ if (v32 == 0x556699AAu) {\n+ s->syskey_state = SYSKEY_UNLOCKED;\n+ } else {\n+ s->syskey_state = SYSKEY_LOCKED;\n+ }\n+ break;\n+ case SYSKEY_UNLOCKED:\n+ /* Any write re-locks (clearing 0x00000000 or random) */\n+ if (v32 != 0xAA996655u && v32 != 0x556699AAu) {\n+ s->syskey_state = SYSKEY_LOCKED;\n+ }\n+ break;\n+ }\n+ return;\n+ }\n+\n+ switch (base) {\n+ case PIC32MK_CFGCON:\n+ s->cfgcon = apply_sci(s->cfgcon, v32, sub);\n+ break;\n+\n+ case PIC32MK_CFGCON2:\n+ s->cfgcon2 = apply_sci(s->cfgcon2, v32, sub);\n+ break;\n+\n+ case PIC32MK_CHECON:\n+ s->checon = apply_sci(s->checon, v32, sub);\n+ break;\n+\n+ /* PMD1–PMD7: gated by PMDLOCK */\n+ case PIC32MK_PMD1:\n+ case PIC32MK_PMD2:\n+ case PIC32MK_PMD3:\n+ case PIC32MK_PMD4:\n+ case PIC32MK_PMD5:\n+ case PIC32MK_PMD6:\n+ case PIC32MK_PMD7:\n+ if (s->cfgcon & PIC32MK_CFGCON_PMDLOCK) {\n+ /* Writes rejected when PMDLOCK = 1 */\n+ return;\n+ }\n+ s->pmd[(base - PIC32MK_PMD1) / 0x10] =\n+ apply_sci(s->pmd[(base - PIC32MK_PMD1) / 0x10], v32, sub);\n+ break;\n+\n+ default:\n+ qemu_log_mask(LOG_UNIMP,\n+ \"pic32mk-cfg: unimplemented write @ 0x%03\" HWADDR_PRIx\n+ \" = 0x%08x\\n\", addr, v32);\n+ break;\n+ }\n+}\n+\n+static const MemoryRegionOps pic32mk_cfg_ops = {\n+ .read = pic32mk_cfg_read,\n+ .write = pic32mk_cfg_write,\n+ .endianness = DEVICE_LITTLE_ENDIAN,\n+ .valid = {\n+ .min_access_size = 4,\n+ .max_access_size = 4,\n+ },\n+};\n+\n+/*\n+ * Realize / Reset\n+ * -----------------------------------------------------------------------\n+ */\n+\n+static void pic32mk_cfg_realize(DeviceState *dev, Error **errp)\n+{\n+ PIC32MKCFGState *s = PIC32MK_CFG(dev);\n+ memory_region_init_io(&s->mmio, OBJECT(s), &pic32mk_cfg_ops, s,\n+ \"pic32mk-cfg\", PIC32MK_CFG_SIZE);\n+ sysbus_init_mmio(SYS_BUS_DEVICE(dev), &s->mmio);\n+}\n+\n+static void pic32mk_cfg_reset_hold(Object *obj, ResetType type)\n+{\n+ PIC32MKCFGState *s = PIC32MK_CFG(obj);\n+ s->cfgcon = 0;\n+ s->cfgcon2 = 0;\n+ s->checon = 0;\n+ s->syskey_state = SYSKEY_LOCKED;\n+ for (int i = 0; i < PIC32MK_PMD_COUNT; i++) {\n+ s->pmd[i] = 0;\n+ }\n+}\n+\n+/*\n+ * QOM boilerplate\n+ * -----------------------------------------------------------------------\n+ */\n+\n+static void pic32mk_cfg_class_init(ObjectClass *klass, const void *data)\n+{\n+ DeviceClass *dc = DEVICE_CLASS(klass);\n+ ResettableClass *rc = RESETTABLE_CLASS(klass);\n+ dc->realize = pic32mk_cfg_realize;\n+ dc->desc = \"PIC32MK Configuration / PMD / SYSKEY\";\n+ rc->phases.hold = pic32mk_cfg_reset_hold;\n+}\n+\n+static const TypeInfo pic32mk_cfg_info = {\n+ .name = TYPE_PIC32MK_CFG,\n+ .parent = TYPE_SYS_BUS_DEVICE,\n+ .instance_size = sizeof(PIC32MKCFGState),\n+ .class_init = pic32mk_cfg_class_init,\n+};\n+\n+static void pic32mk_cfg_register_types(void)\n+{\n+ type_register_static(&pic32mk_cfg_info);\n+}\n+\n+type_init(pic32mk_cfg_register_types)\ndiff --git a/hw/mips/pic32mk_cru.c b/hw/mips/pic32mk_cru.c\nnew file mode 100644\nindex 0000000000..3dead5107d\n--- /dev/null\n+++ b/hw/mips/pic32mk_cru.c\n@@ -0,0 +1,375 @@\n+/*\n+ * Microchip PIC32MK — Clock Reference Unit (CRU)\n+ * Datasheet: DS60001519E §9\n+ *\n+ * Models the CRU register block at 0xBF801200–0xBF80139F:\n+ * OSCCON, OSCTUN, SPLLCON, UPLLCON, RCON, RSWRST, RNMICON, PWRCON,\n+ * REFO1–4 CON/TRIM, PB1–7 DIV, CLKSTAT.\n+ *\n+ * All oscillator ready bits are returned set immediately — no PLL lock\n+ * timing is simulated. Register values are stored but no dynamic clock\n+ * frequency propagation is performed; peripherals use the fixed\n+ * PIC32MK_CPU_HZ constant.\n+ *\n+ * RCON (§7) is also handled here since it falls within the CRU address\n+ * range (offset 0x40 = physical 0xBF801240).\n+ *\n+ * Copyright (c) 2026 QEMU contributors\n+ * SPDX-License-Identifier: GPL-2.0-or-later\n+ */\n+\n+#include \"qemu/osdep.h\"\n+#include \"qemu/log.h\"\n+#include \"qemu/module.h\"\n+#include \"hw/core/sysbus.h\"\n+#include \"hw/mips/pic32mk.h\"\n+#include \"system/runstate.h\"\n+\n+#define TYPE_PIC32MK_CRU \"pic32mk-cru\"\n+OBJECT_DECLARE_SIMPLE_TYPE(PIC32MKCRUState, PIC32MK_CRU)\n+\n+typedef struct PIC32MKCRUState {\n+ SysBusDevice parent_obj;\n+ MemoryRegion mmio;\n+\n+ /* Oscillator / PLL */\n+ uint32_t osccon;\n+ uint32_t osctun;\n+ uint32_t spllcon;\n+ uint32_t upllcon;\n+\n+ /* Reset control (RCON / RSWRST / RNMICON / PWRCON) */\n+ uint32_t rcon;\n+ uint32_t rswrst;\n+ uint32_t rnmicon;\n+ uint32_t pwrcon;\n+\n+ /* Reference clock outputs 1–4 */\n+ uint32_t refo_con[PIC32MK_CRU_NREFO];\n+ uint32_t refo_trim[PIC32MK_CRU_NREFO];\n+\n+ /* Peripheral bus clock dividers 1–7 */\n+ uint32_t pbdiv[PIC32MK_CRU_NPB];\n+} PIC32MKCRUState;\n+\n+/*\n+ * SET/CLR/INV helper — returns the new register value without applying it,\n+ * so the caller can enforce read-only masks.\n+ * sub == 0x0: direct write, 0x4: SET, 0x8: CLR, 0xC: INV\n+ * -----------------------------------------------------------------------\n+ */\n+static uint32_t apply_sci(uint32_t old, uint32_t val, unsigned sub)\n+{\n+ switch (sub) {\n+ case 0x0:\n+ return val;\n+ case 0x4:\n+ return old | val;\n+ case 0x8:\n+ return old & ~val;\n+ case 0xC:\n+ return old ^ val;\n+ default:\n+ return old;\n+ /* unreachable */;\n+ }\n+}\n+\n+/*\n+ * Read handler\n+ * -----------------------------------------------------------------------\n+ */\n+static uint64_t pic32mk_cru_read(void *opaque, hwaddr addr, unsigned size)\n+{\n+ PIC32MKCRUState *s = PIC32MK_CRU(opaque);\n+ hwaddr base = addr & ~0xFu; /* register base (strip SET/CLR/INV) */\n+\n+ switch (base) {\n+ case PIC32MK_CRU_OSCCON:\n+ return s->osccon;\n+ case PIC32MK_CRU_OSCTUN:\n+ return s->osctun;\n+ case PIC32MK_CRU_SPLLCON:\n+ return s->spllcon;\n+ case PIC32MK_CRU_UPLLCON:\n+ return s->upllcon;\n+\n+ case PIC32MK_CRU_RCON:\n+ return s->rcon;\n+ case PIC32MK_CRU_RSWRST:\n+ return s->rswrst;\n+ case PIC32MK_CRU_RNMICON:\n+ return s->rnmicon;\n+ case PIC32MK_CRU_PWRCON:\n+ return s->pwrcon;\n+\n+ /* Reference clock outputs 1–4: CON then TRIM, 0x20 stride */\n+ case PIC32MK_CRU_REFO1CON:\n+ return s->refo_con[0];\n+ case PIC32MK_CRU_REFO1TRIM:\n+ return s->refo_trim[0];\n+ case PIC32MK_CRU_REFO2CON:\n+ return s->refo_con[1];\n+ case PIC32MK_CRU_REFO2TRIM:\n+ return s->refo_trim[1];\n+ case PIC32MK_CRU_REFO3CON:\n+ return s->refo_con[2];\n+ case PIC32MK_CRU_REFO3TRIM:\n+ return s->refo_trim[2];\n+ case PIC32MK_CRU_REFO4CON:\n+ return s->refo_con[3];\n+ case PIC32MK_CRU_REFO4TRIM:\n+ return s->refo_trim[3];\n+\n+ /* Peripheral bus dividers — 0x10 stride starting at 0x100 */\n+ case PIC32MK_CRU_PB1DIV:\n+ return s->pbdiv[0] | PIC32MK_PBDIV_PBDIVRDY;\n+ case PIC32MK_CRU_PB2DIV:\n+ return s->pbdiv[1] | PIC32MK_PBDIV_PBDIVRDY;\n+ case PIC32MK_CRU_PB3DIV:\n+ return s->pbdiv[2] | PIC32MK_PBDIV_PBDIVRDY;\n+ case PIC32MK_CRU_PB4DIV:\n+ return s->pbdiv[3] | PIC32MK_PBDIV_PBDIVRDY;\n+ case PIC32MK_CRU_PB5DIV:\n+ return s->pbdiv[4] | PIC32MK_PBDIV_PBDIVRDY;\n+ case PIC32MK_CRU_PB6DIV:\n+ return s->pbdiv[5] | PIC32MK_PBDIV_PBDIVRDY;\n+ case PIC32MK_CRU_PB7DIV:\n+ return s->pbdiv[6] | PIC32MK_PBDIV_PBDIVRDY;\n+\n+ /* CLKSTAT — always report all clocks ready */\n+ case PIC32MK_CRU_CLKSTAT:\n+ return PIC32MK_CLKSTAT_ALL_RDY;\n+\n+ default:\n+ qemu_log_mask(LOG_UNIMP,\n+ \"pic32mk-cru: unimplemented read @ 0x%03\" HWADDR_PRIx \"\\n\",\n+ addr);\n+ return 0;\n+ }\n+}\n+\n+/*\n+ * Write handler\n+ * -----------------------------------------------------------------------\n+ */\n+static void pic32mk_cru_write(void *opaque, hwaddr addr, uint64_t val,\n+ unsigned size)\n+{\n+ PIC32MKCRUState *s = PIC32MK_CRU(opaque);\n+ hwaddr base = addr & ~0xFu;\n+ unsigned sub = addr & 0xCu;\n+ uint32_t v32 = (uint32_t)val;\n+\n+ switch (base) {\n+ case PIC32MK_CRU_OSCCON:\n+ s->osccon = apply_sci(s->osccon, v32, sub);\n+ /*\n+ * Mirror NOSC → COSC immediately and set SLOCK.\n+ * Real hardware would do an oscillator switch sequence; in emulation\n+ * the switch completes instantly.\n+ */\n+ if (s->osccon & PIC32MK_OSCCON_OSWEN) {\n+ uint32_t nosc = (s->osccon & PIC32MK_OSCCON_NOSC_MASK)\n+ >> PIC32MK_OSCCON_NOSC_SHIFT;\n+ s->osccon &= ~(PIC32MK_OSCCON_COSC_MASK | PIC32MK_OSCCON_OSWEN);\n+ s->osccon |= nosc << PIC32MK_OSCCON_COSC_SHIFT;\n+ }\n+ break;\n+\n+ case PIC32MK_CRU_OSCTUN:\n+ s->osctun = apply_sci(s->osctun, v32, sub);\n+ break;\n+\n+ case PIC32MK_CRU_SPLLCON:\n+ s->spllcon = apply_sci(s->spllcon, v32, sub);\n+ break;\n+\n+ case PIC32MK_CRU_UPLLCON:\n+ s->upllcon = apply_sci(s->upllcon, v32, sub);\n+ break;\n+\n+ case PIC32MK_CRU_RCON:\n+ s->rcon = apply_sci(s->rcon, v32, sub);\n+ break;\n+\n+ case PIC32MK_CRU_RSWRST:\n+ s->rswrst = apply_sci(s->rswrst, v32, sub);\n+ if (s->rswrst & 1u) {\n+ qemu_log_mask(LOG_GUEST_ERROR,\n+ \"pic32mk-cru: software reset triggered (RSWRST)\\n\");\n+ qemu_system_reset_request(SHUTDOWN_CAUSE_GUEST_RESET);\n+ }\n+ break;\n+\n+ case PIC32MK_CRU_RNMICON:\n+ s->rnmicon = apply_sci(s->rnmicon, v32, sub);\n+ break;\n+\n+ case PIC32MK_CRU_PWRCON:\n+ s->pwrcon = apply_sci(s->pwrcon, v32, sub);\n+ break;\n+\n+ /* Reference clock outputs 1–4 */\n+ case PIC32MK_CRU_REFO1CON:\n+ s->refo_con[0] = apply_sci(s->refo_con[0], v32, sub);\n+ if (s->refo_con[0] & PIC32MK_REFOCON_ON) {\n+ s->refo_con[0] |= PIC32MK_REFOCON_ACTIVE;\n+ } else {\n+ s->refo_con[0] &= ~PIC32MK_REFOCON_ACTIVE;\n+ }\n+ break;\n+ case PIC32MK_CRU_REFO1TRIM:\n+ s->refo_trim[0] = apply_sci(s->refo_trim[0], v32, sub);\n+ break;\n+\n+ case PIC32MK_CRU_REFO2CON:\n+ s->refo_con[1] = apply_sci(s->refo_con[1], v32, sub);\n+ if (s->refo_con[1] & PIC32MK_REFOCON_ON) {\n+ s->refo_con[1] |= PIC32MK_REFOCON_ACTIVE;\n+ } else {\n+ s->refo_con[1] &= ~PIC32MK_REFOCON_ACTIVE;\n+ }\n+ break;\n+ case PIC32MK_CRU_REFO2TRIM:\n+ s->refo_trim[1] = apply_sci(s->refo_trim[1], v32, sub);\n+ break;\n+\n+ case PIC32MK_CRU_REFO3CON:\n+ s->refo_con[2] = apply_sci(s->refo_con[2], v32, sub);\n+ if (s->refo_con[2] & PIC32MK_REFOCON_ON) {\n+ s->refo_con[2] |= PIC32MK_REFOCON_ACTIVE;\n+ } else {\n+ s->refo_con[2] &= ~PIC32MK_REFOCON_ACTIVE;\n+ }\n+ break;\n+ case PIC32MK_CRU_REFO3TRIM:\n+ s->refo_trim[2] = apply_sci(s->refo_trim[2], v32, sub);\n+ break;\n+\n+ case PIC32MK_CRU_REFO4CON:\n+ s->refo_con[3] = apply_sci(s->refo_con[3], v32, sub);\n+ if (s->refo_con[3] & PIC32MK_REFOCON_ON) {\n+ s->refo_con[3] |= PIC32MK_REFOCON_ACTIVE;\n+ } else {\n+ s->refo_con[3] &= ~PIC32MK_REFOCON_ACTIVE;\n+ }\n+ break;\n+ case PIC32MK_CRU_REFO4TRIM:\n+ s->refo_trim[3] = apply_sci(s->refo_trim[3], v32, sub);\n+ break;\n+\n+ /* Peripheral bus dividers (PBDIVRDY is read-only, masked out) */\n+ case PIC32MK_CRU_PB1DIV:\n+ s->pbdiv[0] = apply_sci(s->pbdiv[0], v32, sub) & ~PIC32MK_PBDIV_PBDIVRDY;\n+ break;\n+ case PIC32MK_CRU_PB2DIV:\n+ s->pbdiv[1] = apply_sci(s->pbdiv[1], v32, sub) & ~PIC32MK_PBDIV_PBDIVRDY;\n+ break;\n+ case PIC32MK_CRU_PB3DIV:\n+ s->pbdiv[2] = apply_sci(s->pbdiv[2], v32, sub) & ~PIC32MK_PBDIV_PBDIVRDY;\n+ break;\n+ case PIC32MK_CRU_PB4DIV:\n+ s->pbdiv[3] = apply_sci(s->pbdiv[3], v32, sub) & ~PIC32MK_PBDIV_PBDIVRDY;\n+ break;\n+ case PIC32MK_CRU_PB5DIV:\n+ s->pbdiv[4] = apply_sci(s->pbdiv[4], v32, sub) & ~PIC32MK_PBDIV_PBDIVRDY;\n+ break;\n+ case PIC32MK_CRU_PB6DIV:\n+ s->pbdiv[5] = apply_sci(s->pbdiv[5], v32, sub) & ~PIC32MK_PBDIV_PBDIVRDY;\n+ break;\n+ case PIC32MK_CRU_PB7DIV:\n+ s->pbdiv[6] = apply_sci(s->pbdiv[6], v32, sub) & ~PIC32MK_PBDIV_PBDIVRDY;\n+ break;\n+\n+ case PIC32MK_CRU_CLKSTAT:\n+ /* Read-only register */\n+ qemu_log_mask(LOG_GUEST_ERROR,\n+ \"pic32mk-cru: write to read-only CLKSTAT ignored\\n\");\n+ break;\n+\n+ default:\n+ qemu_log_mask(LOG_UNIMP,\n+ \"pic32mk-cru: unimplemented write @ 0x%03\" HWADDR_PRIx\n+ \" = 0x%08x\\n\", addr, v32);\n+ break;\n+ }\n+}\n+\n+static const MemoryRegionOps pic32mk_cru_ops = {\n+ .read = pic32mk_cru_read,\n+ .write = pic32mk_cru_write,\n+ .endianness = DEVICE_LITTLE_ENDIAN,\n+ .valid = {\n+ .min_access_size = 4,\n+ .max_access_size = 4,\n+ },\n+};\n+\n+/*\n+ * Realize / Reset\n+ * -----------------------------------------------------------------------\n+ */\n+\n+static void pic32mk_cru_realize(DeviceState *dev, Error **errp)\n+{\n+ PIC32MKCRUState *s = PIC32MK_CRU(dev);\n+ memory_region_init_io(&s->mmio, OBJECT(s), &pic32mk_cru_ops, s,\n+ \"pic32mk-cru\", PIC32MK_CRU_SIZE);\n+ sysbus_init_mmio(SYS_BUS_DEVICE(dev), &s->mmio);\n+}\n+\n+static void pic32mk_cru_reset_hold(Object *obj, ResetType type)\n+{\n+ PIC32MKCRUState *s = PIC32MK_CRU(obj);\n+\n+ /* OSCCON: COSC = 001 (SPLL), clock locked */\n+ s->osccon = (1u << PIC32MK_OSCCON_COSC_SHIFT);\n+ s->osctun = 0;\n+ s->spllcon = 0;\n+ s->upllcon = 0;\n+\n+ /* RCON: Power-on Reset + Brown-out Reset flags */\n+ s->rcon = PIC32MK_RCON_POR | PIC32MK_RCON_BOR;\n+ s->rswrst = 0;\n+ s->rnmicon = 0;\n+ s->pwrcon = 0;\n+\n+ for (int i = 0; i < PIC32MK_CRU_NREFO; i++) {\n+ s->refo_con[i] = 0;\n+ s->refo_trim[i] = 0;\n+ }\n+\n+ /* PB1 is always on; PB2–7 default ON */\n+ for (int i = 0; i < PIC32MK_CRU_NPB; i++) {\n+ s->pbdiv[i] = PIC32MK_PBDIV_ON;\n+ }\n+}\n+\n+/*\n+ * QOM boilerplate\n+ * -----------------------------------------------------------------------\n+ */\n+\n+static void pic32mk_cru_class_init(ObjectClass *klass, const void *data)\n+{\n+ DeviceClass *dc = DEVICE_CLASS(klass);\n+ ResettableClass *rc = RESETTABLE_CLASS(klass);\n+ dc->realize = pic32mk_cru_realize;\n+ dc->desc = \"PIC32MK Clock Reference Unit\";\n+ rc->phases.hold = pic32mk_cru_reset_hold;\n+}\n+\n+static const TypeInfo pic32mk_cru_info = {\n+ .name = TYPE_PIC32MK_CRU,\n+ .parent = TYPE_SYS_BUS_DEVICE,\n+ .instance_size = sizeof(PIC32MKCRUState),\n+ .class_init = pic32mk_cru_class_init,\n+};\n+\n+static void pic32mk_cru_register_types(void)\n+{\n+ type_register_static(&pic32mk_cru_info);\n+}\n+\n+type_init(pic32mk_cru_register_types)\ndiff --git a/hw/mips/pic32mk_dataee.c b/hw/mips/pic32mk_dataee.c\nnew file mode 100644\nindex 0000000000..c35a8326ad\n--- /dev/null\n+++ b/hw/mips/pic32mk_dataee.c\n@@ -0,0 +1,463 @@\n+/*\n+ * PIC32MK Data EEPROM (DATAEE) emulation\n+ * Datasheet: DS60001519E, §11\n+ *\n+ * 4 KB (1024 × 32-bit words) of on-chip data EEPROM emulated via SFR\n+ * control registers EECON, EEKEY, EEADDR, EEDATA at 0xBF829000.\n+ *\n+ * Optional host-file backing: pass -global pic32mk-dataee.filename=eeprom.bin\n+ * to persist EEPROM contents across QEMU sessions. Without a backing file\n+ * the data lives in RAM only and is lost on exit.\n+ *\n+ * Copyright (c) 2026 QEMU contributors\n+ * SPDX-License-Identifier: GPL-2.0-or-later\n+ */\n+\n+#include \"qemu/osdep.h\"\n+#include \"qemu/log.h\"\n+#include \"qemu/module.h\"\n+#include \"qapi/error.h\"\n+#include \"hw/core/sysbus.h\"\n+#include \"hw/core/irq.h\"\n+#include \"hw/core/qdev-properties.h\"\n+#include \"hw/mips/pic32mk.h\"\n+\n+#include <fcntl.h>\n+#include <unistd.h>\n+\n+#define TYPE_PIC32MK_DATAEE \"pic32mk-dataee\"\n+OBJECT_DECLARE_SIMPLE_TYPE(PIC32MKDataEEState, PIC32MK_DATAEE)\n+\n+/* EECON error codes (bits [5:4]) */\n+#define EECON_ERR_NONE 0u\n+#define EECON_ERR_VERIFY 1u\n+#define EECON_ERR_INVALID 2u\n+#define EECON_ERR_BOR 3u\n+\n+/* EEKEY unlock FSM states */\n+#define EEKEY_LOCKED 0\n+#define EEKEY_KEY1_OK 1\n+#define EEKEY_UNLOCKED 2\n+\n+/* Number of DEVEE config words written during EEPROM_Initialize() */\n+#define DATAEE_NUM_CONFIG 4\n+\n+struct PIC32MKDataEEState {\n+ SysBusDevice parent_obj;\n+ MemoryRegion mr;\n+\n+ /* SFR registers */\n+ uint32_t eecon;\n+ uint32_t eeaddr;\n+ uint32_t eedata;\n+ int eekey_state; /* unlock FSM */\n+\n+ /* EEPROM storage */\n+ uint32_t data[PIC32MK_DATAEE_WORDS];\n+ uint32_t config[DATAEE_NUM_CONFIG]; /* DEVEE0–3 shadow */\n+\n+ /* Host backing file */\n+ char *filename; /* qdev string property */\n+ int backing_fd;\n+ bool dirty;\n+\n+ /* IRQ output → EVIC vector 186 */\n+ qemu_irq irq;\n+};\n+\n+/*\n+ * Backing file helpers\n+ * -----------------------------------------------------------------------\n+ */\n+\n+static void dataee_load_backing(PIC32MKDataEEState *s)\n+{\n+ if (s->backing_fd < 0) {\n+ return;\n+ }\n+ lseek(s->backing_fd, 0, SEEK_SET);\n+ ssize_t n = read(s->backing_fd, s->data, sizeof(s->data));\n+ if (n < 0) {\n+ qemu_log_mask(LOG_GUEST_ERROR,\n+ \"pic32mk_dataee: backing file read error\\n\");\n+ } else if ((size_t)n < sizeof(s->data)) {\n+ /* Short read — zero-pad remainder (first use: file is empty) */\n+ memset((uint8_t *)s->data + n, 0xFF, sizeof(s->data) - (size_t)n);\n+ }\n+}\n+\n+static void dataee_flush_backing(PIC32MKDataEEState *s)\n+{\n+ if (s->backing_fd < 0 || !s->dirty) {\n+ return;\n+ }\n+ lseek(s->backing_fd, 0, SEEK_SET);\n+ ssize_t n = write(s->backing_fd, s->data, sizeof(s->data));\n+ if (n < 0) {\n+ qemu_log_mask(LOG_GUEST_ERROR,\n+ \"pic32mk_dataee: backing file write error\\n\");\n+ }\n+ fdatasync(s->backing_fd);\n+ s->dirty = false;\n+}\n+\n+/*\n+ * SET/CLR/INV helper (PIC32MK convention: +0=REG, +4=CLR, +8=SET, +C=INV)\n+ * -----------------------------------------------------------------------\n+ */\n+\n+static void apply_sci(uint32_t *reg, uint32_t val, int sub)\n+{\n+ switch (sub) {\n+ case 0x0:\n+ *reg = val;\n+ break;\n+ case 0x4:\n+ *reg &= ~val;\n+ break;\n+ case 0x8:\n+ *reg |= val;\n+ break;\n+ case 0xC:\n+ *reg ^= val;\n+ break;\n+ }\n+}\n+\n+/*\n+ * Command execution — triggered when RW transitions 0 → 1\n+ * -----------------------------------------------------------------------\n+ */\n+\n+static void dataee_set_err(PIC32MKDataEEState *s, uint32_t code)\n+{\n+ s->eecon = (s->eecon & ~PIC32MK_EECON_ERR_MASK)\n+ | ((code << PIC32MK_EECON_ERR_SHIFT) & PIC32MK_EECON_ERR_MASK);\n+}\n+\n+static void dataee_execute_cmd(PIC32MKDataEEState *s)\n+{\n+ uint32_t cmd = s->eecon & PIC32MK_EECON_CMD_MASK;\n+ uint32_t addr = s->eeaddr & PIC32MK_EEADDR_MASK;\n+ uint32_t word_idx = addr >> 2;\n+\n+ /* Clear previous error */\n+ dataee_set_err(s, EECON_ERR_NONE);\n+\n+ switch (cmd) {\n+ case PIC32MK_EECMD_WORD_READ:\n+ /* Read does NOT require WREN or unlock */\n+ if (word_idx < PIC32MK_DATAEE_WORDS) {\n+ s->eedata = s->data[word_idx];\n+ } else {\n+ s->eedata = 0xFFFFFFFFu;\n+ dataee_set_err(s, EECON_ERR_INVALID);\n+ }\n+ break;\n+\n+ case PIC32MK_EECMD_WORD_WRITE:\n+ if (!(s->eecon & PIC32MK_EECON_WREN)) {\n+ dataee_set_err(s, EECON_ERR_INVALID);\n+ break;\n+ }\n+ if (s->eekey_state != EEKEY_UNLOCKED) {\n+ dataee_set_err(s, EECON_ERR_INVALID);\n+ break;\n+ }\n+ if (word_idx < PIC32MK_DATAEE_WORDS) {\n+ s->data[word_idx] = s->eedata;\n+ s->dirty = true;\n+ dataee_flush_backing(s);\n+ } else {\n+ dataee_set_err(s, EECON_ERR_INVALID);\n+ }\n+ break;\n+\n+ case PIC32MK_EECMD_PAGE_ERASE: {\n+ if (!(s->eecon & PIC32MK_EECON_WREN)) {\n+ dataee_set_err(s, EECON_ERR_INVALID);\n+ break;\n+ }\n+ if (s->eekey_state != EEKEY_UNLOCKED) {\n+ dataee_set_err(s, EECON_ERR_INVALID);\n+ break;\n+ }\n+ /* Page-align the address */\n+ uint32_t page_start = (word_idx / PIC32MK_DATAEE_PAGE_WORDS)\n+ * PIC32MK_DATAEE_PAGE_WORDS;\n+ for (uint32_t i = 0; i < PIC32MK_DATAEE_PAGE_WORDS; i++) {\n+ if (page_start + i < PIC32MK_DATAEE_WORDS) {\n+ s->data[page_start + i] = 0xFFFFFFFFu;\n+ }\n+ }\n+ s->dirty = true;\n+ dataee_flush_backing(s);\n+ break;\n+ }\n+\n+ case PIC32MK_EECMD_BULK_ERASE:\n+ if (!(s->eecon & PIC32MK_EECON_WREN)) {\n+ dataee_set_err(s, EECON_ERR_INVALID);\n+ break;\n+ }\n+ if (s->eekey_state != EEKEY_UNLOCKED) {\n+ dataee_set_err(s, EECON_ERR_INVALID);\n+ break;\n+ }\n+ memset(s->data, 0xFF, sizeof(s->data));\n+ s->dirty = true;\n+ dataee_flush_backing(s);\n+ break;\n+\n+ case PIC32MK_EECMD_CONFIG_WRITE:\n+ if (!(s->eecon & PIC32MK_EECON_WREN)) {\n+ dataee_set_err(s, EECON_ERR_INVALID);\n+ break;\n+ }\n+ if (s->eekey_state != EEKEY_UNLOCKED) {\n+ dataee_set_err(s, EECON_ERR_INVALID);\n+ break;\n+ }\n+ /* Config writes target DEVEE0–3 shadow (addr 0x00/04/08/0C) */\n+ if (word_idx < DATAEE_NUM_CONFIG) {\n+ s->config[word_idx] = s->eedata;\n+ } else {\n+ dataee_set_err(s, EECON_ERR_INVALID);\n+ }\n+ break;\n+\n+ default:\n+ qemu_log_mask(LOG_UNIMP,\n+ \"pic32mk_dataee: unimplemented CMD %u\\n\", cmd);\n+ dataee_set_err(s, EECON_ERR_INVALID);\n+ break;\n+ }\n+\n+ /* Operation complete — clear RW, reset unlock FSM */\n+ s->eecon &= ~PIC32MK_EECON_RW;\n+ s->eekey_state = EEKEY_LOCKED;\n+\n+ /* Pulse IRQ to signal completion */\n+ qemu_irq_pulse(s->irq);\n+}\n+\n+/*\n+ * MMIO read\n+ * -----------------------------------------------------------------------\n+ */\n+\n+static uint64_t dataee_read(void *opaque, hwaddr addr, unsigned size)\n+{\n+ PIC32MKDataEEState *s = opaque;\n+ hwaddr base = addr & ~(hwaddr)0xF;\n+\n+ switch (base) {\n+ case PIC32MK_EECON:\n+ return s->eecon;\n+\n+ case PIC32MK_EEKEY:\n+ /* Write-only register — reads return 0 */\n+ return 0;\n+\n+ case PIC32MK_EEADDR:\n+ return s->eeaddr;\n+\n+ case PIC32MK_EEDATA_REG:\n+ return s->eedata;\n+\n+ default:\n+ qemu_log_mask(LOG_UNIMP,\n+ \"pic32mk_dataee: unimplemented read @ 0x%04\"\n+ HWADDR_PRIx \"\\n\", addr);\n+ return 0;\n+ }\n+}\n+\n+/*\n+ * MMIO write\n+ * -----------------------------------------------------------------------\n+ */\n+\n+static void dataee_write(void *opaque, hwaddr addr, uint64_t val,\n+ unsigned size)\n+{\n+ PIC32MKDataEEState *s = opaque;\n+ int sub = (int)(addr & 0xF);\n+ hwaddr base = addr & ~(hwaddr)0xF;\n+ uint32_t old_eecon;\n+\n+ switch (base) {\n+ case PIC32MK_EECON:\n+ old_eecon = s->eecon;\n+ apply_sci(&s->eecon, (uint32_t)val, sub);\n+\n+ /* RDY is automatically set when ON is set (no startup delay in emu) */\n+ if (s->eecon & PIC32MK_EECON_ON) {\n+ s->eecon |= PIC32MK_EECON_RDY;\n+ } else {\n+ s->eecon &= ~PIC32MK_EECON_RDY;\n+ }\n+\n+ /* Detect RW 0→1 transition: execute command */\n+ if (!(old_eecon & PIC32MK_EECON_RW) &&\n+ (s->eecon & PIC32MK_EECON_RW)) {\n+ dataee_execute_cmd(s);\n+ }\n+ break;\n+\n+ case PIC32MK_EEKEY:\n+ /*\n+ * Unlock FSM: firmware writes 0xEDB7 then 0x1248.\n+ * Any other value resets the FSM. SET/CLR/INV are not\n+ * meaningful for EEKEY — always treat as direct write.\n+ */\n+ {\n+ uint32_t key = (uint32_t)val & 0xFFFFu;\n+ if (s->eekey_state == EEKEY_LOCKED &&\n+ key == PIC32MK_EEKEY1) {\n+ s->eekey_state = EEKEY_KEY1_OK;\n+ } else if (s->eekey_state == EEKEY_KEY1_OK &&\n+ key == PIC32MK_EEKEY2) {\n+ s->eekey_state = EEKEY_UNLOCKED;\n+ } else {\n+ s->eekey_state = EEKEY_LOCKED;\n+ }\n+ }\n+ break;\n+\n+ case PIC32MK_EEADDR:\n+ apply_sci(&s->eeaddr, (uint32_t)val, sub);\n+ s->eeaddr &= PIC32MK_EEADDR_MASK;\n+ break;\n+\n+ case PIC32MK_EEDATA_REG:\n+ apply_sci(&s->eedata, (uint32_t)val, sub);\n+ break;\n+\n+ default:\n+ qemu_log_mask(LOG_UNIMP,\n+ \"pic32mk_dataee: unimplemented write @ 0x%04\"\n+ HWADDR_PRIx \" = 0x%08\" PRIx64 \"\\n\",\n+ addr, val);\n+ break;\n+ }\n+}\n+\n+static const MemoryRegionOps dataee_ops = {\n+ .read = dataee_read,\n+ .write = dataee_write,\n+ .endianness = DEVICE_LITTLE_ENDIAN,\n+ .valid = { .min_access_size = 4, .max_access_size = 4 },\n+};\n+\n+/*\n+ * Device lifecycle\n+ * -----------------------------------------------------------------------\n+ */\n+\n+static void pic32mk_dataee_reset(DeviceState *dev)\n+{\n+ PIC32MKDataEEState *s = PIC32MK_DATAEE(dev);\n+\n+ s->eecon = 0;\n+ s->eeaddr = 0;\n+ s->eedata = 0;\n+ s->eekey_state = EEKEY_LOCKED;\n+\n+ /* Erased state = all 0xFF */\n+ memset(s->data, 0xFF, sizeof(s->data));\n+ memset(s->config, 0xFF, sizeof(s->config));\n+ s->dirty = false;\n+\n+ /* Load backing file contents (overwrites erased state if file exists) */\n+ dataee_load_backing(s);\n+\n+ qemu_irq_lower(s->irq);\n+}\n+\n+static void pic32mk_dataee_realize(DeviceState *dev, Error **errp)\n+{\n+ PIC32MKDataEEState *s = PIC32MK_DATAEE(dev);\n+\n+ s->backing_fd = -1;\n+\n+ if (s->filename && s->filename[0] != '\\0') {\n+ /* Open or create the backing file (read-write) */\n+ s->backing_fd = open(s->filename, O_RDWR | O_CREAT, 0644);\n+ if (s->backing_fd < 0) {\n+ error_setg_errno(errp, errno,\n+ \"pic32mk_dataee: cannot open '%s'\",\n+ s->filename);\n+ return;\n+ }\n+\n+ /*\n+ * If the file is new or undersized, initialize it with 0xFF\n+ * (the erased state for EEPROM). This avoids the user having\n+ * to pre-create the file with the right content.\n+ */\n+ off_t fsize = lseek(s->backing_fd, 0, SEEK_END);\n+ if (fsize < (off_t)sizeof(s->data)) {\n+ uint8_t ff_buf[PIC32MK_DATAEE_WORDS * 4];\n+ memset(ff_buf, 0xFF, sizeof(ff_buf));\n+ lseek(s->backing_fd, 0, SEEK_SET);\n+ if (write(s->backing_fd, ff_buf, sizeof(ff_buf)) < 0) {\n+ qemu_log_mask(LOG_GUEST_ERROR,\n+ \"pic32mk_dataee: cannot init '%s'\\n\",\n+ s->filename);\n+ }\n+ fdatasync(s->backing_fd);\n+ }\n+ }\n+}\n+\n+static void pic32mk_dataee_unrealize(DeviceState *dev)\n+{\n+ PIC32MKDataEEState *s = PIC32MK_DATAEE(dev);\n+\n+ dataee_flush_backing(s);\n+\n+ if (s->backing_fd >= 0) {\n+ close(s->backing_fd);\n+ s->backing_fd = -1;\n+ }\n+}\n+\n+static void pic32mk_dataee_init(Object *obj)\n+{\n+ PIC32MKDataEEState *s = PIC32MK_DATAEE(obj);\n+\n+ memory_region_init_io(&s->mr, obj, &dataee_ops, s,\n+ TYPE_PIC32MK_DATAEE, PIC32MK_DATAEE_SIZE);\n+ sysbus_init_mmio(SYS_BUS_DEVICE(obj), &s->mr);\n+ sysbus_init_irq(SYS_BUS_DEVICE(obj), &s->irq);\n+}\n+\n+static const Property pic32mk_dataee_props[] = {\n+ DEFINE_PROP_STRING(\"filename\", PIC32MKDataEEState, filename),\n+};\n+\n+static void pic32mk_dataee_class_init(ObjectClass *klass, const void *data)\n+{\n+ DeviceClass *dc = DEVICE_CLASS(klass);\n+\n+ dc->realize = pic32mk_dataee_realize;\n+ dc->unrealize = pic32mk_dataee_unrealize;\n+ device_class_set_legacy_reset(dc, pic32mk_dataee_reset);\n+ device_class_set_props(dc, pic32mk_dataee_props);\n+}\n+\n+static const TypeInfo pic32mk_dataee_info = {\n+ .name = TYPE_PIC32MK_DATAEE,\n+ .parent = TYPE_SYS_BUS_DEVICE,\n+ .instance_size = sizeof(PIC32MKDataEEState),\n+ .instance_init = pic32mk_dataee_init,\n+ .class_init = pic32mk_dataee_class_init,\n+};\n+\n+static void pic32mk_dataee_register_types(void)\n+{\n+ type_register_static(&pic32mk_dataee_info);\n+}\n+\n+type_init(pic32mk_dataee_register_types)\ndiff --git a/hw/mips/pic32mk_dma.c b/hw/mips/pic32mk_dma.c\nnew file mode 100644\nindex 0000000000..c1fd503b16\n--- /dev/null\n+++ b/hw/mips/pic32mk_dma.c\n@@ -0,0 +1,255 @@\n+/*\n+ * PIC32MK DMA Controller × 8 channels\n+ * Datasheet: DS60001519E, §26\n+ *\n+ * Phase 2B stub: accepts all register reads/writes, logs unimplemented\n+ * channel transfers. Actual DMA transfer engine is deferred.\n+ *\n+ * Memory map (base physical 0x1F811000 = SFR_BASE + DMA_OFFSET):\n+ * +0x000: DMACON (global control)\n+ * +0x010: DMASTAT\n+ * +0x020: DMAADDR\n+ * +0x060: Channel 0 registers (DCH0CON, DCH0ECON, DCH0INT, ...)\n+ * +0x120: Channel 1 registers\n+ * ... (stride 0xC0 per channel)\n+ *\n+ * Copyright (c) 2026 QEMU contributors\n+ * SPDX-License-Identifier: GPL-2.0-or-later\n+ */\n+\n+#include \"qemu/osdep.h\"\n+#include \"qemu/log.h\"\n+#include \"qemu/module.h\"\n+#include \"hw/core/sysbus.h\"\n+#include \"hw/core/irq.h\"\n+#include \"hw/mips/pic32mk.h\"\n+\n+/* Total MMIO size: channel 7 ends at 0x060 + 7*0xC0 + 0xC0 = 0x060 + 0x780 = 0x7E0 */\n+#define DMA_MMIO_SIZE 0x1000u /* full 4 KB page */\n+\n+#define TYPE_PIC32MK_DMA \"pic32mk-dma\"\n+OBJECT_DECLARE_SIMPLE_TYPE(PIC32MKDMAState, PIC32MK_DMA)\n+\n+/* Per-channel register state */\n+typedef struct {\n+ uint32_t con; /* DCHxCON */\n+ uint32_t econ; /* DCHxECON */\n+ uint32_t ireg; /* DCHxINT */\n+ uint32_t ssa; /* source start address */\n+ uint32_t dsa; /* destination start address */\n+ uint32_t ssiz; /* source size */\n+ uint32_t dsiz; /* destination size */\n+ uint32_t sptr; /* source pointer */\n+ uint32_t dptr; /* destination pointer */\n+ uint32_t csiz; /* cell size */\n+ uint32_t cptr; /* cell pointer */\n+ uint32_t dat; /* pattern data */\n+} DMAChannel;\n+\n+struct PIC32MKDMAState {\n+ SysBusDevice parent_obj;\n+ MemoryRegion mr;\n+\n+ uint32_t con; /* DMACON */\n+ uint32_t stat; /* DMASTAT */\n+ uint32_t addr; /* DMAADDR */\n+\n+ DMAChannel ch[PIC32MK_DMA_NCHANNELS];\n+ qemu_irq irq[PIC32MK_DMA_NCHANNELS];\n+};\n+\n+/*\n+ * Helpers\n+ * -----------------------------------------------------------------------\n+ */\n+\n+/* PIC32MK: base+0=REG, +4=CLR, +8=SET, +0xC=INV */\n+static void apply_sci(uint32_t *reg, uint32_t val, int sub)\n+{\n+ switch (sub) {\n+ case 0:\n+ *reg = val;\n+ break;\n+ case 4:\n+ *reg &= ~val;\n+ break;\n+ case 8:\n+ *reg |= val;\n+ break;\n+ case 12:\n+ *reg ^= val;\n+ break;\n+ }\n+}\n+\n+/* Map channel register offset to state field */\n+static uint32_t *dma_ch_reg(DMAChannel *ch, hwaddr off)\n+{\n+ switch (off) {\n+ case PIC32MK_DCHxCON:\n+ return &ch->con;\n+ case PIC32MK_DCHxECON:\n+ return &ch->econ;\n+ case PIC32MK_DCHxINT:\n+ return &ch->ireg;\n+ case PIC32MK_DCHxSSA:\n+ return &ch->ssa;\n+ case PIC32MK_DCHxDSA:\n+ return &ch->dsa;\n+ case PIC32MK_DCHxSSIZ:\n+ return &ch->ssiz;\n+ case PIC32MK_DCHxDSIZ:\n+ return &ch->dsiz;\n+ case PIC32MK_DCHxSPTR:\n+ return &ch->sptr;\n+ case PIC32MK_DCHxDPTR:\n+ return &ch->dptr;\n+ case PIC32MK_DCHxCSIZ:\n+ return &ch->csiz;\n+ case PIC32MK_DCHxCPTR:\n+ return &ch->cptr;\n+ case PIC32MK_DCHxDAT:\n+ return &ch->dat;\n+ default:\n+ return NULL;\n+ }\n+}\n+\n+/*\n+ * MMIO\n+ * -----------------------------------------------------------------------\n+ */\n+\n+static uint64_t dma_read(void *opaque, hwaddr addr, unsigned size)\n+{\n+ PIC32MKDMAState *s = opaque;\n+ int sub = (int)(addr & 0xF);\n+ hwaddr base = addr & ~(hwaddr)0xF;\n+\n+ (void)sub; /* reads always return base register value */\n+\n+ /* Global registers */\n+ if (base == PIC32MK_DMACON_OFFSET) {\n+ return s->con;\n+ }\n+ if (base == PIC32MK_DMASTAT_OFFSET) {\n+ return s->stat;\n+ }\n+ if (base == PIC32MK_DMAADDR_OFFSET) {\n+ return s->addr;\n+ }\n+\n+ /* Channel registers */\n+ if (addr >= PIC32MK_DMA_CH_BASE) {\n+ hwaddr ch_off = base - PIC32MK_DMA_CH_BASE;\n+ int ch_idx = (int)(ch_off / PIC32MK_DMA_CH_STRIDE);\n+ hwaddr reg_off = ch_off % PIC32MK_DMA_CH_STRIDE;\n+\n+ if (ch_idx < PIC32MK_DMA_NCHANNELS) {\n+ uint32_t *reg = dma_ch_reg(&s->ch[ch_idx], reg_off);\n+ if (reg) {\n+ return *reg;\n+ }\n+ }\n+ }\n+\n+ qemu_log_mask(LOG_UNIMP,\n+ \"pic32mk_dma: unimplemented read @ 0x%04\" HWADDR_PRIx \"\\n\",\n+ addr);\n+ return 0;\n+}\n+\n+static void dma_write(void *opaque, hwaddr addr, uint64_t val, unsigned size)\n+{\n+ PIC32MKDMAState *s = opaque;\n+ int sub = (int)(addr & 0xF);\n+ hwaddr base = addr & ~(hwaddr)0xF;\n+\n+ /* Global registers */\n+ if (base == PIC32MK_DMACON_OFFSET) {\n+ apply_sci(&s->con, (uint32_t)val, sub);\n+ return;\n+ }\n+ if (base == PIC32MK_DMASTAT_OFFSET || base == PIC32MK_DMAADDR_OFFSET) {\n+ return; /* read-only */\n+ }\n+\n+ /* Channel registers */\n+ if (addr >= PIC32MK_DMA_CH_BASE) {\n+ hwaddr ch_off = base - PIC32MK_DMA_CH_BASE;\n+ int ch_idx = (int)(ch_off / PIC32MK_DMA_CH_STRIDE);\n+ hwaddr reg_off = ch_off % PIC32MK_DMA_CH_STRIDE;\n+\n+ if (ch_idx < PIC32MK_DMA_NCHANNELS) {\n+ uint32_t *reg = dma_ch_reg(&s->ch[ch_idx], reg_off);\n+ if (reg) {\n+ apply_sci(reg, (uint32_t)val, sub);\n+ /* TODO: trigger DMA transfer if CHEN and FORCE bits set */\n+ return;\n+ }\n+ }\n+ }\n+\n+ qemu_log_mask(LOG_UNIMP,\n+ \"pic32mk_dma: unimplemented write @ 0x%04\"\n+ HWADDR_PRIx \" = 0x%08\" PRIx64 \"\\n\",\n+ addr, val);\n+}\n+\n+static const MemoryRegionOps dma_ops = {\n+ .read = dma_read,\n+ .write = dma_write,\n+ .endianness = DEVICE_LITTLE_ENDIAN,\n+ .valid = { .min_access_size = 4, .max_access_size = 4 },\n+};\n+\n+/*\n+ * Device lifecycle\n+ * -----------------------------------------------------------------------\n+ */\n+\n+static void pic32mk_dma_reset(DeviceState *dev)\n+{\n+ PIC32MKDMAState *s = PIC32MK_DMA(dev);\n+ s->con = 0x80000000u; /* DMABUSY=0, ON=1 on reset per datasheet */\n+ s->stat = 0;\n+ s->addr = 0;\n+ memset(s->ch, 0, sizeof(s->ch));\n+ for (int i = 0; i < PIC32MK_DMA_NCHANNELS; i++) {\n+ qemu_irq_lower(s->irq[i]);\n+ }\n+}\n+\n+static void pic32mk_dma_init(Object *obj)\n+{\n+ PIC32MKDMAState *s = PIC32MK_DMA(obj);\n+\n+ memory_region_init_io(&s->mr, obj, &dma_ops, s,\n+ TYPE_PIC32MK_DMA, DMA_MMIO_SIZE);\n+ sysbus_init_mmio(SYS_BUS_DEVICE(obj), &s->mr);\n+\n+ for (int i = 0; i < PIC32MK_DMA_NCHANNELS; i++) {\n+ sysbus_init_irq(SYS_BUS_DEVICE(obj), &s->irq[i]);\n+ }\n+}\n+\n+static void pic32mk_dma_class_init(ObjectClass *klass, const void *data)\n+{\n+ DeviceClass *dc = DEVICE_CLASS(klass);\n+ device_class_set_legacy_reset(dc, pic32mk_dma_reset);\n+}\n+\n+static const TypeInfo pic32mk_dma_info = {\n+ .name = TYPE_PIC32MK_DMA,\n+ .parent = TYPE_SYS_BUS_DEVICE,\n+ .instance_size = sizeof(PIC32MKDMAState),\n+ .instance_init = pic32mk_dma_init,\n+ .class_init = pic32mk_dma_class_init,\n+};\n+\n+static void pic32mk_dma_register_types(void)\n+{\n+ type_register_static(&pic32mk_dma_info);\n+}\n+\n+type_init(pic32mk_dma_register_types)\ndiff --git a/hw/mips/pic32mk_evic.c b/hw/mips/pic32mk_evic.c\nnew file mode 100644\nindex 0000000000..214fe7dd96\n--- /dev/null\n+++ b/hw/mips/pic32mk_evic.c\n@@ -0,0 +1,399 @@\n+/*\n+ * PIC32MK Enhanced Vectored Interrupt Controller (EVIC)\n+ * Datasheet: DS60001519E, §8 (pp. 121–166)\n+ *\n+ * 216 interrupt sources, 7 priority levels × 4 subpriority levels.\n+ * Supports single-vector (INTCON.MVEC=0) and multi-vector (MVEC=1) modes.\n+ * All register banks support atomic SET/CLR/INV sub-registers (+4/+8/+C).\n+ *\n+ * Copyright (c) 2026 QEMU contributors\n+ * SPDX-License-Identifier: GPL-2.0-or-later\n+ */\n+\n+#include \"qemu/osdep.h\"\n+#include \"qemu/log.h\"\n+#include \"qemu/module.h\"\n+#include \"hw/mips/pic32mk_evic.h\"\n+\n+/*\n+ * EVIC MMIO layout (offsets from EVIC base 0x1F810000)\n+ *\n+ * Registers that have SET/CLR/INV sub-regs use a 16-byte stride:\n+ * REG at base+0, SET at base+4, CLR at base+8, INV at base+C\n+ *\n+ * OFFx registers are plain 4-byte (no SET/CLR/INV).\n+ * -----------------------------------------------------------------------\n+ */\n+\n+#define EVIC_MMIO_SIZE 0x1000u /* 4 KB page */\n+\n+/* Number of IFS/IEC words (8 × 32 = 256 bits covers 216 sources + spare) */\n+#define EVIC_IFS_COUNT 8\n+#define EVIC_IEC_COUNT 8\n+#define EVIC_IPC_COUNT 64\n+#define EVIC_OFF_COUNT 191\n+\n+/* INTCON bits */\n+#define INTCON_MVEC (1u << 12) /* multi-vector mode */\n+\n+/*\n+ * Priority extraction helpers\n+ * IPC register format: 4 sources per register, 8 bits per source\n+ * bits[4:2] = IP (priority, 3 bits)\n+ * bits[1:0] = IS (subpriority, 2 bits)\n+ * -----------------------------------------------------------------------\n+ */\n+\n+static int evic_get_priority(PIC32MKEVICState *s, int src)\n+{\n+ int reg = src >> 2;\n+ int shift = (src & 3) << 3; /* 0, 8, 16, or 24 */\n+ return (s->ipcreg[reg] >> (shift + 2)) & 0x7;\n+}\n+\n+/*\n+ * IRQ routing — called whenever IFS, IEC, or IPC registers change.\n+ *\n+ * Algorithm: find the highest pending+enabled priority level and assert\n+ * the matching CPU interrupt pin. Deassert all pins that have no\n+ * pending work at that priority.\n+ * -----------------------------------------------------------------------\n+ */\n+\n+static void evic_update(PIC32MKEVICState *s)\n+{\n+ int best_prio = 0;\n+ int best_src = -1;\n+\n+ for (int i = 0; i < PIC32MK_NUM_IRQ_SOURCES; i++) {\n+ int word = i >> 5;\n+ int bit = i & 31;\n+ if ((s->ifsreg[word] >> bit & 1) && (s->iecreg[word] >> bit & 1)) {\n+ int prio = evic_get_priority(s, i);\n+ if (prio > best_prio) {\n+ best_prio = prio;\n+ best_src = i;\n+ }\n+ }\n+ }\n+\n+ /* Update INTSTAT: bits[7:0] = last interrupt source, bits[15:8] = priority */\n+ if (best_src >= 0) {\n+ s->intstat = ((uint32_t)best_prio << 8) | (uint32_t)(best_src & 0xFF);\n+ }\n+\n+ /*\n+ * Assert/deassert each CPU interrupt pin.\n+ *\n+ * VEIC RIPL encoding: portSAVE_CONTEXT does:\n+ * k0 = Cause >> 10 (extracts bits[17:10] as RIPL)\n+ * Status[16:10] = k0[6:0] (sets IPL = RIPL in ISR)\n+ *\n+ * cpu_mips_irq_request(cpu, N, 1) sets Cause.bit(N+8).\n+ * For portSAVE_CONTEXT to extract RIPL = priority P:\n+ * need Cause.bit(10+P) set, so Cause >> 10 = 1 << (P-1)... but actually\n+ * we want (Cause >> 10) to equal P so Status[16:10] = P = IPL.\n+ * Cause.bit(10+P) >> 10 = 1 << P, not P itself.\n+ *\n+ * Correct approach: priority P → cpu_irq[P+1] → Cause.bit(P+9).\n+ * Cause.bit(P+9) >> 10 = 1 << (P-1) (not P).\n+ *\n+ * Simplest correct mapping: encode priority as a single bit position.\n+ * cpu_irq[2] → Cause.bit(10) → k0 = 1 after >>10 → IPL=1 → blocks\n+ * same-priority re-entry (pending=0x0400 > status=0x0400 = FALSE).\n+ * portDISABLE with IPL=3 → status=3<<10=0x0C00 → blocks prio1(0x0400)\n+ * and prio2(0x0800) but not prio3(0x1000). For our firmware only\n+ * priority 1 is used, so this is sufficient.\n+ *\n+ * Mapping: EVIC priority pin → cpu_irq[pin+1].\n+ * pin=1 → cpu_irq[2] → Cause.bit(10) → RIPL = Cause>>10 = 1 ✓\n+ * pin=2 → cpu_irq[3] → Cause.bit(11) → RIPL = 2 ✓\n+ * pin=3 → cpu_irq[4] → Cause.bit(12) → RIPL = 4 (power-of-2, not equal)\n+ * ...\n+ * This works perfectly for priority 1 and FreeRTOS configKERNEL_INTERRUPT_PRIORITY=1.\n+ */\n+ for (int pin = 1; pin <= 6; pin++) {\n+ int cpu_pin = pin + 1; /* priority P → cpu_irq[P+1] for correct RIPL */\n+ int assert = 0;\n+ if (s->cpu_irq[cpu_pin]) {\n+ for (int i = 0; i < PIC32MK_NUM_IRQ_SOURCES && !assert; i++) {\n+ int word = i >> 5, bit = i & 31;\n+ if ((s->ifsreg[word] >> bit & 1) &&\n+ (s->iecreg[word] >> bit & 1) &&\n+ evic_get_priority(s, i) == pin) {\n+ assert = 1;\n+ }\n+ }\n+ qemu_set_irq(s->cpu_irq[cpu_pin], assert);\n+ }\n+ }\n+ /* Priority 7 has no available cpu_irq slot (would need irq[8]); unused. */\n+}\n+\n+/*\n+ * IRQ input handler — called when a peripheral asserts/deasserts its IRQ.\n+ *\n+ * On the real device, raising the IRQ sets the corresponding IFSx bit.\n+ * Lowering it does NOT clear IFSx — that is software's responsibility\n+ * (write to IFSxCLR in the interrupt service routine).\n+ * -----------------------------------------------------------------------\n+ */\n+\n+static void evic_set_irq(void *opaque, int irq, int level)\n+{\n+ PIC32MKEVICState *s = opaque;\n+\n+ if (irq < 0 || irq >= PIC32MK_NUM_IRQ_SOURCES) {\n+ return;\n+ }\n+\n+ int word = irq >> 5;\n+ uint32_t mask = 1u << (irq & 31);\n+\n+ /* Track the current hardware level of this source */\n+\n+ if (level) {\n+ s->irq_level[word] |= mask;\n+ s->ifsreg[word] |= mask;\n+ } else {\n+ s->irq_level[word] &= ~mask;\n+ }\n+\n+ if (level) {\n+ /*\n+ * Software interrupts CS0 (source 1) and CS1 (source 2) arrive here\n+ * because board code redirected env->irq[0..1] to EVIC inputs.\n+ * cpu_mips_store_cause() already set the direct Cause.IP0/IP1 bit\n+ * before calling us. Clear it so the VEIC pending-vs-status\n+ * comparison only sees the priority pin asserted by evic_update().\n+ * This prevents same-priority nesting in VEIC mode.\n+ *\n+ * Also clear irq_level for these sources — they are edge/pulse\n+ * triggered (software writes to Cause), not level-sensitive like\n+ * hardware peripherals. Without this, IFS re-assertion after\n+ * firmware IFSxCLR would immediately re-set the flag.\n+ */\n+ if (s->cpu && (irq == PIC32MK_IRQ_CS0 || irq == PIC32MK_IRQ_CS1)) {\n+ CPUMIPSState *env = &s->cpu->env;\n+ int ip_bit = (irq == PIC32MK_IRQ_CS0) ? 0 : 1;\n+ env->CP0_Cause &= ~(1u << (ip_bit + 8));\n+ /* Treat as one-shot: IFS is set above, but don't persist level */\n+ s->irq_level[word] &= ~mask;\n+ }\n+ }\n+\n+ evic_update(s);\n+}\n+\n+/*\n+ * Register pointer helper for SET/CLR/INV registers (base addresses only)\n+ * -----------------------------------------------------------------------\n+ */\n+\n+static uint32_t *evic_find_reg(PIC32MKEVICState *s, hwaddr base)\n+{\n+ if (base == PIC32MK_EVIC_INTCON) {\n+ return &s->intcon;\n+ }\n+ if (base == PIC32MK_EVIC_PRISS) {\n+ return &s->priss;\n+ }\n+ if (base == PIC32MK_EVIC_INTSTAT) {\n+ return &s->intstat;\n+ }\n+ if (base == PIC32MK_EVIC_IPTMR) {\n+ return &s->iptmr;\n+ }\n+\n+ if (base >= PIC32MK_EVIC_IFS0 && base < PIC32MK_EVIC_IEC0) {\n+ int i = (int)((base - PIC32MK_EVIC_IFS0) >> 4);\n+ return (i < EVIC_IFS_COUNT) ? &s->ifsreg[i] : NULL;\n+ }\n+ if (base >= PIC32MK_EVIC_IEC0 && base < PIC32MK_EVIC_IPC0) {\n+ int i = (int)((base - PIC32MK_EVIC_IEC0) >> 4);\n+ return (i < EVIC_IEC_COUNT) ? &s->iecreg[i] : NULL;\n+ }\n+ if (base >= PIC32MK_EVIC_IPC0 && base < PIC32MK_EVIC_OFF0) {\n+ int i = (int)((base - PIC32MK_EVIC_IPC0) >> 4);\n+ return (i < EVIC_IPC_COUNT) ? &s->ipcreg[i] : NULL;\n+ }\n+ return NULL;\n+}\n+\n+/* Apply SET/CLR/INV operation (sub = byte offset within 16-byte group) */\n+/* PIC32MK: base+0=REG, +4=CLR, +8=SET, +0xC=INV */\n+static void apply_sci(uint32_t *reg, uint32_t val, int sub)\n+{\n+ switch (sub) {\n+ case 0:\n+ *reg = val;\n+ break;\n+ case 4:\n+ *reg &= ~val;\n+ break;\n+ case 8:\n+ *reg |= val;\n+ break;\n+ case 12:\n+ *reg ^= val;\n+ break;\n+ }\n+}\n+\n+/*\n+ * MMIO read/write\n+ * -----------------------------------------------------------------------\n+ */\n+\n+static uint64_t evic_read(void *opaque, hwaddr addr, unsigned size)\n+{\n+ PIC32MKEVICState *s = opaque;\n+\n+ /* OFFx: plain 4-byte registers, no SET/CLR/INV */\n+ if (addr >= PIC32MK_EVIC_OFF0) {\n+ int idx = (int)((addr - PIC32MK_EVIC_OFF0) >> 2);\n+ if (idx < EVIC_OFF_COUNT) {\n+ return s->offreg[idx];\n+ }\n+ } else {\n+ /* All other registers: 16-byte stride, all sub-addrs read base */\n+ hwaddr base = addr & ~(hwaddr)0xF;\n+ uint32_t *reg = evic_find_reg(s, base);\n+ if (reg) {\n+ return *reg;\n+ }\n+ }\n+\n+ qemu_log_mask(LOG_UNIMP,\n+ \"pic32mk_evic: unimplemented read @ 0x%04\" HWADDR_PRIx \"\\n\",\n+ addr);\n+ return 0;\n+}\n+\n+static void evic_write(void *opaque, hwaddr addr, uint64_t val, unsigned size)\n+{\n+ PIC32MKEVICState *s = opaque;\n+ bool need_update = false;\n+\n+ /* OFFx: plain 4-byte writes */\n+ if (addr >= PIC32MK_EVIC_OFF0) {\n+ int idx = (int)((addr - PIC32MK_EVIC_OFF0) >> 2);\n+ if (idx < EVIC_OFF_COUNT) {\n+ s->offreg[idx] = (uint32_t)val;\n+ }\n+ return;\n+ }\n+\n+ int sub = (int)(addr & 0xF);\n+ hwaddr base = addr & ~(hwaddr)0xF;\n+ uint32_t *reg = evic_find_reg(s, base);\n+\n+ if (!reg) {\n+ qemu_log_mask(LOG_UNIMP,\n+ \"pic32mk_evic: unimplemented write @ 0x%04\"\n+ HWADDR_PRIx \" = 0x%08\" PRIx64 \"\\n\",\n+ addr, val);\n+ return;\n+ }\n+\n+ /* INTSTAT is read-only hardware status */\n+ if (reg == &s->intstat) {\n+ return;\n+ }\n+\n+ apply_sci(reg, (uint32_t)val, sub);\n+\n+ /*\n+ * After any IFS write (especially CLR), re-assert bits where the\n+ * hardware source is still active. On real Microchip devices, IFS\n+ * flags are level-sensitive: if the peripheral still drives the\n+ * interrupt line high, the flag is immediately re-set even after the\n+ * firmware clears it.\n+ */\n+ if (base >= PIC32MK_EVIC_IFS0 && base < PIC32MK_EVIC_IEC0) {\n+ int idx = (int)((base - PIC32MK_EVIC_IFS0) >> 4);\n+ if (idx < EVIC_IFS_COUNT) {\n+ s->ifsreg[idx] |= s->irq_level[idx];\n+ }\n+ }\n+\n+ /* IFS or IEC or IPC write → re-evaluate interrupt routing */\n+ if (base >= PIC32MK_EVIC_IFS0 &&\n+ base < PIC32MK_EVIC_IPC0 + (hwaddr)(EVIC_IPC_COUNT * 0x10)) {\n+ need_update = true;\n+ }\n+\n+ if (need_update) {\n+ evic_update(s);\n+ }\n+}\n+\n+static const MemoryRegionOps evic_ops = {\n+ .read = evic_read,\n+ .write = evic_write,\n+ .endianness = DEVICE_LITTLE_ENDIAN,\n+ .valid = {\n+ .min_access_size = 4,\n+ .max_access_size = 4,\n+ },\n+};\n+\n+/*\n+ * Device lifecycle\n+ * -----------------------------------------------------------------------\n+ */\n+\n+static void pic32mk_evic_reset(DeviceState *dev)\n+{\n+ PIC32MKEVICState *s = PIC32MK_EVIC(dev);\n+\n+ s->intcon = 0;\n+ s->priss = 0;\n+ s->intstat = 0;\n+ s->iptmr = 0;\n+ memset(s->ifsreg, 0, sizeof(s->ifsreg));\n+ memset(s->iecreg, 0, sizeof(s->iecreg));\n+ memset(s->ipcreg, 0, sizeof(s->ipcreg));\n+ memset(s->offreg, 0, sizeof(s->offreg));\n+ memset(s->irq_level, 0, sizeof(s->irq_level));\n+\n+ /* Deassert all CPU interrupt pins */\n+ for (int i = 0; i < 8; i++) {\n+ if (s->cpu_irq[i]) {\n+ qemu_irq_lower(s->cpu_irq[i]);\n+ }\n+ }\n+}\n+\n+static void pic32mk_evic_init(Object *obj)\n+{\n+ PIC32MKEVICState *s = PIC32MK_EVIC(obj);\n+\n+ memory_region_init_io(&s->mr, obj, &evic_ops, s,\n+ TYPE_PIC32MK_EVIC, EVIC_MMIO_SIZE);\n+ sysbus_init_mmio(SYS_BUS_DEVICE(obj), &s->mr);\n+\n+ /* 216 GPIO input lines — one per interrupt source */\n+ qdev_init_gpio_in(DEVICE(obj), evic_set_irq, PIC32MK_NUM_IRQ_SOURCES);\n+}\n+\n+static void pic32mk_evic_class_init(ObjectClass *klass, const void *data)\n+{\n+ DeviceClass *dc = DEVICE_CLASS(klass);\n+ device_class_set_legacy_reset(dc, pic32mk_evic_reset);\n+}\n+\n+static const TypeInfo pic32mk_evic_info = {\n+ .name = TYPE_PIC32MK_EVIC,\n+ .parent = TYPE_SYS_BUS_DEVICE,\n+ .instance_size = sizeof(PIC32MKEVICState),\n+ .instance_init = pic32mk_evic_init,\n+ .class_init = pic32mk_evic_class_init,\n+};\n+\n+static void pic32mk_evic_register_types(void)\n+{\n+ type_register_static(&pic32mk_evic_info);\n+}\n+\n+type_init(pic32mk_evic_register_types)\ndiff --git a/hw/mips/pic32mk_gpio.c b/hw/mips/pic32mk_gpio.c\nnew file mode 100644\nindex 0000000000..ddbb6e6cd5\n--- /dev/null\n+++ b/hw/mips/pic32mk_gpio.c\n@@ -0,0 +1,418 @@\n+/*\n+ * PIC32MK GPIO — PORTA through PORTG (7 ports)\n+ * Datasheet: DS60001519E, §12\n+ *\n+ * Each port is a SysBusDevice that exposes a PIC32MK_GPIO_PORT_SIZE (0x100)\n+ * byte MMIO region containing ANSEL, TRIS, PORT, LAT, ODC, CN* registers.\n+ *\n+ * All registers support SET/CLR/INV sub-registers (+4/+8/+C).\n+ *\n+ * External input model:\n+ * - 16 qdev GPIO input lines per port — driven by board wiring or test code.\n+ * - 16 qdev GPIO output lines per port — assert when an output pin's LAT changes.\n+ * - One IRQ output line → EVIC CN interrupt (fires when CNCON.ON and CNEN0\n+ * enabled bits detect a PORT change).\n+ *\n+ * QOM properties (accessible via qom-get / qom-set from HMP/QMP):\n+ * pin0..pin15 - bool, r/w - inject external input (write) or read PORT bit\n+ * lat-state — uint32, r — current LAT register value\n+ * port-state — uint32, r — current PORT register value (mix of ext & output)\n+ * tris-state — uint32, r — current TRIS register value\n+ *\n+ * Chardev event stream (optional \"chardev\" qdev property):\n+ * When connected, each state change emits a 13-byte little-endian message:\n+ * [port_idx:1] [tris:4] [lat:4] [port:4]\n+ * Enables event-driven host-side GPIO monitoring (gpio_tool.py gui).\n+ *\n+ * Copyright (c) 2026 QEMU contributors\n+ * SPDX-License-Identifier: GPL-2.0-or-later\n+ */\n+\n+#include \"qemu/osdep.h\"\n+#include \"qemu/log.h\"\n+#include \"qemu/module.h\"\n+#include \"qapi/error.h\"\n+#include \"qapi/visitor.h\"\n+#include \"hw/core/irq.h\"\n+#include \"hw/core/sysbus.h\"\n+#include \"hw/core/qdev-properties.h\"\n+#include \"chardev/char.h\"\n+#include \"hw/mips/pic32mk.h\"\n+#include \"qom/object.h\"\n+\n+/*\n+ * Device state — one instance per port (A–G)\n+ * -----------------------------------------------------------------------\n+ */\n+\n+#define TYPE_PIC32MK_GPIO \"pic32mk-gpio\"\n+OBJECT_DECLARE_SIMPLE_TYPE(PIC32MKGpioState, PIC32MK_GPIO)\n+\n+struct PIC32MKGpioState {\n+ SysBusDevice parent_obj;\n+ MemoryRegion mr;\n+\n+ /* SFR registers */\n+ uint32_t ansel;\n+ uint32_t tris; /* 1 = input (reset default) */\n+ uint32_t port; /* pin state: input bits from ext_input, output bits from lat */\n+ uint32_t lat; /* output latch */\n+ uint32_t odc;\n+ uint32_t cnpu;\n+ uint32_t cnpd;\n+ uint32_t cncon;\n+ uint32_t cnen0;\n+ uint32_t cnstat;\n+ uint32_t cnen1;\n+ uint32_t cnf;\n+\n+ /* External input pin levels (driven by qdev GPIO input lines or QOM set) */\n+ uint32_t ext_input;\n+\n+ /* CN reference: last PORT value read by firmware — used for mismatch detect */\n+ uint32_t cn_ref;\n+\n+ /* IRQ output → EVIC CN interrupt */\n+ qemu_irq cn_irq;\n+\n+ /* qdev GPIO output lines (16 per port): asserted when output LAT changes */\n+ qemu_irq output[16];\n+\n+ /*\n+ * Optional shared chardev for streaming state-change events to host tools.\n+ * Multiple GPIO ports write to the same Chardev* (not CharFrontend,\n+ * which enforces 1:1 ownership). Set by board init if chardev exists.\n+ */\n+ Chardev *chr_out;\n+ uint8_t port_idx; /* 0=A, 1=B, …, 6=G */\n+};\n+\n+/*\n+ * MMIO helpers\n+ * -----------------------------------------------------------------------\n+ */\n+\n+/* PIC32MK: base+0=REG, +4=CLR, +8=SET, +0xC=INV */\n+static void apply_sci(uint32_t *reg, uint32_t val, int sub)\n+{\n+ switch (sub) {\n+ case 0:\n+ *reg = val;\n+ break;\n+ case 4:\n+ *reg &= ~val;\n+ break;\n+ case 8:\n+ *reg |= val;\n+ break;\n+ case 12:\n+ *reg ^= val;\n+ break;\n+ }\n+}\n+\n+static uint32_t *gpio_find_reg(PIC32MKGpioState *s, hwaddr base)\n+{\n+ switch (base) {\n+ case PIC32MK_ANSEL:\n+ return &s->ansel;\n+ case PIC32MK_TRIS:\n+ return &s->tris;\n+ case PIC32MK_PORT:\n+ return &s->port;\n+ case PIC32MK_LAT:\n+ return &s->lat;\n+ case PIC32MK_ODC:\n+ return &s->odc;\n+ case PIC32MK_CNPU:\n+ return &s->cnpu;\n+ case PIC32MK_CNPD:\n+ return &s->cnpd;\n+ case PIC32MK_CNCON:\n+ return &s->cncon;\n+ case PIC32MK_CNEN0:\n+ return &s->cnen0;\n+ case PIC32MK_CNSTAT:\n+ return &s->cnstat;\n+ case PIC32MK_CNEN1:\n+ return &s->cnen1;\n+ case PIC32MK_CNF:\n+ return &s->cnf;\n+ default:\n+ return NULL;\n+ }\n+}\n+\n+/* Recompute s->port from current lat, tris, and ext_input. */\n+static void gpio_update_port(PIC32MKGpioState *s)\n+{\n+ /* Input pins reflect external levels; output pins reflect LAT. */\n+ s->port = (s->ext_input & s->tris) | (s->lat & ~s->tris);\n+}\n+\n+/*\n+ * Emit a 13-byte state-change event on the optional chardev.\n+ * Format (little-endian): [port_idx:1] [tris:4] [lat:4] [port:4]\n+ */\n+static void gpio_notify_state(PIC32MKGpioState *s)\n+{\n+ if (!s->chr_out) {\n+ return;\n+ }\n+ uint8_t msg[13];\n+ msg[0] = s->port_idx;\n+ memcpy(&msg[1], &s->tris, 4);\n+ memcpy(&msg[5], &s->lat, 4);\n+ memcpy(&msg[9], &s->port, 4);\n+ qemu_chr_write_all(s->chr_out, msg, sizeof(msg));\n+}\n+\n+/* Drive the 16 qdev output GPIO lines to match current LAT (output pins only). */\n+static void gpio_drive_outputs(PIC32MKGpioState *s)\n+{\n+ uint32_t out = s->lat & ~s->tris;\n+ for (int i = 0; i < 16; i++) {\n+ qemu_set_irq(s->output[i], (out >> i) & 1);\n+ }\n+}\n+\n+/*\n+ * Evaluate CN interrupt.\n+ *\n+ * PIC32MK CN is mismatch-based: CNSTAT bits are set when the current PORT\n+ * value differs from the value captured at the last PORT read (cn_ref).\n+ * The interrupt line is held HIGH as long as any enabled mismatch exists;\n+ * reading PORT (in the ISR) updates cn_ref and clears the mismatch, which\n+ * deasserts the interrupt so the EVIC doesn't immediately re-set IFS.\n+ */\n+static void gpio_eval_cn(PIC32MKGpioState *s)\n+{\n+#define CNCON_ON (1u << 15)\n+ if (!(s->cncon & CNCON_ON)) {\n+ return;\n+ }\n+ /* Mismatch between current port and the reference snapshot */\n+ uint32_t mismatch = (s->port ^ s->cn_ref) & s->cnen0;\n+ s->cnstat = mismatch;\n+\n+ if (mismatch) {\n+ qemu_set_irq(s->cn_irq, 1); /* assert — EVIC sets IFS */\n+ } else {\n+ qemu_set_irq(s->cn_irq, 0); /* deassert — no more mismatch */\n+ }\n+}\n+\n+/*\n+ * qdev GPIO input handler (external pin injection)\n+ * -----------------------------------------------------------------------\n+ */\n+\n+static void gpio_set_input(PIC32MKGpioState *s, int pin, int level)\n+{\n+ uint32_t mask = 1u << pin;\n+\n+ if (level) {\n+ s->ext_input |= mask;\n+ } else {\n+ s->ext_input &= ~mask;\n+ }\n+\n+ gpio_update_port(s);\n+ gpio_eval_cn(s);\n+ gpio_notify_state(s);\n+}\n+\n+static void gpio_set_input_line(void *opaque, int pin, int level)\n+{\n+ gpio_set_input(PIC32MK_GPIO(opaque), pin, level);\n+}\n+\n+/*\n+ * MMIO read/write\n+ * -----------------------------------------------------------------------\n+ */\n+\n+static uint64_t gpio_read(void *opaque, hwaddr addr, unsigned size)\n+{\n+ PIC32MKGpioState *s = opaque;\n+ hwaddr base = addr & ~(hwaddr)0xF;\n+ uint32_t *reg = gpio_find_reg(s, base);\n+\n+ if (reg) {\n+ /*\n+ * Reading PORT captures the current pin state into cn_ref.\n+ * This clears the CN mismatch and deasserts the interrupt line,\n+ * just like real PIC32MK hardware.\n+ */\n+ if (base == PIC32MK_PORT) {\n+ s->cn_ref = s->port;\n+ gpio_eval_cn(s); /* deassert if no longer mismatching */\n+ }\n+ return *reg;\n+ }\n+\n+ qemu_log_mask(LOG_UNIMP,\n+ \"pic32mk_gpio: unimplemented read @ 0x%04\" HWADDR_PRIx \"\\n\",\n+ addr);\n+ return 0;\n+}\n+\n+static void gpio_write(void *opaque, hwaddr addr, uint64_t val, unsigned size)\n+{\n+ PIC32MKGpioState *s = opaque;\n+ int sub = (int)(addr & 0xF);\n+ hwaddr base = addr & ~(hwaddr)0xF;\n+ uint32_t *reg = gpio_find_reg(s, base);\n+\n+ if (!reg) {\n+ qemu_log_mask(LOG_UNIMP,\n+ \"pic32mk_gpio: unimplemented write @ 0x%04\"\n+ HWADDR_PRIx \" = 0x%08\" PRIx64 \"\\n\",\n+ addr, val);\n+ return;\n+ }\n+\n+ apply_sci(reg, (uint32_t)val, sub);\n+\n+ /* Side-effects after write */\n+ if (base == PIC32MK_LAT || base == PIC32MK_TRIS) {\n+ /* Output pin levels or direction changed — update PORT and drive lines */\n+ gpio_update_port(s);\n+ gpio_drive_outputs(s);\n+ gpio_eval_cn(s);\n+ gpio_notify_state(s);\n+ } else if (base == PIC32MK_ANSEL) {\n+ gpio_notify_state(s);\n+ }\n+ if (base == PIC32MK_CNCON || base == PIC32MK_CNEN0 || base == PIC32MK_CNEN1) {\n+ gpio_eval_cn(s);\n+ }\n+}\n+\n+static const MemoryRegionOps gpio_ops = {\n+ .read = gpio_read,\n+ .write = gpio_write,\n+ .endianness = DEVICE_LITTLE_ENDIAN,\n+ .valid = {\n+ .min_access_size = 4,\n+ .max_access_size = 4,\n+ },\n+};\n+\n+/*\n+ * QOM properties — host access via qom-get / qom-set\n+ * -----------------------------------------------------------------------\n+ */\n+\n+static void gpio_prop_pin_get(Object *obj, Visitor *v, const char *name,\n+ void *opaque, Error **errp)\n+{\n+ PIC32MKGpioState *s = PIC32MK_GPIO(obj);\n+ int pin = (int)(intptr_t)opaque;\n+ bool val = (s->port >> pin) & 1;\n+ visit_type_bool(v, name, &val, errp);\n+}\n+\n+static void gpio_prop_pin_set(Object *obj, Visitor *v, const char *name,\n+ void *opaque, Error **errp)\n+{\n+ PIC32MKGpioState *s = PIC32MK_GPIO(obj);\n+ int pin = (int)(intptr_t)opaque;\n+ bool val;\n+\n+ if (!visit_type_bool(v, name, &val, errp)) {\n+ return;\n+ }\n+ gpio_set_input(s, pin, val ? 1 : 0);\n+}\n+\n+/*\n+ * Device lifecycle\n+ * -----------------------------------------------------------------------\n+ */\n+\n+static void pic32mk_gpio_reset(DeviceState *dev)\n+{\n+ PIC32MKGpioState *s = PIC32MK_GPIO(dev);\n+\n+ s->ansel = 0xFFFF; /* all pins analog on reset */\n+ s->tris = 0xFFFF; /* all pins input on reset */\n+ s->port = 0;\n+ s->lat = 0;\n+ s->odc = 0;\n+ s->cnpu = 0;\n+ s->cnpd = 0;\n+ s->cncon = 0;\n+ s->cnen0 = 0;\n+ s->cnstat = 0;\n+ s->cnen1 = 0;\n+ s->cnf = 0;\n+ s->ext_input = 0;\n+ s->cn_ref = 0;\n+}\n+\n+static void pic32mk_gpio_init(Object *obj)\n+{\n+ PIC32MKGpioState *s = PIC32MK_GPIO(obj);\n+ DeviceState *dev = DEVICE(obj);\n+\n+ memory_region_init_io(&s->mr, obj, &gpio_ops, s,\n+ TYPE_PIC32MK_GPIO, PIC32MK_GPIO_PORT_SIZE);\n+ sysbus_init_mmio(SYS_BUS_DEVICE(obj), &s->mr);\n+\n+ /* IRQ output → EVIC CN interrupt */\n+ sysbus_init_irq(SYS_BUS_DEVICE(obj), &s->cn_irq);\n+\n+ /* 16 qdev GPIO input lines: board/test code drives external pin levels */\n+ qdev_init_gpio_in(dev, gpio_set_input_line, 16);\n+\n+ /* 16 qdev GPIO output lines: driven when LAT output pins change */\n+ qdev_init_gpio_out(dev, s->output, 16);\n+\n+ /* Per-pin bool QOM properties: pin0 … pin15 */\n+ for (int i = 0; i < 16; i++) {\n+ char name[8];\n+ snprintf(name, sizeof(name), \"pin%d\", i);\n+ object_property_add(obj, name, \"bool\",\n+ gpio_prop_pin_get,\n+ gpio_prop_pin_set,\n+ NULL, (void *)(intptr_t)i);\n+ }\n+\n+ /* Whole-port read-only convenience properties */\n+ object_property_add_uint32_ptr(obj, \"lat-state\", &s->lat, OBJ_PROP_FLAG_READ);\n+ object_property_add_uint32_ptr(obj, \"port-state\", &s->port, OBJ_PROP_FLAG_READ);\n+ object_property_add_uint32_ptr(obj, \"tris-state\", &s->tris, OBJ_PROP_FLAG_READ);\n+}\n+\n+void pic32mk_gpio_set_chardev(DeviceState *dev, Chardev *chr)\n+{\n+ PIC32MKGpioState *s = PIC32MK_GPIO(dev);\n+ s->chr_out = chr;\n+}\n+\n+static const Property pic32mk_gpio_props[] = {\n+ DEFINE_PROP_UINT8(\"port-index\", PIC32MKGpioState, port_idx, 0),\n+};\n+\n+static void pic32mk_gpio_class_init(ObjectClass *klass, const void *data)\n+{\n+ DeviceClass *dc = DEVICE_CLASS(klass);\n+ device_class_set_legacy_reset(dc, pic32mk_gpio_reset);\n+ device_class_set_props(dc, pic32mk_gpio_props);\n+}\n+\n+static const TypeInfo pic32mk_gpio_info = {\n+ .name = TYPE_PIC32MK_GPIO,\n+ .parent = TYPE_SYS_BUS_DEVICE,\n+ .instance_size = sizeof(PIC32MKGpioState),\n+ .instance_init = pic32mk_gpio_init,\n+ .class_init = pic32mk_gpio_class_init,\n+};\n+\n+static void pic32mk_gpio_register_types(void)\n+{\n+ type_register_static(&pic32mk_gpio_info);\n+}\n+\n+type_init(pic32mk_gpio_register_types)\ndiff --git a/hw/mips/pic32mk_i2c.c b/hw/mips/pic32mk_i2c.c\nnew file mode 100644\nindex 0000000000..b8c7220d92\n--- /dev/null\n+++ b/hw/mips/pic32mk_i2c.c\n@@ -0,0 +1,184 @@\n+/*\n+ * PIC32MK I2C × 4 (I2C1–I2C4)\n+ * Datasheet: DS60001519E, §24\n+ *\n+ * Each I2C instance is a SysBusDevice that stubs the register file and\n+ * attaches a QEMU i2c_bus for downstream device models.\n+ * Master-mode state machine is Phase 2B; firmware init (I2CxCON writes)\n+ * succeeds silently.\n+ *\n+ * Copyright (c) 2026 QEMU contributors\n+ * SPDX-License-Identifier: GPL-2.0-or-later\n+ */\n+\n+#include \"qemu/osdep.h\"\n+#include \"qemu/log.h\"\n+#include \"qemu/module.h\"\n+#include \"hw/core/sysbus.h\"\n+#include \"hw/core/irq.h\"\n+#include \"hw/i2c/i2c.h\"\n+#include \"hw/mips/pic32mk.h\"\n+\n+#define TYPE_PIC32MK_I2C \"pic32mk-i2c\"\n+OBJECT_DECLARE_SIMPLE_TYPE(PIC32MKI2CState, PIC32MK_I2C)\n+\n+struct PIC32MKI2CState {\n+ SysBusDevice parent_obj;\n+ MemoryRegion mr;\n+\n+ uint32_t con; /* I2CxCON */\n+ uint32_t stat; /* I2CxSTAT */\n+ uint32_t add; /* I2CxADD */\n+ uint32_t msk; /* I2CxMSK */\n+ uint32_t trn; /* I2CxTRN — TX data */\n+ uint32_t rcv; /* I2CxRCV — RX data (read-only) */\n+\n+ I2CBus *bus;\n+ qemu_irq irq_master; /* master interrupt */\n+ qemu_irq irq_slave; /* slave interrupt */\n+ qemu_irq irq_bus; /* bus-collision interrupt */\n+};\n+\n+/* I2CxSTAT bits */\n+#define I2C_STAT_TRSTAT (1u << 14) /* transmit status */\n+#define I2C_STAT_ACKSTAT (1u << 15) /* ACK status */\n+\n+/* PIC32MK: base+0=REG, +4=CLR, +8=SET, +0xC=INV */\n+static void apply_sci(uint32_t *reg, uint32_t val, int sub)\n+{\n+ switch (sub) {\n+ case 0:\n+ *reg = val;\n+ break;\n+ case 4:\n+ *reg &= ~val;\n+ break;\n+ case 8:\n+ *reg |= val;\n+ break;\n+ case 12:\n+ *reg ^= val;\n+ break;\n+ }\n+}\n+\n+static uint32_t *i2c_find_reg(PIC32MKI2CState *s, hwaddr base)\n+{\n+ switch (base) {\n+ case PIC32MK_I2CxCON:\n+ return &s->con;\n+ case PIC32MK_I2CxSTAT:\n+ return &s->stat;\n+ case PIC32MK_I2CxADD:\n+ return &s->add;\n+ case PIC32MK_I2CxMSK:\n+ return &s->msk;\n+ case PIC32MK_I2CxTRN:\n+ return &s->trn;\n+ case PIC32MK_I2CxRCV:\n+ return &s->rcv;\n+ default:\n+ return NULL;\n+ }\n+}\n+\n+static uint64_t i2c_read(void *opaque, hwaddr addr, unsigned size)\n+{\n+ PIC32MKI2CState *s = opaque;\n+ hwaddr base = addr & ~(hwaddr)0xF;\n+ uint32_t *reg = i2c_find_reg(s, base);\n+\n+ if (reg) {\n+ return *reg;\n+ }\n+\n+ qemu_log_mask(LOG_UNIMP,\n+ \"pic32mk_i2c: unimplemented read @ 0x%04\" HWADDR_PRIx \"\\n\",\n+ addr);\n+ return 0;\n+}\n+\n+static void i2c_write(void *opaque, hwaddr addr, uint64_t val, unsigned size)\n+{\n+ PIC32MKI2CState *s = opaque;\n+ int sub = (int)(addr & 0xF);\n+ hwaddr base = addr & ~(hwaddr)0xF;\n+ uint32_t *reg = i2c_find_reg(s, base);\n+\n+ if (!reg) {\n+ qemu_log_mask(LOG_UNIMP,\n+ \"pic32mk_i2c: unimplemented write @ 0x%04\"\n+ HWADDR_PRIx \" = 0x%08\" PRIx64 \"\\n\",\n+ addr, val);\n+ return;\n+ }\n+\n+ /* TRN write: transmit byte — stub logs but does not transfer */\n+ if (base == PIC32MK_I2CxTRN) {\n+ s->trn = (uint32_t)val;\n+ qemu_log_mask(LOG_UNIMP,\n+ \"pic32mk_i2c: TX write 0x%02x (I2C master not implemented)\\n\",\n+ (uint32_t)val & 0xFF);\n+ return;\n+ }\n+\n+ /* STAT is partially read-only; accept writes for CLR operations */\n+ apply_sci(reg, (uint32_t)val, sub);\n+}\n+\n+static const MemoryRegionOps i2c_ops = {\n+ .read = i2c_read,\n+ .write = i2c_write,\n+ .endianness = DEVICE_LITTLE_ENDIAN,\n+ .valid = { .min_access_size = 4, .max_access_size = 4 },\n+};\n+\n+static void pic32mk_i2c_reset(DeviceState *dev)\n+{\n+ PIC32MKI2CState *s = PIC32MK_I2C(dev);\n+ s->con = 0;\n+ s->stat = 0;\n+ s->add = 0;\n+ s->msk = 0;\n+ s->trn = 0xFF;\n+ s->rcv = 0;\n+ qemu_irq_lower(s->irq_master);\n+ qemu_irq_lower(s->irq_slave);\n+ qemu_irq_lower(s->irq_bus);\n+}\n+\n+static void pic32mk_i2c_init(Object *obj)\n+{\n+ PIC32MKI2CState *s = PIC32MK_I2C(obj);\n+\n+ memory_region_init_io(&s->mr, obj, &i2c_ops, s,\n+ TYPE_PIC32MK_I2C, PIC32MK_I2C_BLOCK_SIZE);\n+ sysbus_init_mmio(SYS_BUS_DEVICE(obj), &s->mr);\n+\n+ sysbus_init_irq(SYS_BUS_DEVICE(obj), &s->irq_master); /* index 0 */\n+ sysbus_init_irq(SYS_BUS_DEVICE(obj), &s->irq_slave); /* index 1 */\n+ sysbus_init_irq(SYS_BUS_DEVICE(obj), &s->irq_bus); /* index 2 */\n+\n+ s->bus = i2c_init_bus(DEVICE(obj), \"i2c\");\n+}\n+\n+static void pic32mk_i2c_class_init(ObjectClass *klass, const void *data)\n+{\n+ DeviceClass *dc = DEVICE_CLASS(klass);\n+ device_class_set_legacy_reset(dc, pic32mk_i2c_reset);\n+}\n+\n+static const TypeInfo pic32mk_i2c_info = {\n+ .name = TYPE_PIC32MK_I2C,\n+ .parent = TYPE_SYS_BUS_DEVICE,\n+ .instance_size = sizeof(PIC32MKI2CState),\n+ .instance_init = pic32mk_i2c_init,\n+ .class_init = pic32mk_i2c_class_init,\n+};\n+\n+static void pic32mk_i2c_register_types(void)\n+{\n+ type_register_static(&pic32mk_i2c_info);\n+}\n+\n+type_init(pic32mk_i2c_register_types)\ndiff --git a/hw/mips/pic32mk_ic.c b/hw/mips/pic32mk_ic.c\nnew file mode 100644\nindex 0000000000..5b709841fa\n--- /dev/null\n+++ b/hw/mips/pic32mk_ic.c\n@@ -0,0 +1,384 @@\n+/*\n+ * PIC32MK Input Capture × 16 (IC1–IC16)\n+ * Datasheet: DS60001519E, §18\n+ *\n+ * Emulation model: full register model with 4-entry FIFO.\n+ * Two IRQ outputs per instance: capture IRQ and overflow-error IRQ.\n+ *\n+ * Capture injection via optional chardev (\"ic-events\"):\n+ * The chardev is receive-side: the host writes 8-byte packets to\n+ * inject capture events into the FIFO.\n+ *\n+ * Packet format (8 bytes, little-endian):\n+ * [0] index — IC instance (1–16); packet ignored if != this instance\n+ * [1] flags — bit0=error inject, bit1=hi16 valid (32-bit capture pair)\n+ * [2–3] val_lo — 16-bit capture value (low word), LE\n+ * [4–5] val_hi — 16-bit capture value (high word), LE (C32 mode only)\n+ * [6–7] reserved\n+ *\n+ * On inject: push val_lo to FIFO (and val_hi if flags.bit1 and C32=1),\n+ * set ICBNE, assert capture IRQ (if ICI != 0). If FIFO full: set ICOV,\n+ * assert error IRQ. Error-inject (flags.bit0=1): set ICOV, assert error IRQ.\n+ *\n+ * Register layout within each 0x200-byte block\n+ * (SET/CLR/INV sub-regs at +4/+8/+C relative to register base):\n+ * ICxCON +0x00 Control: ON, SIDL, FEDGE, C32, ICTMR, ICI, ICOV, ICBNE, ICM\n+ * ICxBUF +0x10 Capture buffer (read-only FIFO pop; write ignored)\n+ *\n+ * FIFO: 4 entries × 16-bit. ICBNE cleared when FIFO empties.\n+ * In C32 mode, two consecutive ICxBUF reads assemble one 32-bit value.\n+ *\n+ * Copyright (c) 2026 QEMU contributors\n+ * SPDX-License-Identifier: GPL-2.0-or-later\n+ */\n+\n+#include \"qemu/osdep.h\"\n+#include \"qemu/log.h\"\n+#include \"qemu/module.h\"\n+#include \"hw/core/sysbus.h\"\n+#include \"hw/core/qdev-properties.h\"\n+#include \"hw/core/qdev-properties-system.h\"\n+#include \"hw/core/irq.h\"\n+#include \"hw/mips/pic32mk.h\"\n+#include \"chardev/char.h\"\n+#include \"chardev/char-fe.h\"\n+\n+/*\n+ * ICM mode names (ICxCON bits 2:0)\n+ * -----------------------------------------------------------------------\n+ */\n+static const char *icm_mode_names[] = {\n+ [0] = \"Disabled\",\n+ [1] = \"Every edge\",\n+ [2] = \"Every 2nd rising edge\",\n+ [3] = \"Every 3rd rising edge\",\n+ [4] = \"Every 4th rising edge\",\n+ [5] = \"Every 5th rising edge (mode5)\",\n+ [6] = \"Every 6th rising edge (mode6)\",\n+ [7] = \"Every 7th rising edge (mode7)\",\n+};\n+\n+/*\n+ * Global instance table — lets IC1 (the sole chardev owner) dispatch\n+ * packets to any of the 16 IC instances by index.\n+ * -----------------------------------------------------------------------\n+ */\n+\n+static struct PIC32MKICState *g_ic_instances[17]; /* index 1–16 */\n+\n+/*\n+ * Device state\n+ * -----------------------------------------------------------------------\n+ */\n+\n+#define TYPE_PIC32MK_IC \"pic32mk-ic\"\n+OBJECT_DECLARE_SIMPLE_TYPE(PIC32MKICState, PIC32MK_IC)\n+\n+#define PIC32MK_IC_FIFO_DEPTH 4\n+\n+struct PIC32MKICState {\n+ SysBusDevice parent_obj;\n+ MemoryRegion mr;\n+\n+ uint32_t con; /* ICxCON */\n+ uint16_t fifo[PIC32MK_IC_FIFO_DEPTH]; /* capture FIFO */\n+ uint8_t fifo_head; /* read pointer */\n+ uint8_t fifo_tail; /* write pointer */\n+ uint8_t fifo_count; /* entries in use */\n+\n+ qemu_irq irq_capture; /* normal capture IRQ → EVIC */\n+ qemu_irq irq_error; /* overflow / error IRQ → EVIC */\n+ uint8_t index; /* 1–16 */\n+\n+ CharFrontend chr; /* optional chardev for capture injection */\n+\n+ /* Partial-packet accumulator for chardev receive */\n+ uint8_t pkt_buf[8];\n+ int pkt_len;\n+};\n+\n+/*\n+ * FIFO helpers\n+ * -----------------------------------------------------------------------\n+ */\n+\n+static void ic_fifo_reset(PIC32MKICState *s)\n+{\n+ s->fifo_head = 0;\n+ s->fifo_tail = 0;\n+ s->fifo_count = 0;\n+ s->con &= ~(PIC32MK_ICCON_ICBNE | PIC32MK_ICCON_ICOV);\n+}\n+\n+static void ic_fifo_push(PIC32MKICState *s, uint16_t val)\n+{\n+ if (s->fifo_count == PIC32MK_IC_FIFO_DEPTH) {\n+ /* Overflow: set ICOV, pulse error IRQ */\n+ s->con |= PIC32MK_ICCON_ICOV;\n+ qemu_irq_pulse(s->irq_error);\n+ qemu_log_mask(LOG_GUEST_ERROR,\n+ \"pic32mk_ic: IC%u FIFO overflow\\n\", s->index);\n+ return;\n+ }\n+ s->fifo[s->fifo_tail] = val;\n+ s->fifo_tail = (s->fifo_tail + 1) % PIC32MK_IC_FIFO_DEPTH;\n+ s->fifo_count++;\n+ s->con |= PIC32MK_ICCON_ICBNE;\n+}\n+\n+static uint16_t ic_fifo_pop(PIC32MKICState *s)\n+{\n+ if (s->fifo_count == 0) {\n+ return 0;\n+ }\n+ uint16_t val = s->fifo[s->fifo_head];\n+ s->fifo_head = (s->fifo_head + 1) % PIC32MK_IC_FIFO_DEPTH;\n+ s->fifo_count--;\n+ if (s->fifo_count == 0) {\n+ s->con &= ~PIC32MK_ICCON_ICBNE;\n+ }\n+ return val;\n+}\n+\n+/*\n+ * Capture injection — called from chardev receive handler\n+ * -----------------------------------------------------------------------\n+ */\n+\n+static void ic_inject_capture(PIC32MKICState *s, uint8_t flags,\n+ uint16_t val_lo, uint16_t val_hi)\n+{\n+ if (!(s->con & PIC32MK_ICCON_ON)) {\n+ return; /* IC not enabled — ignore */\n+ }\n+\n+ if (flags & 1) {\n+ /* Error inject: set ICOV, pulse error IRQ */\n+ s->con |= PIC32MK_ICCON_ICOV;\n+ qemu_irq_pulse(s->irq_error);\n+ return;\n+ }\n+\n+ ic_fifo_push(s, val_lo);\n+ if ((flags & 2) && (s->con & PIC32MK_ICCON_C32)) {\n+ ic_fifo_push(s, val_hi);\n+ }\n+\n+ /*\n+ * ICI<1:0>: 00=every 1st, 01=every 2nd, 10=every 3rd, 11=every 4th capture.\n+ * Simplified model: always fire on every capture (ICI threshold ignored).\n+ */\n+ if (s->fifo_count > 0) {\n+ qemu_irq_pulse(s->irq_capture);\n+ }\n+}\n+\n+/*\n+ * Chardev receive handler\n+ * -----------------------------------------------------------------------\n+ */\n+\n+static int ic_chr_can_receive(void *opaque)\n+{\n+ /* Accept up to one full packet at a time */\n+ return 8;\n+}\n+\n+static void ic_chr_receive(void *opaque, const uint8_t *buf, int size)\n+{\n+ PIC32MKICState *s = opaque;\n+ int i = 0;\n+\n+ while (i < size) {\n+ int need = 8 - s->pkt_len;\n+ int avail = size - i;\n+ int copy = (avail < need) ? avail : need;\n+\n+ memcpy(s->pkt_buf + s->pkt_len, buf + i, copy);\n+ s->pkt_len += copy;\n+ i += copy;\n+\n+ if (s->pkt_len == 8) {\n+ uint8_t inst = s->pkt_buf[0];\n+ uint8_t flags = s->pkt_buf[1];\n+ uint16_t val_lo, val_hi;\n+ memcpy(&val_lo, &s->pkt_buf[2], 2);\n+ memcpy(&val_hi, &s->pkt_buf[4], 2);\n+ val_lo = le16_to_cpu(val_lo);\n+ val_hi = le16_to_cpu(val_hi);\n+ s->pkt_len = 0;\n+\n+ /* Route to target instance via global table */\n+ if (inst >= 1 && inst <= 16 && g_ic_instances[inst]) {\n+ ic_inject_capture(g_ic_instances[inst], flags, val_lo, val_hi);\n+ }\n+ }\n+ }\n+}\n+\n+/*\n+ * MMIO helpers\n+ * -----------------------------------------------------------------------\n+ */\n+\n+/* PIC32MK: base+0=REG, +4=CLR, +8=SET, +0xC=INV */\n+static void apply_sci(uint32_t *reg, uint32_t val, int sub)\n+{\n+ switch (sub) {\n+ case 0:\n+ *reg = val;\n+ break;\n+ case 4:\n+ *reg &= ~val;\n+ break;\n+ case 8:\n+ *reg |= val;\n+ break;\n+ case 12:\n+ *reg ^= val;\n+ break;\n+ }\n+}\n+\n+/*\n+ * MMIO read / write\n+ * -----------------------------------------------------------------------\n+ */\n+\n+static uint64_t ic_read(void *opaque, hwaddr addr, unsigned size)\n+{\n+ PIC32MKICState *s = opaque;\n+ hwaddr base = addr & ~(hwaddr)0xF;\n+\n+ switch (base) {\n+ case PIC32MK_ICxCON:\n+ return s->con;\n+ case PIC32MK_ICxBUF:\n+ return ic_fifo_pop(s);\n+ default:\n+ qemu_log_mask(LOG_UNIMP,\n+ \"pic32mk_ic: IC%u unimplemented read @ 0x%04\"\n+ HWADDR_PRIx \"\\n\", s->index, addr);\n+ return 0;\n+ }\n+}\n+\n+static void ic_write(void *opaque, hwaddr addr, uint64_t val, unsigned size)\n+{\n+ PIC32MKICState *s = opaque;\n+ int sub = (int)(addr & 0xF);\n+ hwaddr base = addr & ~(hwaddr)0xF;\n+\n+ switch (base) {\n+ case PIC32MK_ICxCON: {\n+ bool was_on = !!(s->con & PIC32MK_ICCON_ON);\n+ apply_sci(&s->con, (uint32_t)val, sub);\n+ bool now_on = !!(s->con & PIC32MK_ICCON_ON);\n+\n+ if (!was_on && now_on) {\n+ /* ON 0→1: reset FIFO, log enable */\n+ ic_fifo_reset(s);\n+ uint32_t icm = (s->con & PIC32MK_ICCON_ICM_MASK);\n+ uint32_t ici = (s->con & PIC32MK_ICCON_ICI_MASK) >> PIC32MK_ICCON_ICI_SHIFT;\n+ bool c32 = !!(s->con & PIC32MK_ICCON_C32);\n+ bool ictmr = !!(s->con & PIC32MK_ICCON_ICTMR);\n+ bool fedge = !!(s->con & PIC32MK_ICCON_FEDGE);\n+ qemu_log(\"pic32mk_ic: IC%u enabled — mode=%s, ICI=%u, \"\n+ \"C32=%u, ICTMR=%u, FEDGE=%u\\n\",\n+ s->index, icm_mode_names[icm], ici, c32, ictmr, fedge);\n+ } else if (was_on && !now_on) {\n+ qemu_log(\"pic32mk_ic: IC%u disabled\\n\", s->index);\n+ }\n+ break;\n+ }\n+ case PIC32MK_ICxBUF:\n+ /* ICxBUF is read-only; writes are ignored */\n+ break;\n+ default:\n+ qemu_log_mask(LOG_UNIMP,\n+ \"pic32mk_ic: IC%u unimplemented write @ 0x%04\"\n+ HWADDR_PRIx \" = 0x%08\" PRIx64 \"\\n\",\n+ s->index, addr, val);\n+ break;\n+ }\n+}\n+\n+static const MemoryRegionOps ic_ops = {\n+ .read = ic_read,\n+ .write = ic_write,\n+ .endianness = DEVICE_LITTLE_ENDIAN,\n+ .valid = {\n+ .min_access_size = 4,\n+ .max_access_size = 4,\n+ },\n+};\n+\n+/*\n+ * Device lifecycle\n+ * -----------------------------------------------------------------------\n+ */\n+\n+static void pic32mk_ic_reset(DeviceState *dev)\n+{\n+ PIC32MKICState *s = PIC32MK_IC(dev);\n+ s->con = 0;\n+ s->pkt_len = 0;\n+ ic_fifo_reset(s);\n+}\n+\n+static void pic32mk_ic_realize(DeviceState *dev, Error **errp)\n+{\n+ PIC32MKICState *s = PIC32MK_IC(dev);\n+\n+ /* Register in global table so the chardev dispatcher can find us */\n+ if (s->index >= 1 && s->index <= 16) {\n+ g_ic_instances[s->index] = s;\n+ }\n+\n+ /* Only IC1 owns the chardev — it dispatches packets to all instances */\n+ if (qemu_chr_fe_backend_connected(&s->chr)) {\n+ qemu_chr_fe_set_handlers(&s->chr,\n+ ic_chr_can_receive,\n+ ic_chr_receive,\n+ NULL, NULL, s, NULL, true);\n+ }\n+}\n+\n+static void pic32mk_ic_init(Object *obj)\n+{\n+ PIC32MKICState *s = PIC32MK_IC(obj);\n+\n+ memory_region_init_io(&s->mr, obj, &ic_ops, s,\n+ TYPE_PIC32MK_IC, PIC32MK_IC_BLOCK_SIZE);\n+ sysbus_init_mmio(SYS_BUS_DEVICE(obj), &s->mr);\n+ sysbus_init_irq(SYS_BUS_DEVICE(obj), &s->irq_capture);\n+ sysbus_init_irq(SYS_BUS_DEVICE(obj), &s->irq_error);\n+}\n+\n+static Property pic32mk_ic_properties[] = {\n+ DEFINE_PROP_UINT8(\"index\", PIC32MKICState, index, 1),\n+ DEFINE_PROP_CHR(\"chardev\", PIC32MKICState, chr),\n+};\n+\n+static void pic32mk_ic_class_init(ObjectClass *klass, const void *data)\n+{\n+ DeviceClass *dc = DEVICE_CLASS(klass);\n+ dc->realize = pic32mk_ic_realize;\n+ device_class_set_legacy_reset(dc, pic32mk_ic_reset);\n+ device_class_set_props(dc, pic32mk_ic_properties);\n+}\n+\n+static const TypeInfo pic32mk_ic_info = {\n+ .name = TYPE_PIC32MK_IC,\n+ .parent = TYPE_SYS_BUS_DEVICE,\n+ .instance_size = sizeof(PIC32MKICState),\n+ .instance_init = pic32mk_ic_init,\n+ .class_init = pic32mk_ic_class_init,\n+};\n+\n+static void pic32mk_ic_register_types(void)\n+{\n+ type_register_static(&pic32mk_ic_info);\n+}\n+\n+type_init(pic32mk_ic_register_types)\ndiff --git a/hw/mips/pic32mk_nvm.c b/hw/mips/pic32mk_nvm.c\nnew file mode 100644\nindex 0000000000..c7538ee1cf\n--- /dev/null\n+++ b/hw/mips/pic32mk_nvm.c\n@@ -0,0 +1,572 @@\n+/*\n+ * PIC32MK NVM / Flash Controller emulation\n+ * Datasheet: DS60001519E, §10\n+ *\n+ * Mediates program/erase operations on the 1 MB Program Flash\n+ * (0x1D000000–0x1D0FFFFF) through the NVMCON register interface at\n+ * SFR offset 0x0A00 (virtual 0xBF800A00).\n+ *\n+ * The flash memory region must be created as RAM (not ROM) by the board\n+ * init code so that this device can write into it. A QOM link property\n+ * \"pflash\" connects to the MemoryRegion backing the program flash.\n+ *\n+ * Copyright (c) 2026 QEMU contributors\n+ * SPDX-License-Identifier: GPL-2.0-or-later\n+ */\n+\n+#include \"qemu/osdep.h\"\n+#include \"qemu/log.h\"\n+#include \"qemu/module.h\"\n+#include \"qapi/error.h\"\n+#include \"hw/core/sysbus.h\"\n+#include \"hw/core/irq.h\"\n+#include \"hw/core/qdev-properties.h\"\n+#include \"hw/mips/pic32mk.h\"\n+#include \"system/dma.h\"\n+\n+#include <fcntl.h>\n+#include <unistd.h>\n+\n+#define TYPE_PIC32MK_NVM \"pic32mk-nvm\"\n+OBJECT_DECLARE_SIMPLE_TYPE(PIC32MKNVMState, PIC32MK_NVM)\n+\n+/* NVMKEY unlock FSM states */\n+#define NVMKEY_LOCKED 0\n+#define NVMKEY_KEY1_OK 1\n+#define NVMKEY_UNLOCKED 2\n+\n+struct PIC32MKNVMState {\n+ SysBusDevice parent_obj;\n+ MemoryRegion mr;\n+\n+ /* SFR registers */\n+ uint32_t nvmcon;\n+ uint32_t nvmaddr;\n+ uint32_t nvmdata[4]; /* NVMDATA0–3 */\n+ uint32_t nvmsrcaddr;\n+ uint32_t nvmpwp;\n+ uint32_t nvmbwp;\n+ uint32_t nvmcon2;\n+ int nvmkey_state; /* unlock FSM */\n+\n+ /* QOM link to program flash MemoryRegion (must be RAM-backed) */\n+ MemoryRegion *pflash_mr;\n+\n+ /* IRQ output → EVIC vector 31 (_FLASH_CONTROL_VECTOR) */\n+ qemu_irq irq;\n+\n+ /* Host backing file for flash persistence */\n+ char *filename; /* qdev string property */\n+ int backing_fd;\n+};\n+\n+/*\n+ * SET/CLR/INV helper (PIC32MK convention: +0=REG, +4=CLR, +8=SET, +C=INV)\n+ * -----------------------------------------------------------------------\n+ */\n+\n+static void apply_sci(uint32_t *reg, uint32_t val, int sub)\n+{\n+ switch (sub) {\n+ case 0x0:\n+ *reg = val;\n+ break;\n+ case 0x4:\n+ *reg &= ~val;\n+ break;\n+ case 0x8:\n+ *reg |= val;\n+ break;\n+ case 0xC:\n+ *reg ^= val;\n+ break;\n+ }\n+}\n+\n+/*\n+ * Flash access helpers\n+ * -----------------------------------------------------------------------\n+ */\n+\n+/*\n+ * Convert a physical NVMADDR value to an offset within the program flash\n+ * region. Returns true on success, false if the address is out of range.\n+ */\n+static bool nvm_addr_to_offset(uint32_t phys_addr, uint32_t *offset)\n+{\n+ if (phys_addr >= PIC32MK_PFLASH_BASE &&\n+ phys_addr < PIC32MK_PFLASH_BASE + PIC32MK_PFLASH_SIZE) {\n+ *offset = phys_addr - PIC32MK_PFLASH_BASE;\n+ return true;\n+ }\n+ return false;\n+}\n+\n+static uint8_t *nvm_flash_ptr(PIC32MKNVMState *s)\n+{\n+ if (!s->pflash_mr) {\n+ return NULL;\n+ }\n+ return memory_region_get_ram_ptr(s->pflash_mr);\n+}\n+\n+/*\n+ * Backing file helpers\n+ * -----------------------------------------------------------------------\n+ */\n+\n+static void nvm_load_backing(PIC32MKNVMState *s)\n+{\n+ if (s->backing_fd < 0 || !s->pflash_mr) {\n+ return;\n+ }\n+ uint8_t *flash = nvm_flash_ptr(s);\n+ if (!flash) {\n+ return;\n+ }\n+ lseek(s->backing_fd, 0, SEEK_SET);\n+ ssize_t n = read(s->backing_fd, flash, PIC32MK_PFLASH_SIZE);\n+ if (n < 0) {\n+ qemu_log_mask(LOG_GUEST_ERROR,\n+ \"pic32mk_nvm: backing file read error\\n\");\n+ } else if ((size_t)n < PIC32MK_PFLASH_SIZE) {\n+ /* Short read — pad with 0xFF (erased flash) */\n+ memset(flash + n, 0xFF, PIC32MK_PFLASH_SIZE - (size_t)n);\n+ }\n+}\n+\n+static void nvm_flush_backing(PIC32MKNVMState *s)\n+{\n+ if (s->backing_fd < 0 || !s->pflash_mr) {\n+ return;\n+ }\n+ uint8_t *flash = nvm_flash_ptr(s);\n+ if (!flash) {\n+ return;\n+ }\n+ lseek(s->backing_fd, 0, SEEK_SET);\n+ ssize_t n = write(s->backing_fd, flash, PIC32MK_PFLASH_SIZE);\n+ if (n < 0) {\n+ qemu_log_mask(LOG_GUEST_ERROR,\n+ \"pic32mk_nvm: backing file write error\\n\");\n+ }\n+ fdatasync(s->backing_fd);\n+}\n+\n+/*\n+ * Command execution — triggered when WR transitions 0 → 1\n+ * -----------------------------------------------------------------------\n+ */\n+\n+static void nvm_execute_cmd(PIC32MKNVMState *s)\n+{\n+ uint32_t op = s->nvmcon & PIC32MK_NVMCON_NVMOP_MASK;\n+ uint32_t offset;\n+ uint8_t *flash;\n+\n+ /* Clear previous errors */\n+ s->nvmcon &= ~(PIC32MK_NVMCON_WRERR | PIC32MK_NVMCON_LVDERR);\n+\n+ /* All write/erase operations require WREN + unlock */\n+ if (op != PIC32MK_NVMOP_NOP) {\n+ if (!(s->nvmcon & PIC32MK_NVMCON_WREN)) {\n+ qemu_log_mask(LOG_GUEST_ERROR,\n+ \"pic32mk_nvm: operation 0x%x without WREN\\n\", op);\n+ s->nvmcon |= PIC32MK_NVMCON_WRERR;\n+ goto done;\n+ }\n+ if (s->nvmkey_state != NVMKEY_UNLOCKED) {\n+ qemu_log_mask(LOG_GUEST_ERROR,\n+ \"pic32mk_nvm: operation 0x%x without unlock\\n\", op);\n+ s->nvmcon |= PIC32MK_NVMCON_WRERR;\n+ goto done;\n+ }\n+ }\n+\n+ flash = nvm_flash_ptr(s);\n+ if (!flash && op != PIC32MK_NVMOP_NOP) {\n+ qemu_log_mask(LOG_GUEST_ERROR,\n+ \"pic32mk_nvm: no pflash region linked\\n\");\n+ s->nvmcon |= PIC32MK_NVMCON_WRERR;\n+ goto done;\n+ }\n+\n+ switch (op) {\n+ case PIC32MK_NVMOP_NOP:\n+ break;\n+\n+ case PIC32MK_NVMOP_WORD_PROG:\n+ if (!nvm_addr_to_offset(s->nvmaddr, &offset)) {\n+ s->nvmcon |= PIC32MK_NVMCON_WRERR;\n+ break;\n+ }\n+ if (offset + 4 > PIC32MK_PFLASH_SIZE) {\n+ s->nvmcon |= PIC32MK_NVMCON_WRERR;\n+ break;\n+ }\n+ /* Word-aligned write */\n+ offset &= ~3u;\n+ memcpy(flash + offset, &s->nvmdata[0], 4);\n+ memory_region_set_dirty(s->pflash_mr, offset, 4);\n+ break;\n+\n+ case PIC32MK_NVMOP_QUAD_WORD_PROG:\n+ if (!nvm_addr_to_offset(s->nvmaddr, &offset)) {\n+ s->nvmcon |= PIC32MK_NVMCON_WRERR;\n+ break;\n+ }\n+ /* Must be 16-byte aligned */\n+ offset &= ~0xFu;\n+ if (offset + 16 > PIC32MK_PFLASH_SIZE) {\n+ s->nvmcon |= PIC32MK_NVMCON_WRERR;\n+ break;\n+ }\n+ memcpy(flash + offset, s->nvmdata, 16);\n+ memory_region_set_dirty(s->pflash_mr, offset, 16);\n+ break;\n+\n+ case PIC32MK_NVMOP_ROW_PROG: {\n+ if (!nvm_addr_to_offset(s->nvmaddr, &offset)) {\n+ s->nvmcon |= PIC32MK_NVMCON_WRERR;\n+ break;\n+ }\n+ /* Row-aligned destination */\n+ offset &= ~(PIC32MK_NVM_ROW_SIZE - 1);\n+ if (offset + PIC32MK_NVM_ROW_SIZE > PIC32MK_PFLASH_SIZE) {\n+ s->nvmcon |= PIC32MK_NVMCON_WRERR;\n+ break;\n+ }\n+ /*\n+ * Copy ROW_SIZE bytes from guest physical address NVMSRCADDR\n+ * into the program flash at the destination offset.\n+ */\n+ {\n+ uint8_t row_buf[PIC32MK_NVM_ROW_SIZE];\n+ MemTxResult res = dma_memory_read(\n+ &address_space_memory, s->nvmsrcaddr,\n+ row_buf, PIC32MK_NVM_ROW_SIZE, MEMTXATTRS_UNSPECIFIED);\n+ if (res != MEMTX_OK) {\n+ qemu_log_mask(LOG_GUEST_ERROR,\n+ \"pic32mk_nvm: row read from 0x%08x failed\\n\",\n+ s->nvmsrcaddr);\n+ s->nvmcon |= PIC32MK_NVMCON_WRERR;\n+ break;\n+ }\n+ memcpy(flash + offset, row_buf, PIC32MK_NVM_ROW_SIZE);\n+ memory_region_set_dirty(s->pflash_mr, offset,\n+ PIC32MK_NVM_ROW_SIZE);\n+ }\n+ break;\n+ }\n+\n+ case PIC32MK_NVMOP_PAGE_ERASE:\n+ if (!nvm_addr_to_offset(s->nvmaddr, &offset)) {\n+ s->nvmcon |= PIC32MK_NVMCON_WRERR;\n+ break;\n+ }\n+ /* Page-align */\n+ offset &= ~(PIC32MK_NVM_PAGE_SIZE - 1);\n+ if (offset + PIC32MK_NVM_PAGE_SIZE > PIC32MK_PFLASH_SIZE) {\n+ s->nvmcon |= PIC32MK_NVMCON_WRERR;\n+ break;\n+ }\n+ memset(flash + offset, 0xFF, PIC32MK_NVM_PAGE_SIZE);\n+ memory_region_set_dirty(s->pflash_mr, offset, PIC32MK_NVM_PAGE_SIZE);\n+ break;\n+\n+ case PIC32MK_NVMOP_PFM_ERASE:\n+ memset(flash, 0xFF, PIC32MK_PFLASH_SIZE);\n+ memory_region_set_dirty(s->pflash_mr, 0, PIC32MK_PFLASH_SIZE);\n+ break;\n+\n+ case PIC32MK_NVMOP_LOWER_PFM_ERASE:\n+ case PIC32MK_NVMOP_UPPER_PFM_ERASE:\n+ qemu_log_mask(LOG_UNIMP,\n+ \"pic32mk_nvm: unimplemented NVMOP 0x%x\\n\", op);\n+ break;\n+\n+ default:\n+ qemu_log_mask(LOG_UNIMP,\n+ \"pic32mk_nvm: unknown NVMOP 0x%x\\n\", op);\n+ s->nvmcon |= PIC32MK_NVMCON_WRERR;\n+ break;\n+ }\n+\n+done:\n+ /* Operation complete — clear WR, reset unlock FSM */\n+ s->nvmcon &= ~PIC32MK_NVMCON_WR;\n+ s->nvmkey_state = NVMKEY_LOCKED;\n+\n+ /* Pulse IRQ to signal completion */\n+ qemu_irq_pulse(s->irq);\n+\n+ /* Flush to backing file after any successful write/erase */\n+ if (op != PIC32MK_NVMOP_NOP &&\n+ !(s->nvmcon & (PIC32MK_NVMCON_WRERR | PIC32MK_NVMCON_LVDERR))) {\n+ nvm_flush_backing(s);\n+ }\n+}\n+\n+/*\n+ * MMIO read\n+ * -----------------------------------------------------------------------\n+ */\n+\n+static uint64_t nvm_read(void *opaque, hwaddr addr, unsigned size)\n+{\n+ PIC32MKNVMState *s = opaque;\n+ hwaddr base = addr & ~(hwaddr)0xF;\n+\n+ switch (base) {\n+ case PIC32MK_NVMCON:\n+ return s->nvmcon;\n+ case PIC32MK_NVMKEY:\n+ return 0;\n+ /* write-only */;\n+ case PIC32MK_NVMADDR:\n+ return s->nvmaddr;\n+ case PIC32MK_NVMDATA0:\n+ return s->nvmdata[0];\n+ case PIC32MK_NVMDATA1:\n+ return s->nvmdata[1];\n+ case PIC32MK_NVMDATA2:\n+ return s->nvmdata[2];\n+ case PIC32MK_NVMDATA3:\n+ return s->nvmdata[3];\n+ case PIC32MK_NVMSRCADDR:\n+ return s->nvmsrcaddr;\n+ case PIC32MK_NVMPWP:\n+ return s->nvmpwp;\n+ case PIC32MK_NVMBWP:\n+ return s->nvmbwp;\n+ case PIC32MK_NVMCON2:\n+ return s->nvmcon2;\n+ default:\n+ qemu_log_mask(LOG_UNIMP,\n+ \"pic32mk_nvm: unimplemented read @ 0x%04\"\n+ HWADDR_PRIx \"\\n\", addr);\n+ return 0;\n+ }\n+}\n+\n+/*\n+ * MMIO write\n+ * -----------------------------------------------------------------------\n+ */\n+\n+static void nvm_write(void *opaque, hwaddr addr, uint64_t val, unsigned size)\n+{\n+ PIC32MKNVMState *s = opaque;\n+ int sub = (int)(addr & 0xF);\n+ hwaddr base = addr & ~(hwaddr)0xF;\n+ uint32_t old_nvmcon;\n+\n+ switch (base) {\n+ case PIC32MK_NVMCON:\n+ old_nvmcon = s->nvmcon;\n+ apply_sci(&s->nvmcon, (uint32_t)val, sub);\n+\n+ /* Detect WR 0→1 transition: execute command */\n+ if (!(old_nvmcon & PIC32MK_NVMCON_WR) &&\n+ (s->nvmcon & PIC32MK_NVMCON_WR)) {\n+ nvm_execute_cmd(s);\n+ }\n+ break;\n+\n+ case PIC32MK_NVMKEY:\n+ /*\n+ * Unlock FSM: firmware writes 0xAA996655 then 0x556699AA.\n+ * Any other value resets the FSM. SET/CLR/INV are not\n+ * meaningful for NVMKEY — always treat as direct write.\n+ */\n+ {\n+ uint32_t key = (uint32_t)val;\n+ if (s->nvmkey_state == NVMKEY_LOCKED &&\n+ key == PIC32MK_NVMKEY1) {\n+ s->nvmkey_state = NVMKEY_KEY1_OK;\n+ } else if (s->nvmkey_state == NVMKEY_KEY1_OK &&\n+ key == PIC32MK_NVMKEY2) {\n+ s->nvmkey_state = NVMKEY_UNLOCKED;\n+ } else {\n+ s->nvmkey_state = NVMKEY_LOCKED;\n+ }\n+ }\n+ break;\n+\n+ case PIC32MK_NVMADDR:\n+ apply_sci(&s->nvmaddr, (uint32_t)val, sub);\n+ break;\n+\n+ case PIC32MK_NVMDATA0:\n+ apply_sci(&s->nvmdata[0], (uint32_t)val, sub);\n+ break;\n+\n+ case PIC32MK_NVMDATA1:\n+ apply_sci(&s->nvmdata[1], (uint32_t)val, sub);\n+ break;\n+\n+ case PIC32MK_NVMDATA2:\n+ apply_sci(&s->nvmdata[2], (uint32_t)val, sub);\n+ break;\n+\n+ case PIC32MK_NVMDATA3:\n+ apply_sci(&s->nvmdata[3], (uint32_t)val, sub);\n+ break;\n+\n+ case PIC32MK_NVMSRCADDR:\n+ apply_sci(&s->nvmsrcaddr, (uint32_t)val, sub);\n+ break;\n+\n+ case PIC32MK_NVMPWP:\n+ apply_sci(&s->nvmpwp, (uint32_t)val, sub);\n+ break;\n+\n+ case PIC32MK_NVMBWP:\n+ apply_sci(&s->nvmbwp, (uint32_t)val, sub);\n+ break;\n+\n+ case PIC32MK_NVMCON2:\n+ apply_sci(&s->nvmcon2, (uint32_t)val, sub);\n+ break;\n+\n+ default:\n+ qemu_log_mask(LOG_UNIMP,\n+ \"pic32mk_nvm: unimplemented write @ 0x%04\"\n+ HWADDR_PRIx \" = 0x%08\" PRIx64 \"\\n\",\n+ addr, val);\n+ break;\n+ }\n+}\n+\n+static const MemoryRegionOps nvm_ops = {\n+ .read = nvm_read,\n+ .write = nvm_write,\n+ .endianness = DEVICE_LITTLE_ENDIAN,\n+ .valid = { .min_access_size = 4, .max_access_size = 4 },\n+};\n+\n+/*\n+ * Device lifecycle\n+ * -----------------------------------------------------------------------\n+ */\n+\n+static void pic32mk_nvm_reset(DeviceState *dev)\n+{\n+ PIC32MKNVMState *s = PIC32MK_NVM(dev);\n+\n+ s->nvmcon = 0;\n+ s->nvmaddr = 0;\n+ s->nvmdata[0] = 0;\n+ s->nvmdata[1] = 0;\n+ s->nvmdata[2] = 0;\n+ s->nvmdata[3] = 0;\n+ s->nvmsrcaddr = 0;\n+ s->nvmpwp = 0;\n+ s->nvmbwp = 0;\n+ s->nvmcon2 = 0;\n+ s->nvmkey_state = NVMKEY_LOCKED;\n+\n+ qemu_irq_lower(s->irq);\n+}\n+\n+static void pic32mk_nvm_realize(DeviceState *dev, Error **errp)\n+{\n+ PIC32MKNVMState *s = PIC32MK_NVM(dev);\n+\n+ if (!s->pflash_mr) {\n+ error_setg(errp, \"pic32mk_nvm: 'pflash' link property not set\");\n+ return;\n+ }\n+\n+ s->backing_fd = -1;\n+\n+ if (s->filename && s->filename[0] != '\\0') {\n+ s->backing_fd = open(s->filename, O_RDWR | O_CREAT, 0644);\n+ if (s->backing_fd < 0) {\n+ error_setg_errno(errp, errno,\n+ \"pic32mk_nvm: cannot open '%s'\",\n+ s->filename);\n+ return;\n+ }\n+\n+ /*\n+ * If the file is new or undersized, initialize it with 0xFF\n+ * (erased flash state).\n+ */\n+ off_t fsize = lseek(s->backing_fd, 0, SEEK_END);\n+ if (fsize < (off_t)PIC32MK_PFLASH_SIZE) {\n+ /* Extend with 0xFF in chunks */\n+ uint8_t ff_buf[4096];\n+ memset(ff_buf, 0xFF, sizeof(ff_buf));\n+ lseek(s->backing_fd, (fsize > 0) ? fsize : 0, SEEK_SET);\n+ size_t remaining = PIC32MK_PFLASH_SIZE - (size_t)((fsize > 0) ? fsize : 0);\n+ while (remaining > 0) {\n+ size_t chunk = (remaining < sizeof(ff_buf)) ? remaining : sizeof(ff_buf);\n+ if (write(s->backing_fd, ff_buf, chunk) < 0) {\n+ qemu_log_mask(LOG_GUEST_ERROR,\n+ \"pic32mk_nvm: cannot init '%s'\\n\",\n+ s->filename);\n+ break;\n+ }\n+ remaining -= chunk;\n+ }\n+ fdatasync(s->backing_fd);\n+ }\n+\n+ /* Load backing file into pflash RAM */\n+ nvm_load_backing(s);\n+ }\n+}\n+\n+static void pic32mk_nvm_unrealize(DeviceState *dev)\n+{\n+ PIC32MKNVMState *s = PIC32MK_NVM(dev);\n+\n+ nvm_flush_backing(s);\n+\n+ if (s->backing_fd >= 0) {\n+ close(s->backing_fd);\n+ s->backing_fd = -1;\n+ }\n+}\n+\n+static void pic32mk_nvm_init(Object *obj)\n+{\n+ PIC32MKNVMState *s = PIC32MK_NVM(obj);\n+\n+ memory_region_init_io(&s->mr, obj, &nvm_ops, s,\n+ TYPE_PIC32MK_NVM, PIC32MK_NVM_SIZE);\n+ sysbus_init_mmio(SYS_BUS_DEVICE(obj), &s->mr);\n+ sysbus_init_irq(SYS_BUS_DEVICE(obj), &s->irq);\n+}\n+\n+static const Property pic32mk_nvm_props[] = {\n+ DEFINE_PROP_LINK(\"pflash\", PIC32MKNVMState, pflash_mr,\n+ TYPE_MEMORY_REGION, MemoryRegion *),\n+ DEFINE_PROP_STRING(\"filename\", PIC32MKNVMState, filename),\n+};\n+\n+static void pic32mk_nvm_class_init(ObjectClass *klass, const void *data)\n+{\n+ DeviceClass *dc = DEVICE_CLASS(klass);\n+\n+ dc->realize = pic32mk_nvm_realize;\n+ dc->unrealize = pic32mk_nvm_unrealize;\n+ device_class_set_legacy_reset(dc, pic32mk_nvm_reset);\n+ device_class_set_props(dc, pic32mk_nvm_props);\n+}\n+\n+static const TypeInfo pic32mk_nvm_info = {\n+ .name = TYPE_PIC32MK_NVM,\n+ .parent = TYPE_SYS_BUS_DEVICE,\n+ .instance_size = sizeof(PIC32MKNVMState),\n+ .instance_init = pic32mk_nvm_init,\n+ .class_init = pic32mk_nvm_class_init,\n+};\n+\n+static void pic32mk_nvm_register_types(void)\n+{\n+ type_register_static(&pic32mk_nvm_info);\n+}\n+\n+type_init(pic32mk_nvm_register_types)\ndiff --git a/hw/mips/pic32mk_oc.c b/hw/mips/pic32mk_oc.c\nnew file mode 100644\nindex 0000000000..df47cba59f\n--- /dev/null\n+++ b/hw/mips/pic32mk_oc.c\n@@ -0,0 +1,293 @@\n+/*\n+ * PIC32MK Output Compare × 16 (OC1–OC16)\n+ * Datasheet: DS60001519E, §19\n+ *\n+ * Diagnostic-only emulation: when firmware enables an OC instance\n+ * (OCxCON.ON goes 0→1), the emulator emits a 12-byte binary event\n+ * to an optional chardev (\"oc-events\") for GUI waveform rendering,\n+ * and also writes a human-readable fallback to qemu_log().\n+ *\n+ * Event message format (16 bytes, little-endian):\n+ * [0] index — OC instance number (1–16)\n+ * [1] enabled — 1 = enabled, 0 = disabled\n+ * [2] ocm — OCM mode bits [2:0]\n+ * [3] flags — bit0=OC32, bit1=OCTSEL\n+ * [4..7] ocr — OCxR (primary compare), uint32 LE\n+ * [8..11] ocrs — OCxRS (secondary compare), uint32 LE\n+ * [12..15] pr — timer PRx period register, uint32 LE (0 on disable)\n+ *\n+ * Consumer duty/pulse-width formula by mode:\n+ * PWM (ocm=6,7): pulse_pct = ocrs / pr * 100\n+ * Single/continuous (ocm=4,5): pulse_pct = (ocrs - ocr) / pr * 100\n+ *\n+ * Register layout within each 0x200-byte block\n+ * (registers have SET/CLR/INV sub-regs at +4/+8/+C):\n+ * OCxCON +0x00 Control: ON, SIDL, OC32, OCFLT, OCTSEL, OCM\n+ * OCxR +0x10 Primary compare value\n+ * OCxRS +0x20 Secondary compare value\n+ *\n+ * Copyright (c) 2026 QEMU contributors\n+ * SPDX-License-Identifier: GPL-2.0-or-later\n+ */\n+\n+#include \"qemu/osdep.h\"\n+#include \"qemu/log.h\"\n+#include \"qemu/module.h\"\n+#include \"hw/core/sysbus.h\"\n+#include \"hw/core/qdev-properties.h\"\n+#include \"hw/mips/pic32mk.h\"\n+#include \"chardev/char.h\"\n+#include \"system/address-spaces.h\"\n+\n+/* OCM mode decode table (bits 2:0 of OCxCON) */\n+static const char *ocm_mode_names[] = {\n+ [0] = \"Disabled\",\n+ [1] = \"High on match, then low\",\n+ [2] = \"Init low, force high\",\n+ [3] = \"Toggle on match\",\n+ [4] = \"Single pulse\",\n+ [5] = \"Continuous pulses\",\n+ [6] = \"PWM (no fault)\",\n+ [7] = \"PWM (fault enabled)\",\n+};\n+\n+/*\n+ * Timer PRx KSEG1 addresses for OCTSEL=0 (Timer2) and OCTSEL=1 (Timer3).\n+ * OC1-OC9 use the peripheral-1 timer block (SFR base 0xBF820000).\n+ * T2 base = 0xBF820200, PR2 = T2 base + 0x20 = 0xBF820220\n+ * T3 base = 0xBF820400, PR3 = T3 base + 0x20 = 0xBF820420\n+ * TODO: OC10-OC16 use peripheral-2 block timers (T4-T9) — extend table if needed.\n+ */\n+static const uint32_t oc_timer_pr_addr[2] = {\n+ 0x1F820220u, /* OCTSEL=0 → Timer2 PRx (physical: SFR_BASE + T2_OFFSET + 0x20) */\n+ 0x1F820420u, /* OCTSEL=1 → Timer3 PRx (physical: SFR_BASE + T3_OFFSET + 0x20) */\n+};\n+\n+/*\n+ * Device state\n+ * -----------------------------------------------------------------------\n+ */\n+\n+#define TYPE_PIC32MK_OC \"pic32mk-oc\"\n+OBJECT_DECLARE_SIMPLE_TYPE(PIC32MKOCState, PIC32MK_OC)\n+\n+struct PIC32MKOCState {\n+ SysBusDevice parent_obj;\n+ MemoryRegion mr;\n+\n+ uint32_t con; /* OCxCON */\n+ uint32_t r; /* OCxR */\n+ uint32_t rs; /* OCxRS */\n+\n+ qemu_irq irq;\n+ uint8_t index; /* 1-16 */\n+ Chardev *chr_out; /* optional chardev for waveform event streaming */\n+};\n+\n+/*\n+ * MMIO helpers\n+ * -----------------------------------------------------------------------\n+ */\n+\n+/* PIC32MK: base+0=REG, +4=CLR, +8=SET, +0xC=INV */\n+static void apply_sci(uint32_t *reg, uint32_t val, int sub)\n+{\n+ switch (sub) {\n+ case 0:\n+ *reg = val;\n+ break;\n+ case 4:\n+ *reg &= ~val;\n+ break;\n+ case 8:\n+ *reg |= val;\n+ break;\n+ case 12:\n+ *reg ^= val;\n+ break;\n+ }\n+}\n+\n+static uint32_t *oc_find_reg(PIC32MKOCState *s, hwaddr base)\n+{\n+ switch (base) {\n+ case PIC32MK_OCxCON:\n+ return &s->con;\n+ case PIC32MK_OCxR:\n+ return &s->r;\n+ case PIC32MK_OCxRS:\n+ return &s->rs;\n+ default:\n+ return NULL;\n+ }\n+}\n+\n+/*\n+ * Timer PRx read — needed for duty-cycle in event messages\n+ * -----------------------------------------------------------------------\n+ */\n+\n+static uint32_t oc_read_pr(PIC32MKOCState *s)\n+{\n+ int sel = !!(s->con & PIC32MK_OCCON_OCTSEL);\n+ MemTxResult res;\n+ return address_space_ldl_le(&address_space_memory,\n+ oc_timer_pr_addr[sel],\n+ MEMTXATTRS_UNSPECIFIED, &res);\n+}\n+\n+/*\n+ * Event emission — qemu_log fallback + optional chardev binary stream\n+ * -----------------------------------------------------------------------\n+ */\n+\n+static void oc_emit_event(PIC32MKOCState *s, bool enabled)\n+{\n+ uint32_t ocm = (s->con & PIC32MK_OCCON_OCM_MASK) >> PIC32MK_OCCON_OCM_SHIFT;\n+ bool oc32 = !!(s->con & PIC32MK_OCCON_OC32);\n+ bool octsel = !!(s->con & PIC32MK_OCCON_OCTSEL);\n+\n+ /* Human-readable fallback always goes to qemu_log */\n+ if (enabled) {\n+ qemu_log(\"pic32mk_oc: OC%u enabled — mode=%s, OC32=%u, OCTSEL=%u, \"\n+ \"OCxR=0x%08X, OCxRS=0x%08X\\n\",\n+ s->index, ocm_mode_names[ocm], oc32, octsel, s->r, s->rs);\n+ } else {\n+ qemu_log(\"pic32mk_oc: OC%u disabled\\n\", s->index);\n+ }\n+\n+ if (!s->chr_out) {\n+ return;\n+ }\n+\n+ uint32_t pr = enabled ? oc_read_pr(s) : 0;\n+\n+ uint8_t msg[16];\n+ uint32_t ocr_le = cpu_to_le32(s->r);\n+ uint32_t ocrs_le = cpu_to_le32(s->rs);\n+ uint32_t pr_le = cpu_to_le32(pr);\n+\n+ msg[0] = s->index;\n+ msg[1] = enabled ? 1 : 0;\n+ msg[2] = (uint8_t)ocm;\n+ msg[3] = (uint8_t)(oc32 | (octsel << 1));\n+ memcpy(&msg[4], &ocr_le, 4);\n+ memcpy(&msg[8], &ocrs_le, 4);\n+ memcpy(&msg[12], &pr_le, 4);\n+ qemu_chr_write_all(s->chr_out, msg, sizeof(msg));\n+}\n+\n+/* Public setter called by pic32mk.c after device realisation. */\n+void pic32mk_oc_set_chardev(DeviceState *dev, Chardev *chr)\n+{\n+ PIC32MK_OC(dev)->chr_out = chr;\n+}\n+\n+/*\n+ * MMIO read/write\n+ * -----------------------------------------------------------------------\n+ */\n+\n+static uint64_t oc_read(void *opaque, hwaddr addr, unsigned size)\n+{\n+ PIC32MKOCState *s = opaque;\n+ hwaddr base = addr & ~(hwaddr)0xF;\n+ uint32_t *reg = oc_find_reg(s, base);\n+\n+ if (reg) {\n+ return *reg;\n+ }\n+\n+ qemu_log_mask(LOG_UNIMP,\n+ \"pic32mk_oc: OC%u unimplemented read @ 0x%04\" HWADDR_PRIx \"\\n\",\n+ s->index, addr);\n+ return 0;\n+}\n+\n+static void oc_write(void *opaque, hwaddr addr, uint64_t val, unsigned size)\n+{\n+ PIC32MKOCState *s = opaque;\n+ int sub = (int)(addr & 0xF);\n+ hwaddr base = addr & ~(hwaddr)0xF;\n+ uint32_t *reg = oc_find_reg(s, base);\n+\n+ if (!reg) {\n+ qemu_log_mask(LOG_UNIMP,\n+ \"pic32mk_oc: OC%u unimplemented write @ 0x%04\"\n+ HWADDR_PRIx \" = 0x%08\" PRIx64 \"\\n\",\n+ s->index, addr, val);\n+ return;\n+ }\n+\n+ bool was_on = !!(s->con & PIC32MK_OCCON_ON);\n+ apply_sci(reg, (uint32_t)val, sub);\n+\n+ /* Handle ON bit transitions for OCxCON writes */\n+ if (base == PIC32MK_OCxCON) {\n+ bool now_on = !!(s->con & PIC32MK_OCCON_ON);\n+ if (!was_on && now_on) {\n+ oc_emit_event(s, true);\n+ } else if (was_on && !now_on) {\n+ oc_emit_event(s, false);\n+ }\n+ }\n+}\n+\n+static const MemoryRegionOps oc_ops = {\n+ .read = oc_read,\n+ .write = oc_write,\n+ .endianness = DEVICE_LITTLE_ENDIAN,\n+ .valid = {\n+ .min_access_size = 4,\n+ .max_access_size = 4,\n+ },\n+};\n+\n+/*\n+ * Device lifecycle\n+ * -----------------------------------------------------------------------\n+ */\n+\n+static void pic32mk_oc_reset(DeviceState *dev)\n+{\n+ PIC32MKOCState *s = PIC32MK_OC(dev);\n+ s->con = 0;\n+ s->r = 0;\n+ s->rs = 0;\n+}\n+\n+static void pic32mk_oc_init(Object *obj)\n+{\n+ PIC32MKOCState *s = PIC32MK_OC(obj);\n+\n+ memory_region_init_io(&s->mr, obj, &oc_ops, s,\n+ TYPE_PIC32MK_OC, PIC32MK_OC_BLOCK_SIZE);\n+ sysbus_init_mmio(SYS_BUS_DEVICE(obj), &s->mr);\n+ sysbus_init_irq(SYS_BUS_DEVICE(obj), &s->irq);\n+}\n+\n+static Property pic32mk_oc_properties[] = {\n+ DEFINE_PROP_UINT8(\"index\", PIC32MKOCState, index, 1),\n+};\n+\n+static void pic32mk_oc_class_init(ObjectClass *klass, const void *data)\n+{\n+ DeviceClass *dc = DEVICE_CLASS(klass);\n+ device_class_set_legacy_reset(dc, pic32mk_oc_reset);\n+ device_class_set_props(dc, pic32mk_oc_properties);\n+}\n+\n+static const TypeInfo pic32mk_oc_info = {\n+ .name = TYPE_PIC32MK_OC,\n+ .parent = TYPE_SYS_BUS_DEVICE,\n+ .instance_size = sizeof(PIC32MKOCState),\n+ .instance_init = pic32mk_oc_init,\n+ .class_init = pic32mk_oc_class_init,\n+};\n+\n+static void pic32mk_oc_register_types(void)\n+{\n+ type_register_static(&pic32mk_oc_info);\n+}\n+\n+type_init(pic32mk_oc_register_types)\ndiff --git a/hw/mips/pic32mk_spi.c b/hw/mips/pic32mk_spi.c\nnew file mode 100644\nindex 0000000000..339a790a58\n--- /dev/null\n+++ b/hw/mips/pic32mk_spi.c\n@@ -0,0 +1,532 @@\n+/*\n+ * PIC32MK SPI × 6 (SPI1–SPI6)\n+ * Datasheet: DS60001519E, §23\n+ *\n+ * Each SPI instance is a SysBusDevice providing a stub register file.\n+ * Full transfer emulation (loopback, slave select, IRQs) is Phase 2B.\n+ * Firmware doing register-level init (SPIxCON, SPIxBRG) will succeed;\n+ * actual data transfers log LOG_UNIMP.\n+ *\n+ * Copyright (c) 2026 QEMU contributors\n+ * SPDX-License-Identifier: GPL-2.0-or-later\n+ */\n+\n+#include \"qemu/osdep.h\"\n+#include \"qemu/log.h\"\n+#include \"qemu/module.h\"\n+#include \"hw/core/sysbus.h\"\n+#include \"hw/core/irq.h\"\n+#include \"hw/core/qdev-properties.h\"\n+#include \"hw/core/qdev-properties-system.h\"\n+#include \"chardev/char-fe.h\"\n+#include \"hw/mips/pic32mk.h\"\n+\n+#define TYPE_PIC32MK_SPI \"pic32mk-spi\"\n+OBJECT_DECLARE_SIMPLE_TYPE(PIC32MKSpiState, PIC32MK_SPI)\n+\n+struct PIC32MKSpiState {\n+ SysBusDevice parent_obj;\n+ MemoryRegion mr;\n+\n+ uint32_t con; /* SPIxCON */\n+ uint32_t stat; /* SPIxSTAT */\n+ uint32_t buf; /* SPIxBUF — TX write / RX read */\n+ uint32_t brg; /* SPIxBRG */\n+ uint32_t con2; /* SPIxCON2 */\n+\n+ uint8_t index; /* SPI instance index (1..6) */\n+\n+ uint8_t tx_fifo[8];\n+ uint8_t rx_fifo[8];\n+ uint8_t tx_head;\n+ uint8_t tx_tail;\n+ uint8_t tx_count;\n+ uint8_t rx_head;\n+ uint8_t rx_tail;\n+ uint8_t rx_count;\n+\n+ uint8_t frame_buf[260];\n+ uint16_t frame_pos;\n+ uint16_t frame_len;\n+\n+ bool cs_active;\n+\n+ CharFrontend chr;\n+\n+ qemu_irq irq_rx; /* RX interrupt */\n+ qemu_irq irq_tx; /* TX interrupt */\n+ qemu_irq irq_err; /* Fault/overflow */\n+};\n+\n+/* CON bits (subset used by emulation + Harmony plib) */\n+#define SPI_CON_ON (1u << 15)\n+#define SPI_CON_MSTEN (1u << 5)\n+#define SPI_CON_MODE16 (1u << 10)\n+#define SPI_CON_MODE32 (1u << 11)\n+#define SPI_CON_ENHBUF (1u << 16)\n+#define SPI_CON_SRXISEL_MASK (3u << 0)\n+#define SPI_CON_STXISEL_MASK (3u << 2)\n+#define SPI_CON_SRXISEL_SHIFT 0\n+#define SPI_CON_STXISEL_SHIFT 2\n+\n+/* STAT bits (subset used by emulation + Harmony plib) */\n+#define SPI_STAT_SPIRBF (1u << 0) /* RX buffer full */\n+#define SPI_STAT_SPITBF (1u << 1) /* TX buffer full */\n+#define SPI_STAT_SPITBE (1u << 3) /* TX buffer empty */\n+#define SPI_STAT_SPIRBE (1u << 5) /* RX buffer empty */\n+#define SPI_STAT_SPIROV (1u << 6) /* RX overflow */\n+#define SPI_STAT_SRMT (1u << 11) /* Shift register empty */\n+\n+/* Chardev frame flags */\n+#define SPI_FRAME_CS_ASSERT (1u << 0)\n+#define SPI_FRAME_CS_DEASSERT (1u << 1)\n+\n+/* PIC32MK: base+0=REG, +4=CLR, +8=SET, +0xC=INV */\n+static void apply_sci(uint32_t *reg, uint32_t val, int sub)\n+{\n+ switch (sub) {\n+ case 0:\n+ *reg = val;\n+ break;\n+ case 4:\n+ *reg &= ~val;\n+ break;\n+ case 8:\n+ *reg |= val;\n+ break;\n+ case 12:\n+ *reg ^= val;\n+ break;\n+ }\n+}\n+\n+static int spi_fifo_depth(PIC32MKSpiState *s)\n+{\n+ return (s->con & SPI_CON_ENHBUF) ? 8 : 1;\n+}\n+\n+static void spi_update_stat(PIC32MKSpiState *s)\n+{\n+ const int depth = spi_fifo_depth(s);\n+\n+ if (s->rx_count > 0) {\n+ s->stat |= SPI_STAT_SPIRBF;\n+ s->stat &= ~SPI_STAT_SPIRBE;\n+ } else {\n+ s->stat &= ~SPI_STAT_SPIRBF;\n+ s->stat |= SPI_STAT_SPIRBE;\n+ }\n+\n+ if (s->tx_count == 0) {\n+ s->stat |= SPI_STAT_SPITBE;\n+ s->stat &= ~SPI_STAT_SPITBF;\n+ } else if (s->tx_count >= depth) {\n+ s->stat |= SPI_STAT_SPITBF;\n+ s->stat &= ~SPI_STAT_SPITBE;\n+ } else {\n+ s->stat &= ~SPI_STAT_SPITBF;\n+ s->stat &= ~SPI_STAT_SPITBE;\n+ }\n+\n+ if (s->tx_count == 0) {\n+ s->stat |= SPI_STAT_SRMT;\n+ } else {\n+ s->stat &= ~SPI_STAT_SRMT;\n+ }\n+}\n+\n+static void spi_update_irq(PIC32MKSpiState *s)\n+{\n+ if (!(s->con & SPI_CON_ON)) {\n+ qemu_set_irq(s->irq_rx, 0);\n+ qemu_set_irq(s->irq_tx, 0);\n+ qemu_set_irq(s->irq_err, 0);\n+ return;\n+ }\n+\n+ /* SRXISEL: 0=empty, 1=not-empty, 2=half-full, 3=full */\n+ int srxisel = (s->con & SPI_CON_SRXISEL_MASK) >> SPI_CON_SRXISEL_SHIFT;\n+ const int depth = spi_fifo_depth(s);\n+ bool rx_pending;\n+ switch (srxisel) {\n+ case 0:\n+ rx_pending = (s->rx_count == 0);\n+ break;\n+ case 1:\n+ rx_pending = (s->rx_count > 0);\n+ break;\n+ case 2:\n+ rx_pending = (s->rx_count >= depth / 2);\n+ break;\n+ case 3:\n+ rx_pending = (s->rx_count >= depth);\n+ break;\n+ default:\n+ rx_pending = false;\n+ break;\n+ }\n+\n+ /* STXISEL: 0=empty, 1=empty, 2=half-empty, 3=not-full */\n+ int stxisel = (s->con & SPI_CON_STXISEL_MASK) >> SPI_CON_STXISEL_SHIFT;\n+ bool tx_pending;\n+ switch (stxisel) {\n+ case 0:\n+ tx_pending = (s->tx_count == 0);\n+ break;\n+ case 1:\n+ tx_pending = (s->tx_count == 0);\n+ break;\n+ case 2:\n+ tx_pending = (s->tx_count <= depth / 2);\n+ break;\n+ case 3:\n+ tx_pending = (s->tx_count < depth);\n+ break;\n+ default:\n+ tx_pending = false;\n+ break;\n+ }\n+\n+ bool err_pending = (s->stat & SPI_STAT_SPIROV) != 0;\n+\n+ qemu_set_irq(s->irq_rx, rx_pending ? 1 : 0);\n+ qemu_set_irq(s->irq_tx, tx_pending ? 1 : 0);\n+ qemu_set_irq(s->irq_err, err_pending ? 1 : 0);\n+}\n+\n+static void spi_rx_push(PIC32MKSpiState *s, uint8_t val)\n+{\n+ const int depth = spi_fifo_depth(s);\n+ if (s->rx_count >= depth) {\n+ s->stat |= SPI_STAT_SPIROV;\n+ spi_update_irq(s);\n+ return;\n+ }\n+\n+ s->rx_fifo[s->rx_head] = val;\n+ s->rx_head = (uint8_t)((s->rx_head + 1) % depth);\n+ s->rx_count++;\n+}\n+\n+static bool spi_rx_pop(PIC32MKSpiState *s, uint8_t *val)\n+{\n+ const int depth = spi_fifo_depth(s);\n+ if (s->rx_count == 0) {\n+ *val = 0;\n+ return false;\n+ }\n+\n+ *val = s->rx_fifo[s->rx_tail];\n+ s->rx_tail = (uint8_t)((s->rx_tail + 1) % depth);\n+ s->rx_count--;\n+ return true;\n+}\n+\n+static void spi_tx_push(PIC32MKSpiState *s, uint8_t val)\n+{\n+ const int depth = spi_fifo_depth(s);\n+ if (s->tx_count >= depth) {\n+ return;\n+ }\n+\n+ s->tx_fifo[s->tx_head] = val;\n+ s->tx_head = (uint8_t)((s->tx_head + 1) % depth);\n+ s->tx_count++;\n+}\n+\n+static bool spi_tx_pop(PIC32MKSpiState *s, uint8_t *val)\n+{\n+ const int depth = spi_fifo_depth(s);\n+ if (s->tx_count == 0) {\n+ *val = 0xFFu;\n+ return false;\n+ }\n+\n+ *val = s->tx_fifo[s->tx_tail];\n+ s->tx_tail = (uint8_t)((s->tx_tail + 1) % depth);\n+ s->tx_count--;\n+ return true;\n+}\n+\n+static void spi_send_frame(PIC32MKSpiState *s, uint8_t flags,\n+ const uint8_t *payload, uint8_t len)\n+{\n+ uint8_t hdr[2];\n+\n+ if (!qemu_chr_fe_backend_connected(&s->chr)) {\n+ return;\n+ }\n+\n+ hdr[0] = flags;\n+ hdr[1] = len;\n+ qemu_chr_fe_write_all(&s->chr, hdr, 2);\n+ if (len) {\n+ qemu_chr_fe_write_all(&s->chr, payload, len);\n+ }\n+}\n+\n+static void spi_handle_frame(PIC32MKSpiState *s, uint8_t flags,\n+ const uint8_t *payload, uint8_t len)\n+{\n+ uint8_t response[255];\n+ uint8_t i;\n+\n+ if (flags & SPI_FRAME_CS_ASSERT) {\n+ s->cs_active = true;\n+ }\n+\n+ for (i = 0; i < len; i++) {\n+ spi_rx_push(s, payload[i]);\n+ (void)spi_tx_pop(s, &response[i]);\n+ }\n+\n+ if (len) {\n+ spi_send_frame(s, 0, response, len);\n+ }\n+\n+ if (flags & SPI_FRAME_CS_DEASSERT) {\n+ s->cs_active = false;\n+ }\n+\n+ spi_update_stat(s);\n+ spi_update_irq(s);\n+}\n+\n+static int spi_chr_can_receive(void *opaque)\n+{\n+ PIC32MKSpiState *s = opaque;\n+ return qemu_chr_fe_backend_connected(&s->chr) ? 1 : 0;\n+}\n+\n+static void spi_chr_receive(void *opaque, const uint8_t *buf, int size)\n+{\n+ PIC32MKSpiState *s = opaque;\n+\n+ for (int i = 0; i < size; i++) {\n+ s->frame_buf[s->frame_pos++] = buf[i];\n+ if (s->frame_pos == 2) {\n+ s->frame_len = (uint16_t)s->frame_buf[1];\n+ }\n+ if (s->frame_pos >= 2 && s->frame_pos == (uint16_t)(2 + s->frame_len)) {\n+ uint8_t flags = s->frame_buf[0];\n+ uint8_t len = (uint8_t)s->frame_len;\n+ spi_handle_frame(s, flags, &s->frame_buf[2], len);\n+ s->frame_pos = 0;\n+ s->frame_len = 0;\n+ }\n+ }\n+}\n+\n+static void spi_chr_event(void *opaque, QEMUChrEvent event)\n+{\n+ PIC32MKSpiState *s = opaque;\n+\n+ if (event == CHR_EVENT_CLOSED) {\n+ s->frame_pos = 0;\n+ s->frame_len = 0;\n+ }\n+}\n+\n+static uint32_t *spi_find_reg(PIC32MKSpiState *s, hwaddr base)\n+{\n+ switch (base) {\n+ case PIC32MK_SPIxCON:\n+ return &s->con;\n+ case PIC32MK_SPIxSTAT:\n+ return &s->stat;\n+ case PIC32MK_SPIxBRG:\n+ return &s->brg;\n+ case PIC32MK_SPIxCON2:\n+ return &s->con2;\n+ default:\n+ return NULL;\n+ }\n+}\n+\n+static uint64_t spi_read(void *opaque, hwaddr addr, unsigned size)\n+{\n+ PIC32MKSpiState *s = opaque;\n+ hwaddr base = addr & ~(hwaddr)0xF;\n+\n+ /* BUF read: pop from RX FIFO */\n+ if (base == PIC32MK_SPIxBUF) {\n+ uint32_t val = 0;\n+ uint8_t b0 = 0;\n+ uint8_t b1 = 0;\n+ uint8_t b2 = 0;\n+ uint8_t b3 = 0;\n+\n+ if (s->con & SPI_CON_MODE32) {\n+ spi_rx_pop(s, &b0);\n+ spi_rx_pop(s, &b1);\n+ spi_rx_pop(s, &b2);\n+ spi_rx_pop(s, &b3);\n+ val = (uint32_t)b0 | ((uint32_t)b1 << 8) |\n+ ((uint32_t)b2 << 16) | ((uint32_t)b3 << 24);\n+ } else if (s->con & SPI_CON_MODE16) {\n+ spi_rx_pop(s, &b0);\n+ spi_rx_pop(s, &b1);\n+ val = (uint32_t)b0 | ((uint32_t)b1 << 8);\n+ } else {\n+ spi_rx_pop(s, &b0);\n+ val = b0;\n+ }\n+\n+ spi_update_stat(s);\n+ spi_update_irq(s);\n+ return val;\n+ }\n+\n+ uint32_t *reg = spi_find_reg(s, base);\n+ if (reg) {\n+ if (base == PIC32MK_SPIxSTAT) {\n+ spi_update_stat(s);\n+ spi_update_irq(s);\n+ }\n+ return *reg;\n+ }\n+\n+ qemu_log_mask(LOG_UNIMP,\n+ \"pic32mk_spi: unimplemented read @ 0x%04\" HWADDR_PRIx \"\\n\",\n+ addr);\n+ return 0;\n+}\n+\n+static void spi_write(void *opaque, hwaddr addr, uint64_t val, unsigned size)\n+{\n+ PIC32MKSpiState *s = opaque;\n+ int sub = (int)(addr & 0xF);\n+ hwaddr base = addr & ~(hwaddr)0xF;\n+\n+ /* BUF write: transmit */\n+ if (base == PIC32MK_SPIxBUF) {\n+ uint8_t bytes[4];\n+ int count = 1;\n+\n+ bytes[0] = (uint8_t)val;\n+ bytes[1] = (uint8_t)(val >> 8);\n+ bytes[2] = (uint8_t)(val >> 16);\n+ bytes[3] = (uint8_t)(val >> 24);\n+\n+ if (s->con & SPI_CON_MODE32) {\n+ count = 4;\n+ } else if (s->con & SPI_CON_MODE16) {\n+ count = 2;\n+ }\n+\n+ for (int i = 0; i < count; i++) {\n+ spi_tx_push(s, bytes[i]);\n+\n+ if (s->con & SPI_CON_MSTEN) {\n+ if (qemu_chr_fe_backend_connected(&s->chr)) {\n+ uint8_t flags = SPI_FRAME_CS_ASSERT | SPI_FRAME_CS_DEASSERT;\n+ spi_send_frame(s, flags, &bytes[i], 1);\n+ } else {\n+ spi_rx_push(s, bytes[i]);\n+ }\n+ spi_tx_pop(s, &bytes[i]);\n+ }\n+ }\n+\n+ spi_update_stat(s);\n+ spi_update_irq(s);\n+ return;\n+ }\n+\n+ uint32_t *reg = spi_find_reg(s, base);\n+ if (reg) {\n+ apply_sci(reg, (uint32_t)val, sub);\n+ if (base == PIC32MK_SPIxCON && !(s->con & SPI_CON_ON)) {\n+ s->tx_head = s->tx_tail = s->tx_count = 0;\n+ s->rx_head = s->rx_tail = s->rx_count = 0;\n+ s->stat = SPI_STAT_SPITBE | SPI_STAT_SPIRBE | SPI_STAT_SRMT;\n+ }\n+ spi_update_stat(s);\n+ spi_update_irq(s);\n+ return;\n+ }\n+\n+ qemu_log_mask(LOG_UNIMP,\n+ \"pic32mk_spi: unimplemented write @ 0x%04\"\n+ HWADDR_PRIx \" = 0x%08\" PRIx64 \"\\n\",\n+ addr, val);\n+}\n+\n+static const MemoryRegionOps spi_ops = {\n+ .read = spi_read,\n+ .write = spi_write,\n+ .endianness = DEVICE_LITTLE_ENDIAN,\n+ .valid = { .min_access_size = 4, .max_access_size = 4 },\n+};\n+\n+static void pic32mk_spi_reset(DeviceState *dev)\n+{\n+ PIC32MKSpiState *s = PIC32MK_SPI(dev);\n+ s->con = 0;\n+ s->stat = SPI_STAT_SPITBE | SPI_STAT_SPIRBE | SPI_STAT_SRMT;\n+ s->buf = 0;\n+ s->brg = 0;\n+ s->con2 = 0;\n+ s->tx_head = s->tx_tail = s->tx_count = 0;\n+ s->rx_head = s->rx_tail = s->rx_count = 0;\n+ s->frame_pos = 0;\n+ s->frame_len = 0;\n+ s->cs_active = false;\n+ qemu_irq_lower(s->irq_rx);\n+ qemu_irq_lower(s->irq_tx);\n+ qemu_irq_lower(s->irq_err);\n+}\n+\n+static void pic32mk_spi_init(Object *obj)\n+{\n+ PIC32MKSpiState *s = PIC32MK_SPI(obj);\n+\n+ memory_region_init_io(&s->mr, obj, &spi_ops, s,\n+ TYPE_PIC32MK_SPI, PIC32MK_SPI_BLOCK_SIZE);\n+ sysbus_init_mmio(SYS_BUS_DEVICE(obj), &s->mr);\n+ sysbus_init_irq(SYS_BUS_DEVICE(obj), &s->irq_rx);\n+ sysbus_init_irq(SYS_BUS_DEVICE(obj), &s->irq_tx);\n+ sysbus_init_irq(SYS_BUS_DEVICE(obj), &s->irq_err);\n+}\n+\n+static void pic32mk_spi_realize(DeviceState *dev, Error **errp)\n+{\n+ PIC32MKSpiState *s = PIC32MK_SPI(dev);\n+\n+ qemu_chr_fe_set_handlers(&s->chr,\n+ spi_chr_can_receive,\n+ spi_chr_receive,\n+ spi_chr_event,\n+ NULL,\n+ s,\n+ NULL,\n+ true);\n+}\n+\n+static const Property pic32mk_spi_props[] = {\n+ DEFINE_PROP_UINT8(\"spi-index\", PIC32MKSpiState, index, 0),\n+ DEFINE_PROP_CHR(\"chardev\", PIC32MKSpiState, chr),\n+};\n+\n+static void pic32mk_spi_class_init(ObjectClass *klass, const void *data)\n+{\n+ DeviceClass *dc = DEVICE_CLASS(klass);\n+ device_class_set_legacy_reset(dc, pic32mk_spi_reset);\n+ device_class_set_props(dc, pic32mk_spi_props);\n+ dc->realize = pic32mk_spi_realize;\n+}\n+\n+static const TypeInfo pic32mk_spi_info = {\n+ .name = TYPE_PIC32MK_SPI,\n+ .parent = TYPE_SYS_BUS_DEVICE,\n+ .instance_size = sizeof(PIC32MKSpiState),\n+ .instance_init = pic32mk_spi_init,\n+ .class_init = pic32mk_spi_class_init,\n+};\n+\n+static void pic32mk_spi_register_types(void)\n+{\n+ type_register_static(&pic32mk_spi_info);\n+}\n+\n+type_init(pic32mk_spi_register_types)\ndiff --git a/hw/mips/pic32mk_timer.c b/hw/mips/pic32mk_timer.c\nnew file mode 100644\nindex 0000000000..d53d647d33\n--- /dev/null\n+++ b/hw/mips/pic32mk_timer.c\n@@ -0,0 +1,294 @@\n+/*\n+ * PIC32MK General Purpose Timers × 9 (T1–T9)\n+ * Datasheet: DS60001519E, §14\n+ *\n+ * Timer types:\n+ * Type A: Timer 1 only — 16-bit, asynchronous clock option\n+ * Type B: Timer 2, 4, 6, 8 — 16-bit, 32-bit pairable with Type C\n+ * Type C: Timer 3, 5, 7, 9 — 16-bit, pairs with preceding Type B\n+ *\n+ * Each timer instance is a SysBusDevice with:\n+ * - One MMIO region (PIC32MK_TIMER_BLOCK_SIZE bytes)\n+ * - QEMU ptimer for timing\n+ * - One IRQ output (connected to EVIC input)\n+ *\n+ * Register layout within each 0x200-byte block\n+ * (registers have SET/CLR/INV sub-regs at +4/+8/+C):\n+ * TxCON +0x00 Control: ON, TCKPS, T32(Type B only), TCS\n+ * TMRx +0x10 Current count value (16 or 32-bit)\n+ * PRx +0x20 Period register — fires IRQ when TMR == PR\n+ *\n+ * Copyright (c) 2026 QEMU contributors\n+ * SPDX-License-Identifier: GPL-2.0-or-later\n+ */\n+\n+#include \"qemu/osdep.h\"\n+#include \"qemu/log.h\"\n+#include \"qemu/module.h\"\n+#include \"hw/core/sysbus.h\"\n+#include \"hw/core/irq.h\"\n+#include \"hw/core/ptimer.h\"\n+#include \"hw/core/qdev-properties.h\"\n+#include \"hw/mips/pic32mk.h\"\n+\n+/*\n+ * Prescaler lookup tables:\n+ * Type A (Timer1): 2-bit TCKPS at bits[5:4], values 0..3 → {1,8,64,256}\n+ * Type B/C: 3-bit TCKPS at bits[6:4], values 0..7 → {1,2,4,8,16,32,64,256}\n+ */\n+static const uint32_t timer_prescalers_a[4] = { 1, 8, 64, 256 };\n+static const uint32_t timer_prescalers_bc[8] = { 1, 2, 4, 8, 16, 32, 64, 256 };\n+\n+/*\n+ * Device state\n+ * -----------------------------------------------------------------------\n+ */\n+\n+#define TYPE_PIC32MK_TIMER \"pic32mk-timer\"\n+OBJECT_DECLARE_SIMPLE_TYPE(PIC32MKTimerState, PIC32MK_TIMER)\n+\n+struct PIC32MKTimerState {\n+ SysBusDevice parent_obj;\n+ MemoryRegion mr;\n+\n+ uint32_t con; /* TxCON */\n+ uint32_t tmr; /* TMRx */\n+ uint32_t pr; /* PRx — period (0xFFFF on reset for 16-bit) */\n+\n+ ptimer_state *ptimer;\n+ qemu_irq irq;\n+ bool type_a; /* true = Timer1 (Type A, 2-bit TCKPS {1,8,64,256}) */\n+};\n+\n+/*\n+ * ptimer callback — fires when TMRx wraps (hits PRx)\n+ * -----------------------------------------------------------------------\n+ */\n+\n+static void pic32mk_timer_expired(void *opaque)\n+{\n+ PIC32MKTimerState *s = opaque;\n+\n+ /*\n+ * The ptimer already reloaded with the period; just assert the IRQ.\n+ * The EVIC input is level-sensitive: a pulse is sufficient because\n+ * the EVIC latches IFSx on the rising edge.\n+ */\n+ qemu_irq_pulse(s->irq);\n+}\n+\n+/*\n+ * Start/stop/reload helpers\n+ * -----------------------------------------------------------------------\n+ */\n+\n+static void timer_reload(PIC32MKTimerState *s)\n+{\n+ uint32_t prescale_idx = (s->con & PIC32MK_TCON_TCKPS_MASK)\n+ >> PIC32MK_TCON_TCKPS_SHIFT;\n+ uint32_t prescale = s->type_a\n+ ? timer_prescalers_a[prescale_idx & 3]\n+ : timer_prescalers_bc[prescale_idx & 7];\n+ uint32_t period = (s->pr == 0) ? 0xFFFFu : s->pr;\n+\n+ ptimer_transaction_begin(s->ptimer);\n+ ptimer_set_freq(s->ptimer, PIC32MK_CPU_HZ / prescale);\n+ ptimer_set_limit(s->ptimer, period, 1);\n+ ptimer_run(s->ptimer, 0); /* 0 = periodic */\n+ ptimer_transaction_commit(s->ptimer);\n+}\n+\n+static void timer_stop(PIC32MKTimerState *s)\n+{\n+ ptimer_transaction_begin(s->ptimer);\n+ ptimer_stop(s->ptimer);\n+ ptimer_transaction_commit(s->ptimer);\n+}\n+\n+/*\n+ * MMIO helpers\n+ * -----------------------------------------------------------------------\n+ */\n+\n+/* PIC32MK: base+0=REG, +4=CLR, +8=SET, +0xC=INV */\n+static void apply_sci(uint32_t *reg, uint32_t val, int sub)\n+{\n+ switch (sub) {\n+ case 0:\n+ *reg = val;\n+ break;\n+ case 4:\n+ *reg &= ~val;\n+ break;\n+ case 8:\n+ *reg |= val;\n+ break;\n+ case 12:\n+ *reg ^= val;\n+ break;\n+ }\n+}\n+\n+static uint32_t *timer_find_reg(PIC32MKTimerState *s, hwaddr base)\n+{\n+ switch (base) {\n+ case PIC32MK_TxCON:\n+ return &s->con;\n+ case PIC32MK_TMRx:\n+ return &s->tmr;\n+ case PIC32MK_PRx:\n+ return &s->pr;\n+ default:\n+ return NULL;\n+ }\n+}\n+\n+/*\n+ * MMIO read/write\n+ * -----------------------------------------------------------------------\n+ */\n+\n+static uint64_t timer_read(void *opaque, hwaddr addr, unsigned size)\n+{\n+ PIC32MKTimerState *s = opaque;\n+ hwaddr base = addr & ~(hwaddr)0xF;\n+\n+ /* TMRx: return ptimer current count when timer is running */\n+ if (base == PIC32MK_TMRx) {\n+ if (s->con & PIC32MK_TCON_ON) {\n+ return (uint32_t)ptimer_get_count(s->ptimer);\n+ }\n+ return s->tmr;\n+ }\n+\n+ uint32_t *reg = timer_find_reg(s, base);\n+ if (reg) {\n+ return *reg;\n+ }\n+\n+ qemu_log_mask(LOG_UNIMP,\n+ \"pic32mk_timer: unimplemented read @ 0x%04\" HWADDR_PRIx \"\\n\",\n+ addr);\n+ return 0;\n+}\n+\n+static void timer_write(void *opaque, hwaddr addr, uint64_t val, unsigned size)\n+{\n+ PIC32MKTimerState *s = opaque;\n+ int sub = (int)(addr & 0xF);\n+ hwaddr base = addr & ~(hwaddr)0xF;\n+ uint32_t *reg = timer_find_reg(s, base);\n+\n+ if (!reg) {\n+ qemu_log_mask(LOG_UNIMP,\n+ \"pic32mk_timer: unimplemented write @ 0x%04\"\n+ HWADDR_PRIx \" = 0x%08\" PRIx64 \"\\n\",\n+ addr, val);\n+ return;\n+ }\n+\n+ bool was_on = !!(s->con & PIC32MK_TCON_ON);\n+ apply_sci(reg, (uint32_t)val, sub);\n+\n+ /* TMRx write: update count when timer is stopped */\n+ if (base == PIC32MK_TMRx) {\n+ if (!was_on) {\n+ ptimer_transaction_begin(s->ptimer);\n+ ptimer_set_count(s->ptimer, s->tmr);\n+ ptimer_transaction_commit(s->ptimer);\n+ }\n+ return;\n+ }\n+\n+ /* TxCON write: handle ON bit transitions */\n+ if (base == PIC32MK_TxCON) {\n+ bool now_on = !!(s->con & PIC32MK_TCON_ON);\n+ if (!was_on && now_on) {\n+ timer_reload(s);\n+ } else if (was_on && !now_on) {\n+ timer_stop(s);\n+ /* Save current count */\n+ s->tmr = (uint32_t)ptimer_get_count(s->ptimer);\n+ } else if (now_on) {\n+ /* Prescaler may have changed; reload */\n+ timer_reload(s);\n+ }\n+ }\n+\n+ /* PRx write: update period on the fly */\n+ if (base == PIC32MK_PRx && (s->con & PIC32MK_TCON_ON)) {\n+ timer_reload(s);\n+ }\n+}\n+\n+static const MemoryRegionOps timer_ops = {\n+ .read = timer_read,\n+ .write = timer_write,\n+ .endianness = DEVICE_LITTLE_ENDIAN,\n+ .valid = {\n+ .min_access_size = 4,\n+ .max_access_size = 4,\n+ },\n+};\n+\n+/*\n+ * Device lifecycle\n+ * -----------------------------------------------------------------------\n+ */\n+\n+static void pic32mk_timer_reset(DeviceState *dev)\n+{\n+ PIC32MKTimerState *s = PIC32MK_TIMER(dev);\n+\n+ timer_stop(s);\n+ s->con = 0;\n+ s->tmr = 0;\n+ s->pr = 0xFFFF; /* 16-bit period register reset value */\n+ qemu_irq_lower(s->irq);\n+}\n+\n+static void pic32mk_timer_init(Object *obj)\n+{\n+ PIC32MKTimerState *s = PIC32MK_TIMER(obj);\n+\n+ memory_region_init_io(&s->mr, obj, &timer_ops, s,\n+ TYPE_PIC32MK_TIMER, PIC32MK_TIMER_BLOCK_SIZE);\n+ sysbus_init_mmio(SYS_BUS_DEVICE(obj), &s->mr);\n+\n+ sysbus_init_irq(SYS_BUS_DEVICE(obj), &s->irq);\n+\n+ s->ptimer = ptimer_init(pic32mk_timer_expired, s, PTIMER_POLICY_NO_IMMEDIATE_TRIGGER);\n+}\n+\n+static void pic32mk_timer_finalize(Object *obj)\n+{\n+ PIC32MKTimerState *s = PIC32MK_TIMER(obj);\n+ ptimer_free(s->ptimer);\n+}\n+\n+static Property pic32mk_timer_properties[] = {\n+ DEFINE_PROP_BOOL(\"type-a\", PIC32MKTimerState, type_a, false),\n+};\n+\n+static void pic32mk_timer_class_init(ObjectClass *klass, const void *data)\n+{\n+ DeviceClass *dc = DEVICE_CLASS(klass);\n+ device_class_set_legacy_reset(dc, pic32mk_timer_reset);\n+ device_class_set_props(dc, pic32mk_timer_properties);\n+}\n+\n+static const TypeInfo pic32mk_timer_info = {\n+ .name = TYPE_PIC32MK_TIMER,\n+ .parent = TYPE_SYS_BUS_DEVICE,\n+ .instance_size = sizeof(PIC32MKTimerState),\n+ .instance_init = pic32mk_timer_init,\n+ .instance_finalize = pic32mk_timer_finalize,\n+ .class_init = pic32mk_timer_class_init,\n+};\n+\n+static void pic32mk_timer_register_types(void)\n+{\n+ type_register_static(&pic32mk_timer_info);\n+}\n+\n+type_init(pic32mk_timer_register_types)\ndiff --git a/hw/mips/pic32mk_uart.c b/hw/mips/pic32mk_uart.c\nnew file mode 100644\nindex 0000000000..e2e83a2b7e\n--- /dev/null\n+++ b/hw/mips/pic32mk_uart.c\n@@ -0,0 +1,334 @@\n+/*\n+ * PIC32MK UART × 6 (U1–U6)\n+ * Datasheet: DS60001519E, §21 (pp. 475–552)\n+ *\n+ * Each UART instance is a SysBusDevice with:\n+ * - One MMIO region (PIC32MK_UART_BLOCK_SIZE bytes)\n+ * - CharFrontend for TX/RX via QEMU backend (-serial stdio, etc.)\n+ * - Three IRQ outputs: index 0 = RX, 1 = TX, 2 = Error\n+ *\n+ * Register layout (all 4-byte registers with SET/CLR/INV at +4/+8/+C):\n+ * UxMODE +0x00 Mode (UARTEN/ON bits, parity, stop bits)\n+ * UxSTA +0x10 Status + TX/RX interrupt select\n+ * UxTXREG +0x20 TX data (write-only)\n+ * UxRXREG +0x30 RX data (read-only)\n+ * UxBRG +0x40 Baud rate generator\n+ *\n+ * Copyright (c) 2026 QEMU contributors\n+ * SPDX-License-Identifier: GPL-2.0-or-later\n+ */\n+\n+#include \"qemu/osdep.h\"\n+#include \"qemu/log.h\"\n+#include \"qemu/module.h\"\n+#include \"hw/core/sysbus.h\"\n+#include \"hw/core/irq.h\"\n+#include \"hw/core/qdev-properties.h\"\n+#include \"hw/core/qdev-properties-system.h\"\n+#include \"chardev/char-fe.h\"\n+#include \"hw/mips/pic32mk.h\"\n+\n+/*\n+ * Device state\n+ * -----------------------------------------------------------------------\n+ */\n+\n+#define TYPE_PIC32MK_UART \"pic32mk-uart\"\n+OBJECT_DECLARE_SIMPLE_TYPE(PIC32MKUartState, PIC32MK_UART)\n+\n+struct PIC32MKUartState {\n+ SysBusDevice parent_obj;\n+ MemoryRegion mr;\n+\n+ /* Registers (base values; SET/CLR/INV are decoded in the write handler) */\n+ uint32_t mode; /* UxMODE */\n+ uint32_t sta; /* UxSTA */\n+ uint32_t brg; /* UxBRG */\n+\n+ /* RX single-byte buffer */\n+ uint8_t rxbuf;\n+ bool rxbuf_full;\n+\n+ CharFrontend chr;\n+\n+ /* IRQ outputs: 0=RX, 1=TX, 2=Error */\n+ qemu_irq irq_rx;\n+ qemu_irq irq_tx;\n+ qemu_irq irq_err;\n+};\n+\n+/*\n+ * UxSTA status helpers\n+ * -----------------------------------------------------------------------\n+ */\n+\n+static void uart_update_sta(PIC32MKUartState *s)\n+{\n+ /* TRMT (bit 8): TX shift register empty — always 1 (we forward immediately) */\n+ s->sta |= PIC32MK_USTA_TRMT;\n+ /* UTXBF (bit 9): TX buffer full — always 0 */\n+ s->sta &= ~PIC32MK_USTA_UTXBF;\n+\n+ /* URXDA (bit 0): RX data available */\n+ if (s->rxbuf_full) {\n+ s->sta |= PIC32MK_USTA_URXDA;\n+ } else {\n+ s->sta &= ~PIC32MK_USTA_URXDA;\n+ }\n+}\n+\n+static void uart_update_irq(PIC32MKUartState *s)\n+{\n+ /* RX IRQ: assert while URXDA=1 and URXEN is set */\n+ bool rx_pending = s->rxbuf_full && (s->sta & PIC32MK_USTA_URXEN);\n+ qemu_set_irq(s->irq_rx, rx_pending ? 1 : 0);\n+\n+ /*\n+ * TX IRQ: level-based. In QEMU the TX path is instantaneous — chars\n+ * go out immediately via qemu_chr_fe_write_all(), so TRMT=1 and\n+ * UTXBF=0 at all times. Regardless of UTXISEL[1:0], the TX\n+ * interrupt condition is always met whenever ON=1 and UTXEN=1.\n+ *\n+ * The EVIC tracks source levels (irq_level[]) and re-asserts IFS\n+ * bits after firmware clears them, so keeping irq_tx asserted at\n+ * level=1 causes the IFS flag to be re-set immediately after clear\n+ * — matching real Microchip behavior. The ISR must disable IEC\n+ * (the enable bit) to stop re-entry, which is exactly what the\n+ * Microchip plib does.\n+ */\n+ bool tx_active = (s->mode & PIC32MK_UMODE_ON) &&\n+ (s->sta & PIC32MK_USTA_UTXEN);\n+ qemu_set_irq(s->irq_tx, tx_active ? 1 : 0);\n+}\n+\n+/*\n+ * CharFrontend callbacks (RX path)\n+ * -----------------------------------------------------------------------\n+ */\n+\n+static int uart_can_receive(void *opaque)\n+{\n+ PIC32MKUartState *s = opaque;\n+ return s->rxbuf_full ? 0 : 1;\n+}\n+\n+static void uart_receive(void *opaque, const uint8_t *buf, int size)\n+{\n+ PIC32MKUartState *s = opaque;\n+\n+ if (size == 0) {\n+ return;\n+ }\n+\n+ if (s->rxbuf_full) {\n+ /* Overrun: set OERR bit */\n+ s->sta |= PIC32MK_USTA_OERR;\n+ qemu_set_irq(s->irq_err, 1);\n+ return;\n+ }\n+\n+ s->rxbuf = buf[0];\n+ s->rxbuf_full = true;\n+ uart_update_sta(s);\n+ uart_update_irq(s);\n+}\n+\n+/*\n+ * MMIO helpers\n+ * -----------------------------------------------------------------------\n+ */\n+\n+/* Apply SET/CLR/INV operation based on sub-register offset */\n+/* PIC32MK: base+0=REG, +4=CLR, +8=SET, +0xC=INV */\n+static void apply_sci(uint32_t *reg, uint32_t val, int sub)\n+{\n+ switch (sub) {\n+ case 0:\n+ *reg = val;\n+ break;\n+ case 4:\n+ *reg &= ~val;\n+ break;\n+ case 8:\n+ *reg |= val;\n+ break;\n+ case 12:\n+ *reg ^= val;\n+ break;\n+ }\n+}\n+\n+/* Map UART register base address to state field */\n+static uint32_t *uart_find_reg(PIC32MKUartState *s, hwaddr base)\n+{\n+ switch (base) {\n+ case PIC32MK_UxMODE:\n+ return &s->mode;\n+ case PIC32MK_UxSTA:\n+ return &s->sta;\n+ case PIC32MK_UxBRG:\n+ return &s->brg;\n+ default:\n+ return NULL;\n+ }\n+}\n+\n+/*\n+ * MMIO read/write\n+ * -----------------------------------------------------------------------\n+ */\n+\n+static uint64_t uart_read(void *opaque, hwaddr addr, unsigned size)\n+{\n+ PIC32MKUartState *s = opaque;\n+\n+ uart_update_sta(s);\n+\n+ /* RX register is special: reading consumes the byte */\n+ if ((addr & ~(hwaddr)0xF) == PIC32MK_UxRXREG) {\n+ if (s->rxbuf_full) {\n+ uint32_t val = s->rxbuf;\n+ s->rxbuf_full = false;\n+ uart_update_sta(s);\n+ uart_update_irq(s);\n+ qemu_chr_fe_accept_input(&s->chr);\n+ return val;\n+ }\n+ return 0;\n+ }\n+\n+ /* TX register is write-only */\n+ if ((addr & ~(hwaddr)0xF) == PIC32MK_UxTXREG) {\n+ return 0;\n+ }\n+\n+ hwaddr base = addr & ~(hwaddr)0xF;\n+ uint32_t *reg = uart_find_reg(s, base);\n+ if (reg) {\n+ return *reg;\n+ }\n+\n+ qemu_log_mask(LOG_UNIMP,\n+ \"pic32mk_uart: unimplemented read @ 0x%04\" HWADDR_PRIx \"\\n\",\n+ addr);\n+ return 0;\n+}\n+\n+static void uart_write(void *opaque, hwaddr addr, uint64_t val, unsigned size)\n+{\n+ PIC32MKUartState *s = opaque;\n+\n+ /* TX register: writing transmits a byte */\n+ if ((addr & ~(hwaddr)0xF) == PIC32MK_UxTXREG) {\n+ uint8_t ch = (uint8_t)(val & 0xFF);\n+ qemu_chr_fe_write_all(&s->chr, &ch, 1);\n+ return;\n+ }\n+\n+ int sub = (int)(addr & 0xF);\n+ hwaddr base = addr & ~(hwaddr)0xF;\n+ uint32_t *reg = uart_find_reg(s, base);\n+\n+ if (!reg) {\n+ qemu_log_mask(LOG_UNIMP,\n+ \"pic32mk_uart: unimplemented write @ 0x%04\"\n+ HWADDR_PRIx \" = 0x%08\" PRIx64 \"\\n\",\n+ addr, val);\n+ return;\n+ }\n+\n+ apply_sci(reg, (uint32_t)val, sub);\n+\n+ /* Keep status bits consistent after firmware writes to STA */\n+ if (base == PIC32MK_UxSTA) {\n+ /* Firmware clears OERR by writing 0 to that bit */\n+ if (!(s->sta & PIC32MK_USTA_OERR)) {\n+ qemu_set_irq(s->irq_err, 0);\n+ }\n+ uart_update_irq(s);\n+ }\n+ if (base == PIC32MK_UxMODE) {\n+ uart_update_irq(s);\n+ }\n+}\n+\n+static const MemoryRegionOps uart_ops = {\n+ .read = uart_read,\n+ .write = uart_write,\n+ .endianness = DEVICE_LITTLE_ENDIAN,\n+ .valid = {\n+ .min_access_size = 1,\n+ .max_access_size = 4,\n+ },\n+};\n+\n+/*\n+ * Device lifecycle\n+ * -----------------------------------------------------------------------\n+ */\n+\n+static void pic32mk_uart_reset(DeviceState *dev)\n+{\n+ PIC32MKUartState *s = PIC32MK_UART(dev);\n+\n+ s->mode = 0;\n+ /* Reset STA: TRMT=1, TX buffer not full */\n+ s->sta = PIC32MK_USTA_TRMT;\n+ s->brg = 0;\n+ s->rxbuf_full = false;\n+ s->rxbuf = 0;\n+\n+ qemu_set_irq(s->irq_rx, 0);\n+ qemu_set_irq(s->irq_tx, 0);\n+ qemu_set_irq(s->irq_err, 0);\n+}\n+\n+static void pic32mk_uart_realize(DeviceState *dev, Error **errp)\n+{\n+ PIC32MKUartState *s = PIC32MK_UART(dev);\n+\n+ qemu_chr_fe_set_handlers(&s->chr,\n+ uart_can_receive, uart_receive,\n+ NULL, NULL,\n+ s, NULL, true);\n+}\n+\n+static void pic32mk_uart_init(Object *obj)\n+{\n+ PIC32MKUartState *s = PIC32MK_UART(obj);\n+\n+ memory_region_init_io(&s->mr, obj, &uart_ops, s,\n+ TYPE_PIC32MK_UART, PIC32MK_UART_BLOCK_SIZE);\n+ sysbus_init_mmio(SYS_BUS_DEVICE(obj), &s->mr);\n+\n+ sysbus_init_irq(SYS_BUS_DEVICE(obj), &s->irq_rx); /* index 0 */\n+ sysbus_init_irq(SYS_BUS_DEVICE(obj), &s->irq_tx); /* index 1 */\n+ sysbus_init_irq(SYS_BUS_DEVICE(obj), &s->irq_err); /* index 2 */\n+}\n+\n+static const Property pic32mk_uart_props[] = {\n+ DEFINE_PROP_CHR(\"chardev\", PIC32MKUartState, chr),\n+};\n+\n+static void pic32mk_uart_class_init(ObjectClass *klass, const void *data)\n+{\n+ DeviceClass *dc = DEVICE_CLASS(klass);\n+ device_class_set_legacy_reset(dc, pic32mk_uart_reset);\n+ device_class_set_props(dc, pic32mk_uart_props);\n+ dc->realize = pic32mk_uart_realize;\n+}\n+\n+static const TypeInfo pic32mk_uart_info = {\n+ .name = TYPE_PIC32MK_UART,\n+ .parent = TYPE_SYS_BUS_DEVICE,\n+ .instance_size = sizeof(PIC32MKUartState),\n+ .instance_init = pic32mk_uart_init,\n+ .class_init = pic32mk_uart_class_init,\n+};\n+\n+static void pic32mk_uart_register_types(void)\n+{\n+ type_register_static(&pic32mk_uart_info);\n+}\n+\n+type_init(pic32mk_uart_register_types)\ndiff --git a/hw/mips/pic32mk_usb.c b/hw/mips/pic32mk_usb.c\nnew file mode 100644\nindex 0000000000..24a877fbeb\n--- /dev/null\n+++ b/hw/mips/pic32mk_usb.c\n@@ -0,0 +1,1033 @@\n+/*\n+ * Microchip PIC32MK USB OTG Full-Speed controller emulation\n+ * Datasheet: DS60001519E §25\n+ * Register addresses verified against p32mk1024mcm100.h (XC32 v4.60 pack).\n+ *\n+ * Phase 4A — register-file stub (complete)\n+ * Phase 4B — USB enumeration simulation + CDC TX chardev output:\n+ * • QEMUTimer drives the EP0 state machine (USB Reset → enumerate)\n+ * • SETUP packet injection via BDT write + TRNIF interrupt\n+ * • CDC TX polling: EP1-IN BDT entry harvested → chardev write\n+ * • chardev property \"chardev\" exposes CDC output as host PTY/socket\n+ *\n+ * BDT layout (PIC32MK device mode, ping-pong off / PPBRST):\n+ * Each endpoint has 4 BDT entries (RX-even, RX-odd, TX-even, TX-odd).\n+ * Entry size = 8 bytes (4-byte ctrl word + 4-byte buffer address).\n+ * EP0-OUT-even = offset 0x00, EP0-IN-even = offset 0x10,\n+ * EP1-OUT-even = offset 0x20, EP1-IN-even = offset 0x30.\n+ *\n+ * Copyright (c) 2026 QEMU contributors\n+ * SPDX-License-Identifier: GPL-2.0-or-later\n+ */\n+\n+#include \"qemu/osdep.h\"\n+#include \"qemu/log.h\"\n+#include \"qemu/timer.h\"\n+#include \"qapi/error.h\"\n+#include \"hw/core/sysbus.h\"\n+#include \"hw/core/irq.h\"\n+#include \"hw/mips/pic32mk.h\"\n+#include \"hw/mips/pic32mk_usb.h\"\n+#include \"exec/cpu-common.h\" /* cpu_physical_memory_read/write */\n+#include \"chardev/char-fe.h\"\n+#include \"hw/core/qdev-properties.h\"\n+#include \"hw/core/qdev-properties-system.h\"\n+\n+/*\n+ * Helpers\n+ * -----------------------------------------------------------------------\n+ */\n+\n+/*\n+ * Apply SET / CLR / INV operation.\n+ * sub = addr & 0xF: 0 → write, 4 → CLR, 8 → SET, C → INV\n+ */\n+static void apply_sci(uint32_t *reg, uint32_t val, int sub)\n+{\n+ switch (sub) {\n+ case 0x0:\n+ *reg = val;\n+ break;\n+ case 0x4:\n+ *reg &= ~val;\n+ break;\n+ case 0x8:\n+ *reg |= val;\n+ break;\n+ case 0xC:\n+ *reg ^= val;\n+ break;\n+ }\n+}\n+\n+/*\n+ * Apply write-1-clear (W1C) operation.\n+ */\n+static void apply_w1c(uint32_t *reg, uint32_t val, int sub)\n+{\n+ if (sub == 0x0 || sub == 0x4) {\n+ *reg &= ~val;\n+ }\n+}\n+\n+static void usb_update_irq(PIC32MKUSBState *s)\n+{\n+ /*\n+ * SESSION_VALID (OTGIR bit 3 / SESVDIF):\n+ *\n+ * On real PIC32MK hardware, SESVDIF is edge-triggered: it fires once when\n+ * SESVD transitions. To prevent an infinite ISR loop (the Harmony ISR\n+ * clears SESVDIF but VBUS stays valid), we use an edge-latch flag:\n+ * sesvd_edge_latched is set when SESVDIF is first asserted, and cleared\n+ * when firmware W1C-clears SESVDIF from OTGIR. We only include SESVDIF\n+ * in the IRQ level calculation while the edge latch is active.\n+ */\n+ uint32_t otg_pending = s->otgir & s->otgie;\n+ if (!s->sesvd_edge_latched) {\n+ otg_pending &= ~USB_OTG_IR_SESSION_VALID;\n+ }\n+\n+ bool fire = ((s->uir & s->uie) != 0)\n+ || ((s->ueir & s->ueie) != 0)\n+ || (otg_pending != 0);\n+ qemu_set_irq(s->irq, fire ? 1 : 0);\n+}\n+\n+/*\n+ * USTAT FIFO helpers (PIC32MK has a 4-deep hardware FIFO)\n+ *\n+ * On real hardware, each completed transaction pushes an USTAT value onto\n+ * the FIFO and asserts TRNIF. When firmware W1C-clears TRNIF, the FIFO\n+ * pops; if more entries remain, TRNIF is re-asserted immediately.\n+ * Without this FIFO, two transactions completing in quick succession\n+ * (e.g. chardev RX + timer TX poll) overwrite the single USTAT register,\n+ * causing the firmware to miss one transaction entirely — the root cause\n+ * of the write stall.\n+ * -----------------------------------------------------------------------\n+ */\n+\n+static void usb_stat_fifo_push(PIC32MKUSBState *s, uint32_t stat_val)\n+{\n+ if (s->stat_fifo_count >= 4) {\n+ qemu_log_mask(LOG_GUEST_ERROR,\n+ \"pic32mk_usb: USTAT FIFO overflow (dropped 0x%02x)\\n\",\n+ stat_val);\n+ return;\n+ }\n+ s->stat_fifo[s->stat_fifo_head] = stat_val;\n+ s->stat_fifo_head = (s->stat_fifo_head + 1) & 3;\n+ s->stat_fifo_count++;\n+ /* Expose the front of the FIFO via the USTAT register */\n+ s->ustat = s->stat_fifo[s->stat_fifo_tail];\n+ s->uir |= USB_IR_TRNIF;\n+}\n+\n+/*\n+ * Pop the front entry. Called when firmware W1C-clears TRNIF.\n+ * If more entries remain, re-assert TRNIF with the next USTAT value.\n+ */\n+static void usb_stat_fifo_pop(PIC32MKUSBState *s)\n+{\n+ if (s->stat_fifo_count == 0) {\n+ return;\n+ }\n+ s->stat_fifo_tail = (s->stat_fifo_tail + 1) & 3;\n+ s->stat_fifo_count--;\n+ if (s->stat_fifo_count > 0) {\n+ s->ustat = s->stat_fifo[s->stat_fifo_tail];\n+ s->uir |= USB_IR_TRNIF;\n+ }\n+}\n+\n+/*\n+ * BDT helpers\n+ * -----------------------------------------------------------------------\n+ */\n+\n+static hwaddr usb_bdt_phys(PIC32MKUSBState *s)\n+{\n+ /*\n+ * BDT base = (BDTP3[7:0] << 24) | (BDTP2[7:0] << 16) | (BDTP1[7:1] << 8)\n+ * BDTP1 bits [7:1] map to BDT address bits [15:9].\n+ */\n+ return ((hwaddr)(s->bdtp3 & 0xFFu) << 24)\n+ | ((hwaddr)(s->bdtp2 & 0xFFu) << 16)\n+ | ((hwaddr)(s->bdtp1 & 0xFEu) << 8);\n+}\n+\n+/*\n+ * Inject an 8-byte SETUP packet into the EP0-OUT-even BDT buffer.\n+ * The BDT entry at offset 0x00 from BDT base must be UOWN=1 (firmware armed).\n+ * On success: writes the SETUP data to the buffer, clears UOWN, sets\n+ * TOK_PID=SETUP (0xD) in byte[0] bits[5:2]=0x34, bc=8 in shortWord[1]\n+ * (bits[31:16]), sets ustat=EP0/OUT/EVEN, fires TRNIF.\n+ *\n+ * BDT 32-bit control word layout (Harmony DRV_USBFS_BDT_ENTRY union):\n+ * bits[ 7: 0] = byte[0]: UOWN(7), DATA01(6), DTSEN(4), BSTALL(2), TOK_PID[5:2]\n+ * bits[15: 8] = byte[1]: (reserved / padding)\n+ * bits[31:16] = shortWord[1]: byte count (BC)\n+ * word[1] = buffer physical address\n+ *\n+ * Harmony TRNIF switch uses (byte[0] & 0x3C):\n+ * 0x34 → SETUP (TOK_PID=0xD)\n+ * 0x04 → OUT (TOK_PID=0x1)\n+ * 0x24 → IN (TOK_PID=0x9)\n+ */\n+static bool usb_inject_setup(PIC32MKUSBState *s, const uint8_t setup[8])\n+{\n+ hwaddr bdt = usb_bdt_phys(s);\n+ if (!bdt) {\n+ return false;\n+ }\n+\n+ /* Check both ping-pong entries for EP0-OUT (even=BDT+0, odd=BDT+8). */\n+ for (int ppbi = 0; ppbi < 2; ppbi++) {\n+ hwaddr out_entry = bdt + (ppbi ? 8u : 0u);\n+ uint8_t entry[8];\n+ cpu_physical_memory_read(out_entry, entry, 8);\n+\n+ uint32_t ctrl = le32_to_cpu(*(uint32_t *)entry);\n+ uint32_t addr = le32_to_cpu(*(uint32_t *)(entry + 4));\n+\n+ if (!(ctrl & BDT_UOWN) || !addr) {\n+ continue; /* not armed — try other ping-pong */\n+ }\n+\n+ /* Write SETUP packet into the EP0-OUT buffer */\n+ cpu_physical_memory_write(addr, setup, 8);\n+\n+ /*\n+ * Return BDT entry to firmware:\n+ * byte[0] = 0x34: TOK_PID=SETUP(0xD) in bits[5:2], UOWN=0\n+ * shortWord[1] = 8: byte count\n+ */\n+ ctrl = 0x00000034u | (8u << 16);\n+ *(uint32_t *)entry = cpu_to_le32(ctrl);\n+ cpu_physical_memory_write(out_entry, entry, 8);\n+\n+ /* UxSTAT: EP=0, DIR=OUT(0), PPBI=ppbi */\n+ usb_stat_fifo_push(s, (uint32_t)(ppbi ? 0x04u : 0x00u));\n+ usb_update_irq(s);\n+ return true;\n+ }\n+ return false; /* neither entry armed yet */\n+}\n+\n+/*\n+ * Accept an EP0-IN response from the firmware: read the data the firmware\n+ * placed in EP0-IN-even (BDT offset 0x10), set TOK_PID=IN in byte[0],\n+ * clear UOWN, preserve bc in shortWord[1], fire TRNIF.\n+ * Returns true if an IN entry was accepted, false if not armed yet.\n+ */\n+static bool usb_accept_ep0_in(PIC32MKUSBState *s)\n+{\n+ hwaddr bdt = usb_bdt_phys(s);\n+ if (!bdt) {\n+ return false;\n+ }\n+\n+ /* Check both ping-pong entries for EP0-IN (even=BDT+0x10, odd=BDT+0x18). */\n+ for (int ppbi = 0; ppbi < 2; ppbi++) {\n+ hwaddr in_entry = bdt + 0x10u + (ppbi ? 8u : 0u);\n+ uint8_t entry[8];\n+ cpu_physical_memory_read(in_entry, entry, 8);\n+\n+ uint32_t ctrl = le32_to_cpu(*(uint32_t *)entry);\n+ if (!(ctrl & BDT_UOWN)) {\n+ continue; /* not armed */\n+ }\n+\n+ /*\n+ * Return BDT entry to firmware:\n+ * byte[0] = 0x24: TOK_PID=IN(0x9), UOWN=0\n+ * shortWord[1]: preserve bc\n+ */\n+ ctrl = (ctrl & 0xFFFF0000u) | 0x24u;\n+ *(uint32_t *)entry = cpu_to_le32(ctrl);\n+ cpu_physical_memory_write(in_entry, entry, 8);\n+\n+ /* UxSTAT: EP=0, DIR=IN(bit3), PPBI=ppbi(bit2) */\n+ usb_stat_fifo_push(s, 0x08u | (uint32_t)(ppbi ? 0x04u : 0x00u));\n+ usb_update_irq(s);\n+ return true;\n+ }\n+ return false;\n+}\n+\n+/*\n+ * Simulate the STATUS phase OUT for a control read (host→device ZLP).\n+ * Write EP0-OUT-even BDT entry: TOK_PID=OUT(0x1)→byte[0]=0x04, UOWN=0,\n+ * bc=0 in shortWord[1]. Then fire TRNIF so the Harmony TRNIF handler\n+ * hits case 0x04, sees shortWord[1]=0 < maxPacketSize, marks the IRP\n+ * complete, invokes the callback, and re-arms EP0-OUT for the next SETUP.\n+ */\n+static void usb_send_status_out(PIC32MKUSBState *s)\n+{\n+ hwaddr bdt = usb_bdt_phys(s);\n+ if (!bdt) {\n+ return;\n+ }\n+\n+ /*\n+ * Find the armed EP0-OUT ping-pong entry (even=BDT+0, odd=BDT+8).\n+ * Write: byte[0]=0x04 (TOK_PID=OUT), UOWN=0, shortWord[1]=0 (ZLP).\n+ */\n+ for (int ppbi = 0; ppbi < 2; ppbi++) {\n+ hwaddr out_entry = bdt + (ppbi ? 8u : 0u);\n+ uint8_t entry[8];\n+ cpu_physical_memory_read(out_entry, entry, 8);\n+ uint32_t ctrl = le32_to_cpu(*(uint32_t *)entry);\n+\n+ if (ctrl & BDT_UOWN) {\n+ ctrl = 0x00000004u; /* TOK_PID=OUT, UOWN=0, bc=0 */\n+ *(uint32_t *)entry = cpu_to_le32(ctrl);\n+ cpu_physical_memory_write(out_entry, entry, 8);\n+ usb_stat_fifo_push(s, (uint32_t)(ppbi ? 0x04u : 0x00u));\n+ usb_update_irq(s);\n+ return;\n+ }\n+ }\n+\n+ /* Neither armed — fire even anyway (fallback; should not happen) */\n+ uint8_t entry[8];\n+ cpu_physical_memory_read(bdt, entry, 8);\n+ *(uint32_t *)entry = cpu_to_le32(0x00000004u);\n+ cpu_physical_memory_write(bdt, entry, 8);\n+ usb_stat_fifo_push(s, USB_STAT_EP0_OUT_EVEN);\n+ usb_update_irq(s);\n+}\n+\n+/*\n+ * Chardev RX callbacks (CDC Host→Device path, EP2-OUT)\n+ * -----------------------------------------------------------------------\n+ */\n+\n+/*\n+ * Returns 64 (max bulk packet) when enumeration is done and EP2-OUT BDT\n+ * is armed by the firmware (UOWN=1). Returns 0 otherwise so the chardev\n+ * layer does not deliver data before the firmware is ready.\n+ */\n+static int usb_chr_can_receive(void *opaque)\n+{\n+ PIC32MKUSBState *s = opaque;\n+ if (s->ep0_sim != EP0_SIM_DONE) {\n+ return 0;\n+ }\n+ hwaddr bdt = usb_bdt_phys(s);\n+ if (!bdt) {\n+ return 0;\n+ }\n+ /* EP2-OUT-even = BDT+0x40, EP2-OUT-odd = BDT+0x48 */\n+ for (int ppbi = 0; ppbi < 2; ppbi++) {\n+ uint8_t entry[4];\n+ cpu_physical_memory_read(bdt + 0x40u + (ppbi ? 8u : 0u), entry, 4);\n+ uint32_t ctrl = le32_to_cpu(*(uint32_t *)entry);\n+ if (ctrl & BDT_UOWN) {\n+ return 64;\n+ }\n+ }\n+ return 0;\n+}\n+\n+/*\n+ * Called by the QEMU chardev layer when the host sends data over the PTY/socket.\n+ * Writes the payload into the firmware's EP2-OUT buffer, returns the BDT entry\n+ * to firmware (UOWN=0, TOK_PID=OUT, bc=n), and fires TRNIF so the Harmony ISR\n+ * picks it up via USB_ReadByte() → circular_buf_get().\n+ */\n+static void usb_chr_receive(void *opaque, const uint8_t *buf, int size)\n+{\n+ PIC32MKUSBState *s = opaque;\n+ hwaddr bdt = usb_bdt_phys(s);\n+ if (!bdt) {\n+ return;\n+ }\n+\n+ for (int ppbi = 0; ppbi < 2; ppbi++) {\n+ hwaddr ep2_out = bdt + 0x40u + (ppbi ? 8u : 0u);\n+ uint8_t entry[8];\n+ cpu_physical_memory_read(ep2_out, entry, 8);\n+\n+ uint32_t ctrl = le32_to_cpu(*(uint32_t *)entry);\n+ uint32_t addr = le32_to_cpu(*(uint32_t *)(entry + 4));\n+\n+ if (!(ctrl & BDT_UOWN) || !addr) {\n+ continue; /* not armed — try other ping-pong */\n+ }\n+\n+ int n = MIN(size, 64);\n+ cpu_physical_memory_write(addr, buf, n);\n+\n+ /*\n+ * Return BDT entry to firmware:\n+ * byte[0] = 0x04: TOK_PID=OUT(0x1) in bits[5:2], UOWN=0\n+ * shortWord[1] = n: byte count received\n+ */\n+ ctrl = 0x00000004u | ((uint32_t)n << 16);\n+ *(uint32_t *)entry = cpu_to_le32(ctrl);\n+ cpu_physical_memory_write(ep2_out, entry, 8);\n+\n+ /* UxSTAT: EP=2(bits[7:4]=0x20), DIR=OUT(bit3=0), PPBI=ppbi(bit2) */\n+ usb_stat_fifo_push(s, 0x20u | (uint32_t)(ppbi ? 0x04u : 0x00u));\n+ usb_update_irq(s);\n+ return;\n+ }\n+}\n+\n+/*\n+ * Poll the CDC TX endpoint (EP2-IN) and the CDC notification endpoint (EP1-IN).\n+ * If the firmware has queued data (UOWN=1), drain it, release the BDT entry,\n+ * and fire TRNIF so the firmware can refill.\n+ *\n+ * EP1-IN (BDT+0x30): CDC Serial State Notification — data discarded (host-side\n+ * notification only), but BDT must be returned to prevent firmware stall.\n+ * EP2-IN (BDT+0x50): CDC bulk TX — data forwarded to chardev.\n+ */\n+static void usb_check_cdc_bdt(PIC32MKUSBState *s)\n+{\n+ hwaddr bdt = usb_bdt_phys(s);\n+ if (!bdt) {\n+ return;\n+ }\n+\n+ /*\n+ * EP1-IN = CDC Serial State Notification (interrupt IN, 16 bytes max).\n+ * BDT: EP1-IN-even = BDT+0x30, EP1-IN-odd = BDT+0x38.\n+ * Drain and return to firmware without forwarding to chardev.\n+ */\n+ for (int ppbi = 0; ppbi < 2; ppbi++) {\n+ hwaddr ep1_in = bdt + 0x30u + (ppbi ? 8u : 0u);\n+ uint8_t entry[8];\n+ cpu_physical_memory_read(ep1_in, entry, 8);\n+\n+ uint32_t ctrl = le32_to_cpu(*(uint32_t *)entry);\n+ if (!(ctrl & BDT_UOWN)) {\n+ continue;\n+ }\n+\n+ /* Return BDT entry: TOK_PID=IN, UOWN=0, preserve bc */\n+ ctrl = (ctrl & 0xFFFF0000u) | 0x24u;\n+ *(uint32_t *)entry = cpu_to_le32(ctrl);\n+ cpu_physical_memory_write(ep1_in, entry, 8);\n+\n+ /* UxSTAT: EP=1(bits[7:4]=0x10), DIR=IN(bit3=0x08), PPBI=ppbi(bit2) */\n+ usb_stat_fifo_push(s, 0x18u | (uint32_t)(ppbi ? 0x04u : 0x00u));\n+ usb_update_irq(s);\n+ break; /* one notification per tick is enough */\n+ }\n+\n+ /*\n+ * EP2-IN = CDC bulk TX. BDT: EP2-IN-even = BDT+0x50, EP2-IN-odd = BDT+0x58.\n+ * Forward data to chardev backend (PTY / socket / file).\n+ */\n+ for (int ppbi = 0; ppbi < 2; ppbi++) {\n+ hwaddr ep2_in = bdt + 0x50u + (ppbi ? 8u : 0u);\n+ uint8_t entry[8];\n+ cpu_physical_memory_read(ep2_in, entry, 8);\n+\n+ uint32_t ctrl = le32_to_cpu(*(uint32_t *)entry);\n+ if (!(ctrl & BDT_UOWN)) {\n+ continue;\n+ }\n+\n+ uint32_t bc = ctrl >> 16;\n+ uint32_t addr = le32_to_cpu(*(uint32_t *)(entry + 4));\n+\n+ if (bc > 0 && addr) {\n+ uint8_t txbuf[64];\n+ bc = MIN(bc, sizeof(txbuf));\n+ cpu_physical_memory_read(addr, txbuf, bc);\n+ if (qemu_chr_fe_backend_connected(&s->chr)) {\n+ qemu_chr_fe_write_all(&s->chr, txbuf, bc);\n+ }\n+ }\n+\n+ /* Return BDT entry: TOK_PID=IN, UOWN=0, preserve bc */\n+ ctrl = (ctrl & 0xFFFF0000u) | 0x24u;\n+ *(uint32_t *)entry = cpu_to_le32(ctrl);\n+ cpu_physical_memory_write(ep2_in, entry, 8);\n+\n+ /* UxSTAT: EP=2(bits[7:4]=0x20), DIR=IN(bit3=0x08), PPBI=ppbi(bit2) */\n+ usb_stat_fifo_push(s, 0x28u | (uint32_t)(ppbi ? 0x04u : 0x00u));\n+ usb_update_irq(s);\n+ return; /* drain one packet per tick; FIFO handles concurrency */\n+ }\n+}\n+\n+/*\n+ * EP0 enumeration timer\n+ * -----------------------------------------------------------------------\n+ */\n+\n+static void usb_timer_cb(void *opaque)\n+{\n+ PIC32MKUSBState *s = opaque;\n+\n+ /* Standard USB SETUP packets for enumeration sequence */\n+ static const uint8_t setup_get_dev_desc[8] = {\n+ 0x80, 0x06, 0x00, 0x01, 0x00, 0x00, 18, 0x00\n+ }; /* GET_DESCRIPTOR(Device, length=18) */\n+\n+ static const uint8_t setup_set_address[8] = {\n+ 0x00, 0x05, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00\n+ }; /* SET_ADDRESS(1) */\n+\n+ static const uint8_t setup_get_cfg_desc[8] = {\n+ 0x80, 0x06, 0x00, 0x02, 0x00, 0x00, 67, 0x00\n+ }; /* GET_DESCRIPTOR(Configuration, length=67) */\n+\n+ static const uint8_t setup_set_config[8] = {\n+ 0x00, 0x09, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00\n+ }; /* SET_CONFIGURATION(1) */\n+\n+ switch (s->ep0_sim) {\n+\n+ case EP0_SIM_IDLE:\n+ /* Do nothing until USBPWR is set */\n+ break;\n+\n+ case EP0_SIM_RESET:\n+ /*\n+ * Wait until firmware enables URSTIE (bit 0) in UIE.\n+ * The UIE write handler fires URSTIF immediately when URSTIE is first\n+ * enabled (edge-detected). This path is a poll fallback: if URSTIE\n+ * is already set by the time we arrive here, fire now; otherwise retry\n+ * every 5ms. The 50ms initial delay usually isn't long enough for the\n+ * Harmony stack to complete Init→Attach→EnableInterrupts.\n+ */\n+ if (!(s->uie & USB_IR_URSTIF)) {\n+ /*\n+ * SESSION_VALID fallback: if the initial SESVDIF pulse was missed\n+ * (e.g. IEC not enabled yet when pulse fired), re-latch SESVDIF\n+ * every few retries so the EVIC can deliver the interrupt once\n+ * IEC is enabled.\n+ */\n+ s->sesvd_retry_count++;\n+ /*\n+ * Only re-latch if firmware has never acknowledged SESVDIF.\n+ * Once sesvd_acked is true, the Attach→URSTIE chain has\n+ * started and retries would cause an IRQ storm.\n+ */\n+ if (!s->sesvd_acked &&\n+ (s->sesvd_retry_count % 3) == 0 &&\n+ (s->otgstat & USB_OTG_IR_SESSION_VALID) &&\n+ (s->otgie & USB_OTG_IR_SESSION_VALID)) {\n+ s->otgir |= USB_OTG_IR_SESSION_VALID;\n+ s->sesvd_edge_latched = true;\n+ usb_update_irq(s);\n+ }\n+ timer_mod(s->usb_timer,\n+ qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + 5 * SCALE_MS);\n+ return;\n+ }\n+ s->uir |= USB_IR_URSTIF;\n+ usb_update_irq(s);\n+ s->ep0_sim = EP0_SIM_GET_DEV_DESC;\n+ timer_mod(s->usb_timer,\n+ qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + 10 * SCALE_MS);\n+ return;\n+\n+ case EP0_SIM_GET_DEV_DESC:\n+ if (usb_inject_setup(s, setup_get_dev_desc)) {\n+ s->ep0_sim = EP0_SIM_WAIT_DEV_DESC;\n+ timer_mod(s->usb_timer,\n+ qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + 5 * SCALE_MS);\n+ } else {\n+ /* Retry every 2 ms until BDT is armed */\n+ timer_mod(s->usb_timer,\n+ qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + 2 * SCALE_MS);\n+ }\n+ return;\n+\n+ case EP0_SIM_WAIT_DEV_DESC:\n+ /* Consume the EP0-IN response(s) — may be multi-packet (64+3 bytes) */\n+ if (usb_accept_ep0_in(s)) {\n+ /* Give firmware 2ms to re-arm IN or finish */\n+ timer_mod(s->usb_timer,\n+ qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + 2 * SCALE_MS);\n+ } else {\n+ /* IN response not ready yet, or we just consumed last packet */\n+ usb_send_status_out(s); /* status phase complete */\n+ s->ep0_sim = EP0_SIM_SET_ADDRESS;\n+ timer_mod(s->usb_timer,\n+ qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + 5 * SCALE_MS);\n+ }\n+ return;\n+\n+ case EP0_SIM_SET_ADDRESS:\n+ if (usb_inject_setup(s, setup_set_address)) {\n+ s->uaddr = 1; /* pretend host acknowledged new address */\n+ s->ep0_sim = EP0_SIM_WAIT_ADDRESS;\n+ timer_mod(s->usb_timer,\n+ qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + 5 * SCALE_MS);\n+ } else {\n+ timer_mod(s->usb_timer,\n+ qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + 2 * SCALE_MS);\n+ }\n+ return;\n+\n+ case EP0_SIM_WAIT_ADDRESS:\n+ /* Consume status-phase ZLP IN from SET_ADDRESS */\n+ if (usb_accept_ep0_in(s)) {\n+ s->ep0_sim = EP0_SIM_GET_CFG_DESC;\n+ timer_mod(s->usb_timer,\n+ qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + 5 * SCALE_MS);\n+ } else {\n+ /* Retry */\n+ timer_mod(s->usb_timer,\n+ qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + 2 * SCALE_MS);\n+ }\n+ return;\n+\n+ case EP0_SIM_GET_CFG_DESC:\n+ if (usb_inject_setup(s, setup_get_cfg_desc)) {\n+ s->ep0_sim = EP0_SIM_WAIT_CFG_DESC;\n+ timer_mod(s->usb_timer,\n+ qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + 5 * SCALE_MS);\n+ } else {\n+ timer_mod(s->usb_timer,\n+ qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + 2 * SCALE_MS);\n+ }\n+ return;\n+\n+ case EP0_SIM_WAIT_CFG_DESC:\n+ /* May be multi-packet; keep accepting until no more IN pending */\n+ if (usb_accept_ep0_in(s)) {\n+ timer_mod(s->usb_timer,\n+ qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + 2 * SCALE_MS);\n+ } else {\n+ usb_send_status_out(s); /* status phase */\n+ s->ep0_sim = EP0_SIM_SET_CONFIG;\n+ timer_mod(s->usb_timer,\n+ qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + 5 * SCALE_MS);\n+ }\n+ return;\n+\n+ case EP0_SIM_SET_CONFIG:\n+ if (usb_inject_setup(s, setup_set_config)) {\n+ s->ep0_sim = EP0_SIM_WAIT_CONFIG;\n+ timer_mod(s->usb_timer,\n+ qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + 5 * SCALE_MS);\n+ } else {\n+ timer_mod(s->usb_timer,\n+ qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + 2 * SCALE_MS);\n+ }\n+ return;\n+\n+ case EP0_SIM_WAIT_CONFIG:\n+ /* Consume status-phase ZLP IN from SET_CONFIGURATION */\n+ if (usb_accept_ep0_in(s)) {\n+ s->configured = true;\n+ s->ep0_sim = EP0_SIM_DONE;\n+ qemu_log_mask(LOG_UNIMP, \"pic32mk_usb: USB enumeration DONE\\n\");\n+ timer_mod(s->usb_timer,\n+ qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + 5 * SCALE_MS);\n+ } else {\n+ timer_mod(s->usb_timer,\n+ qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + 2 * SCALE_MS);\n+ }\n+ return;\n+\n+ case EP0_SIM_DONE:\n+ /*\n+ * Poll CDC TX every 1 ms (was 5 ms — too slow for bootloader\n+ * throughput; each WRITE command needs a response drained from\n+ * EP2-IN before the firmware can process the next one).\n+ */\n+ usb_check_cdc_bdt(s);\n+ /* Advance the frame counter (1 frame = 1 ms at full-speed) */\n+ {\n+ uint32_t frm = ((s->ufrmh & 0x07u) << 8) | s->ufrml;\n+ frm = (frm + 1) & 0x7FFu;\n+ s->ufrml = frm & 0xFFu;\n+ s->ufrmh = (frm >> 8) & 0x07u;\n+ }\n+ timer_mod(s->usb_timer,\n+ qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + 1 * SCALE_MS);\n+ return;\n+ }\n+}\n+\n+/*\n+ * MMIO read\n+ * -----------------------------------------------------------------------\n+ */\n+\n+static uint64_t usb_read(void *opaque, hwaddr addr, unsigned size)\n+{\n+ PIC32MKUSBState *s = opaque;\n+ hwaddr base = addr & ~(hwaddr)0xFu; /* strip sub-register bits */\n+\n+ switch (base) {\n+ /* OTG block */\n+ case PIC32MK_UxOTGIR:\n+ return s->otgir;\n+ case PIC32MK_UxOTGIE:\n+ return s->otgie;\n+ case PIC32MK_UxOTGSTAT:\n+ return s->otgstat;\n+ case PIC32MK_UxOTGCON:\n+ return s->otgcon;\n+ case PIC32MK_UxPWRC:\n+ return s->pwrc;\n+\n+ /* Core registers */\n+ case PIC32MK_UxIR:\n+ return s->uir;\n+ case PIC32MK_UxIE:\n+ return s->uie;\n+ case PIC32MK_UxEIR:\n+ return s->ueir;\n+ case PIC32MK_UxEIE:\n+ return s->ueie;\n+ case PIC32MK_UxSTAT:\n+ return s->ustat;\n+ case PIC32MK_UxCON:\n+ return s->ucon;\n+ case PIC32MK_UxADDR:\n+ return s->uaddr;\n+ case PIC32MK_UxBDTP1:\n+ return s->bdtp1;\n+ case PIC32MK_UxFRML:\n+ return s->ufrml;\n+ case PIC32MK_UxFRMH:\n+ return s->ufrmh;\n+ case PIC32MK_UxTOK:\n+ return s->utok;\n+ case PIC32MK_UxSOF:\n+ return s->usof;\n+ case PIC32MK_UxBDTP2:\n+ return s->bdtp2;\n+ case PIC32MK_UxBDTP3:\n+ return s->bdtp3;\n+ case PIC32MK_UxCNFG1:\n+ return s->cnfg1;\n+\n+ default:\n+ break;\n+ }\n+\n+ /* Endpoint control registers UxEP0–UxEP15 */\n+ if (base >= PIC32MK_UxEP_BASE &&\n+ base < PIC32MK_UxEP_BASE + PIC32MK_USB_NEPS * PIC32MK_UxEP_STRIDE) {\n+ unsigned ep = (base - PIC32MK_UxEP_BASE) / PIC32MK_UxEP_STRIDE;\n+ return s->uep[ep];\n+ }\n+\n+ qemu_log_mask(LOG_UNIMP,\n+ \"pic32mk_usb: unimplemented read @ 0x%04\" HWADDR_PRIx \"\\n\",\n+ addr);\n+ return 0;\n+}\n+\n+/*\n+ * MMIO write\n+ * -----------------------------------------------------------------------\n+ */\n+\n+static void usb_write(void *opaque, hwaddr addr, uint64_t val, unsigned size)\n+{\n+ PIC32MKUSBState *s = opaque;\n+ hwaddr base = addr & ~(hwaddr)0xFu;\n+ int sub = (int)(addr & 0xFu);\n+ uint32_t v = (uint32_t)val;\n+\n+ switch (base) {\n+\n+ /* ----- OTG block ----- */\n+ case PIC32MK_UxOTGIR:\n+ /*\n+ * Clear the edge latch before W1C so that usb_update_irq() deasserts\n+ * the IRQ line when SESVDIF is cleared by firmware.\n+ */\n+ if (v & USB_OTG_IR_SESSION_VALID) {\n+ s->sesvd_edge_latched = false;\n+ s->sesvd_acked = true; /* firmware processed SESSION_VALID */\n+ }\n+ apply_w1c(&s->otgir, v, sub);\n+ usb_update_irq(s);\n+ return;\n+\n+ case PIC32MK_UxOTGIE: {\n+ uint32_t old_otgie = s->otgie;\n+ apply_sci(&s->otgie, v, sub);\n+ /*\n+ * SESSION_VALID edge simulation:\n+ * Latch SESVDIF ONLY on the 0→1 transition of the SESSION_VALID enable\n+ * bit, and only if firmware hasn't already acknowledged it. Without\n+ * edge detection, any OTGIE write (e.g. Harmony's Attach() enabling\n+ * all OTG ints from inside the SESSION_VALID ISR) would re-latch\n+ * SESVDIF and cause an infinite ISR loop.\n+ */\n+ if (!(old_otgie & USB_OTG_IR_SESSION_VALID) &&\n+ (s->otgie & USB_OTG_IR_SESSION_VALID) &&\n+ (s->otgstat & USB_OTG_IR_SESSION_VALID) &&\n+ !s->sesvd_acked) {\n+ s->otgir |= USB_OTG_IR_SESSION_VALID;\n+ s->sesvd_edge_latched = true;\n+ fprintf(stderr, \"pic32mk_usb: SESSION_VALID latched → IRQ (persistent)\\n\");\n+ }\n+ usb_update_irq(s);\n+ return;\n+ }\n+\n+ case PIC32MK_UxOTGSTAT:\n+ return; /* read-only */\n+\n+ case PIC32MK_UxOTGCON:\n+ apply_sci(&s->otgcon, v, sub);\n+ return;\n+\n+ case PIC32MK_UxPWRC: {\n+ uint32_t old_pwrc = s->pwrc;\n+ apply_sci(&s->pwrc, v, sub);\n+ /* When USBPWR first asserted: schedule USB Reset after 50 ms */\n+ if ((s->pwrc & USB_PWRC_USBPWR) && !(old_pwrc & USB_PWRC_USBPWR)\n+ && s->ep0_sim == EP0_SIM_IDLE) {\n+ fprintf(stderr, \"pic32mk_usb: USBPWR set — scheduling USB Reset in 50ms\\n\");\n+ s->ep0_sim = EP0_SIM_RESET;\n+ timer_mod(s->usb_timer,\n+ qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + 50 * SCALE_MS);\n+ }\n+ return;\n+ }\n+\n+ /* ----- Core interrupt registers ----- */\n+ case PIC32MK_UxIR:\n+ /*\n+ * TRNIF W1C: pop the USTAT FIFO. On real PIC32MK hardware, clearing\n+ * TRNIF advances the 4-deep STAT FIFO. If more entries remain,\n+ * TRNIF is re-asserted immediately with the next USTAT value, so the\n+ * Harmony ISR's while(TRNIF) loop processes all queued transactions.\n+ */\n+ if (v & USB_IR_TRNIF) {\n+ usb_stat_fifo_pop(s);\n+ }\n+ apply_w1c(&s->uir, v, sub);\n+ /*\n+ * Opportunistic CDC TX check: firmware just cleared TRNIF, meaning it\n+ * finished processing a transaction. If it also armed EP2-IN (TX\n+ * response) during that processing, drain it NOW instead of waiting\n+ * for the next timer tick. This brings TX latency close to zero.\n+ */\n+ if (s->configured) {\n+ usb_check_cdc_bdt(s);\n+ }\n+ usb_update_irq(s);\n+ return;\n+\n+ case PIC32MK_UxIE: {\n+ uint32_t old_uie = s->uie;\n+ apply_sci(&s->uie, v, sub);\n+ /*\n+ * When firmware enables URSTIE (bit 0) for the first time and the EP0\n+ * state machine is armed (USBPWR was set earlier), fire URSTIF now.\n+ * On real hardware the host drives USB Reset after D+ pullup is seen.\n+ * We use the same edge-detection pattern as SESSION_VALID: pulse assert\n+ * + deassert so EVIC latches IFS exactly once, then let usb_update_irq\n+ * handle the persistent URSTIF level (UIR & UIE != 0).\n+ */\n+ if (!(old_uie & USB_IR_URSTIF) && (s->uie & USB_IR_URSTIF)\n+ && s->ep0_sim == EP0_SIM_RESET) {\n+ fprintf(stderr, \"pic32mk_usb: URSTIE enabled → firing USB Reset\\n\");\n+ s->uir |= USB_IR_URSTIF;\n+ }\n+ usb_update_irq(s);\n+ return;\n+ }\n+\n+ case PIC32MK_UxEIR:\n+ apply_w1c(&s->ueir, v, sub);\n+ usb_update_irq(s);\n+ return;\n+\n+ case PIC32MK_UxEIE:\n+ apply_sci(&s->ueie, v, sub);\n+ usb_update_irq(s);\n+ return;\n+\n+ case PIC32MK_UxSTAT:\n+ return; /* read-only */\n+\n+ case PIC32MK_UxCON:\n+ apply_sci(&s->ucon, v, sub);\n+ return;\n+\n+ case PIC32MK_UxADDR:\n+ apply_sci(&s->uaddr, v, sub);\n+ return;\n+\n+ case PIC32MK_UxBDTP1:\n+ apply_sci(&s->bdtp1, v, sub);\n+ return;\n+\n+ case PIC32MK_UxFRML:\n+ case PIC32MK_UxFRMH:\n+ return; /* read-only */\n+\n+ case PIC32MK_UxTOK:\n+ apply_sci(&s->utok, v, sub);\n+ return;\n+\n+ case PIC32MK_UxSOF:\n+ apply_sci(&s->usof, v, sub);\n+ return;\n+\n+ case PIC32MK_UxBDTP2:\n+ apply_sci(&s->bdtp2, v, sub);\n+ return;\n+\n+ case PIC32MK_UxBDTP3:\n+ apply_sci(&s->bdtp3, v, sub);\n+ return;\n+\n+ case PIC32MK_UxCNFG1:\n+ apply_sci(&s->cnfg1, v, sub);\n+ return;\n+\n+ default:\n+ break;\n+ }\n+\n+ /* Endpoint control registers */\n+ if (base >= PIC32MK_UxEP_BASE &&\n+ base < PIC32MK_UxEP_BASE + PIC32MK_USB_NEPS * PIC32MK_UxEP_STRIDE) {\n+ unsigned ep = (base - PIC32MK_UxEP_BASE) / PIC32MK_UxEP_STRIDE;\n+ apply_sci(&s->uep[ep], v, sub);\n+ return;\n+ }\n+\n+ qemu_log_mask(LOG_UNIMP,\n+ \"pic32mk_usb: unimplemented write @ 0x%04\" HWADDR_PRIx\n+ \" = 0x%08x\\n\", addr, v);\n+}\n+\n+static const MemoryRegionOps usb_ops = {\n+ .read = usb_read,\n+ .write = usb_write,\n+ .endianness = DEVICE_LITTLE_ENDIAN,\n+ .valid = {\n+ .min_access_size = 1,\n+ .max_access_size = 4,\n+ },\n+};\n+\n+/*\n+ * Device lifecycle\n+ * -----------------------------------------------------------------------\n+ */\n+\n+static void pic32mk_usb_reset(DeviceState *dev)\n+{\n+ PIC32MKUSBState *s = PIC32MK_USB(dev);\n+\n+ /* Reset values: all zero unless specified in DS60001519E §25 */\n+ s->otgir = 0;\n+ s->otgie = 0;\n+ /*\n+ * SESVD (bit 3): simulate VBUS present from power-on.\n+ * The Harmony driver polls PLIB_USB_OTG_SessionValid() (reads SESVD) to\n+ * decide whether to call USB_DEVICE_Attach(). If SESVD=0 at boot the\n+ * driver never calls Attach(), USBPWR is never set, and our timer-based\n+ * EP0 simulation never starts — a permanent deadlock. Pre-setting SESVD\n+ * breaks the cycle: firmware sees VBUS valid, calls Attach(), sets USBPWR,\n+ * and our EP0 timer fires as expected.\n+ */\n+ s->otgstat = 0x08u;\n+ s->otgcon = 0;\n+ s->pwrc = 0;\n+ s->uir = 0;\n+ s->uie = 0;\n+ s->ueir = 0;\n+ s->ueie = 0;\n+ s->ustat = 0;\n+ s->ucon = 0;\n+ s->uaddr = 0;\n+ s->bdtp1 = 0;\n+ s->bdtp2 = 0;\n+ s->bdtp3 = 0;\n+ s->ufrml = 0;\n+ s->ufrmh = 0;\n+ s->utok = 0;\n+ s->usof = 0x4Bu;\n+ s->cnfg1 = 0;\n+ memset(s->uep, 0, sizeof(s->uep));\n+\n+ s->ep0_sim = EP0_SIM_IDLE;\n+ s->configured = false;\n+ s->sesvd_edge_latched = false;\n+ s->sesvd_acked = false;\n+ s->sesvd_retry_count = 0;\n+\n+ /* USTAT FIFO reset */\n+ memset(s->stat_fifo, 0, sizeof(s->stat_fifo));\n+ s->stat_fifo_head = 0;\n+ s->stat_fifo_tail = 0;\n+ s->stat_fifo_count = 0;\n+\n+ if (s->usb_timer) {\n+ timer_del(s->usb_timer);\n+ }\n+\n+ qemu_set_irq(s->irq, 0);\n+}\n+\n+static void pic32mk_usb_init(Object *obj)\n+{\n+ PIC32MKUSBState *s = PIC32MK_USB(obj);\n+\n+ memory_region_init_io(&s->sfr_mmio, obj, &usb_ops, s,\n+ TYPE_PIC32MK_USB, PIC32MK_USB_SFR_SIZE);\n+ sysbus_init_mmio(SYS_BUS_DEVICE(obj), &s->sfr_mmio);\n+ sysbus_init_irq(SYS_BUS_DEVICE(obj), &s->irq);\n+\n+ s->usb_timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, usb_timer_cb, s);\n+ s->ep0_sim = EP0_SIM_IDLE;\n+ s->configured = false;\n+ s->sesvd_edge_latched = false;\n+ s->sesvd_acked = false;\n+ s->sesvd_retry_count = 0;\n+ s->stat_fifo_head = 0;\n+ s->stat_fifo_tail = 0;\n+ s->stat_fifo_count = 0;\n+}\n+\n+static void pic32mk_usb_realize(DeviceState *dev, Error **errp)\n+{\n+ PIC32MKUSBState *s = PIC32MK_USB(dev);\n+ if (qemu_chr_fe_backend_connected(&s->chr)) {\n+ qemu_chr_fe_set_handlers(&s->chr,\n+ usb_chr_can_receive,\n+ usb_chr_receive,\n+ NULL, NULL,\n+ s, NULL, true);\n+ }\n+}\n+\n+static const Property pic32mk_usb_properties[] = {\n+ DEFINE_PROP_CHR(\"chardev\", PIC32MKUSBState, chr),\n+};\n+\n+static void pic32mk_usb_class_init(ObjectClass *klass, const void *data)\n+{\n+ DeviceClass *dc = DEVICE_CLASS(klass);\n+ device_class_set_legacy_reset(dc, pic32mk_usb_reset);\n+ device_class_set_props(dc, pic32mk_usb_properties);\n+ dc->realize = pic32mk_usb_realize;\n+ dc->desc = \"PIC32MK USB OTG Full-Speed\";\n+}\n+\n+static const TypeInfo pic32mk_usb_info = {\n+ .name = TYPE_PIC32MK_USB,\n+ .parent = TYPE_SYS_BUS_DEVICE,\n+ .instance_size = sizeof(PIC32MKUSBState),\n+ .instance_init = pic32mk_usb_init,\n+ .class_init = pic32mk_usb_class_init,\n+};\n+\n+static void pic32mk_usb_register_types(void)\n+{\n+ type_register_static(&pic32mk_usb_info);\n+}\n+\n+type_init(pic32mk_usb_register_types)\ndiff --git a/hw/mips/pic32mk_wdt.c b/hw/mips/pic32mk_wdt.c\nnew file mode 100644\nindex 0000000000..93d61bf55f\n--- /dev/null\n+++ b/hw/mips/pic32mk_wdt.c\n@@ -0,0 +1,230 @@\n+/*\n+ * Microchip PIC32MK — Watchdog Timer (WDT)\n+ * Datasheet: DS60001519E §17, Register 17-1\n+ *\n+ * Models the WDTCON register, the clear-key mechanism (write 0x5743\n+ * to upper 16 bits via 16-bit write to WDTCON+2), and a real countdown\n+ * timer that triggers a system reset if firmware fails to clear the\n+ * WDT in time.\n+ *\n+ * WDTCON layout:\n+ * bits[31:16] WDTCLRKEY — write 0x5743 here to clear the counter\n+ * bit 15 ON — WDT enable\n+ * bits[12:8] RUNDIV — read-only post-scaler (reflects config bits)\n+ * bits[5:1] SLPDIV — read-only post-scaler (reflects config bits)\n+ * bit 0 WDTWINEN — windowed mode enable\n+ *\n+ * The timeout period is derived from the LPRC (32 kHz) oscillator\n+ * and the RUNDIV post-scaler. Default RUNDIV=0 ≈ 1 ms base period,\n+ * giving a ~1 s timeout with the default post-scaler (WDTPS ≈ 20).\n+ * In emulation we use a configurable period (default 2 s) that can\n+ * be tuned via the \"timeout-ms\" qdev property.\n+ *\n+ * Copyright (c) 2026 QEMU contributors\n+ * SPDX-License-Identifier: GPL-2.0-or-later\n+ */\n+\n+#include \"qemu/osdep.h\"\n+#include \"qemu/log.h\"\n+#include \"qemu/module.h\"\n+#include \"qemu/timer.h\"\n+#include \"hw/core/sysbus.h\"\n+#include \"hw/core/qdev-properties.h\"\n+#include \"hw/mips/pic32mk.h\"\n+#include \"system/runstate.h\"\n+\n+/*\n+ * WDTCON register bit positions (DS60001519E §17, Register 17-1)\n+ * -----------------------------------------------------------------------\n+ */\n+#define WDTCON_ON (1u << 15) /* WDT enable */\n+#define WDTCON_WDTWINEN (1u << 0) /* Windowed mode enable — bit 0 */\n+#define WDTCON_WR_MASK (WDTCON_ON | WDTCON_WDTWINEN) /* writable bits */\n+\n+/* Upper-halfword clear key written by firmware WDT_Clear() */\n+#define WDT_CLRKEY 0x5743u\n+\n+/* Default timeout in milliseconds (~2 s, conservative for emulation) */\n+#define WDT_DEFAULT_TIMEOUT_MS 2000\n+\n+#define TYPE_PIC32MK_WDT \"pic32mk-wdt\"\n+OBJECT_DECLARE_SIMPLE_TYPE(PIC32MKWDTState, PIC32MK_WDT)\n+\n+typedef struct PIC32MKWDTState {\n+ SysBusDevice parent_obj;\n+ MemoryRegion mmio;\n+ uint32_t wdtcon; /* WDTCON register value */\n+ QEMUTimer timer; /* Countdown timer */\n+ uint32_t timeout_ms; /* Timeout period (ms), qdev property */\n+} PIC32MKWDTState;\n+\n+/*\n+ * Timer helpers\n+ * -----------------------------------------------------------------------\n+ */\n+\n+static void pic32mk_wdt_rearm(PIC32MKWDTState *s)\n+{\n+ int64_t now = qemu_clock_get_ms(QEMU_CLOCK_VIRTUAL);\n+ timer_mod(&s->timer, now + s->timeout_ms);\n+}\n+\n+static void pic32mk_wdt_stop(PIC32MKWDTState *s)\n+{\n+ timer_del(&s->timer);\n+}\n+\n+static void pic32mk_wdt_update(PIC32MKWDTState *s, uint32_t old)\n+{\n+ bool was_on = (old & WDTCON_ON) != 0;\n+ bool is_on = (s->wdtcon & WDTCON_ON) != 0;\n+\n+ if (is_on && !was_on) {\n+ /* Turning ON — start countdown */\n+ pic32mk_wdt_rearm(s);\n+ } else if (!is_on && was_on) {\n+ /* Turning OFF — stop countdown */\n+ pic32mk_wdt_stop(s);\n+ }\n+}\n+\n+/* Timer callback — WDT expired, trigger system reset */\n+static void pic32mk_wdt_expire(void *opaque)\n+{\n+ PIC32MKWDTState *s = PIC32MK_WDT(opaque);\n+\n+ if (!(s->wdtcon & WDTCON_ON)) {\n+ return; /* Race: turned off between schedule and callback */\n+ }\n+\n+ qemu_log_mask(LOG_GUEST_ERROR,\n+ \"pic32mk-wdt: watchdog timeout — system reset\\n\");\n+ qemu_system_reset_request(SHUTDOWN_CAUSE_GUEST_RESET);\n+}\n+\n+/*\n+ * Read handler — WDTCON + SET/CLR/INV aliases\n+ * -----------------------------------------------------------------------\n+ */\n+static uint64_t pic32mk_wdt_read(void *opaque, hwaddr addr, unsigned size)\n+{\n+ PIC32MKWDTState *s = PIC32MK_WDT(opaque);\n+\n+ switch (addr & ~0x3u) {\n+ case 0x00:\n+ /* WDTCON (base, +4 SET, +8 CLR, +C INV all read same) */;\n+ case 0x04:\n+ case 0x08:\n+ case 0x0C:\n+ return s->wdtcon;\n+ default:\n+ qemu_log_mask(LOG_UNIMP,\n+ \"pic32mk-wdt: unimplemented read @ 0x%\" HWADDR_PRIx \"\\n\",\n+ addr);\n+ return 0;\n+ }\n+}\n+\n+/*\n+ * Write handler — WDTCON + SET/CLR/INV; clear-key on upper 16 bits\n+ * -----------------------------------------------------------------------\n+ */\n+static void pic32mk_wdt_write(void *opaque, hwaddr addr, uint64_t val,\n+ unsigned size)\n+{\n+ PIC32MKWDTState *s = PIC32MK_WDT(opaque);\n+ uint32_t old = s->wdtcon;\n+\n+ /*\n+ * firmware WDT_Clear() does a 16-bit write of 0x5743 to WDTCON+2\n+ * (the upper halfword). Detect by: addr==2, size==2, val==0x5743.\n+ * Restart the countdown timer.\n+ */\n+ if (addr == 2 && size == 2 && (uint16_t)val == WDT_CLRKEY) {\n+ if (s->wdtcon & WDTCON_ON) {\n+ pic32mk_wdt_rearm(s);\n+ }\n+ return;\n+ }\n+\n+ switch (addr & 0xCu) {\n+ case 0x0:\n+ /* WDTCON write */;\n+ s->wdtcon = (uint32_t)val & WDTCON_WR_MASK;\n+ break;\n+ case 0x4:\n+ /* SET */;\n+ s->wdtcon |= (uint32_t)val & WDTCON_WR_MASK;\n+ break;\n+ case 0x8:\n+ /* CLR */;\n+ s->wdtcon &= ~((uint32_t)val & WDTCON_WR_MASK);\n+ break;\n+ case 0xC:\n+ /* INV */;\n+ s->wdtcon ^= (uint32_t)val & WDTCON_WR_MASK;\n+ break;\n+ }\n+\n+ pic32mk_wdt_update(s, old);\n+}\n+\n+static const MemoryRegionOps pic32mk_wdt_ops = {\n+ .read = pic32mk_wdt_read,\n+ .write = pic32mk_wdt_write,\n+ .endianness = DEVICE_LITTLE_ENDIAN,\n+ .valid = {\n+ .min_access_size = 2,\n+ .max_access_size = 4,\n+ },\n+ .impl = {\n+ .min_access_size = 2,\n+ .max_access_size = 4,\n+ },\n+};\n+\n+static void pic32mk_wdt_realize(DeviceState *dev, Error **errp)\n+{\n+ PIC32MKWDTState *s = PIC32MK_WDT(dev);\n+ memory_region_init_io(&s->mmio, OBJECT(s), &pic32mk_wdt_ops, s,\n+ \"pic32mk-wdt\", PIC32MK_WDT_SIZE);\n+ sysbus_init_mmio(SYS_BUS_DEVICE(dev), &s->mmio);\n+ timer_init_ms(&s->timer, QEMU_CLOCK_VIRTUAL,\n+ pic32mk_wdt_expire, s);\n+}\n+\n+static void pic32mk_wdt_reset_hold(Object *obj, ResetType type)\n+{\n+ PIC32MKWDTState *s = PIC32MK_WDT(obj);\n+ timer_del(&s->timer);\n+ s->wdtcon = 0;\n+}\n+\n+static const Property pic32mk_wdt_properties[] = {\n+ DEFINE_PROP_UINT32(\"timeout-ms\", PIC32MKWDTState, timeout_ms,\n+ WDT_DEFAULT_TIMEOUT_MS),\n+};\n+\n+static void pic32mk_wdt_class_init(ObjectClass *klass, const void *data)\n+{\n+ DeviceClass *dc = DEVICE_CLASS(klass);\n+ ResettableClass *rc = RESETTABLE_CLASS(klass);\n+ dc->realize = pic32mk_wdt_realize;\n+ dc->desc = \"PIC32MK Watchdog Timer\";\n+ device_class_set_props(dc, pic32mk_wdt_properties);\n+ rc->phases.hold = pic32mk_wdt_reset_hold;\n+}\n+\n+static const TypeInfo pic32mk_wdt_info = {\n+ .name = TYPE_PIC32MK_WDT,\n+ .parent = TYPE_SYS_BUS_DEVICE,\n+ .instance_size = sizeof(PIC32MKWDTState),\n+ .class_init = pic32mk_wdt_class_init,\n+};\n+\n+static void pic32mk_wdt_register_types(void)\n+{\n+ type_register_static(&pic32mk_wdt_info);\n+}\n+\n+type_init(pic32mk_wdt_register_types)\ndiff --git a/include/hw/mips/pic32mk.h b/include/hw/mips/pic32mk.h\nnew file mode 100644\nindex 0000000000..e1317183d0\n--- /dev/null\n+++ b/include/hw/mips/pic32mk.h\n@@ -0,0 +1,952 @@\n+/*\n+ * PIC32MK GPK/MCM with CAN FD — shared constants\n+ * Datasheet: DS60001519E\n+ *\n+ * Copyright (c) 2026 QEMU contributors\n+ * SPDX-License-Identifier: GPL-2.0-or-later\n+ */\n+\n+#ifndef HW_MIPS_PIC32MK_H\n+#define HW_MIPS_PIC32MK_H\n+\n+/*\n+ * Physical memory map (§4, pp. 70-73)\n+ * -----------------------------------------------------------------------\n+ */\n+\n+#define PIC32MK_RAM_BASE 0x00000000u /* 256 KB SRAM */\n+#define PIC32MK_RAM_SIZE (256 * 1024)\n+\n+#define PIC32MK_PFLASH_BASE 0x1D000000u /* 1 MB Program Flash */\n+#define PIC32MK_PFLASH_SIZE (1 * 1024 * 1024)\n+\n+/*\n+ * Boot vector ROM — fills the gap between the MIPS reset vector (0x1FC00000)\n+ * and Boot Flash 1 (0x1FC40000). Contains a trampoline that jumps to BFlash1.\n+ */\n+#define PIC32MK_BOOTVEC_BASE 0x1FC00000u /* physical reset vector */\n+#define PIC32MK_BOOTVEC_SIZE 0x00040000u /* 256 KB gap to BFlash1 */\n+\n+#define PIC32MK_BFLASH1_BASE 0x1FC40000u /* Boot Flash 1 — enlarged for firmware */\n+#define PIC32MK_BFLASH1_SIZE (256 * 1024)\n+\n+#define PIC32MK_BFLASH2_BASE 0x1FC60000u /* Boot Flash 2 ~20 KB */\n+#define PIC32MK_BFLASH2_SIZE (20 * 1024)\n+\n+#define PIC32MK_SFR_BASE 0x1F800000u /* SFR window, 1 MB */\n+#define PIC32MK_SFR_SIZE (1 * 1024 * 1024)\n+\n+/* Reset vector (KSEG1 uncached alias of 0x1FC00000) */\n+#define PIC32MK_RESET_VECTOR 0xBFC00000u\n+\n+/*\n+ * SFR sub-block bases (offsets within the 1 MB SFR window, Table 4-2)\n+ * All addresses below are KSEG1 virtual (0xBF800000 + offset).\n+ * -----------------------------------------------------------------------\n+ */\n+\n+/* CFG / PMD block (0xBF800000) */\n+#define PIC32MK_CFG_OFFSET 0x000000u\n+#define PIC32MK_CFG_SIZE 0x000900u /* CFGCON..CFGCON2+INV + CHECON@0x800 */\n+\n+/* CRU — Clock Reference Unit (0xBF801200) */\n+#define PIC32MK_CRU_OFFSET 0x001200u\n+#define PIC32MK_CRU_SIZE 0x0001A0u /* OSCCON..CLKSTAT+INV inclusive */\n+\n+/* PPS (Peripheral Pin Select) input/output registers (0xBF801400–0xBF8017FF) */\n+#define PIC32MK_PPS_OFFSET 0x001400u\n+#define PIC32MK_PPS_SIZE 0x000400u\n+\n+/* WDT register block (0xBF800C00) */\n+#define PIC32MK_WDT_OFFSET 0x000C00u\n+#define PIC32MK_WDT_SIZE 0x000040u /* WDTCON + SET/CLR/INV + padding */\n+\n+/* EVIC 0xBF810000 */\n+#define PIC32MK_EVIC_OFFSET 0x010000u\n+\n+/* DMA (shares page with EVIC) 0xBF811000 */\n+#define PIC32MK_DMA_OFFSET 0x011000u\n+\n+/* Timers / IC / OC / I2C1-2 / SPI1-2 / UART1-2 / PWM / QEI / CMP / CDAC1 */\n+#define PIC32MK_PER1_OFFSET 0x020000u /* 0xBF820000, size 0xD000 */\n+\n+/* I2C3-4 / SPI3-6 / UART3-6 / CDAC2-3 0xBF840000 */\n+#define PIC32MK_PER2_OFFSET 0x040000u\n+\n+/* GPIO PORTA-PORTG 0xBF860000 */\n+#define PIC32MK_GPIO_OFFSET 0x060000u\n+\n+/* CAN1-4 / ADC 0xBF880000 */\n+#define PIC32MK_CAN_OFFSET 0x080000u\n+\n+/*\n+ * CAN1–4 SFR offsets from SFR base\n+ * Verified against DS60001519E / p32mk1024mcm100.h:\n+ * CFD1CON @ 0xBF880000, CFD2CON @ 0xBF881000,\n+ * CFD3CON @ 0xBF884000, CFD4CON @ 0xBF885000\n+ */\n+#define PIC32MK_CAN1_OFFSET 0x080000u /* physical 0x1F880000 */\n+#define PIC32MK_CAN2_OFFSET 0x081000u /* physical 0x1F881000 */\n+#define PIC32MK_CAN3_OFFSET 0x084000u /* physical 0x1F884000 */\n+#define PIC32MK_CAN4_OFFSET 0x085000u /* physical 0x1F885000 */\n+#define PIC32MK_CAN_SFR_SIZE 0x1000u /* 4 KB SFR block per instance */\n+\n+/*\n+ * Message RAM physical base per instance.\n+ * Allocated just above the SFR window, beyond the ADC / USB blocks.\n+ * ⚠ Verify exact addresses from DS60001519E §4 before final integration.\n+ * Each instance gets 76 KB (75 KB data + alignment).\n+ */\n+#define PIC32MK_CAN1_MSGRAM_BASE 0x1F900000u\n+#define PIC32MK_CAN2_MSGRAM_BASE 0x1F913000u\n+#define PIC32MK_CAN3_MSGRAM_BASE 0x1F926000u\n+#define PIC32MK_CAN4_MSGRAM_BASE 0x1F939000u\n+#define PIC32MK_CAN_MSGRAM_SIZE (75u * 1024u) /* max 74 KB, rounded up */\n+\n+/*\n+ * USB OTG 1 _USB_BASE_ADDRESS = 0xBF889040 (p32mk1024mcm100.h / xc.h)\n+ * struct usb_registers_t pointer starts at 0xBF889040 (= device_base + 0x040).\n+ * Device MMIO window starts at 0xBF889000 so UxOTGIR lands at offset 0x040.\n+ */\n+#define PIC32MK_USB1_OFFSET 0x089000u\n+#define PIC32MK_USB_OFFSET PIC32MK_USB1_OFFSET /* legacy alias */\n+\n+/* USB OTG 2 _USB2_BASE_ADDRESS = 0xBF88A040 */\n+#define PIC32MK_USB2_OFFSET 0x08A000u\n+\n+/* RTCC 0xBF8C0000 */\n+#define PIC32MK_RTCC_OFFSET 0x0C0000u\n+\n+/*\n+ * Reset-control registers (§7, p. 114) — offsets from SFR base\n+ * -----------------------------------------------------------------------\n+ */\n+\n+#define PIC32MK_RCON_OFFSET 0x001240u /* Reset Control */\n+#define PIC32MK_RSWRST_OFFSET 0x001250u /* Software Reset trigger */\n+#define PIC32MK_RNMICON_OFFSET 0x001260u /* NMI Control */\n+#define PIC32MK_PWRCON_OFFSET 0x001270u /* Power Control */\n+\n+/* RCON reset flags (§7 register map) */\n+#define PIC32MK_RCON_POR (1u << 2) /* Power-on Reset */\n+#define PIC32MK_RCON_BOR (1u << 3) /* Brown-out Reset */\n+\n+/*\n+ * CFG / PMD registers (§6) — offsets from PIC32MK_CFG_OFFSET (0xBF800000)\n+ * -----------------------------------------------------------------------\n+ */\n+\n+#define PIC32MK_CFGCON 0x0000u /* Configuration control */\n+#define PIC32MK_SYSKEY 0x0030u /* System key unlock */\n+#define PIC32MK_PMD1 0x0040u /* Peripheral Module Disable 1 */\n+#define PIC32MK_PMD2 0x0050u\n+#define PIC32MK_PMD3 0x0060u\n+#define PIC32MK_PMD4 0x0070u\n+#define PIC32MK_PMD5 0x0080u\n+#define PIC32MK_PMD6 0x0090u\n+#define PIC32MK_PMD7 0x00A0u\n+#define PIC32MK_CFGCON2 0x0110u /* Extended configuration control */\n+#define PIC32MK_CHECON 0x0800u /* Prefetch Cache Control (CHECON) */\n+\n+/* CFGCON bits */\n+#define PIC32MK_CFGCON_PGLOCK (1u << 28) /* Permission-group lock */\n+#define PIC32MK_CFGCON_PMDLOCK (1u << 29) /* PMD lock */\n+#define PIC32MK_CFGCON_IOLOCK (1u << 30) /* I/O lock */\n+\n+/* Number of PMD registers */\n+#define PIC32MK_PMD_COUNT 7\n+\n+/*\n+ * CRU registers (§9) — offsets from PIC32MK_CRU_OFFSET (0xBF801200)\n+ * Each register has SET/CLR/INV aliases at +4/+8/+C.\n+ * -----------------------------------------------------------------------\n+ */\n+\n+/* Oscillator / PLL registers */\n+#define PIC32MK_CRU_OSCCON 0x00u /* Oscillator Control */\n+#define PIC32MK_CRU_OSCTUN 0x10u /* Oscillator Tuning */\n+#define PIC32MK_CRU_SPLLCON 0x20u /* System PLL Control */\n+#define PIC32MK_CRU_UPLLCON 0x30u /* USB PLL Control */\n+\n+/* Reset-control registers (formerly in separate RCON stub) */\n+#define PIC32MK_CRU_RCON 0x40u /* Reset Control */\n+#define PIC32MK_CRU_RSWRST 0x50u /* Software Reset trigger */\n+#define PIC32MK_CRU_RNMICON 0x60u /* NMI Control */\n+#define PIC32MK_CRU_PWRCON 0x70u /* Power Control */\n+\n+/* Reference clock outputs 1–4 (CON + TRIM pairs, 0x20 stride) */\n+#define PIC32MK_CRU_REFO1CON 0x80u\n+#define PIC32MK_CRU_REFO1TRIM 0x90u\n+#define PIC32MK_CRU_REFO2CON 0xA0u\n+#define PIC32MK_CRU_REFO2TRIM 0xB0u\n+#define PIC32MK_CRU_REFO3CON 0xC0u\n+#define PIC32MK_CRU_REFO3TRIM 0xD0u\n+#define PIC32MK_CRU_REFO4CON 0xE0u\n+#define PIC32MK_CRU_REFO4TRIM 0xF0u\n+\n+/* Peripheral bus clock dividers 1–7 (0x10 stride) */\n+#define PIC32MK_CRU_PB1DIV 0x100u\n+#define PIC32MK_CRU_PB2DIV 0x110u\n+#define PIC32MK_CRU_PB3DIV 0x120u\n+#define PIC32MK_CRU_PB4DIV 0x130u\n+#define PIC32MK_CRU_PB5DIV 0x140u\n+#define PIC32MK_CRU_PB6DIV 0x150u\n+#define PIC32MK_CRU_PB7DIV 0x160u\n+\n+/* Clock status */\n+#define PIC32MK_CRU_CLKSTAT 0x190u\n+\n+/* Number of reference clocks and peripheral buses */\n+#define PIC32MK_CRU_NREFO 4\n+#define PIC32MK_CRU_NPB 7\n+\n+/* OSCCON bits (§9, Register 9-1) */\n+#define PIC32MK_OSCCON_OSWEN (1u << 0)\n+#define PIC32MK_OSCCON_SOSCEN (1u << 1)\n+#define PIC32MK_OSCCON_CF (1u << 3)\n+#define PIC32MK_OSCCON_SLPEN (1u << 4)\n+#define PIC32MK_OSCCON_CLKLOCK (1u << 7)\n+#define PIC32MK_OSCCON_NOSC_MASK 0x00000700u /* bits [10:8] */\n+#define PIC32MK_OSCCON_NOSC_SHIFT 8\n+#define PIC32MK_OSCCON_COSC_MASK 0x00007000u /* bits [14:12] */\n+#define PIC32MK_OSCCON_COSC_SHIFT 12\n+#define PIC32MK_OSCCON_FRCDIV_MASK 0x07000000u /* bits [26:24] */\n+#define PIC32MK_OSCCON_FRCDIV_SHIFT 24\n+\n+/* SPLLCON bits (§9, Register 9-4) */\n+#define PIC32MK_SPLLCON_PLLRANGE_MASK 0x00000007u /* bits [2:0] */\n+#define PIC32MK_SPLLCON_PLLICLK (1u << 7)\n+#define PIC32MK_SPLLCON_PLLIDIV_MASK 0x00000700u /* bits [10:8] */\n+#define PIC32MK_SPLLCON_PLLMULT_MASK 0x007F0000u /* bits [22:16] */\n+#define PIC32MK_SPLLCON_PLLODIV_MASK 0x07000000u /* bits [26:24] */\n+\n+/* UPLLCON bits (§9) — same layout as SPLLCON plus UPOSCEN */\n+#define PIC32MK_UPLLCON_UPOSCEN (1u << 25)\n+\n+/* REFOxCON bits (§9, Register 9-16) */\n+#define PIC32MK_REFOCON_ROSEL_MASK 0x0000000Fu /* bits [3:0] */\n+#define PIC32MK_REFOCON_ACTIVE (1u << 8)\n+#define PIC32MK_REFOCON_DIVSWEN (1u << 9)\n+#define PIC32MK_REFOCON_RSLP (1u << 11)\n+#define PIC32MK_REFOCON_OE (1u << 12)\n+#define PIC32MK_REFOCON_SIDL (1u << 13)\n+#define PIC32MK_REFOCON_ON (1u << 15)\n+#define PIC32MK_REFOCON_RODIV_MASK 0xFFFF0000u /* bits [31:16] */\n+\n+/* PBxDIV bits (§9) */\n+#define PIC32MK_PBDIV_PBDIV_MASK 0x0000007Fu /* bits [6:0] */\n+#define PIC32MK_PBDIV_PBDIVRDY (1u << 11)\n+#define PIC32MK_PBDIV_ON (1u << 15)\n+\n+/* CLKSTAT bits (§9, Register 9-31) */\n+#define PIC32MK_CLKSTAT_FRCRDY (1u << 0)\n+#define PIC32MK_CLKSTAT_POSCRDY (1u << 2)\n+#define PIC32MK_CLKSTAT_SOSCRDY (1u << 4)\n+#define PIC32MK_CLKSTAT_LPRCRDY (1u << 5)\n+#define PIC32MK_CLKSTAT_SPLLRDY (1u << 7)\n+#define PIC32MK_CLKSTAT_UPLLRDY (1u << 8)\n+#define PIC32MK_CLKSTAT_ALL_RDY (PIC32MK_CLKSTAT_FRCRDY | \\\n+ PIC32MK_CLKSTAT_POSCRDY | \\\n+ PIC32MK_CLKSTAT_SOSCRDY | \\\n+ PIC32MK_CLKSTAT_LPRCRDY | \\\n+ PIC32MK_CLKSTAT_SPLLRDY | \\\n+ PIC32MK_CLKSTAT_UPLLRDY)\n+\n+/*\n+ * EVIC registers (§8, p. 159) — offsets from 0xBF810000\n+ * -----------------------------------------------------------------------\n+ */\n+\n+#define PIC32MK_EVIC_INTCON 0x0000u /* MVEC, TPC, INTxEP */\n+#define PIC32MK_EVIC_PRISS 0x0010u /* Priority shadow reg select */\n+#define PIC32MK_EVIC_INTSTAT 0x0020u /* Last IRQ serviced, SRIPL */\n+#define PIC32MK_EVIC_IPTMR 0x0030u /* Interrupt proximity timer */\n+#define PIC32MK_EVIC_IFS0 0x0040u /* Interrupt flag status [0..7] */\n+#define PIC32MK_EVIC_IEC0 0x00C0u /* Interrupt enable control [0..7] */\n+#define PIC32MK_EVIC_IPC0 0x0140u /* Interrupt priority control [0..63] */\n+#define PIC32MK_EVIC_OFF0 0x0540u /* Vector address offsets [0..190] */\n+\n+/*\n+ * Number of interrupt sources / vectors.\n+ * The EVIC IFS/IEC registers span 8×32 = 256 bits; USB1 (244) and USB2 (246)\n+ * are in IFS7. Use 256 to cover the full IPC/IFS table.\n+ */\n+#define PIC32MK_NUM_IRQ_SOURCES 256\n+#define PIC32MK_NUM_VECTORS 190\n+\n+/*\n+ * UART1 registers (§21, Table 21-2) — U1MODE base 0xBF828000\n+ * Offset from SFR base: 0x028000\n+ * -----------------------------------------------------------------------\n+ */\n+\n+#define PIC32MK_UART1_OFFSET 0x028000u /* SFR offset → physical 0x1F828000 */\n+#define PIC32MK_UART1_SIZE 0x000050u /* U1MODE..U1BRG+INV */\n+\n+/* Register offsets within the UART block (same layout for all UARTs) */\n+#define PIC32MK_UxMODE 0x00u /* Mode */\n+#define PIC32MK_UxSTA 0x10u /* Status & control */\n+#define PIC32MK_UxTXREG 0x20u /* TX data */\n+#define PIC32MK_UxRXREG 0x30u /* RX data */\n+#define PIC32MK_UxBRG 0x40u /* Baud rate */\n+\n+/* U1STA bits relevant to TX polling */\n+#define PIC32MK_USTA_TRMT (1u << 8) /* TX shift register empty (1=empty) */\n+#define PIC32MK_USTA_UTXBF (1u << 9) /* TX buffer full (0=not full) */\n+\n+/*\n+ * CPU clock (§3)\n+ * -----------------------------------------------------------------------\n+ */\n+\n+#define PIC32MK_CPU_HZ 120000000u /* 120 MHz max */\n+\n+/*\n+ * Interrupt source numbers (§8, Table 8-1, DS60001519E)\n+ * TODO: verify each number against the actual datasheet table.\n+ * -----------------------------------------------------------------------\n+ */\n+\n+#define PIC32MK_IRQ_CT 0 /* Core Timer */\n+#define PIC32MK_IRQ_CS0 1 /* Core Software Interrupt 0 */\n+#define PIC32MK_IRQ_CS1 2 /* Core Software Interrupt 1 */\n+#define PIC32MK_IRQ_INT0 3 /* External Interrupt 0 */\n+#define PIC32MK_IRQ_T1 4 /* Timer 1 */\n+#define PIC32MK_IRQ_T2 9 /* Timer 2 */\n+#define PIC32MK_IRQ_T3 14 /* Timer 3 */\n+#define PIC32MK_IRQ_T4 19 /* Timer 4 */\n+#define PIC32MK_IRQ_T5 24 /* Timer 5 */\n+#define PIC32MK_IRQ_T6 28 /* Timer 6 */\n+#define PIC32MK_IRQ_T7 32 /* Timer 7 */\n+#define PIC32MK_IRQ_T8 36 /* Timer 8 */\n+#define PIC32MK_IRQ_T9 40 /* Timer 9 */\n+/* UART1: error=38, RX=39, TX=40 (IFS1 bits 6/7/8, p32mk1024mcm100.h) */\n+#define PIC32MK_IRQ_U1E 38\n+#define PIC32MK_IRQ_U1RX 39\n+#define PIC32MK_IRQ_U1TX 40\n+#define PIC32MK_IRQ_U2E 115\n+#define PIC32MK_IRQ_U2RX 116\n+#define PIC32MK_IRQ_U2TX 117\n+#define PIC32MK_IRQ_U3E 118\n+#define PIC32MK_IRQ_U3RX 119\n+#define PIC32MK_IRQ_U3TX 120\n+#define PIC32MK_IRQ_U4E 121\n+#define PIC32MK_IRQ_U4RX 122\n+#define PIC32MK_IRQ_U4TX 123\n+#define PIC32MK_IRQ_U5E 124\n+#define PIC32MK_IRQ_U5RX 125\n+#define PIC32MK_IRQ_U5TX 126\n+#define PIC32MK_IRQ_U6E 127\n+#define PIC32MK_IRQ_U6RX 128\n+#define PIC32MK_IRQ_U6TX 129\n+/* CAN FD — single IRQ per instance (§8, Table 8-1, DS60001519E) */\n+#define PIC32MK_IRQ_CAN1 167\n+#define PIC32MK_IRQ_CAN2 168\n+#define PIC32MK_IRQ_CAN3 187\n+#define PIC32MK_IRQ_CAN4 188\n+\n+/*\n+ * USB OTG — interrupt vector numbers from XC32 p32mk1024mcm100.h\n+ * USB1: vector 34 → IFS1 bit 2, IPC8[18:16]\n+ * USB2: vector 244 → IFS7 bit 20, IPC61[2:0]\n+ */\n+#define PIC32MK_IRQ_USB1 34\n+#define PIC32MK_IRQ_USB2 244\n+\n+/* DMA channels 0-7 */\n+#define PIC32MK_IRQ_DMA0 134\n+#define PIC32MK_IRQ_DMA1 135\n+#define PIC32MK_IRQ_DMA2 136\n+#define PIC32MK_IRQ_DMA3 137\n+#define PIC32MK_IRQ_DMA4 138\n+#define PIC32MK_IRQ_DMA5 139\n+#define PIC32MK_IRQ_DMA6 140\n+#define PIC32MK_IRQ_DMA7 141\n+\n+/*\n+ * GPIO Change-Notice interrupt vectors (DS60001519E Table 8-1,\n+ * _CHANGE_NOTICE_x_VECTOR from p32mk1024mcm100.h)\n+ */\n+#define PIC32MK_IRQ_CNA 44\n+#define PIC32MK_IRQ_CNB 45\n+#define PIC32MK_IRQ_CNC 46\n+#define PIC32MK_IRQ_CND 47\n+#define PIC32MK_IRQ_CNE 48\n+#define PIC32MK_IRQ_CNF 49\n+#define PIC32MK_IRQ_CNG 50\n+\n+/*\n+ * Timer peripheral registers (§14, DS60001519E)\n+ * Offsets from PIC32MK_PER1_OFFSET (0xBF820000).\n+ * Each timer block is 0x200 bytes; register stride 0x10 (with SET/CLR/INV).\n+ * TODO: verify exact base offsets for PIC32MK GPK from Table 4-2.\n+ * -----------------------------------------------------------------------\n+ */\n+\n+/* Timer base offsets from SFR base */\n+#define PIC32MK_T1_OFFSET 0x020000u /* Timer 1 (Type A, 16-bit) */\n+#define PIC32MK_T2_OFFSET 0x020200u /* Timer 2 (Type B) */\n+#define PIC32MK_T3_OFFSET 0x020400u /* Timer 3 (Type C) */\n+#define PIC32MK_T4_OFFSET 0x020600u /* Timer 4 (Type B) */\n+#define PIC32MK_T5_OFFSET 0x020800u /* Timer 5 (Type C) */\n+#define PIC32MK_T6_OFFSET 0x020A00u /* Timer 6 (Type B) */\n+#define PIC32MK_T7_OFFSET 0x020C00u /* Timer 7 (Type C) */\n+#define PIC32MK_T8_OFFSET 0x020E00u /* Timer 8 (Type B) */\n+#define PIC32MK_T9_OFFSET 0x021000u /* Timer 9 (Type C) */\n+\n+#define PIC32MK_TIMER_BLOCK_SIZE 0x200u /* per-timer SFR block */\n+\n+/* Timer register offsets within block (with SET/CLR/INV at +4/+8/+C) */\n+#define PIC32MK_TxCON 0x00u /* Control: ON, TCKPS, T32, TCS... */\n+#define PIC32MK_TMRx 0x10u /* Current count */\n+#define PIC32MK_PRx 0x20u /* Period register */\n+\n+/* TxCON bits */\n+#define PIC32MK_TCON_ON (1u << 15) /* Timer ON */\n+#define PIC32MK_TCON_T32 (1u << 3) /* 32-bit mode (Type B only) */\n+#define PIC32MK_TCON_TCS (1u << 1) /* Clock source select */\n+#define PIC32MK_TCON_TCKPS_MASK 0x0070u /* Prescaler bits [6:4] */\n+#define PIC32MK_TCON_TCKPS_SHIFT 4\n+\n+/*\n+ * OC (Output Compare) register offsets from SFR base (§19, DS60001519E)\n+ * OC1-OC9: 0xBF824000..0xBF825000 (0x200 stride)\n+ * OC10-OC16: 0xBF845200..0xBF845E00 (0x200 stride)\n+ * -----------------------------------------------------------------------\n+ */\n+#define PIC32MK_OC1_OFFSET 0x024000u\n+#define PIC32MK_OC2_OFFSET 0x024200u\n+#define PIC32MK_OC3_OFFSET 0x024400u\n+#define PIC32MK_OC4_OFFSET 0x024600u\n+#define PIC32MK_OC5_OFFSET 0x024800u\n+#define PIC32MK_OC6_OFFSET 0x024A00u\n+#define PIC32MK_OC7_OFFSET 0x024C00u\n+#define PIC32MK_OC8_OFFSET 0x024E00u\n+#define PIC32MK_OC9_OFFSET 0x025000u\n+#define PIC32MK_OC10_OFFSET 0x045200u\n+#define PIC32MK_OC11_OFFSET 0x045400u\n+#define PIC32MK_OC12_OFFSET 0x045600u\n+#define PIC32MK_OC13_OFFSET 0x045800u\n+#define PIC32MK_OC14_OFFSET 0x045A00u\n+#define PIC32MK_OC15_OFFSET 0x045C00u\n+#define PIC32MK_OC16_OFFSET 0x045E00u\n+\n+#define PIC32MK_OC_BLOCK_SIZE 0x200u\n+\n+/* OC register offsets within each 0x200-byte block */\n+#define PIC32MK_OCxCON 0x00u /* Control */\n+#define PIC32MK_OCxR 0x10u /* Primary compare value */\n+#define PIC32MK_OCxRS 0x20u /* Secondary compare value */\n+\n+/* OCxCON bits (Register 19-1) */\n+#define PIC32MK_OCCON_ON (1u << 15)\n+#define PIC32MK_OCCON_SIDL (1u << 13)\n+#define PIC32MK_OCCON_OC32 (1u << 5)\n+#define PIC32MK_OCCON_OCFLT (1u << 4)\n+#define PIC32MK_OCCON_OCTSEL (1u << 3)\n+#define PIC32MK_OCCON_OCM_MASK 0x0007u\n+#define PIC32MK_OCCON_OCM_SHIFT 0\n+\n+/* OC interrupt vector numbers */\n+#define PIC32MK_IRQ_OC1 7\n+#define PIC32MK_IRQ_OC2 12\n+#define PIC32MK_IRQ_OC3 17\n+#define PIC32MK_IRQ_OC4 22\n+#define PIC32MK_IRQ_OC5 27\n+#define PIC32MK_IRQ_OC6 79\n+#define PIC32MK_IRQ_OC7 83\n+#define PIC32MK_IRQ_OC8 87\n+#define PIC32MK_IRQ_OC9 91\n+#define PIC32MK_IRQ_OC10 199\n+#define PIC32MK_IRQ_OC11 202\n+#define PIC32MK_IRQ_OC12 205\n+#define PIC32MK_IRQ_OC13 208\n+#define PIC32MK_IRQ_OC14 211\n+#define PIC32MK_IRQ_OC15 214\n+#define PIC32MK_IRQ_OC16 217\n+\n+/*\n+ * Input Capture (IC1–IC16) — §18, DS60001519E\n+ * IC1–IC9: SFR bank 1, base 0xBF822000, stride 0x200\n+ * IC10–IC16: SFR bank 2, base 0xBF843200, stride 0x200\n+ * -----------------------------------------------------------------------\n+ */\n+#define PIC32MK_IC1_OFFSET 0x022000u\n+#define PIC32MK_IC2_OFFSET 0x022200u\n+#define PIC32MK_IC3_OFFSET 0x022400u\n+#define PIC32MK_IC4_OFFSET 0x022600u\n+#define PIC32MK_IC5_OFFSET 0x022800u\n+#define PIC32MK_IC6_OFFSET 0x022A00u\n+#define PIC32MK_IC7_OFFSET 0x022C00u\n+#define PIC32MK_IC8_OFFSET 0x022E00u\n+#define PIC32MK_IC9_OFFSET 0x023000u\n+#define PIC32MK_IC10_OFFSET 0x043200u\n+#define PIC32MK_IC11_OFFSET 0x043400u\n+#define PIC32MK_IC12_OFFSET 0x043600u\n+#define PIC32MK_IC13_OFFSET 0x043800u\n+#define PIC32MK_IC14_OFFSET 0x043A00u\n+#define PIC32MK_IC15_OFFSET 0x043C00u\n+#define PIC32MK_IC16_OFFSET 0x043E00u\n+\n+#define PIC32MK_IC_BLOCK_SIZE 0x200u\n+\n+/* IC register offsets within each block */\n+#define PIC32MK_ICxCON 0x00u /* Control (+ SET/CLR/INV at +4/+8/+C) */\n+#define PIC32MK_ICxBUF 0x10u /* Capture buffer (read-only FIFO pop) */\n+\n+/* ICxCON bits (Register 18-1, DS60001519E) */\n+#define PIC32MK_ICCON_ON (1u << 15) /* Module enable */\n+#define PIC32MK_ICCON_SIDL (1u << 13) /* Stop in idle */\n+#define PIC32MK_ICCON_FEDGE (1u << 9) /* First edge select */\n+#define PIC32MK_ICCON_C32 (1u << 8) /* 32-bit capture mode */\n+#define PIC32MK_ICCON_ICTMR (1u << 7) /* Timer source select */\n+#define PIC32MK_ICCON_ICI_MASK 0x0060u /* Interrupt on every Nth capture [6:5] */\n+#define PIC32MK_ICCON_ICI_SHIFT 5\n+#define PIC32MK_ICCON_ICOV (1u << 4) /* Overflow (buffer full) */\n+#define PIC32MK_ICCON_ICBNE (1u << 3) /* Buffer not empty */\n+#define PIC32MK_ICCON_ICM_MASK 0x0007u /* Capture mode [2:0] */\n+\n+/* IC IRQ numbers (Table 8-3, DS60001519E) — paired: error IRQ, capture IRQ */\n+#define PIC32MK_IRQ_IC1E 5\n+#define PIC32MK_IRQ_IC1 6\n+#define PIC32MK_IRQ_IC2E 10\n+#define PIC32MK_IRQ_IC2 11\n+#define PIC32MK_IRQ_IC3E 15\n+#define PIC32MK_IRQ_IC3 16\n+#define PIC32MK_IRQ_IC4E 20\n+#define PIC32MK_IRQ_IC4 21\n+#define PIC32MK_IRQ_IC5E 25\n+#define PIC32MK_IRQ_IC5 26\n+#define PIC32MK_IRQ_IC6E 77\n+#define PIC32MK_IRQ_IC6 78\n+#define PIC32MK_IRQ_IC7E 81\n+#define PIC32MK_IRQ_IC7 82\n+#define PIC32MK_IRQ_IC8E 85\n+#define PIC32MK_IRQ_IC8 86\n+#define PIC32MK_IRQ_IC9E 89\n+#define PIC32MK_IRQ_IC9 90\n+#define PIC32MK_IRQ_IC10E 197\n+#define PIC32MK_IRQ_IC10 198\n+#define PIC32MK_IRQ_IC11E 200\n+#define PIC32MK_IRQ_IC11 201\n+#define PIC32MK_IRQ_IC12E 203\n+#define PIC32MK_IRQ_IC12 204\n+#define PIC32MK_IRQ_IC13E 206\n+#define PIC32MK_IRQ_IC13 207\n+#define PIC32MK_IRQ_IC14E 209\n+#define PIC32MK_IRQ_IC14 210\n+#define PIC32MK_IRQ_IC15E 212\n+#define PIC32MK_IRQ_IC15 213\n+#define PIC32MK_IRQ_IC16E 215\n+#define PIC32MK_IRQ_IC16 216\n+\n+/* SPI1-6 IRQ numbers (Table 8-3, DS60001519E) */\n+#define PIC32MK_IRQ_SPI1_FAULT 35\n+#define PIC32MK_IRQ_SPI1_RX 36\n+#define PIC32MK_IRQ_SPI1_TX 37\n+#define PIC32MK_IRQ_SPI2_FAULT 53\n+#define PIC32MK_IRQ_SPI2_RX 54\n+#define PIC32MK_IRQ_SPI2_TX 55\n+#define PIC32MK_IRQ_SPI3_FAULT 154\n+#define PIC32MK_IRQ_SPI3_RX 155\n+#define PIC32MK_IRQ_SPI3_TX 156\n+#define PIC32MK_IRQ_SPI4_FAULT 166\n+#define PIC32MK_IRQ_SPI4_RX 167\n+#define PIC32MK_IRQ_SPI4_TX 168\n+#define PIC32MK_IRQ_SPI5_FAULT 169\n+#define PIC32MK_IRQ_SPI5_RX 170\n+#define PIC32MK_IRQ_SPI5_TX 171\n+#define PIC32MK_IRQ_SPI6_FAULT 172\n+#define PIC32MK_IRQ_SPI6_RX 173\n+#define PIC32MK_IRQ_SPI6_TX 174\n+\n+/*\n+ * UART2–6 register base offsets from SFR base\n+ * UART1 is already defined as PIC32MK_UART1_OFFSET = 0x028000\n+ * TODO: verify exact offsets against DS60001519E Table 4-2.\n+ * -----------------------------------------------------------------------\n+ */\n+\n+#define PIC32MK_UART2_OFFSET 0x028200u /* UART2 (PER1 block) */\n+#define PIC32MK_UART3_OFFSET 0x048400u /* UART3 (PER2 block, 0xBF848400) */\n+#define PIC32MK_UART4_OFFSET 0x048600u /* UART4 (0xBF848600) */\n+#define PIC32MK_UART5_OFFSET 0x048800u /* UART5 (0xBF848800) */\n+#define PIC32MK_UART6_OFFSET 0x048A00u /* UART6 (0xBF848A00) */\n+\n+#define PIC32MK_UART_BLOCK_SIZE 0x200u /* per-UART SFR block */\n+\n+/* Additional UxSTA bits */\n+#define PIC32MK_USTA_URXDA (1u << 0) /* RX data available */\n+#define PIC32MK_USTA_OERR (1u << 1) /* Overrun error */\n+#define PIC32MK_USTA_FERR (1u << 2) /* Framing error */\n+#define PIC32MK_USTA_PERR (1u << 3) /* Parity error */\n+#define PIC32MK_USTA_RIDLE (1u << 4) /* Receiver idle */\n+#define PIC32MK_USTA_UTXEN (1u << 10) /* TX enable */\n+#define PIC32MK_USTA_UTXISEL1 (1u << 14) /* TX interrupt select */\n+#define PIC32MK_USTA_URXEN (1u << 12) /* RX enable */\n+#define PIC32MK_USTA_URXISEL1 (1u << 6) /* RX interrupt select */\n+#define PIC32MK_UMODE_ON (1u << 15) /* UART enable */\n+\n+/*\n+ * SPI peripheral registers (§23, DS60001519E)\n+ * TODO: verify exact base offsets.\n+ * -----------------------------------------------------------------------\n+ */\n+\n+#define PIC32MK_SPI1_OFFSET 0x021800u\n+#define PIC32MK_SPI2_OFFSET 0x021A00u\n+#define PIC32MK_SPI3_OFFSET 0x040800u\n+#define PIC32MK_SPI4_OFFSET 0x040A00u\n+#define PIC32MK_SPI5_OFFSET 0x047800u /* 0xBF847800 */\n+#define PIC32MK_SPI6_OFFSET 0x040E00u\n+#define PIC32MK_SPI_BLOCK_SIZE 0x200u\n+\n+/* SPI register offsets within block */\n+#define PIC32MK_SPIxCON 0x00u\n+#define PIC32MK_SPIxSTAT 0x10u\n+#define PIC32MK_SPIxBUF 0x20u\n+#define PIC32MK_SPIxBRG 0x30u\n+#define PIC32MK_SPIxCON2 0x40u\n+\n+/*\n+ * I2C peripheral registers (§24, DS60001519E)\n+ * TODO: verify exact base offsets.\n+ * -----------------------------------------------------------------------\n+ */\n+\n+#define PIC32MK_I2C1_OFFSET 0x026000u /* 0xBF826000 — §24, Table 4-2 */\n+#define PIC32MK_I2C2_OFFSET 0x026200u /* 0xBF826200 */\n+#define PIC32MK_I2C3_OFFSET 0x046400u /* 0xBF846400 (PER2 bank) */\n+#define PIC32MK_I2C4_OFFSET 0x046600u /* 0xBF846600 */\n+#define PIC32MK_I2C_BLOCK_SIZE 0x200u\n+\n+/* I2C register offsets within block */\n+#define PIC32MK_I2CxCON 0x00u\n+#define PIC32MK_I2CxSTAT 0x10u\n+#define PIC32MK_I2CxADD 0x20u\n+#define PIC32MK_I2CxMSK 0x30u\n+#define PIC32MK_I2CxTRN 0x40u\n+#define PIC32MK_I2CxRCV 0x50u\n+\n+/*\n+ * GPIO peripheral registers (§12, DS60001519E)\n+ * Base: PIC32MK_GPIO_OFFSET = 0x060000 (0xBF860000)\n+ * Each port (A–G) occupies 0x100 bytes.\n+ * -----------------------------------------------------------------------\n+ */\n+\n+#define PIC32MK_GPIO_PORT_SIZE 0x100u /* per-port register block */\n+\n+/* GPIO register offsets within each port block */\n+#define PIC32MK_ANSEL 0x00u /* Analog select */\n+#define PIC32MK_TRIS 0x10u /* Direction (1=input) */\n+#define PIC32MK_PORT 0x20u /* Read pin state */\n+#define PIC32MK_LAT 0x30u /* Latch (write output) */\n+#define PIC32MK_ODC 0x40u /* Open-drain control */\n+#define PIC32MK_CNPU 0x50u /* Change-notice pull-up */\n+#define PIC32MK_CNPD 0x60u /* Change-notice pull-down */\n+#define PIC32MK_CNCON 0x70u /* Change-notice control */\n+#define PIC32MK_CNEN0 0x80u /* CN edge enable 0 */\n+#define PIC32MK_CNSTAT 0x90u /* CN status */\n+#define PIC32MK_CNEN1 0xA0u /* CN edge enable 1 */\n+#define PIC32MK_CNF 0xB0u /* CN flag */\n+\n+/* Number of GPIO ports (A–G) */\n+#define PIC32MK_GPIO_NPORTS 7\n+\n+/*\n+ * Set shared chardev for GPIO state-change event streaming.\n+ * Called from board init; multiple ports share the same Chardev*.\n+ */\n+void pic32mk_gpio_set_chardev(DeviceState *dev, Chardev *chr);\n+void pic32mk_oc_set_chardev(DeviceState *dev, Chardev *chr);\n+\n+/*\n+ * DMA controller registers (§26, DS60001519E)\n+ * Base: PIC32MK_DMA_OFFSET = 0x011000 (within EVIC page, 0xBF811000)\n+ * Global registers at base, then 8 channel blocks at +0x60 each.\n+ * -----------------------------------------------------------------------\n+ */\n+\n+/* DMA global registers */\n+#define PIC32MK_DMACON_OFFSET 0x00u /* DMA control */\n+#define PIC32MK_DMASTAT_OFFSET 0x10u /* DMA status */\n+#define PIC32MK_DMAADDR_OFFSET 0x20u /* DMA address */\n+\n+/* DMA channel registers base: +0x60 + channel * 0xC0 */\n+#define PIC32MK_DMA_CH_BASE 0x60u\n+#define PIC32MK_DMA_CH_STRIDE 0xC0u\n+#define PIC32MK_DMA_NCHANNELS 8\n+\n+/* DMA channel register offsets within each channel block */\n+#define PIC32MK_DCHxCON 0x00u\n+#define PIC32MK_DCHxECON 0x10u\n+#define PIC32MK_DCHxINT 0x20u\n+#define PIC32MK_DCHxSSA 0x30u\n+#define PIC32MK_DCHxDSA 0x40u\n+#define PIC32MK_DCHxSSIZ 0x50u\n+#define PIC32MK_DCHxDSIZ 0x60u\n+#define PIC32MK_DCHxSPTR 0x70u\n+#define PIC32MK_DCHxDPTR 0x80u\n+#define PIC32MK_DCHxCSIZ 0x90u\n+#define PIC32MK_DCHxCPTR 0xA0u\n+#define PIC32MK_DCHxDAT 0xB0u\n+\n+/*\n+ * ADCHS peripheral registers (§22, DS60001519E)\n+ * Base: 0xBF887000 (SFR offset 0x087000)\n+ * Register block spans ~4 KB (0x000–0xE1C).\n+ * Each register has SET/CLR/INV aliases at +4/+8/+C.\n+ * -----------------------------------------------------------------------\n+ */\n+\n+#define PIC32MK_ADC_OFFSET 0x087000u /* 0xBF887000 */\n+#define PIC32MK_ADC_SIZE 0x001000u /* 4 KB register window */\n+\n+/* Control registers */\n+#define PIC32MK_ADCCON1 0x000u\n+#define PIC32MK_ADCCON2 0x010u\n+#define PIC32MK_ADCCON3 0x020u\n+#define PIC32MK_ADCTRGMODE 0x030u\n+\n+/* Input mode control (signed/unsigned per channel group) */\n+#define PIC32MK_ADCIMCON1 0x040u\n+#define PIC32MK_ADCIMCON2 0x050u\n+#define PIC32MK_ADCIMCON3 0x060u\n+#define PIC32MK_ADCIMCON4 0x070u\n+\n+/* Global interrupt enable (result ready, 2 × 32 bits) */\n+#define PIC32MK_ADCGIRQEN1 0x080u\n+#define PIC32MK_ADCGIRQEN2 0x090u\n+\n+/* Channel scan select */\n+#define PIC32MK_ADCCSS1 0x0A0u\n+#define PIC32MK_ADCCSS2 0x0B0u\n+\n+/* Data ready status */\n+#define PIC32MK_ADCDSTAT1 0x0C0u\n+#define PIC32MK_ADCDSTAT2 0x0D0u\n+\n+/* Compare enable */\n+#define PIC32MK_ADCCMPEN1 0x0E0u\n+#define PIC32MK_ADCCMPEN2 0x100u\n+#define PIC32MK_ADCCMPEN3 0x120u\n+#define PIC32MK_ADCCMPEN4 0x140u\n+\n+/* Compare values */\n+#define PIC32MK_ADCCMP1 0x0F0u\n+#define PIC32MK_ADCCMP2 0x110u\n+#define PIC32MK_ADCCMP3 0x130u\n+#define PIC32MK_ADCCMP4 0x150u\n+\n+/* Digital filter registers */\n+#define PIC32MK_ADCFLTR1 0x1A0u\n+#define PIC32MK_ADCFLTR2 0x1B0u\n+#define PIC32MK_ADCFLTR3 0x1C0u\n+#define PIC32MK_ADCFLTR4 0x1D0u\n+\n+/* Trigger configuration */\n+#define PIC32MK_ADCTRG1 0x200u\n+#define PIC32MK_ADCTRG2 0x210u\n+#define PIC32MK_ADCTRG3 0x220u\n+#define PIC32MK_ADCTRG4 0x230u\n+#define PIC32MK_ADCTRG5 0x240u\n+#define PIC32MK_ADCTRG6 0x250u\n+#define PIC32MK_ADCTRG7 0x260u\n+\n+/* Compare control */\n+#define PIC32MK_ADCCMPCON1 0x280u\n+#define PIC32MK_ADCCMPCON2 0x290u\n+#define PIC32MK_ADCCMPCON3 0x2A0u\n+#define PIC32MK_ADCCMPCON4 0x2B0u\n+\n+/* Misc registers */\n+#define PIC32MK_ADCBASE 0x300u\n+#define PIC32MK_ADCTRGSNS 0x340u\n+\n+/* Sampling time (per-module) */\n+#define PIC32MK_ADC0TIME 0x350u\n+#define PIC32MK_ADC1TIME 0x360u\n+#define PIC32MK_ADC2TIME 0x370u\n+#define PIC32MK_ADC3TIME 0x380u\n+#define PIC32MK_ADC4TIME 0x390u\n+#define PIC32MK_ADC5TIME 0x3A0u\n+\n+/* Early interrupt enable / status */\n+#define PIC32MK_ADCEIEN1 0x3C0u\n+#define PIC32MK_ADCEIEN2 0x3D0u\n+#define PIC32MK_ADCEISTAT1 0x3E0u\n+#define PIC32MK_ADCEISTAT2 0x3F0u\n+\n+/* Analog module enable / warm-up control */\n+#define PIC32MK_ADCANCON 0x400u\n+\n+/* Conversion data registers (stride 0x10 per channel index) */\n+#define PIC32MK_ADCDATA_BASE 0x600u\n+#define PIC32MK_ADCDATA_STRIDE 0x010u\n+\n+/* Per-module configuration */\n+#define PIC32MK_ADC0CFG 0xD00u\n+#define PIC32MK_ADC1CFG 0xD10u\n+#define PIC32MK_ADC2CFG 0xD20u\n+#define PIC32MK_ADC3CFG 0xD30u\n+#define PIC32MK_ADC4CFG 0xD40u\n+#define PIC32MK_ADC5CFG 0xD50u\n+#define PIC32MK_ADC6CFG 0xD60u\n+#define PIC32MK_ADC7CFG 0xD70u\n+\n+/* System configuration */\n+#define PIC32MK_ADCSYSCFG0 0xE00u\n+#define PIC32MK_ADCSYSCFG1 0xE10u\n+\n+/* Maximum channel index (0–53, with gaps) */\n+#define PIC32MK_ADC_MAX_CH 54\n+\n+/* ADCCON1 bits */\n+#define PIC32MK_ADCCON1_ON (1u << 15)\n+\n+/* ADCCON2 bits */\n+#define PIC32MK_ADCCON2_BGVRRDY (1u << 31) /* Band-gap voltage ref ready */\n+#define PIC32MK_ADCCON2_REFFLT (1u << 30) /* Reference fault */\n+\n+/* ADCCON3 bits */\n+#define PIC32MK_ADCCON3_ADINSEL_MASK 0x3Fu /* bits [5:0] channel select */\n+#define PIC32MK_ADCCON3_RQCNVRT (1u << 8) /* Request conversion */\n+#define PIC32MK_ADCCON3_GSWTRG (1u << 6) /* Global software trigger */\n+#define PIC32MK_ADCCON3_GLSWTRG (1u << 5) /* Global level SW trigger */\n+#define PIC32MK_ADCCON3_DIGEN_SHIFT 16 /* DIGENx at bits [23:16] */\n+\n+/* ADCANCON bits — ANENx and WKRDYx (modules 0–5, 7) */\n+#define PIC32MK_ADCANCON_ANEN_SHIFT 0 /* ANENx at bits [7:0] */\n+#define PIC32MK_ADCANCON_WKRDY_SHIFT 8 /* WKRDYx at bits [15:8] */\n+\n+/*\n+ * ADCHS interrupt source numbers (§8, Table 8-1) — vector/IRQ numbers\n+ * -----------------------------------------------------------------------\n+ */\n+\n+#define PIC32MK_IRQ_ADC 92 /* Main ADC interrupt */\n+#define PIC32MK_IRQ_ADC_DC1 94 /* Digital comparator 1 */\n+#define PIC32MK_IRQ_ADC_DC2 95 /* Digital comparator 2 */\n+#define PIC32MK_IRQ_ADC_DF1 96 /* Digital filter 1 */\n+#define PIC32MK_IRQ_ADC_DF2 97 /* Digital filter 2 */\n+#define PIC32MK_IRQ_ADC_DF3 98 /* Digital filter 3 */\n+#define PIC32MK_IRQ_ADC_DF4 99 /* Digital filter 4 */\n+#define PIC32MK_IRQ_ADC_FAULT 100 /* ADC fault */\n+#define PIC32MK_IRQ_ADC_EOS 101 /* End of scan */\n+#define PIC32MK_IRQ_ADC_ARDY 102 /* Analog ready */\n+#define PIC32MK_IRQ_ADC_URDY 103 /* Update ready */\n+#define PIC32MK_IRQ_ADC_DMA 104 /* DMA */\n+#define PIC32MK_IRQ_ADC_EARLY 105 /* Early interrupt */\n+#define PIC32MK_IRQ_ADC_DATA0 106 /* Data ready channel 0 (base) */\n+/* DATA1..DATA27 = 107..133, DATA33..41 = 139..147, DATA45..53 = 151..159 */\n+#define PIC32MK_IRQ_ADC_DC3 245 /* Digital comparator 3 */\n+#define PIC32MK_IRQ_ADC_DC4 246 /* Digital comparator 4 */\n+\n+/*\n+ * NVM / Flash Controller (§10, DS60001519E)\n+ * Base: 0xBF800A00 (SFR offset 0x000A00)\n+ * Register block: NVMCON, NVMKEY, NVMADDR, NVMDATA0–3, NVMSRCADDR,\n+ * NVMPWP, NVMBWP, NVMCON2 (each 0x10 stride with SET/CLR/INV aliases,\n+ * except NVMKEY which is write-only, no aliases).\n+ * -----------------------------------------------------------------------\n+ */\n+\n+#define PIC32MK_NVM_OFFSET 0x000A00u /* 0xBF800A00 */\n+#define PIC32MK_NVM_SIZE 0x0000B0u /* NVMCON..NVMCON2+INV inclusive */\n+\n+/* Register offsets within the NVM block */\n+#define PIC32MK_NVMCON 0x00u /* +CLR/SET/INV at +4/+8/+C */\n+#define PIC32MK_NVMKEY 0x10u\n+#define PIC32MK_NVMADDR 0x20u /* +CLR/SET/INV */\n+#define PIC32MK_NVMDATA0 0x30u /* +CLR/SET/INV */\n+#define PIC32MK_NVMDATA1 0x40u\n+#define PIC32MK_NVMDATA2 0x50u\n+#define PIC32MK_NVMDATA3 0x60u\n+#define PIC32MK_NVMSRCADDR 0x70u /* +CLR/SET/INV */\n+#define PIC32MK_NVMPWP 0x80u\n+#define PIC32MK_NVMBWP 0x90u\n+#define PIC32MK_NVMCON2 0xA0u\n+\n+/* NVMCON bits */\n+#define PIC32MK_NVMCON_NVMOP_MASK 0x000Fu /* bits [3:0] */\n+#define PIC32MK_NVMCON_BFSWAP (1u << 6)\n+#define PIC32MK_NVMCON_PFSWAP (1u << 7)\n+#define PIC32MK_NVMCON_LVDERR (1u << 12)\n+#define PIC32MK_NVMCON_WRERR (1u << 13)\n+#define PIC32MK_NVMCON_WREN (1u << 14)\n+#define PIC32MK_NVMCON_WR (1u << 15)\n+\n+/* NVM operation codes (NVMOP field, bits [3:0]) */\n+#define PIC32MK_NVMOP_NOP 0x0u\n+#define PIC32MK_NVMOP_WORD_PROG 0x1u\n+#define PIC32MK_NVMOP_QUAD_WORD_PROG 0x2u\n+#define PIC32MK_NVMOP_ROW_PROG 0x3u\n+#define PIC32MK_NVMOP_PAGE_ERASE 0x4u\n+#define PIC32MK_NVMOP_LOWER_PFM_ERASE 0x5u\n+#define PIC32MK_NVMOP_UPPER_PFM_ERASE 0x6u\n+#define PIC32MK_NVMOP_PFM_ERASE 0x7u\n+\n+/* Unlock keys (written sequentially to NVMKEY) */\n+#define PIC32MK_NVMKEY1 0xAA996655u\n+#define PIC32MK_NVMKEY2 0x556699AAu\n+\n+/* Flash geometry */\n+#define PIC32MK_NVM_PAGE_SIZE 4096u /* erase granularity */\n+#define PIC32MK_NVM_ROW_SIZE 512u /* write-row granularity */\n+\n+/* Interrupt — Flash Control Error vector 31 */\n+#define PIC32MK_IRQ_FCE 31\n+\n+/*\n+ * Data EEPROM (§11, DS60001519E)\n+ * Base: 0xBF829000 (SFR offset 0x029000)\n+ * Register block: EECON, EEKEY, EEADDR, EEDATA (each 0x10 stride with\n+ * SET/CLR/INV aliases, except EEKEY which is write-only, no aliases).\n+ * -----------------------------------------------------------------------\n+ */\n+\n+#define PIC32MK_DATAEE_OFFSET 0x029000u /* 0xBF829000 */\n+#define PIC32MK_DATAEE_SIZE 0x000040u /* EECON..EEDATA+INV inclusive */\n+\n+/* Register offsets within the DATAEE block */\n+#define PIC32MK_EECON 0x00u\n+#define PIC32MK_EEKEY 0x10u\n+#define PIC32MK_EEADDR 0x20u\n+#define PIC32MK_EEDATA_REG 0x30u /* \"_REG\" avoids clash with EEDATA macro */\n+\n+/* EECON bits (p32mk1024mcm100.h) */\n+#define PIC32MK_EECON_CMD_MASK 0x00000007u /* bits [2:0] */\n+#define PIC32MK_EECON_CMD_SHIFT 0\n+#define PIC32MK_EECON_ILW (1u << 3)\n+#define PIC32MK_EECON_ERR_MASK 0x00000030u /* bits [5:4] */\n+#define PIC32MK_EECON_ERR_SHIFT 4\n+#define PIC32MK_EECON_WREN (1u << 6)\n+#define PIC32MK_EECON_RW (1u << 7)\n+#define PIC32MK_EECON_ABORT (1u << 12)\n+#define PIC32MK_EECON_SIDL (1u << 13)\n+#define PIC32MK_EECON_RDY (1u << 14)\n+#define PIC32MK_EECON_ON (1u << 15)\n+\n+/* EEADDR valid bits — 14-bit, word-aligned */\n+#define PIC32MK_EEADDR_MASK 0x00003FFCu\n+\n+/* EEPROM storage geometry */\n+#define PIC32MK_DATAEE_WORDS 1024 /* 4 KB = 1024 × 32-bit words */\n+#define PIC32MK_DATAEE_PAGE_WORDS 32 /* 128-byte page */\n+\n+/* Unlock keys (written sequentially to EEKEY) */\n+#define PIC32MK_EEKEY1 0xEDB7u\n+#define PIC32MK_EEKEY2 0x1248u\n+\n+/* EECON CMD field values */\n+#define PIC32MK_EECMD_WORD_READ 0\n+#define PIC32MK_EECMD_WORD_WRITE 1\n+#define PIC32MK_EECMD_PAGE_ERASE 2\n+#define PIC32MK_EECMD_BULK_ERASE 3\n+#define PIC32MK_EECMD_CONFIG_WRITE 4\n+\n+/* Interrupt — Data EEPROM vector 186 */\n+#define PIC32MK_IRQ_DATAEE 186\n+\n+#endif /* HW_MIPS_PIC32MK_H */\ndiff --git a/include/hw/mips/pic32mk_adchs.h b/include/hw/mips/pic32mk_adchs.h\nnew file mode 100644\nindex 0000000000..2c4149c3a3\n--- /dev/null\n+++ b/include/hw/mips/pic32mk_adchs.h\n@@ -0,0 +1,87 @@\n+/*\n+ * PIC32MK ADCHS (High-Speed ADC) peripheral — device interface\n+ * Datasheet: DS60001519E, §22\n+ *\n+ * Copyright (c) 2026 QEMU contributors\n+ * SPDX-License-Identifier: GPL-2.0-or-later\n+ */\n+\n+#ifndef HW_MIPS_PIC32MK_ADCHS_H\n+#define HW_MIPS_PIC32MK_ADCHS_H\n+\n+#include \"hw/core/sysbus.h\"\n+#include \"qom/object.h\"\n+#include \"hw/mips/pic32mk.h\"\n+\n+#define TYPE_PIC32MK_ADCHS \"pic32mk-adchs\"\n+OBJECT_DECLARE_SIMPLE_TYPE(PIC32MKADCHSState, PIC32MK_ADCHS)\n+\n+struct PIC32MKADCHSState {\n+ SysBusDevice parent_obj;\n+ MemoryRegion mr;\n+\n+ /* Control registers */\n+ uint32_t adccon1;\n+ uint32_t adccon2;\n+ uint32_t adccon3;\n+ uint32_t adctrgmode;\n+\n+ /* Input mode control */\n+ uint32_t adcimcon[4];\n+\n+ /* Interrupt enable (result ready) */\n+ uint32_t adcgirqen[2];\n+\n+ /* Channel scan select */\n+ uint32_t adccss[2];\n+\n+ /* Data ready status */\n+ uint32_t adcdstat[2];\n+\n+ /* Compare enable & values */\n+ uint32_t adccmpen[4];\n+ uint32_t adccmp[4];\n+ uint32_t adccmpcon[4];\n+\n+ /* Digital filters */\n+ uint32_t adcfltr[4];\n+\n+ /* Trigger configuration */\n+ uint32_t adctrg[7];\n+ uint32_t adctrgsns;\n+\n+ /* Sampling time per module */\n+ uint32_t adctime[6]; /* ADC0TIME … ADC5TIME */\n+\n+ /* Early interrupt enable / status */\n+ uint32_t adceien[2];\n+ uint32_t adceistat[2];\n+\n+ /* Analog module control */\n+ uint32_t adcancon;\n+\n+ /* Misc */\n+ uint32_t adcbase;\n+\n+ /* Per-module configuration */\n+ uint32_t adccfg[8]; /* ADC0CFG … ADC7CFG */\n+\n+ /* System configuration */\n+ uint32_t adcsyscfg[2];\n+\n+ /* Conversion data registers (indexed by channel 0–53) */\n+ uint32_t adcdata[PIC32MK_ADC_MAX_CH];\n+\n+ /*\n+ * Host-injectable analog input values (12-bit, 0–4095).\n+ * When a conversion is triggered, analog_input[ch] is copied to\n+ * adcdata[ch]. Set via QOM property \"adc-ch<N>\".\n+ */\n+ uint16_t analog_input[PIC32MK_ADC_MAX_CH];\n+\n+ /* IRQ outputs to EVIC */\n+ qemu_irq irq_eos; /* End-of-scan (IRQ 101) */\n+ qemu_irq irq_main; /* Main ADC (IRQ 92) */\n+};\n+\n+#endif /* HW_MIPS_PIC32MK_ADCHS_H */\ndiff --git a/include/hw/mips/pic32mk_canfd.h b/include/hw/mips/pic32mk_canfd.h\nnew file mode 100644\nindex 0000000000..3408c54e31\n--- /dev/null\n+++ b/include/hw/mips/pic32mk_canfd.h\n@@ -0,0 +1,210 @@\n+/*\n+ * Microchip PIC32MK CAN FD controller — device interface\n+ * Based on PIC32MK GPK/MCM with CAN FD Family Datasheet (DS60001519E)\n+ * and Microchip CAN FD Controller Reference Manual (DS60001507).\n+ *\n+ * Copyright (c) 2026 QEMU contributors\n+ * SPDX-License-Identifier: GPL-2.0-or-later\n+ */\n+\n+#ifndef HW_MIPS_PIC32MK_CANFD_H\n+#define HW_MIPS_PIC32MK_CANFD_H\n+\n+#include \"hw/core/sysbus.h\"\n+#include \"net/can_emu.h\"\n+#include \"qom/object.h\"\n+\n+#define TYPE_PIC32MK_CANFD \"pic32mk-canfd\"\n+OBJECT_DECLARE_SIMPLE_TYPE(PIC32MKCANFDState, PIC32MK_CANFD)\n+\n+/*\n+ * CAN FD SFR register offsets (from instance base).\n+ *\n+ * Layout verified against XC32 device header p32mk1024mcm100.h.\n+ * Each logical register occupies 0x10 bytes:\n+ * +0x0 = base, +0x4 = SET, +0x8 = CLR, +0xC = INV\n+ * -----------------------------------------------------------------------\n+ */\n+\n+#define CANFD_CiCON 0x000u /* Control */\n+#define CANFD_CiNBTCFG 0x010u /* Nominal bit-time config */\n+#define CANFD_CiDBTCFG 0x020u /* Data bit-time config */\n+#define CANFD_CiTDC 0x030u /* Transmitter delay compensation */\n+#define CANFD_CiTBC 0x040u /* Time base counter */\n+#define CANFD_CiTSCON 0x050u /* Timestamp control */\n+#define CANFD_CiVEC 0x060u /* Interrupt flag code */\n+#define CANFD_CiINT 0x070u /* Interrupt aggregator */\n+#define CANFD_CiRXIF 0x080u /* RX interrupt flags per FIFO */\n+#define CANFD_CiTXIF 0x090u /* TX interrupt flags per FIFO */\n+#define CANFD_CiRXOVIF 0x0A0u /* RX overflow flags per FIFO */\n+#define CANFD_CiTXATIF 0x0B0u /* TX attempts interrupt flags */\n+#define CANFD_CiTXREQ 0x0C0u /* TX request bits per FIFO */\n+#define CANFD_CiTREC 0x0D0u /* TX/RX error counters */\n+#define CANFD_CiBDIAG0 0x0E0u /* Bus diagnostic 0 */\n+#define CANFD_CiBDIAG1 0x0F0u /* Bus diagnostic 1 */\n+#define CANFD_CiTEFCON 0x100u /* TX Event FIFO control */\n+#define CANFD_CiTEFSTA 0x110u /* TX Event FIFO status */\n+#define CANFD_CiTEFUA 0x120u /* TX Event FIFO user address */\n+#define CANFD_CiFIFOBA 0x130u /* Message RAM base address (Phase 3C) */\n+#define CANFD_CiTXQCON 0x140u /* TX Queue control */\n+#define CANFD_CiTXQSTA 0x150u /* TX Queue status */\n+#define CANFD_CiTXQUA 0x160u /* TX Queue user address */\n+/* FIFO n (n=1..31): stride 0x30 (3 registers × 0x10 each) */\n+#define CANFD_CiFIFOCON(n) (0x170u + ((n) - 1u) * 0x30u)\n+#define CANFD_CiFIFOSTA(n) (0x180u + ((n) - 1u) * 0x30u)\n+#define CANFD_CiFIFOUA(n) (0x190u + ((n) - 1u) * 0x30u)\n+/* Filter control: stride 0x10 per register, 8 registers */\n+#define CANFD_CiFLTCON(r) (0x740u + (r) * 0x10u)\n+/* Filter object/mask pairs: stride 0x20 per pair */\n+#define CANFD_CiFLTOBJ(n) (0x7C0u + (n) * 0x20u)\n+#define CANFD_CiMASK(n) (0x7D0u + (n) * 0x20u)\n+\n+/*\n+ * CiCON bit fields\n+ * -----------------------------------------------------------------------\n+ */\n+/*\n+ * CiCON field positions verified against DS60001519E / MCP251xFD IP core:\n+ * reset value 0x04980760 → REQOP=4 (Config), OPMOD=4 (Config)\n+ */\n+#define CANFD_CON_REQOP_SHIFT 24u\n+#define CANFD_CON_REQOP_MASK (0x7u << CANFD_CON_REQOP_SHIFT)\n+#define CANFD_CON_OPMOD_SHIFT 21u\n+#define CANFD_CON_OPMOD_MASK (0x7u << CANFD_CON_OPMOD_SHIFT)\n+#define CANFD_CON_TXQEN (1u << 20u)\n+#define CANFD_CON_STEF (1u << 19u)\n+#define CANFD_CON_ABAT (1u << 27u)\n+#define CANFD_CON_RESET 0x04980760u /* Config mode on reset */\n+\n+/* OPMOD values */\n+#define CANFD_OPMOD_NORMAL 0u\n+#define CANFD_OPMOD_RESTRICTED 1u\n+#define CANFD_OPMOD_LISTEN 2u\n+#define CANFD_OPMOD_CONFIG 4u\n+#define CANFD_OPMOD_EXT_LOOP 5u\n+#define CANFD_OPMOD_INT_LOOP 7u\n+\n+/*\n+ * CiINT bit fields — verified against XC32 p32mk1024mcm100.h\n+ * Lower 16 bits = status flags, upper 16 bits = enable bits.\n+ * -----------------------------------------------------------------------\n+ */\n+#define CANFD_INT_TXIF (1u << 0u) /* TX FIFO interrupt flag */\n+#define CANFD_INT_RXIF (1u << 1u) /* RX FIFO interrupt flag */\n+#define CANFD_INT_MODIF (1u << 3u) /* Mode change flag */\n+#define CANFD_INT_TEFIF (1u << 4u) /* TEF interrupt flag */\n+#define CANFD_INT_TXATIF (1u << 10u) /* TX attempts flag */\n+#define CANFD_INT_RXOVIF (1u << 11u) /* RX overflow flag */\n+#define CANFD_INT_TXIE (1u << 16u) /* TX IRQ enable */\n+#define CANFD_INT_RXIE (1u << 17u) /* RX IRQ enable */\n+#define CANFD_INT_MODIE (1u << 19u) /* Mode change IRQ enable */\n+#define CANFD_INT_RXOVIE (1u << 27u) /* RX overflow IRQ enable */\n+#define CANFD_INT_SERRIE (1u << 28u) /* System error IRQ enable */\n+#define CANFD_INT_CERRIE (1u << 29u) /* CAN bus error IRQ enable */\n+#define CANFD_INT_IVMIE (1u << 31u) /* Invalid message IRQ enable */\n+\n+/*\n+ * CiFIFOCONn / CiTXQCON bit fields\n+ * Bit positions verified against XC32 p32mk1024mcm100.h device header.\n+ * -----------------------------------------------------------------------\n+ */\n+#define CANFD_FIFO_PLSIZE_SHIFT 29u\n+#define CANFD_FIFO_PLSIZE_MASK (0x7u << CANFD_FIFO_PLSIZE_SHIFT)\n+#define CANFD_FIFO_FSIZE_SHIFT 24u\n+#define CANFD_FIFO_FSIZE_MASK (0x1Fu << CANFD_FIFO_FSIZE_SHIFT)\n+#define CANFD_FIFO_TFNRFNIE (1u << 0u) /* RX: FIFO Not Empty interrupt enable */\n+#define CANFD_FIFO_TXEN (1u << 7u) /* TX enable (1=TX FIFO, 0=RX FIFO) */\n+#define CANFD_FIFO_UINC (1u << 8u) /* User increment (pulse bit) */\n+#define CANFD_FIFO_TXREQ (1u << 9u) /* TX request (pulse bit) */\n+#define CANFD_FIFO_FRESET (1u << 10u) /* FIFO reset (pulse bit) */\n+\n+/* CiFIFOSTAn bit fields — verified against XC32 device header */\n+#define CANFD_FIFOSTA_TFNRFNIF (1u << 0u) /* TX not full / RX not empty */\n+#define CANFD_FIFOSTA_TXATIF (1u << 4u) /* TX attempts exhausted */\n+#define CANFD_TXQSTA_TXQNIF (1u << 0u) /* CiTXQSTA: TX Queue not full */\n+\n+/*\n+ * Device state\n+ * -----------------------------------------------------------------------\n+ */\n+\n+struct PIC32MKCANFDState {\n+ SysBusDevice parent_obj;\n+\n+ /* Two MemoryRegions per instance */\n+ MemoryRegion sfr_mmio; /* SFR registers: PIC32MK_CAN_SFR_SIZE bytes */\n+ MemoryRegion msg_ram; /* Message RAM: PIC32MK_CAN_MSGRAM_SIZE bytes */\n+\n+ /* Key SFRs (reset values per DS60001507 §3) */\n+ uint32_t con; /* CiCON — reset 0x04980760 */\n+ uint32_t nbtcfg; /* CiNBTCFG */\n+ uint32_t dbtcfg; /* CiDBTCFG */\n+ uint32_t tdc; /* CiTDC */\n+ uint32_t tbc; /* CiTBC (free-running) */\n+ uint32_t tscon; /* CiTSCON */\n+ uint32_t vec; /* CiVEC */\n+ uint32_t cint; /* CiINT */\n+ uint32_t rxif; /* CiRXIF */\n+ uint32_t txif; /* CiTXIF */\n+ uint32_t rxovif; /* CiRXOVIF */\n+ uint32_t txreq; /* CiTXREQ */\n+ uint32_t trec; /* CiTREC */\n+\n+ /* TXQ */\n+ uint32_t txqcon;\n+ uint32_t txqsta;\n+ uint32_t txqua;\n+ uint8_t txq_head; /* oldest pending frame (TX processes from here) */\n+ uint8_t txq_tail; /* next empty slot firmware will write to */\n+ uint8_t txq_count; /* number of frames queued */\n+\n+ /* TEF */\n+ uint32_t tefcon;\n+ uint32_t tefsta;\n+ uint32_t tefua;\n+\n+ /* Per-FIFO state (index 1–31; [0] unused) */\n+ uint32_t fifocon[32];\n+ uint32_t fifosta[32];\n+ uint32_t fifoua[32];\n+ uint8_t fifo_head[32];\n+ uint8_t fifo_tail[32];\n+ uint8_t fifo_count[32];\n+\n+ /* Acceptance filters */\n+ uint32_t fltcon[8]; /* 4 filters per reg × 8 = 32 filters */\n+ uint32_t fltobj[32];\n+ uint32_t mask[32];\n+\n+ /* Message RAM backing store */\n+ uint8_t *msg_ram_buf;\n+ uint32_t msg_ram_phys; /* physical base (injected via property) */\n+\n+ /* Instance index (0–3 for CAN1–4) */\n+ uint32_t instance_id;\n+\n+ /* Single IRQ line to EVIC */\n+ qemu_irq irq;\n+\n+ /* SocketCAN virtual bus (Phase 3B) */\n+ CanBusClientState bus_client;\n+ CanBusState *canbus; /* linked can-bus object, or NULL */\n+\n+ /*\n+ * Bus-side software ring buffer — decouples SocketCAN delivery timing\n+ * from the guest CPU. Frames land here when the target RX FIFO is full;\n+ * canfd_bus_buf_drain() moves them into the FIFO after each UINC.\n+ */\n+ uint32_t bus_buf_id[64];\n+ bool bus_buf_xtd[64];\n+ bool bus_buf_fdf[64];\n+ uint8_t bus_buf_dlc[64];\n+ uint8_t bus_buf_data[64][64];\n+ int bus_buf_len[64];\n+ int bus_buf_dest[64];\n+ int bus_buf_head;\n+ int bus_buf_tail;\n+ int bus_buf_count;\n+};\n+\n+#endif /* HW_MIPS_PIC32MK_CANFD_H */\ndiff --git a/include/hw/mips/pic32mk_evic.h b/include/hw/mips/pic32mk_evic.h\nnew file mode 100644\nindex 0000000000..207309719c\n--- /dev/null\n+++ b/include/hw/mips/pic32mk_evic.h\n@@ -0,0 +1,45 @@\n+/*\n+ * PIC32MK EVIC — public API header\n+ *\n+ * Copyright (c) 2026 QEMU contributors\n+ * SPDX-License-Identifier: GPL-2.0-or-later\n+ */\n+\n+#ifndef HW_MIPS_PIC32MK_EVIC_H\n+#define HW_MIPS_PIC32MK_EVIC_H\n+\n+#include \"qom/object.h\"\n+#include \"hw/core/sysbus.h\"\n+#include \"hw/core/irq.h\"\n+#include \"hw/mips/pic32mk.h\"\n+#include \"cpu.h\"\n+\n+#define TYPE_PIC32MK_EVIC \"pic32mk-evic\"\n+OBJECT_DECLARE_SIMPLE_TYPE(PIC32MKEVICState, PIC32MK_EVIC)\n+\n+struct PIC32MKEVICState {\n+ SysBusDevice parent_obj;\n+ MemoryRegion mr;\n+\n+ uint32_t intcon;\n+ uint32_t priss;\n+ uint32_t intstat;\n+ uint32_t iptmr;\n+\n+ uint32_t ifsreg[8];\n+ uint32_t iecreg[8];\n+ uint32_t ipcreg[64];\n+ uint32_t offreg[191];\n+\n+ /*\n+ * Current hardware IRQ levels for each source. Microchip IFS flags\n+ * are level-sensitive: if the source still asserts its line after\n+ * firmware clears IFSx, the flag is immediately re-set.\n+ */\n+ uint32_t irq_level[8];\n+\n+ qemu_irq cpu_irq[8];\n+ MIPSCPU *cpu; /* set by board code for SW-IRQ Cause.IP fixup */\n+};\n+\n+#endif /* HW_MIPS_PIC32MK_EVIC_H */\ndiff --git a/include/hw/mips/pic32mk_usb.h b/include/hw/mips/pic32mk_usb.h\nnew file mode 100644\nindex 0000000000..342444e9db\n--- /dev/null\n+++ b/include/hw/mips/pic32mk_usb.h\n@@ -0,0 +1,181 @@\n+/*\n+ * Microchip PIC32MK USB OTG Full-Speed controller — device model header\n+ * Datasheet: DS60001519E §25; register addresses from p32mk1024mcm100.h\n+ *\n+ * Copyright (c) 2026 QEMU contributors\n+ * SPDX-License-Identifier: GPL-2.0-or-later\n+ */\n+\n+#ifndef HW_MIPS_PIC32MK_USB_H\n+#define HW_MIPS_PIC32MK_USB_H\n+\n+#include \"hw/core/sysbus.h\"\n+#include \"qom/object.h\"\n+#include \"chardev/char-fe.h\"\n+#include \"qemu/timer.h\"\n+\n+/*\n+ * Register offsets from the USB instance SFR base (e.g. 0xBF889000)\n+ * Derived from p32mk1024mcm100.h register addresses.\n+ * All registers with CLR/SET/INV aliases follow the standard PIC32 convention\n+ * (+4 = CLR, +8 = SET, +C = INV), except where noted.\n+ * -----------------------------------------------------------------------\n+ */\n+\n+/* OTG registers */\n+#define PIC32MK_UxOTGIR 0x040u /* OTG interrupt flags (W1C, CLR alias only) */\n+#define PIC32MK_UxOTGIE 0x050u /* OTG interrupt enable (CLR/SET/INV) */\n+#define PIC32MK_UxOTGSTAT 0x060u /* OTG status (read-only) */\n+#define PIC32MK_UxOTGCON 0x070u /* OTG control (CLR/SET/INV) */\n+#define PIC32MK_UxPWRC 0x080u /* Power control (CLR/SET/INV) */\n+\n+/* USB core registers */\n+#define PIC32MK_UxIR 0x200u /* Interrupt flags (W1C, CLR alias only) */\n+#define PIC32MK_UxIE 0x210u /* Interrupt enable (CLR/SET/INV) */\n+#define PIC32MK_UxEIR 0x220u /* Error int flags (W1C, CLR alias only) */\n+#define PIC32MK_UxEIE 0x230u /* Error int enable (CLR/SET/INV) */\n+#define PIC32MK_UxSTAT 0x240u /* Status (read-only) */\n+#define PIC32MK_UxCON 0x250u /* Control (CLR/SET/INV) */\n+#define PIC32MK_UxADDR 0x260u /* Device address (CLR/SET/INV) */\n+#define PIC32MK_UxBDTP1 0x270u /* BDT ptr [15:9] (CLR/SET/INV) */\n+#define PIC32MK_UxFRML 0x280u /* Frame# low (read-only) */\n+#define PIC32MK_UxFRMH 0x290u /* Frame# high (read-only) */\n+#define PIC32MK_UxTOK 0x2A0u /* Token register (CLR/SET/INV) */\n+#define PIC32MK_UxSOF 0x2B0u /* SOF threshold (CLR/SET/INV) */\n+#define PIC32MK_UxBDTP2 0x2C0u /* BDT ptr [23:16] (CLR/SET/INV) */\n+#define PIC32MK_UxBDTP3 0x2D0u /* BDT ptr [31:24] (CLR/SET/INV) */\n+#define PIC32MK_UxCNFG1 0x2E0u /* Config 1 (CLR/SET/INV) */\n+\n+/* Endpoint control registers UxEPn: base + 0x300 + n * 0x10, n = 0..15 */\n+#define PIC32MK_UxEP_BASE 0x300u\n+#define PIC32MK_UxEP_STRIDE 0x010u\n+#define PIC32MK_USB_NEPS 16\n+\n+/* MMIO size per instance — covers all registers up to UxEP15+CLR/SET/INV */\n+#define PIC32MK_USB_SFR_SIZE 0x1000u\n+\n+/* UxCON bits */\n+#define USB_CON_USBEN (1u << 0) /* USB enable (device) / SOF (host) */\n+#define USB_CON_PPBRST (1u << 1) /* Ping-pong buffer reset */\n+#define USB_CON_RESUME (1u << 3) /* Resume signaling */\n+#define USB_CON_HOSTEN (1u << 4) /* Host mode enable */\n+#define USB_CON_RESET (1u << 4) /* Bus reset (host) */\n+\n+/* UxOTGSTAT bits */\n+#define USB_OTGSTAT_SESVD (1u << 3) /* Session valid (VBUS > V_A_SESS_VLD) */\n+\n+/* UxOTGIR / UxOTGIE bits (Harmony: USB_OTG_INTERRUPTS enum) */\n+#define USB_OTG_IR_SESSION_VALID (1u << 3) /* VBUS session valid change */\n+#define USB_OTG_IR_ACTIVITY_DETECT (1u << 4) /* USB activity detected */\n+\n+/* UxPWRC bits */\n+#define USB_PWRC_USBPWR (1u << 0) /* USB power enable */\n+#define USB_PWRC_USUSPND (1u << 1) /* Suspend */\n+\n+/* UxIR interrupt bits (same layout for UxIE) */\n+#define USB_IR_URSTIF (1u << 0) /* USB Reset */\n+#define USB_IR_UERRIF (1u << 1) /* Error */\n+#define USB_IR_SOFIF (1u << 2) /* Start-of-frame */\n+#define USB_IR_TRNIF (1u << 3) /* Token done */\n+#define USB_IR_IDLEIF (1u << 4) /* Idle detect */\n+#define USB_IR_RESUMEIF (1u << 5) /* Resume */\n+#define USB_IR_ATTACHIF (1u << 6) /* Attach (host mode) */\n+#define USB_IR_STALLIF (1u << 7) /* Stall sent */\n+\n+/* UxSTAT encoding: EP[7:4] | DIR[3] | PPBI[2] */\n+#define USB_STAT_EP0_OUT_EVEN 0x00u\n+#define USB_STAT_EP0_IN_EVEN 0x08u\n+#define USB_STAT_EP1_IN_EVEN 0x18u\n+\n+/* BDT entry control word bits */\n+#define BDT_UOWN (1u << 7) /* USB owns this buffer (firmware arms it) */\n+\n+/*\n+ * Phase 4B EP0 enumeration state machine\n+ * -----------------------------------------------------------------------\n+ */\n+\n+typedef enum {\n+ EP0_SIM_IDLE, /* waiting for USBPWR / USBEN */\n+ EP0_SIM_RESET, /* fire USB Reset interrupt */\n+ EP0_SIM_GET_DEV_DESC, /* inject GET_DESCRIPTOR(Device, 18) */\n+ EP0_SIM_WAIT_DEV_DESC, /* waiting for firmware to fill EP0-IN */\n+ EP0_SIM_SET_ADDRESS, /* inject SET_ADDRESS(1) */\n+ EP0_SIM_WAIT_ADDRESS, /* waiting for status phase */\n+ EP0_SIM_GET_CFG_DESC, /* inject GET_DESCRIPTOR(Config, 67) */\n+ EP0_SIM_WAIT_CFG_DESC, /* waiting for firmware */\n+ EP0_SIM_SET_CONFIG, /* inject SET_CONFIGURATION(1) */\n+ EP0_SIM_WAIT_CONFIG, /* waiting for status phase */\n+ EP0_SIM_DONE, /* fully enumerated — poll CDC TX BDT */\n+} EP0SimState;\n+\n+/*\n+ * QEMU device model\n+ * -----------------------------------------------------------------------\n+ */\n+\n+#define TYPE_PIC32MK_USB \"pic32mk-usb\"\n+OBJECT_DECLARE_SIMPLE_TYPE(PIC32MKUSBState, PIC32MK_USB)\n+\n+struct PIC32MKUSBState {\n+ SysBusDevice parent_obj;\n+\n+ MemoryRegion sfr_mmio; /* 0x1000 bytes */\n+\n+ /* OTG / Power registers */\n+ uint32_t otgir; /* UxOTGIR — OTG interrupt flags */\n+ uint32_t otgie; /* UxOTGIE */\n+ uint32_t otgstat; /* UxOTGSTAT (read-only) */\n+ uint32_t otgcon; /* UxOTGCON */\n+ uint32_t pwrc; /* UxPWRC */\n+\n+ /* Core interrupt registers */\n+ uint32_t uir; /* UxIR — USB interrupt flags */\n+ uint32_t uie; /* UxIE */\n+ uint32_t ueir; /* UxEIR — USB error interrupt flags */\n+ uint32_t ueie; /* UxEIE */\n+\n+ /* Control / Status */\n+ uint32_t ustat; /* UxSTAT (read-only, updated by emulator) */\n+ uint32_t ucon; /* UxCON */\n+ uint32_t uaddr; /* UxADDR */\n+\n+ /* BDT address pages */\n+ uint32_t bdtp1; /* UxBDTP1 — bits [15:9] of BDT base */\n+ uint32_t bdtp2; /* UxBDTP2 — bits [23:16] */\n+ uint32_t bdtp3; /* UxBDTP3 — bits [31:24] */\n+\n+ /* Frame counter (read-only; advanced by timer) */\n+ uint32_t ufrml;\n+ uint32_t ufrmh;\n+\n+ /* Token / SOF */\n+ uint32_t utok;\n+ uint32_t usof;\n+\n+ /* Config */\n+ uint32_t cnfg1;\n+\n+ /* Endpoint control UxEP0–UxEP15 */\n+ uint32_t uep[PIC32MK_USB_NEPS];\n+\n+ /* IRQ line to EVIC */\n+ qemu_irq irq;\n+\n+ /* ---- Phase 4B additions ---- */\n+ QEMUTimer *usb_timer; /* drives enumeration + BDT poll */\n+ CharFrontend chr; /* virtual serial port for CDC data */\n+ EP0SimState ep0_sim; /* enumeration state machine */\n+ bool configured; /* SET_CONFIGURATION accepted */\n+ bool sesvd_edge_latched; /* SESSION_VALID edge latch for IRQ */\n+ bool sesvd_acked; /* firmware has W1C-cleared SESVDIF at least once */\n+ int sesvd_retry_count; /* fallback re-fire counter */\n+\n+ /* ---- USTAT FIFO (PIC32MK has a 4-deep hardware FIFO) ---- */\n+ uint32_t stat_fifo[4]; /* circular buffer of pending USTAT values */\n+ int stat_fifo_head; /* next write position */\n+ int stat_fifo_tail; /* next read position */\n+ int stat_fifo_count; /* number of entries in FIFO */\n+};\n+\n+#endif /* HW_MIPS_PIC32MK_USB_H */\ndiff --git a/tests/functional/mipsel/pic32mk_test_fw.S b/tests/functional/mipsel/pic32mk_test_fw.S\nnew file mode 100644\nindex 0000000000..4e63bbd698\n--- /dev/null\n+++ b/tests/functional/mipsel/pic32mk_test_fw.S\n@@ -0,0 +1,64 @@\n+/*\n+ * pic32mk_test_fw.S — Minimal boot firmware for PIC32MK functional test\n+ *\n+ * Loaded by QEMU at physical 0x1FC40000 (KSEG1: 0xBFC40000) via -bios.\n+ * The board reset vector at 0xBFC00000 has a j 0xBFC40000 trampoline.\n+ *\n+ * Writes \"Hello, PIC32MK!\\r\\n\" to UART1 TX register then spins.\n+ * The functional test waits for this string on the serial console.\n+ *\n+ * UART1 SFR addresses (DS60001519E §21, Table 21-2):\n+ * U1MODE 0xBF828000 bit 15 = UARTEN\n+ * U1STA 0xBF828010 bit 10 = UTXEN\n+ * U1TXREG 0xBF828020 TX data register (write-only)\n+ * U1BRG 0xBF828040 baud rate divisor\n+ *\n+ * SPDX-License-Identifier: GPL-2.0-or-later\n+ */\n+\n+ .section .text\n+ .set noreorder\n+ .set mips32r2\n+ .global _start\n+ .type _start, @function\n+_start:\n+ /* Disable all interrupts, enter kernel mode */\n+ mtc0 $zero, $12 /* CP0 Status = 0 */\n+ nop\n+\n+ /* Stack pointer = top of KSEG1 RAM (physical 256 KB = 0x00040000) */\n+ lui $sp, 0xA004 /* 0xA0040000 */\n+ addiu $sp, $sp, -8\n+\n+ /* ---- UART1 init ---- */\n+ lui $t0, 0xBF82\n+ ori $t0, $t0, 0x8000 /* t0 = 0xBF828000 (U1MODE base) */\n+\n+ addiu $t1, $zero, 64\n+ sw $t1, 0x40($t0) /* U1BRG = 64 (baud ignored by emulator) */\n+ ori $t1, $zero, 0x0400\n+ sw $t1, 0x10($t0) /* U1STA = UTXEN (bit 10) */\n+ lui $t1, 0x8000\n+ sw $t1, 0x00($t0) /* U1MODE = UARTEN (bit 15) */\n+\n+ /* ---- Transmit string byte-by-byte ---- */\n+ addiu $t2, $t0, 0x20 /* t2 = U1TXREG = 0xBF828020 */\n+ la $t1, _msg\n+_putc:\n+ lbu $t3, 0($t1)\n+ beq $t3, $zero, _halt\n+ nop\n+ sw $t3, 0($t2)\n+ addiu $t1, $t1, 1\n+ b _putc\n+ nop\n+\n+_halt:\n+ wait\n+ b _halt\n+ nop\n+\n+ .size _start, . - _start\n+\n+_msg:\n+ .ascii \"Hello, PIC32MK!\\r\\n\\0\"\ndiff --git a/tests/functional/mipsel/pic32mk_test_fw.ld b/tests/functional/mipsel/pic32mk_test_fw.ld\nnew file mode 100644\nindex 0000000000..7e817f276f\n--- /dev/null\n+++ b/tests/functional/mipsel/pic32mk_test_fw.ld\n@@ -0,0 +1,20 @@\n+/*\n+ * pic32mk_test_fw.ld — Linker script for PIC32MK functional test firmware\n+ *\n+ * Links everything at Boot Flash 1 (KSEG1 0xBFC40000, physical 0x1FC40000).\n+ * QEMU loads the raw binary there via load_image_targphys().\n+ *\n+ * SPDX-License-Identifier: GPL-2.0-or-later\n+ */\n+\n+OUTPUT_ARCH(mips)\n+ENTRY(_start)\n+\n+SECTIONS {\n+ . = 0xBFC40000;\n+ .text : {\n+ *(.text*)\n+ *(.rodata*)\n+ }\n+ /DISCARD/ : { *(*) }\n+}\ndiff --git a/tests/functional/mipsel/test_pic32mk.py b/tests/functional/mipsel/test_pic32mk.py\nnew file mode 100644\nindex 0000000000..4af91c4f88\n--- /dev/null\n+++ b/tests/functional/mipsel/test_pic32mk.py\n@@ -0,0 +1,92 @@\n+#!/usr/bin/env python3\n+#\n+# Functional tests for the PIC32MK board emulation\n+#\n+# Copyright (c) 2026 QEMU contributors\n+# SPDX-License-Identifier: GPL-2.0-or-later\n+\n+import os\n+\n+from qemu_test import QemuSystemTest\n+from qemu_test import wait_for_console_pattern\n+\n+\n+class PIC32MKMachine(QemuSystemTest):\n+ \"\"\"\n+ Tests for the PIC32MK MCU board (-M pic32mk).\n+\n+ The FreeRTOS firmware is expected to be pre-built at\n+ tests/freertos/hello_freertos.bin relative to the source tree.\n+ \"\"\"\n+\n+ timeout = 30\n+\n+ def get_firmware_path(self):\n+ \"\"\"Locate the pre-built FreeRTOS firmware binary.\"\"\"\n+ # data_file() resolves relative to tests/functional/\n+ # We need to go up two levels to reach the source root\n+ src_root = os.path.join(\n+ os.path.dirname(__file__), '..', '..', '..')\n+ fw_path = os.path.realpath(\n+ os.path.join(src_root, 'tests', 'freertos', 'hello_freertos.bin'))\n+ if not os.path.isfile(fw_path):\n+ self.skipTest(\n+ f'FreeRTOS firmware not built: {fw_path}\\n'\n+ 'Run: make -C tests/freertos')\n+ return fw_path\n+\n+ def test_boot_message(self):\n+ \"\"\"PIC32MK boots and prints the FreeRTOS banner.\"\"\"\n+ self.set_machine('pic32mk')\n+ self.vm.set_console()\n+ self.vm.add_args('-bios', self.get_firmware_path(),\n+ '-nographic')\n+ self.vm.launch()\n+ wait_for_console_pattern(self,\n+ 'PIC32MK QEMU booting FreeRTOS...')\n+\n+ def test_freertos_hello_task(self):\n+ \"\"\"FreeRTOS scheduler starts and the Hello task fires.\"\"\"\n+ self.set_machine('pic32mk')\n+ self.vm.set_console()\n+ self.vm.add_args('-bios', self.get_firmware_path(),\n+ '-nographic')\n+ self.vm.launch()\n+ # First: boot banner\n+ wait_for_console_pattern(self,\n+ 'PIC32MK QEMU booting FreeRTOS...')\n+ # Second: at least one Hello message (proves Timer1 tick + UART TX IRQ)\n+ wait_for_console_pattern(self,\n+ 'Hello from FreeRTOS!')\n+\n+ def test_freertos_recurring_ticks(self):\n+ \"\"\"Timer1 delivers recurring ticks — multiple Hello messages appear.\"\"\"\n+ self.set_machine('pic32mk')\n+ self.vm.set_console()\n+ self.vm.add_args('-bios', self.get_firmware_path(),\n+ '-nographic')\n+ self.vm.launch()\n+ wait_for_console_pattern(self,\n+ 'PIC32MK QEMU booting FreeRTOS...')\n+ # Wait for a second Hello — proves the tick ISR fires repeatedly\n+ # and context switches work (CS0 yield + Timer1 preemption).\n+ wait_for_console_pattern(self,\n+ 'Hello from FreeRTOS!')\n+ wait_for_console_pattern(self,\n+ 'Hello from FreeRTOS!')\n+\n+ def test_freertos_ping_task(self):\n+ \"\"\"Second FreeRTOS task (Ping) also runs — proves multitasking.\"\"\"\n+ self.set_machine('pic32mk')\n+ self.vm.set_console()\n+ self.vm.add_args('-bios', self.get_firmware_path(),\n+ '-nographic')\n+ self.vm.launch()\n+ wait_for_console_pattern(self,\n+ 'PIC32MK QEMU booting FreeRTOS...')\n+ # Ping task fires every 5 seconds\n+ wait_for_console_pattern(self, 'Ping')\n+\n+\n+if __name__ == '__main__':\n+ QemuSystemTest.main()\ndiff --git a/tests/qtest/pic32mk-canfd-test.c b/tests/qtest/pic32mk-canfd-test.c\nnew file mode 100644\nindex 0000000000..eafb28b3d6\n--- /dev/null\n+++ b/tests/qtest/pic32mk-canfd-test.c\n@@ -0,0 +1,411 @@\n+/*\n+ * QTest for PIC32MK CAN FD controller emulation (pic32mk_canfd.c)\n+ *\n+ * Exercises:\n+ * - Reset state of CiCON, CiINT, CiTXQSTA\n+ * - Mode transitions: Config → Normal, Config → Internal Loopback\n+ * - CiINT.MODIF assertion and firmware clear via read-modify-write\n+ * - TXQ / FIFO configuration and UA register computation\n+ * - Internal loopback: TX frame → acceptance filter → RX FIFO\n+ * - UINC pointer protocol (TX and RX sides)\n+ * - RX overflow detection via CiRXOVIF / CiINT.RXOVIF\n+ * - IRQ flag propagation (CiRXIF, CiTXIF, CiINT.RXIF, CiINT.TXIF)\n+ *\n+ * All addresses are physical (QTest bypasses the MIPS KSEG1 mapping).\n+ * CAN FD SFR registers use 0x10-byte blocks: base/+4(SET)/+8(CLR)/+C(INV).\n+ * Tests use plain writes to the base address (sub=0).\n+ *\n+ * Register offsets verified against XC32 p32mk1024mcm100.h device header.\n+ *\n+ * Copyright (c) 2026 QEMU contributors\n+ * SPDX-License-Identifier: GPL-2.0-or-later\n+ */\n+\n+#include \"qemu/osdep.h\"\n+#include \"libqtest-single.h\"\n+\n+/*\n+ * Address map\n+ * Physical SFR base = 0x1F800000\n+ * CAN1 SFR = SFR + 0x080000 = 0x1F880000\n+ * CAN1 MSGRAM = 0x1F900000 (placeholder, Phase 3C will use CiFIFOBA)\n+ * -----------------------------------------------------------------------\n+ */\n+\n+#define SFR_BASE 0x1F800000u\n+#define CAN1_BASE (SFR_BASE + 0x080000u)\n+#define CAN1_MSGRAM_BASE 0x1F900000u\n+\n+/* CiXxx register physical addresses for CAN1 (base of each 0x10-byte block) */\n+#define CiCON (CAN1_BASE + 0x000u)\n+#define CiNBTCFG (CAN1_BASE + 0x010u)\n+#define CiDBTCFG (CAN1_BASE + 0x020u)\n+#define CiINT (CAN1_BASE + 0x070u)\n+#define CiRXIF (CAN1_BASE + 0x080u)\n+#define CiTXIF (CAN1_BASE + 0x090u)\n+#define CiRXOVIF (CAN1_BASE + 0x0A0u)\n+#define CiTXREQ (CAN1_BASE + 0x0C0u)\n+#define CiTEFCON (CAN1_BASE + 0x100u)\n+#define CiFIFOBA (CAN1_BASE + 0x130u)\n+#define CiTXQCON (CAN1_BASE + 0x140u)\n+#define CiTXQSTA (CAN1_BASE + 0x150u)\n+#define CiTXQUA (CAN1_BASE + 0x160u)\n+\n+/* FIFO1 registers: base = 0x170 + (1-1)*0x30 = 0x170 */\n+#define CiFIFOCON1 (CAN1_BASE + 0x170u)\n+#define CiFIFOSTA1 (CAN1_BASE + 0x180u)\n+#define CiFIFOUA1 (CAN1_BASE + 0x190u)\n+\n+/* FIFO2 registers: base = 0x170 + (2-1)*0x30 = 0x1A0 */\n+#define CiFIFOCON2 (CAN1_BASE + 0x1A0u)\n+#define CiFIFOSTA2 (CAN1_BASE + 0x1B0u)\n+#define CiFIFOUA2 (CAN1_BASE + 0x1C0u)\n+\n+/* Filter control: stride 0x10 per reg (r=0..7) */\n+#define CiFLTCON0 (CAN1_BASE + 0x740u)\n+\n+/* Filter object/mask: stride 0x20 per pair */\n+#define CiFLTOBJ0 (CAN1_BASE + 0x7C0u)\n+#define CiMASK0 (CAN1_BASE + 0x7D0u)\n+\n+/* SET/CLR aliases for key registers (used in some tests) */\n+#define CiCON_SET (CAN1_BASE + 0x004u)\n+#define CiCON_CLR (CAN1_BASE + 0x008u)\n+\n+/*\n+ * CiCON field masks — verified against reset value 0x04980760\n+ * -----------------------------------------------------------------------\n+ */\n+#define CON_REQOP_SHIFT 24u\n+#define CON_REQOP_MASK (0x7u << CON_REQOP_SHIFT)\n+#define CON_OPMOD_SHIFT 21u\n+#define CON_OPMOD_MASK (0x7u << CON_OPMOD_SHIFT)\n+#define CON_TXQEN (1u << 20u)\n+#define CON_RESET_VAL 0x04980760u\n+\n+#define OPMOD_NORMAL 0u\n+#define OPMOD_CONFIG 4u\n+#define OPMOD_INT_LOOP 7u\n+\n+/* CiINT bits — verified against XC32 device header */\n+#define INT_TXIF (1u << 0u)\n+#define INT_RXIF (1u << 1u)\n+#define INT_MODIF (1u << 3u) /* bit 3 on real hardware */\n+#define INT_RXOVIF (1u << 11u)\n+#define INT_TXIE (1u << 16u) /* bit 16 on real hardware */\n+#define INT_RXIE (1u << 17u) /* bit 17 on real hardware */\n+#define INT_MODIE (1u << 19u) /* bit 19 on real hardware */\n+#define INT_RXOVIE (1u << 27u)\n+\n+/* CiFIFOCONn / CiTXQCON bits — verified against XC32 device header */\n+#define FIFO_PLSIZE_SHIFT 29u\n+#define FIFO_FSIZE_SHIFT 24u\n+#define FIFO_TXEN (1u << 7u) /* bit 7 on real hardware */\n+#define FIFO_UINC (1u << 8u) /* bit 8 on real hardware */\n+#define FIFO_TXREQ (1u << 9u) /* bit 9 on real hardware */\n+#define FIFO_FRESET (1u << 10u) /* bit 10 on real hardware */\n+\n+/* CiFIFOSTAn bits */\n+#define FIFOSTA_TFNRFNIF (1u << 0u) /* TX not full / RX not empty */\n+#define FIFOSTA_TXATIF (1u << 4u)\n+\n+/* CiTXQSTA bits */\n+#define TXQSTA_TXQNIF (1u << 0u) /* bit 0 on real hardware */\n+\n+/*\n+ * Helpers\n+ * -----------------------------------------------------------------------\n+ */\n+\n+/* Enter the requested mode; asserts OPMOD matches after write */\n+static void set_mode(uint32_t opmod_val)\n+{\n+ uint32_t con = readl(CiCON);\n+ con = (con & ~CON_REQOP_MASK) | (opmod_val << CON_REQOP_SHIFT);\n+ writel(CiCON, con);\n+ uint32_t result = readl(CiCON);\n+ g_assert_cmphex((result & CON_OPMOD_MASK) >> CON_OPMOD_SHIFT,\n+ ==, opmod_val);\n+}\n+\n+/* Clear specific bits in CiINT via read-modify-write */\n+static void clear_cint_bits(uint32_t mask)\n+{\n+ writel(CiINT, readl(CiINT) & ~mask);\n+}\n+\n+/*\n+ * T1: Reset state\n+ * -----------------------------------------------------------------------\n+ */\n+\n+static void test_reset_state(void)\n+{\n+ /* CiCON must come up in Config mode */\n+ uint32_t con = readl(CiCON);\n+ g_assert_cmphex(con, ==, CON_RESET_VAL);\n+\n+ uint8_t opmod = (con & CON_OPMOD_MASK) >> CON_OPMOD_SHIFT;\n+ g_assert_cmpuint(opmod, ==, OPMOD_CONFIG);\n+\n+ /* CiINT must be clear */\n+ g_assert_cmphex(readl(CiINT), ==, 0u);\n+\n+ /* CiTXQSTA: TXQNIF (bit 0) should be set (slot available) */\n+ g_assert_true((readl(CiTXQSTA) & TXQSTA_TXQNIF) != 0u);\n+}\n+\n+/*\n+ * T2: Mode transition Config → Normal, MODIF assertion, and clear\n+ * -----------------------------------------------------------------------\n+ */\n+\n+static void test_mode_transition(void)\n+{\n+ /* Start in Config mode (reset state) */\n+ g_assert_cmpuint((readl(CiCON) & CON_OPMOD_MASK) >> CON_OPMOD_SHIFT,\n+ ==, OPMOD_CONFIG);\n+\n+ /* Request Normal mode */\n+ set_mode(OPMOD_NORMAL);\n+\n+ /* MODIF (bit 3) must be set in CiINT */\n+ g_assert_true((readl(CiINT) & INT_MODIF) != 0u);\n+\n+ /* Clear MODIF via read-modify-write */\n+ clear_cint_bits(INT_MODIF);\n+ g_assert_false((readl(CiINT) & INT_MODIF) != 0u);\n+\n+ /* Transition back to Config mode for subsequent tests */\n+ set_mode(OPMOD_CONFIG);\n+ clear_cint_bits(INT_MODIF);\n+}\n+\n+/*\n+ * T3: TXQ configuration and UA register computation\n+ * -----------------------------------------------------------------------\n+ */\n+\n+static void test_txq_configure(void)\n+{\n+ /* Must be in Config mode */\n+ set_mode(OPMOD_CONFIG);\n+\n+ /*\n+ * Configure TXQ: PLSIZE=0 (8-byte payload), FSIZE=0 (1 entry).\n+ * Enable TXQ in CiCON via read-modify-write.\n+ */\n+ uint32_t txqcon = (0u << FIFO_PLSIZE_SHIFT) | (0u << FIFO_FSIZE_SHIFT);\n+ writel(CiTXQCON, txqcon);\n+ writel(CiCON, readl(CiCON) | CON_TXQEN);\n+\n+ /* UA must point to the message RAM base for CAN1 */\n+ uint32_t ua = readl(CiTXQUA);\n+ g_assert_cmphex(ua, ==, CAN1_MSGRAM_BASE);\n+}\n+\n+/*\n+ * T4: Internal loopback — TX a frame via TXQ, receive it in FIFO1\n+ * -----------------------------------------------------------------------\n+ */\n+\n+static void test_loopback_tx_rx(void)\n+{\n+ /* --- Configure in Config mode --- */\n+ set_mode(OPMOD_CONFIG);\n+\n+ /* TXQ: 8-byte payload, depth 2 (FSIZE=1) */\n+ uint32_t txqcon = (0u << FIFO_PLSIZE_SHIFT) | (1u << FIFO_FSIZE_SHIFT);\n+ writel(CiTXQCON, txqcon);\n+ writel(CiCON, readl(CiCON) | CON_TXQEN);\n+\n+ /* FIFO1 as RX: TXEN=0, PLSIZE=0 (8B), FSIZE=1 (2 entries), FRESET */\n+ uint32_t fifocon1 = (0u << FIFO_PLSIZE_SHIFT) | (1u << FIFO_FSIZE_SHIFT)\n+ | FIFO_FRESET;\n+ writel(CiFIFOCON1, fifocon1);\n+\n+ /*\n+ * Filter 0: match SID 0x123 exactly.\n+ * CiFLTOBJ0: standard ID in bits [10:0] (XTD=0).\n+ * CiMASK0: 0x1FFFFFFF forces all SID bits to match.\n+ * CiFLTCON0 byte 0: FLTEN=1 (bit7), FIFO=1 (bits [4:0]) → 0x81.\n+ */\n+ writel(CiFLTOBJ0, 0x00000123u); /* SID=0x123, XTD=0 */\n+ writel(CiMASK0, 0x1FFFFFFFu); /* exact match */\n+ /* CiFLTCON0: byte 0 = 0x81 (FLTEN=1, FIFO destination=1) */\n+ writel(CiFLTCON0, 0x00000081u);\n+\n+ /* Enable RXIE and TXIE */\n+ writel(CiINT, readl(CiINT) | INT_RXIE | INT_TXIE);\n+\n+ /* Enter internal loopback mode */\n+ set_mode(OPMOD_INT_LOOP);\n+ clear_cint_bits(INT_MODIF);\n+\n+ /* --- Write TX object at TXQ UA --- */\n+ uint32_t txqua = readl(CiTXQUA);\n+ g_assert_cmphex(txqua, !=, 0u);\n+\n+ /*\n+ * T0: Standard ID 0x123 in bits [28:18] (shifted left by 18).\n+ * T1: DLC=2 (2 bytes), classic CAN (FDF=0).\n+ * Payload: 0xDEAD in first two bytes (little-endian word).\n+ */\n+ uint32_t t0 = 0x123u << 18u;\n+ uint32_t t1 = 2u;\n+ uint32_t payload = 0x0000DEADu;\n+\n+ writel(txqua + 0u, t0);\n+ writel(txqua + 4u, t1);\n+ writel(txqua + 8u, payload);\n+\n+ /* UINC — advance write pointer (hardware clears the bit) */\n+ writel(CiTXQCON, readl(CiTXQCON) | FIFO_UINC);\n+ g_assert_false((readl(CiTXQCON) & FIFO_UINC) != 0u);\n+\n+ /* TXREQ — trigger transmission */\n+ writel(CiTXQCON, readl(CiTXQCON) | FIFO_TXREQ);\n+\n+ /* --- Verify RX delivery --- */\n+\n+ /* CiRXIF bit 1 (FIFO1) must be set */\n+ g_assert_true((readl(CiRXIF) & (1u << 1u)) != 0u);\n+\n+ /* CiINT.RXIF must be set */\n+ g_assert_true((readl(CiINT) & INT_RXIF) != 0u);\n+\n+ /* CiINT.TXIF must be set */\n+ g_assert_true((readl(CiINT) & INT_TXIF) != 0u);\n+\n+ /* Read RX object from FIFO1 UA */\n+ uint32_t fifoua1 = readl(CiFIFOUA1);\n+ g_assert_cmphex(fifoua1, !=, 0u);\n+\n+ uint32_t r0 = readl(fifoua1 + 0u);\n+ uint32_t r1 = readl(fifoua1 + 4u);\n+ uint32_t rdata = readl(fifoua1 + 8u);\n+\n+ /* R0: standard ID in [28:18] */\n+ uint32_t rx_sid = (r0 >> 18u) & 0x7FFu;\n+ g_assert_cmphex(rx_sid, ==, 0x123u);\n+\n+ /* R1: DLC=2 */\n+ g_assert_cmpuint(r1 & 0xFu, ==, 2u);\n+\n+ /* FILHIT in R1[15:11] should be 1 (filter 0 maps to FIFO 1) */\n+ uint32_t filhit = (r1 >> 11u) & 0x1Fu;\n+ g_assert_cmpuint(filhit, ==, 1u);\n+\n+ /* Payload: first word */\n+ g_assert_cmphex(rdata & 0x0000FFFFu, ==, 0x0000DEADu);\n+\n+ /* UINC on FIFO1 — firmware done reading, advance read pointer */\n+ writel(CiFIFOCON1, readl(CiFIFOCON1) | FIFO_UINC);\n+ g_assert_false((readl(CiFIFOCON1) & FIFO_UINC) != 0u);\n+\n+ /* CiRXIF bit 1 must now be clear (FIFO empty) */\n+ g_assert_false((readl(CiRXIF) & (1u << 1u)) != 0u);\n+\n+ /* CiINT.RXIF must be clear */\n+ g_assert_false((readl(CiINT) & INT_RXIF) != 0u);\n+\n+ /* --- Cleanup --- */\n+ clear_cint_bits(INT_RXIE | INT_TXIE | INT_TXIF | INT_RXIF | INT_MODIF);\n+ set_mode(OPMOD_CONFIG);\n+ clear_cint_bits(INT_MODIF);\n+}\n+\n+/*\n+ * T5: RX overflow — FIFO depth 1, deliver 2 frames, verify RXOVIF\n+ * -----------------------------------------------------------------------\n+ */\n+\n+static void test_rx_overflow(void)\n+{\n+ set_mode(OPMOD_CONFIG);\n+\n+ /* TXQ: 8-byte payload, depth 4 (FSIZE=3) */\n+ uint32_t txqcon = (0u << FIFO_PLSIZE_SHIFT) | (3u << FIFO_FSIZE_SHIFT);\n+ writel(CiTXQCON, txqcon);\n+ writel(CiCON, readl(CiCON) | CON_TXQEN);\n+\n+ /* FIFO2 as RX, depth 1 (FSIZE=0) — will overflow on 2nd frame */\n+ writel(CiFIFOCON2, (0u << FIFO_PLSIZE_SHIFT) | (0u << FIFO_FSIZE_SHIFT)\n+ | FIFO_FRESET);\n+\n+ /*\n+ * Filter 1 (byte 1 in CiFLTCON0): match all IDs → FIFO2.\n+ * byte0 = 0x00 (filter 0 disabled), byte1 = 0x82 (FLTEN=1, FIFO=2).\n+ */\n+ writel(CiFLTOBJ0, 0x00000000u);\n+ writel(CiMASK0, 0x00000000u); /* no bits required to match */\n+ writel(CiFLTCON0, 0x00008200u);\n+\n+ /* Enable RXOVIE */\n+ writel(CiINT, readl(CiINT) | INT_RXOVIE | INT_RXIE);\n+\n+ set_mode(OPMOD_INT_LOOP);\n+ clear_cint_bits(INT_MODIF);\n+\n+ /* Send two frames via TXQ */\n+ for (int i = 0; i < 2; i++) {\n+ uint32_t ua = readl(CiTXQUA);\n+ writel(ua + 0u, 0u); /* T0: SID=0, XTD=0 */\n+ writel(ua + 4u, 1u); /* T1: DLC=1 */\n+ writel(ua + 8u, (uint32_t)i); /* payload */\n+ writel(CiTXQCON, readl(CiTXQCON) | FIFO_UINC);\n+ writel(CiTXQCON, readl(CiTXQCON) | FIFO_TXREQ);\n+ }\n+\n+ /* CiRXOVIF bit 2 (FIFO2) must be set */\n+ g_assert_true((readl(CiRXOVIF) & (1u << 2u)) != 0u);\n+\n+ /* CiINT.RXOVIF must be set */\n+ g_assert_true((readl(CiINT) & INT_RXOVIF) != 0u);\n+\n+ /* Cleanup */\n+ writel(CiRXOVIF, 0u);\n+ clear_cint_bits(INT_RXOVIE | INT_RXIE | INT_RXOVIF | INT_RXIF | INT_MODIF);\n+ set_mode(OPMOD_CONFIG);\n+ clear_cint_bits(INT_MODIF);\n+}\n+\n+/*\n+ * T6: CiCON TXQEN bit set/clear via read-modify-write\n+ * -----------------------------------------------------------------------\n+ */\n+\n+static void test_con_rmw(void)\n+{\n+ set_mode(OPMOD_CONFIG);\n+\n+ /* Clear TXQEN */\n+ writel(CiCON, readl(CiCON) & ~CON_TXQEN);\n+ g_assert_false((readl(CiCON) & CON_TXQEN) != 0u);\n+\n+ /* Set TXQEN */\n+ writel(CiCON, readl(CiCON) | CON_TXQEN);\n+ g_assert_true((readl(CiCON) & CON_TXQEN) != 0u);\n+}\n+\n+/*\n+ * Main\n+ * -----------------------------------------------------------------------\n+ */\n+\n+int main(int argc, char **argv)\n+{\n+ g_test_init(&argc, &argv, NULL);\n+ qtest_start(\"-machine pic32mk\");\n+\n+ qtest_add_func(\"/pic32mk-canfd/reset_state\", test_reset_state);\n+ qtest_add_func(\"/pic32mk-canfd/mode_transition\", test_mode_transition);\n+ qtest_add_func(\"/pic32mk-canfd/txq_configure\", test_txq_configure);\n+ qtest_add_func(\"/pic32mk-canfd/loopback_tx_rx\", test_loopback_tx_rx);\n+ qtest_add_func(\"/pic32mk-canfd/rx_overflow\", test_rx_overflow);\n+ qtest_add_func(\"/pic32mk-canfd/con_rmw\", test_con_rmw);\n+\n+ int r = g_test_run();\n+ qtest_end();\n+ return r;\n+}\ndiff --git a/tests/qtest/pic32mk-test.c b/tests/qtest/pic32mk-test.c\nnew file mode 100644\nindex 0000000000..6f7a1088ba\n--- /dev/null\n+++ b/tests/qtest/pic32mk-test.c\n@@ -0,0 +1,457 @@\n+/*\n+ * QTest for PIC32MK peripheral device models\n+ *\n+ * Tests MMIO register access, SET/CLR/INV operations, IRQ assertion,\n+ * and timer tick generation for the PIC32MK board emulation.\n+ *\n+ * Peripherals tested:\n+ * - UART (pic32mk_uart.c) — register reset values, SET/CLR, TX, IRQ\n+ * - Timer (pic32mk_timer.c) — ON/OFF, prescaler, period match, IRQ\n+ * - EVIC (pic32mk_evic.c) — IFS/IEC/IPC, interrupt routing\n+ *\n+ * Copyright (c) 2026 QEMU contributors\n+ * SPDX-License-Identifier: GPL-2.0-or-later\n+ */\n+\n+#include \"qemu/osdep.h\"\n+#include \"libqtest-single.h\"\n+\n+/*\n+ * PIC32MK physical addresses (QTest uses physical, not KSEG1 virtual)\n+ * SFR base = 0x1F800000\n+ * -----------------------------------------------------------------------\n+ */\n+\n+#define SFR_BASE 0x1F800000u\n+\n+/* UART1 base = SFR + 0x028000 */\n+#define UART1_BASE (SFR_BASE + 0x028000u)\n+#define UART1_MODE (UART1_BASE + 0x00u)\n+#define UART1_MODECLR (UART1_BASE + 0x04u)\n+#define UART1_MODESET (UART1_BASE + 0x08u)\n+#define UART1_MODEINV (UART1_BASE + 0x0Cu)\n+#define UART1_STA (UART1_BASE + 0x10u)\n+#define UART1_STACLR (UART1_BASE + 0x14u)\n+#define UART1_STASET (UART1_BASE + 0x18u)\n+#define UART1_STAINV (UART1_BASE + 0x1Cu)\n+#define UART1_TXREG (UART1_BASE + 0x20u)\n+#define UART1_RXREG (UART1_BASE + 0x30u)\n+#define UART1_BRG (UART1_BASE + 0x40u)\n+\n+/* UART bits */\n+#define UMODE_ON (1u << 15)\n+#define USTA_URXDA (1u << 0)\n+#define USTA_OERR (1u << 1)\n+#define USTA_TRMT (1u << 8)\n+#define USTA_UTXBF (1u << 9)\n+#define USTA_UTXEN (1u << 10)\n+#define USTA_URXEN (1u << 12)\n+\n+/* Timer1 base = SFR + 0x020000 */\n+#define T1_BASE (SFR_BASE + 0x020000u)\n+#define T1CON (T1_BASE + 0x00u)\n+#define T1CONCLR (T1_BASE + 0x04u)\n+#define T1CONSET (T1_BASE + 0x08u)\n+#define TMR1 (T1_BASE + 0x10u)\n+#define PR1 (T1_BASE + 0x20u)\n+\n+/* Timer bits */\n+#define TCON_ON (1u << 15)\n+\n+/* EVIC base = SFR + 0x010000 */\n+#define EVIC_BASE (SFR_BASE + 0x010000u)\n+#define EVIC_INTCON (EVIC_BASE + 0x0000u)\n+#define EVIC_INTSTAT (EVIC_BASE + 0x0020u)\n+#define EVIC_IFS0 (EVIC_BASE + 0x0040u)\n+#define EVIC_IFS0CLR (EVIC_BASE + 0x0044u)\n+#define EVIC_IFS0SET (EVIC_BASE + 0x0048u)\n+#define EVIC_IFS1 (EVIC_BASE + 0x0050u)\n+#define EVIC_IFS1CLR (EVIC_BASE + 0x0054u)\n+#define EVIC_IFS1SET (EVIC_BASE + 0x0058u)\n+#define EVIC_IEC0 (EVIC_BASE + 0x00C0u)\n+#define EVIC_IEC0CLR (EVIC_BASE + 0x00C4u)\n+#define EVIC_IEC0SET (EVIC_BASE + 0x00C8u)\n+#define EVIC_IEC1 (EVIC_BASE + 0x00D0u)\n+#define EVIC_IEC1CLR (EVIC_BASE + 0x00D4u)\n+#define EVIC_IEC1SET (EVIC_BASE + 0x00D8u)\n+#define EVIC_IPC0 (EVIC_BASE + 0x0140u)\n+#define EVIC_IPC1 (EVIC_BASE + 0x0150u)\n+\n+/* EVIC source bit positions */\n+#define IRQ_T1_BIT (1u << 4) /* Timer1 = source 4, IFS0 bit 4 */\n+#define IRQ_U1E_BIT (1u << 6) /* U1E = source 38 = IFS1 bit 6 */\n+#define IRQ_U1RX_BIT (1u << 7) /* U1RX = source 39 = IFS1 bit 7 */\n+#define IRQ_U1TX_BIT (1u << 8) /* U1TX = source 40 = IFS1 bit 8 */\n+\n+/*\n+ * ===================================================================\n+ * UART1 register tests\n+ * ===================================================================\n+ */\n+\n+static void test_uart_reset_values(void)\n+{\n+ /* After machine reset: MODE=0, STA=TRMT (shift register empty), BRG=0 */\n+ g_assert_cmphex(readl(UART1_MODE), ==, 0x00000000);\n+ g_assert_cmphex(readl(UART1_STA) & (USTA_TRMT | USTA_UTXBF), ==, USTA_TRMT);\n+ g_assert_cmphex(readl(UART1_BRG), ==, 0x00000000);\n+}\n+\n+static void test_uart_set_clr_inv(void)\n+{\n+ /* Write BRG via direct write */\n+ writel(UART1_BRG, 29);\n+ g_assert_cmphex(readl(UART1_BRG), ==, 29);\n+\n+ /* SET operation: set bit 0 of MODE (STSEL) */\n+ writel(UART1_MODESET, 0x1);\n+ g_assert_cmphex(readl(UART1_MODE) & 0x1, ==, 0x1);\n+\n+ /* CLR operation: clear bit 0 */\n+ writel(UART1_MODECLR, 0x1);\n+ g_assert_cmphex(readl(UART1_MODE) & 0x1, ==, 0x0);\n+\n+ /* INV operation: toggle bit 3 (BRGH) */\n+ uint32_t before = readl(UART1_MODE);\n+ writel(UART1_MODEINV, 0x8);\n+ g_assert_cmphex(readl(UART1_MODE), ==, before ^ 0x8);\n+\n+ /* INV again to toggle back */\n+ writel(UART1_MODEINV, 0x8);\n+ g_assert_cmphex(readl(UART1_MODE), ==, before);\n+\n+ /* Clean up */\n+ writel(UART1_MODE, 0);\n+ writel(UART1_BRG, 0);\n+}\n+\n+static void test_uart_enable(void)\n+{\n+ /* Enable UART: MODESET ON, STASET UTXEN | URXEN */\n+ writel(UART1_MODESET, UMODE_ON);\n+ g_assert_cmphex(readl(UART1_MODE) & UMODE_ON, ==, UMODE_ON);\n+\n+ writel(UART1_STASET, USTA_UTXEN | USTA_URXEN);\n+ g_assert_cmphex(readl(UART1_STA) & (USTA_UTXEN | USTA_URXEN),\n+ ==, USTA_UTXEN | USTA_URXEN);\n+\n+ /* TRMT should still be 1 (TX buffer always empty in QEMU) */\n+ g_assert_cmphex(readl(UART1_STA) & USTA_TRMT, ==, USTA_TRMT);\n+\n+ /* UTXBF should be 0 (transmission is instantaneous) */\n+ g_assert_cmphex(readl(UART1_STA) & USTA_UTXBF, ==, 0);\n+\n+ /* Disable UART */\n+ writel(UART1_MODECLR, UMODE_ON);\n+ g_assert_cmphex(readl(UART1_MODE) & UMODE_ON, ==, 0);\n+}\n+\n+static void test_uart_tx_write(void)\n+{\n+ /* Enable UART TX */\n+ writel(UART1_MODESET, UMODE_ON);\n+ writel(UART1_STASET, USTA_UTXEN);\n+\n+ /* Write a character — should not crash, TRMT stays 1 */\n+ writel(UART1_TXREG, 'A');\n+ g_assert_cmphex(readl(UART1_STA) & USTA_TRMT, ==, USTA_TRMT);\n+\n+ /* Write multiple characters */\n+ writel(UART1_TXREG, 'B');\n+ writel(UART1_TXREG, 'C');\n+ g_assert_cmphex(readl(UART1_STA) & USTA_TRMT, ==, USTA_TRMT);\n+\n+ /* Clean up */\n+ writel(UART1_MODECLR, UMODE_ON);\n+}\n+\n+static void test_uart_rx_not_ready(void)\n+{\n+ /* No byte received yet: URXDA should be 0 */\n+ g_assert_cmphex(readl(UART1_STA) & USTA_URXDA, ==, 0);\n+\n+ /* Reading RXREG when empty returns 0 */\n+ g_assert_cmphex(readl(UART1_RXREG), ==, 0);\n+}\n+\n+/*\n+ * ===================================================================\n+ * EVIC register tests\n+ * ===================================================================\n+ */\n+\n+static void test_evic_reset_values(void)\n+{\n+ /* After reset: IFS0=0, IEC0=0, INTCON=0 */\n+ g_assert_cmphex(readl(EVIC_IFS0), ==, 0x00000000);\n+ g_assert_cmphex(readl(EVIC_IEC0), ==, 0x00000000);\n+ g_assert_cmphex(readl(EVIC_INTCON), ==, 0x00000000);\n+}\n+\n+static void test_evic_ifs_set_clr(void)\n+{\n+ /* SET a flag in IFS0 — e.g. Timer1 (bit 4) */\n+ writel(EVIC_IFS0SET, IRQ_T1_BIT);\n+ g_assert_cmphex(readl(EVIC_IFS0) & IRQ_T1_BIT, ==, IRQ_T1_BIT);\n+\n+ /* CLR the flag */\n+ writel(EVIC_IFS0CLR, IRQ_T1_BIT);\n+ g_assert_cmphex(readl(EVIC_IFS0) & IRQ_T1_BIT, ==, 0);\n+}\n+\n+static void test_evic_iec_set_clr(void)\n+{\n+ /* Enable Timer1 interrupt */\n+ writel(EVIC_IEC0SET, IRQ_T1_BIT);\n+ g_assert_cmphex(readl(EVIC_IEC0) & IRQ_T1_BIT, ==, IRQ_T1_BIT);\n+\n+ /* Disable it */\n+ writel(EVIC_IEC0CLR, IRQ_T1_BIT);\n+ g_assert_cmphex(readl(EVIC_IEC0) & IRQ_T1_BIT, ==, 0);\n+}\n+\n+static void test_evic_ipc_priority(void)\n+{\n+ /* IPC1 holds Timer1 priority (source 4 → IPC1, shift=0, bits[4:2]=IP) */\n+ /* Set T1 priority to 3: write (3 << 2) = 0x0C into IPC1 bits [4:2] */\n+ writel(EVIC_IPC1, 0x0C);\n+ g_assert_cmphex(readl(EVIC_IPC1) & 0x1F, ==, 0x0C);\n+\n+ /* Clear it back */\n+ writel(EVIC_IPC1, 0x00);\n+ g_assert_cmphex(readl(EVIC_IPC1) & 0x1F, ==, 0x00);\n+}\n+\n+static void test_evic_uart_ifs1(void)\n+{\n+ /* UART1 sources are in IFS1: U1E=bit6, U1RX=bit7, U1TX=bit8 */\n+ writel(EVIC_IFS1SET, IRQ_U1TX_BIT);\n+ g_assert_cmphex(readl(EVIC_IFS1) & IRQ_U1TX_BIT, ==, IRQ_U1TX_BIT);\n+\n+ writel(EVIC_IFS1CLR, IRQ_U1TX_BIT);\n+ g_assert_cmphex(readl(EVIC_IFS1) & IRQ_U1TX_BIT, ==, 0);\n+\n+ /* Set all three UART1 flags */\n+ writel(EVIC_IFS1SET, IRQ_U1E_BIT | IRQ_U1RX_BIT | IRQ_U1TX_BIT);\n+ g_assert_cmphex(readl(EVIC_IFS1) & (IRQ_U1E_BIT | IRQ_U1RX_BIT | IRQ_U1TX_BIT),\n+ ==, IRQ_U1E_BIT | IRQ_U1RX_BIT | IRQ_U1TX_BIT);\n+\n+ /* Clear all */\n+ writel(EVIC_IFS1CLR, IRQ_U1E_BIT | IRQ_U1RX_BIT | IRQ_U1TX_BIT);\n+ g_assert_cmphex(readl(EVIC_IFS1) & (IRQ_U1E_BIT | IRQ_U1RX_BIT | IRQ_U1TX_BIT),\n+ ==, 0);\n+}\n+\n+static void test_evic_intstat_readonly(void)\n+{\n+ /* Write to INTSTAT should be ignored (read-only) */\n+ uint32_t before = readl(EVIC_INTSTAT);\n+ writel(EVIC_INTSTAT, 0xDEADBEEF);\n+ g_assert_cmphex(readl(EVIC_INTSTAT), ==, before);\n+}\n+\n+/*\n+ * ===================================================================\n+ * Timer1 register tests\n+ * ===================================================================\n+ */\n+\n+static void test_timer_reset_values(void)\n+{\n+ /* After reset: T1CON=0 (OFF), TMR1=0, PR1=0xFFFF (16-bit max) */\n+ g_assert_cmphex(readl(T1CON), ==, 0x00000000);\n+ g_assert_cmphex(readl(TMR1), ==, 0x00000000);\n+ g_assert_cmphex(readl(PR1), ==, 0x0000FFFF);\n+}\n+\n+static void test_timer_on_off(void)\n+{\n+ /* Set period */\n+ writel(PR1, 14999);\n+\n+ /* Turn ON via SET register */\n+ writel(T1CONSET, TCON_ON);\n+ g_assert_cmphex(readl(T1CON) & TCON_ON, ==, TCON_ON);\n+\n+ /* Turn OFF via CLR register */\n+ writel(T1CONCLR, TCON_ON);\n+ g_assert_cmphex(readl(T1CON) & TCON_ON, ==, 0);\n+\n+ /* Clean up */\n+ writel(PR1, 0);\n+}\n+\n+static void test_timer_period_register(void)\n+{\n+ /* Write period value and read back */\n+ writel(PR1, 14999);\n+ g_assert_cmpuint(readl(PR1), ==, 14999);\n+\n+ /* Different value */\n+ writel(PR1, 999);\n+ g_assert_cmpuint(readl(PR1), ==, 999);\n+\n+ /* Clean up */\n+ writel(PR1, 0);\n+}\n+\n+static void test_timer_prescaler_config(void)\n+{\n+ /*\n+ * Timer1 (Type A) prescaler: bits[5:4] of T1CON\n+ * 00=1:1, 01=1:8, 10=1:64, 11=1:256\n+ */\n+ writel(T1CONSET, (1u << 4)); /* TCKPS=01 → 1:8 */\n+ g_assert_cmphex(readl(T1CON) & 0x30, ==, 0x10);\n+\n+ writel(T1CON, 0); /* reset */\n+ writel(T1CONSET, (3u << 4)); /* TCKPS=11 → 1:256 */\n+ g_assert_cmphex(readl(T1CON) & 0x30, ==, 0x30);\n+\n+ writel(T1CON, 0);\n+}\n+\n+static void test_timer_tick_fires_irq(void)\n+{\n+ /*\n+ * Configure Timer1:\n+ * Prescaler 1:1 (default), PR1=9 → fires after 10 ticks\n+ * Timer clock = 120 MHz internal / 2 (ptimer counts in ns)\n+ * ptimer period = (PR1+1) ticks at system clock\n+ *\n+ * Timer1 is wired to EVIC source 4 (IFS0 bit 4).\n+ * Enable IEC0.T1IE and set IPC1.T1IP=1 so the EVIC routes it.\n+ */\n+\n+ /* Ensure timer is off and clean */\n+ writel(T1CON, 0);\n+ writel(TMR1, 0);\n+\n+ /* Set period to 9 (fires after 10 timer ticks) */\n+ writel(PR1, 9);\n+\n+ /* Enable T1 interrupt in EVIC: IEC0 bit 4, priority 1 in IPC1 */\n+ writel(EVIC_IFS0CLR, IRQ_T1_BIT);\n+ writel(EVIC_IEC0SET, IRQ_T1_BIT);\n+ writel(EVIC_IPC1, (1u << 2)); /* T1IP = 1 */\n+\n+ /* IFS0 should be clear before starting */\n+ g_assert_cmphex(readl(EVIC_IFS0) & IRQ_T1_BIT, ==, 0);\n+\n+ /* Start Timer1: prescaler 1:8 (Type A: TCKPS=01), ON */\n+ writel(T1CON, (1u << 4) | TCON_ON);\n+\n+ /*\n+ * Timer clock = 120 MHz / 8 = 15 MHz → 66.67 ns per tick.\n+ * With ptimer driven by per-CPU clock, the exact virtual time\n+ * depends on ptimer frequency setting. Step enough virtual time\n+ * for the timer to expire: (PR1+1) * tick_period + margin.\n+ *\n+ * ptimer is typically configured at 15 MHz (120/8), so period\n+ * of 10 ticks = 10 * 66.67ns ≈ 667 ns. Step 2000 ns to be safe.\n+ */\n+ clock_step(2000);\n+\n+ /* Timer should have fired: IFS0.T1IF should be set */\n+ g_assert_cmphex(readl(EVIC_IFS0) & IRQ_T1_BIT, ==, IRQ_T1_BIT);\n+\n+ /* Clear it */\n+ writel(EVIC_IFS0CLR, IRQ_T1_BIT);\n+ g_assert_cmphex(readl(EVIC_IFS0) & IRQ_T1_BIT, ==, 0);\n+\n+ /* Stop timer, clean up */\n+ writel(T1CON, 0);\n+ writel(EVIC_IEC0CLR, IRQ_T1_BIT);\n+ writel(EVIC_IPC1, 0);\n+}\n+\n+/*\n+ * ===================================================================\n+ * UART1 TX IRQ integration test\n+ *\n+ * When UART1 is enabled (ON + UTXEN), the TX IRQ line is asserted.\n+ * The EVIC should latch IFS1.U1TXIF.\n+ * ===================================================================\n+ */\n+\n+static void test_uart_tx_irq_asserts_ifs(void)\n+{\n+ /* Clean initial state */\n+ writel(EVIC_IFS1CLR, IRQ_U1TX_BIT);\n+ writel(UART1_MODE, 0);\n+\n+ /* IFS1.U1TXIF should be clear before UART is enabled */\n+ g_assert_cmphex(readl(EVIC_IFS1) & IRQ_U1TX_BIT, ==, 0);\n+\n+ /* Enable UART1: ON + UTXEN */\n+ writel(UART1_MODESET, UMODE_ON);\n+ writel(UART1_STASET, USTA_UTXEN);\n+\n+ /*\n+ * The UART TX IRQ is level-based — enabling ON+UTXEN asserts it.\n+ * The EVIC should have set IFS1.U1TXIF.\n+ */\n+ g_assert_cmphex(readl(EVIC_IFS1) & IRQ_U1TX_BIT, ==, IRQ_U1TX_BIT);\n+\n+ /* Clear the flag in IFS1 */\n+ writel(EVIC_IFS1CLR, IRQ_U1TX_BIT);\n+\n+ /* Since IRQ line is still high (level-based), EVIC re-asserts IFS */\n+ g_assert_cmphex(readl(EVIC_IFS1) & IRQ_U1TX_BIT, ==, IRQ_U1TX_BIT);\n+\n+ /* Disable UART TX — IRQ should deassert */\n+ writel(UART1_STACLR, USTA_UTXEN);\n+\n+ /* Now clear IFS1 — it should stay clear since the line is low */\n+ writel(EVIC_IFS1CLR, IRQ_U1TX_BIT);\n+ g_assert_cmphex(readl(EVIC_IFS1) & IRQ_U1TX_BIT, ==, 0);\n+\n+ /* Clean up */\n+ writel(UART1_MODECLR, UMODE_ON);\n+}\n+\n+/*\n+ * ===================================================================\n+ * main\n+ * ===================================================================\n+ */\n+\n+int main(int argc, char **argv)\n+{\n+ int r;\n+\n+ g_test_init(&argc, &argv, NULL);\n+\n+ qtest_start(\"-machine pic32mk\");\n+\n+ /* UART1 tests */\n+ qtest_add_func(\"/pic32mk/uart/reset-values\", test_uart_reset_values);\n+ qtest_add_func(\"/pic32mk/uart/set-clr-inv\", test_uart_set_clr_inv);\n+ qtest_add_func(\"/pic32mk/uart/enable\", test_uart_enable);\n+ qtest_add_func(\"/pic32mk/uart/tx-write\", test_uart_tx_write);\n+ qtest_add_func(\"/pic32mk/uart/rx-not-ready\", test_uart_rx_not_ready);\n+\n+ /* EVIC tests */\n+ qtest_add_func(\"/pic32mk/evic/reset-values\", test_evic_reset_values);\n+ qtest_add_func(\"/pic32mk/evic/ifs-set-clr\", test_evic_ifs_set_clr);\n+ qtest_add_func(\"/pic32mk/evic/iec-set-clr\", test_evic_iec_set_clr);\n+ qtest_add_func(\"/pic32mk/evic/ipc-priority\", test_evic_ipc_priority);\n+ qtest_add_func(\"/pic32mk/evic/uart-ifs1\", test_evic_uart_ifs1);\n+ qtest_add_func(\"/pic32mk/evic/intstat-readonly\", test_evic_intstat_readonly);\n+\n+ /* Timer1 tests */\n+ qtest_add_func(\"/pic32mk/timer/reset-values\", test_timer_reset_values);\n+ qtest_add_func(\"/pic32mk/timer/on-off\", test_timer_on_off);\n+ qtest_add_func(\"/pic32mk/timer/period-register\", test_timer_period_register);\n+ qtest_add_func(\"/pic32mk/timer/prescaler-config\", test_timer_prescaler_config);\n+ qtest_add_func(\"/pic32mk/timer/tick-fires-irq\", test_timer_tick_fires_irq);\n+\n+ /* Integration: UART TX IRQ → EVIC */\n+ qtest_add_func(\"/pic32mk/integration/uart-tx-irq\", test_uart_tx_irq_asserts_ifs);\n+\n+ r = g_test_run();\n+\n+ qtest_end();\n+\n+ return r;\n+}\n", "prefixes": [ "RFC", "1/3" ] }