[PATCH v2] hw/net/can: Add mcp25625 model

Ben Dooks posted 1 patch 1 year, 1 month ago
hw/net/Kconfig                |    5 +
hw/net/can/can_mcp25625.c     | 1312 +++++++++++++++++++++++++++++++++
hw/net/can/meson.build        |    1 +
hw/net/can/trace-events       |   13 +
include/hw/net/can_mcp25625.h |   10 +
5 files changed, 1341 insertions(+)
create mode 100644 hw/net/can/can_mcp25625.c
create mode 100644 include/hw/net/can_mcp25625.h
[PATCH v2] hw/net/can: Add mcp25625 model
Posted by Ben Dooks 1 year, 1 month ago
From: Ben Dooks <ben.dooks@sifive.com>

Add support for Microchip MCP25625 SPI based CAN controller which is
very similar to the MCP2515 (and covered by the same Linux driver).

This can be added to any machine with SPI support in the machine
model file.

Example for using this when configured into a machine:

	-object can-bus,id=canbus0 \
	-object can-host-socketcan,id=canhost0,if=vcan0,canbus=canbus0 \
	-global driver=mcp25625,property=canbus,value=canbus0

There is tracing support with --trace "*mcp25*"

Signed-off-by: Ben Dooks <ben.dooks@sifive.com>
Co-developed-by: Nazar Kazakov <nazar.kazakov@codethink.co.uk>
Signed-off-by: Nazar Kazakov <nazar.kazakov@codethink.co.uk>
Co-developed-by: Lawrence Hunter <lawrence.hunter@codethink.co.uk>
Signed-off-by: Lawrence Hunter <lawrence.hunter@codethink.co.uk>
Reviewed-by: Frank Chang <frank.chang@sifive.com>
---
 hw/net/Kconfig                |    5 +
 hw/net/can/can_mcp25625.c     | 1312 +++++++++++++++++++++++++++++++++
 hw/net/can/meson.build        |    1 +
 hw/net/can/trace-events       |   13 +
 include/hw/net/can_mcp25625.h |   10 +
 5 files changed, 1341 insertions(+)
 create mode 100644 hw/net/can/can_mcp25625.c
 create mode 100644 include/hw/net/can_mcp25625.h

diff --git a/hw/net/Kconfig b/hw/net/Kconfig
index 18c7851efe..36f1cd1f3e 100644
--- a/hw/net/Kconfig
+++ b/hw/net/Kconfig
@@ -137,6 +137,11 @@ config ROCKER
 config CAN_BUS
     bool
 
+config CAN_MCP25625
+    bool
+    default y if SSI
+    select CAN_BUS
+
 config CAN_SJA1000
     bool
     default y if PCI_DEVICES
diff --git a/hw/net/can/can_mcp25625.c b/hw/net/can/can_mcp25625.c
new file mode 100644
index 0000000000..bc035721d9
--- /dev/null
+++ b/hw/net/can/can_mcp25625.c
@@ -0,0 +1,1312 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * CAN device - MCP25625 chip model
+ *
+ * Copyright (c) 2022 SiFive, Inc.
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/log.h"
+#include "qapi/error.h"
+#include "chardev/char.h"
+#include "hw/irq.h"
+#include "migration/vmstate.h"
+#include "net/can_emu.h"
+#include "hw/ssi/ssi.h"
+#include "hw/qdev-properties.h"
+#include "hw/net/can_mcp25625.h"
+
+#include "trace.h"
+
+/*
+ * Register definitons, addresses and offsets. Since the RX and TX registers
+ * exist in multiple banks we don't tend to refer to these by absolute addresses
+ * as each bank has the same offsets for each register.
+ *
+ * note: filter registers read back 0 unless in config mode
+ */
+
+#define BANK_TXB(__b)  ((__b) + 3)
+#define BANK_RXB(__b)  ((__b) + 6)
+
+#define ADDR_RXB0SIDH           (0x61)
+
+#define OFF_RXFSIDH             (0x0)
+#define OFF_RXFSIDL             (0x1)
+#define OFF_RXFEID8             (0x2)
+#define OFF_RXFEID0             (0x3)
+
+#define RXFSIDL_EXIDE           (1 << 3)
+#define RXFSIDL_WRITEMASK       (0xE0 | RXFSIDL_EXIDE | 0x3)
+
+struct rxfilter {
+    uint8_t data[4];
+};
+
+#define BFPCTRL_WRITEMASK       (0x3F)
+#define BFPCTRL_B0BFM           (1 << 0)
+#define BFPCTRL_B0BFE           (1 << 2)
+#define BFPCTRL_B0BFS           (1 << 4)
+
+#define TXRTSCTRL_WRITEMASK     (0x7)
+
+/* rx mask register offsets (read back 0 unless in config mode) */
+#define OFF_RXMSIDH             (0x0)
+#define OFF_RXMSIDL             (0x1)
+#define OFF_RXMEID8             (0x2)
+#define OFF_RXMEID0             (0x3)
+
+#define RXMSIDL_WRITEMASK       (0xE3)
+
+struct rxmask {
+    uint8_t data[4];
+};
+
+#define CNF3_WRITEMASK          (0xC7)
+
+#define ADDR_TXB0SIDH           (0x31)
+
+#define OFF_TXBCTRL             (0x0)
+#define TXBCTRL_ABTF            (1 << 6)
+#define TXBCTRL_MLOA            (1 << 5)
+#define TXBCTRL_TXREQ           (1 << 3)
+#define TXBCTRL_TXP1            (1 << 1)
+#define TXBCTRL_TXP0            (1 << 0)
+#define TXBCTRL_TXP             (TXBCTRL_TXP1 | TXBCTRL_TXP0)
+#define TXBCTRL_WRITEMASK       (TXBCTRL_TXREQ | TXBCTRL_TXP)
+
+#define OFF_TXBSIDH             (0x1)
+
+#define OFF_TXBSIDL             (0x2)
+#define TXBSIDL_EXIDE           (1 << 3)
+/* bits 7..5 are SID[2:0], bits 1..0 are EID[17:16] */
+#define TXBSIDL_WRITEMASK       (0xE0 | TXBSIDL_EXIDE | 0x3)
+
+#define OFF_TXBEID8             (0x3)   /* this is EID[15:8] */
+#define OFF_TXBEID0             (0x4)   /* this is EID[7:0] */
+
+#define OFF_TXBDLC              (0x5)   /* bits 3..0 are DLC */
+#define TXBDLC_RTR              (1 << 6)
+#define TXBDLC_WRITEMASK        (TXBDLC_RTR | 0xF)
+
+#define OFF_TXBDATA             (0x6)
+
+struct txbuff {
+    uint8_t data[14];
+};
+
+#define OFF_RXBCTRL             (0x0)
+#define RXBCTRL_RXM_ANY         (3 << 5)
+#define RXBCTRL_RXM_VALID       (0 << 5)
+#define RXBCTRL_RXM_MASK        (3 << 5)
+
+#define RXBCTRL_RXRTR           (1 << 3)
+#define RXBCTRL_BUKT            (1 << 2)
+#define RXBCTRL_BUKT1           (1 << 1)
+/* RXBCTRL0, bit0 = filter hit for message */
+/* RXBCTRL1, bits 2..0 show filter hit */
+#define RXBCTRL0_WRITEMASK      (RXBCTRL_RXM_MASK | RXBCTRL_BUKT)
+#define RXBCTRL1_WRITEMASK      (RXBCTRL_RXM_MASK)
+
+#define OFF_RXBSIDH             (0x1)
+/* bits 7..0 = SID[10:3] */
+
+#define OFF_RXBSIDL             (0x2)
+#define RXBSIDL_SRR             (1 << 4)
+#define RXBSIDL_IDE             (1 << 3)
+/* bits 7..5 = SID[2:0], bits 1..0 = EID[17:16] */
+
+#define OFF_RXBEID8             (0x3)   /* bits 7..0 = EID[15:8] */
+#define OFF_RXBEID0             (0x4)   /* bits 7..0 = EID[7:0] */
+
+#define OFF_RXBDLC              (0x5)
+#define RXBDLC_RTR              (1 << 6)  /* bits 3..0 = number of bytes */
+
+#define OFF_RXBDATA             (0x6)
+
+struct rxbuff {
+    uint8_t data[14];
+};
+
+#define EFLG_RX1OVR             (1 << 7)
+#define EFLG_RX0OVR             (1 << 6)
+/* don't think we need any other errors */
+#define EFLG_WRITEMASK          (EFLG_RX1OVR | EFLG_RX0OVR)
+
+#define IRQ_MERR        (1 << 7)
+#define IRQ_WAKE        (1 << 6)
+#define IRQ_ERR         (1 << 5)
+#define IRQ_TX(__n)     (1 << ((__n) + 2))
+#define IRQ_RX(__n)     (1 << (__n))
+
+#define OFF_CANSTAT     (0x0E)
+#define OFF_CANCTRL     (0x0F)
+
+#define CANSTAT_ICOD_MASK (0x7 << 1)
+
+#define CTRL_REQ_NORMAL (0 << 5)
+#define CTRL_REQ_SLEEP  (1 << 5)
+#define CTRL_REQ_LOOP   (2 << 5)
+#define CTRL_REQ_LISTEN (3 << 5)
+#define CTRL_REQ_CFG    (4 << 5)
+#define CTRL_REQ_MASK   (7 << 5)
+
+#define CTRL_CLK_EN     (1 << 2)
+#define CTRL_DEF_CLK    (CTRL_CLK_EN | 0x3)
+#define CTRL_ABAT       (1 << 4)
+
+/*
+ * The state of the current spi access, from SSI_INSTRUCTION where the
+ * system is waiting for the instruction byte to then what steps are
+ * taken given the instruction received.
+ */
+enum mcp_ssi_state {
+    SSI_INSTRUCTION,
+    SSI_ADDRESS,
+    SSI_WR_DATA,
+    SSI_RD_DATA,
+    SSI_WAIT,
+    SSI_RD_STATUS,
+    SSI_RD_RXSTATUS,
+    SSI_MODIFY_ADDR,
+    SSI_MODIFY_MASK,
+    SSI_MODIFY_DATA,
+};
+
+struct MCP25625State;
+
+typedef struct MCP25625State {
+    SSIPeripheralClass parent_class;
+
+    qemu_irq            irq;
+    qemu_irq            rxb_irq[2];
+
+    CanBusClientState   bus_client;
+    CanBusState         *canbus;
+    const char          *trace_name;
+
+    /* spi bus state */
+
+    enum mcp_ssi_state ssi_state;
+    bool ssi_write;
+    bool ssi_only_cfg_rd;
+    bool ssi_can_bitmodify;
+    uint8_t ssi_addr;
+    uint8_t ssi_modify_mask;
+    uint8_t *ssi_ptr;
+    uint8_t ssi_writemask;
+    uint8_t ssi_rxbuff;
+
+    /* internal state */
+
+    uint32_t lastirq;
+
+    /* registers */
+
+    uint8_t canstat;
+    uint8_t canctrl;
+    uint8_t bfpctrl;
+    uint8_t txrtsctrl;
+    uint8_t tec;
+    uint8_t rec;
+    uint8_t cnfs[4];    /* note, cnfs[0] is not used */
+    uint8_t caninte;
+    uint8_t canintf;
+    uint8_t eflg;
+
+    struct txbuff txbuffs[3];
+    struct rxbuff rxbuffs[2];
+    struct rxfilter rxfilters[6];
+    struct rxmask rxmasks[2];
+} MCP25625State;
+
+#define trace_name(__s) ((__s)->trace_name)
+
+static ssize_t mcp25625_can_receive(CanBusClientState *client,
+                                    const qemu_can_frame *buf, size_t frames_cnt);
+static void mcp25625_do_rts(MCP25625State *s, uint32_t tx);
+static void mcp25625_update_canctrl(MCP25625State *s, bool wakeup_happened);
+static void mcp25625_update_irqs(MCP25625State *s, unsigned flags);
+
+static bool mcp25625_is_in_cfg(MCP25625State *s)
+{
+    return (s->canstat & CTRL_REQ_MASK) == CTRL_REQ_CFG;
+}
+
+static bool mcp25625_is_in_sleep(MCP25625State *s)
+{
+    return (s->canstat & CTRL_REQ_MASK) == CTRL_REQ_SLEEP;
+}
+
+static bool mcp25625_is_in_normal(MCP25625State *s)
+{
+    return (s->canstat & CTRL_REQ_MASK) == CTRL_REQ_NORMAL;
+}
+
+static bool mcp25625_is_in_loopback(MCP25625State *s)
+{
+    return (s->canstat & CTRL_REQ_MASK) == CTRL_REQ_LOOP;
+}
+
+static bool mcp25625_mode_exists(uint8_t mode)
+{
+    return (mode == CTRL_REQ_NORMAL) ||
+           (mode == CTRL_REQ_SLEEP) ||
+           (mode == CTRL_REQ_LOOP) ||
+           (mode == CTRL_REQ_LISTEN) ||
+           (mode == CTRL_REQ_CFG);
+}
+
+/*
+ * Map the regsiter number to our internal storage and set various
+ * state data about how it can be modified and when. Since the registers
+ * are in banks, we don't use a linear array. The three TX and two RX banks
+ * are pretty much the same.
+ *
+ * Registers that can be modified with the BIT-MODIFY command:
+ * TXBxCTRL, RXBxCTRL, CNF[1-3], CANINTE, CANINTF, EFLG, CANCTRL,
+ * BFPCTRL and TXRTSCTRL
+ *
+ * We currently assume that the A7 bit is jut not used even through it
+ * is shown on the SPI diagrams. The last register is 0x7f so we wrap
+ * around at that point back to 0x0.
+ */
+static uint8_t *addr_to_reg(MCP25625State *s, unsigned reg)
+{
+    unsigned low = reg & 0xf;
+    unsigned bank = (reg >> 4) & 0x7;
+
+    s->ssi_only_cfg_rd = false;
+    s->ssi_can_bitmodify = false;
+    s->ssi_writemask = 0xff;
+
+    /* CANSTAT and CANCTRL are in all register banks, deal with these first */
+    if (low == OFF_CANSTAT) {
+        s->ssi_writemask = 0x0;
+        return &s->canstat;
+    }
+
+    if (low == OFF_CANCTRL) {
+        s->ssi_can_bitmodify = true;
+        return &s->canctrl;
+    }
+
+    /* check for rxfilter controls next */
+    if (bank < 2 && low < 12) {
+        if (bank == 1) {
+            low += 3 * 4;
+        }
+
+        if (low % 4 == OFF_RXFSIDL) {
+            s->ssi_writemask = RXFSIDL_WRITEMASK;
+        }
+
+        s->ssi_only_cfg_rd = true;
+        return &s->rxfilters[low / 4].data[low % 4];
+    }
+
+    /* process banks for the high-part of the address */
+    switch (bank) {
+    case 0x0:
+        /* left with BPFCTRL and TXRTSCTRL here */
+        s->ssi_can_bitmodify = true;
+        if (low == 12) {
+            s->ssi_writemask = BFPCTRL_WRITEMASK;
+            return &s->bfpctrl;
+        } else if (low == 13) {
+            s->ssi_writemask = TXRTSCTRL_WRITEMASK;
+            s->ssi_only_cfg_rd = true;
+            return &s->txrtsctrl;
+        }
+        break;
+
+    case 0x01:
+        /* left with TEC and REC registers */
+        if (low == 12) {
+            s->ssi_writemask = 0x0;
+            return &s->tec;
+        } else if (low == 13) {
+            s->ssi_writemask = 0x0;
+            return &s->rec;
+        }
+        break;
+
+    case 0x02:
+        if (low < 8) {
+            if (low % 4 == 1) {
+                s->ssi_writemask = RXMSIDL_WRITEMASK;
+            }
+            s->ssi_only_cfg_rd = true;
+            return &s->rxmasks[low / 4].data[low % 4];
+        }
+
+        s->ssi_can_bitmodify = true;
+        switch (low) {
+        case 8:
+            s->ssi_writemask = CNF3_WRITEMASK;
+            s->ssi_only_cfg_rd = true;
+            return &s->cnfs[3];
+        case 9:
+            s->ssi_only_cfg_rd = true;
+            return &s->cnfs[2];
+        case 10:
+            s->ssi_only_cfg_rd = true;
+            return &s->cnfs[1];
+        case 11:
+            return &s->caninte;
+        case 12:
+            return &s->canintf;
+        case 13:
+            s->ssi_writemask = EFLG_WRITEMASK;
+            return &s->eflg;
+        }
+        break;
+
+    case BANK_TXB(0) ... BANK_TXB(2):
+        if (low == OFF_TXBCTRL) {
+            s->ssi_writemask = TXBCTRL_WRITEMASK;
+            s->ssi_can_bitmodify = true;
+        } else if (low == OFF_TXBSIDL) {
+            s->ssi_writemask = TXBSIDL_WRITEMASK;
+        } else if (low == OFF_TXBDLC) {
+            s->ssi_writemask = TXBDLC_WRITEMASK;
+        }
+        return &s->txbuffs[bank - BANK_TXB(0)].data[low];
+
+    case BANK_RXB(0) ... BANK_RXB(1):
+        if (low == OFF_RXFSIDH) {
+            s->ssi_writemask =
+                    (bank == BANK_RXB(0) ? RXBCTRL0_WRITEMASK : RXBCTRL1_WRITEMASK);
+            s->ssi_can_bitmodify = true;
+        } else {
+            s->ssi_writemask = 0x0;
+        }
+        return &s->rxbuffs[bank - BANK_RXB(0)].data[low];
+    }
+
+    /* we shouldn't really get here */
+    return NULL;
+}
+
+static void mcp25625_clear_rx(MCP25625State *s)
+{
+    trace_mcp25625_rx_clear(trace_name(s), s->ssi_rxbuff);
+    s->canintf &= ~IRQ_RX(s->ssi_rxbuff);
+
+    mcp25625_update_irqs(s, 0x0);
+}
+
+static int mcp25625_cs(SSIPeripheral *ss, bool select)
+{
+    MCP25625State *s = MCP25625(ss);
+
+    if (select) {
+        /* chip-select has gone inactive */
+        if (s->ssi_rxbuff != 0xff) {
+            mcp25625_clear_rx(s);
+        }
+    } else {
+        /* chip-select is going active */
+        s->ssi_rxbuff = 0xff;
+        s->ssi_state = SSI_INSTRUCTION;
+    }
+
+    return 0;
+}
+
+static unsigned irq_flags_to_icod(uint32_t val)
+{
+    if (val & IRQ_ERR) {
+        return 1 << 1;
+    } else if (val & IRQ_WAKE) {
+        return 2 << 1;
+    } else if (val & IRQ_TX(0)) {
+        return 3 << 1;
+    } else if (val & IRQ_TX(1)) {
+        return 4 << 1;
+    } else if (val & IRQ_TX(2)) {
+        return 5 << 1;
+    } else if (val & IRQ_RX(0)) {
+        return 6 << 1;
+    } else if (val & IRQ_RX(1)) {
+        return 7 << 1;
+    }
+
+    return 0;
+}
+
+/*
+ * RXBFx pins can be mapped to respective buffer full status of the
+ * CANINTF bits (3.7.3 in datasheet). The pin can either be disabled
+ * an output or an indicator if the relevant buffer is full.
+ */
+static void mcp25625_update_rxbf(MCP25625State *s, unsigned buff)
+{
+    uint8_t bfpctrl = s->bfpctrl;
+    unsigned val = 1;
+
+    /* buffer 1 is just buffer 0 controls shifted down by 1 */
+    if (buff != 0) {
+        bfpctrl >>= 1;
+    }
+
+    if (bfpctrl & BFPCTRL_B0BFE) {
+        if (bfpctrl & BFPCTRL_B0BFM) {
+            val = (s->canintf & IRQ_RX(buff)) ? 0 : 1;
+        } else {
+            val = (bfpctrl & BFPCTRL_B0BFS) ? 1 : 0;
+        }
+    } else {
+        val = 1;
+    }
+
+    qemu_set_irq(s->rxb_irq[buff], val);
+}
+
+static void mcp25625_update_irqs(MCP25625State *s, unsigned flags)
+{
+    uint8_t newirq;
+
+    trace_mcp25625_irq_update(trace_name(s), flags);
+    s->canintf |= flags;
+    newirq = s->canintf & s->caninte;
+
+    if (s->lastirq != newirq) {
+        trace_mcp25625_irq_change(trace_name(s), s->lastirq, newirq);
+
+        s->lastirq = newirq;
+        s->canstat &= ~CANSTAT_ICOD_MASK;
+        if (newirq != 0x0) {
+            s->canstat |= irq_flags_to_icod(newirq);
+        }
+
+        if ((newirq & IRQ_WAKE) && mcp25625_is_in_sleep(s)) {
+            mcp25625_update_canctrl(s, true);
+        }
+    }
+
+    mcp25625_update_rxbf(s, 0);
+    mcp25625_update_rxbf(s, 1);
+    qemu_set_irq(s->irq, newirq ? 1 : 0);
+}
+
+static void mcp25625_got_reset(MCP25625State *s)
+{
+    /*
+     * most of tx/rx buffs have undefined values after reset,
+     * set them to 0 for simplicity and ease of initialisation.
+     */
+    memset(&s->txbuffs, 0x00, sizeof(s->txbuffs));
+    memset(&s->rxbuffs, 0x00, sizeof(s->rxbuffs));
+    memset(&s->rxfilters, 0x00, sizeof(s->rxfilters));
+    memset(&s->rxmasks, 0x00, sizeof(s->rxmasks));
+
+    /* reset all irqs */
+    s->caninte = 0x0;
+    s->canintf = 0x0;
+    s->lastirq = ~(uint32_t)0x0;
+    mcp25625_update_irqs(s, 0x00);
+    s->eflg = 0x0;
+
+    s->bfpctrl = 0x0;
+    s->txrtsctrl = 0x0;
+    s->tec = 0x0;
+    s->rec = 0x0;
+    memset(&s->cnfs, 0x00, sizeof(s->cnfs));
+
+    /* put controller into config mode now */
+    s->canstat = CTRL_REQ_CFG;
+    s->canctrl = CTRL_REQ_CFG | CTRL_DEF_CLK;
+}
+
+static void mcp25625_send_txb(MCP25625State *s, unsigned buff)
+{
+    struct txbuff *txb = &s->txbuffs[buff];
+    qemu_can_frame frame;
+    qemu_canid_t id;
+    unsigned len;
+
+    if (!mcp25625_is_in_normal(s) && !mcp25625_is_in_loopback(s)) {
+        return;
+    }
+
+    len = txb->data[OFF_TXBDLC] & 0xf;
+    if (len > 8) {
+        len = 8;
+    }
+    trace_mcp25625_send_txb(trace_name(s), buff, len);
+
+    id = txb->data[OFF_TXBSIDH] << 3;
+    id |= (txb->data[OFF_TXBSIDL] & 0xE0) >> 5;
+
+    if (txb->data[OFF_TXBSIDL] & TXBSIDL_EXIDE) {
+        id <<= 18;
+        id |= QEMU_CAN_EFF_FLAG;
+        id |= (qemu_canid_t)(txb->data[OFF_TXBSIDL] & 3) << 16;
+        id |= (qemu_canid_t)txb->data[OFF_TXBEID8] << 8;
+        id |= (qemu_canid_t)txb->data[OFF_TXBEID0];
+    }
+
+    if (txb->data[OFF_TXBDLC] & TXBDLC_RTR) {
+        id |= QEMU_CAN_RTR_FLAG;
+    }
+
+    memcpy(frame.data, txb->data + OFF_TXBDATA, len);
+    frame.can_dlc = len;
+    frame.can_id = id;
+    frame.flags = 0;
+
+    if (mcp25625_is_in_normal(s)) {
+        can_bus_client_send(&s->bus_client, &frame, 1);
+    } else {
+        mcp25625_can_receive(&s->bus_client, &frame, 1);
+    }
+
+    txb->data[OFF_TXBCTRL] &= ~TXBCTRL_TXREQ;
+    mcp25625_update_irqs(s, IRQ_TX(buff));
+}
+
+static void mcp25625_update_txbctrl(MCP25625State *s, unsigned txbuff)
+{
+    struct txbuff *txb = &s->txbuffs[txbuff];
+    uint8_t txbctrl = txb->data[OFF_TXBCTRL];
+
+    if (txbctrl & TXBCTRL_TXREQ) {
+        mcp25625_send_txb(s, txbuff);
+    }
+}
+
+static void mcp25625_update_canctrl(MCP25625State *s, bool wakeup_happened)
+{
+    uint8_t ctrl_op, stat_op;
+    int txbuff;
+
+    /* check and clear aborted buffers */
+    if (s->canctrl & CTRL_ABAT) {
+        for (txbuff = 0; txbuff < 2; txbuff++) {
+            struct txbuff *txb = &s->txbuffs[txbuff];
+            uint8_t *txbctrl = &txb->data[OFF_TXBCTRL];
+            if (*txbctrl & TXBCTRL_TXREQ) {
+                *txbctrl &= ~TXBCTRL_TXREQ;
+                *txbctrl |= TXBCTRL_ABTF;
+            }
+        }
+    }
+
+    ctrl_op = s->canctrl & CTRL_REQ_MASK;
+    stat_op = s->canstat & CTRL_REQ_MASK;
+
+    if (!mcp25625_mode_exists(ctrl_op)) {
+        ctrl_op = stat_op;
+        s->canctrl &= ~CTRL_REQ_MASK;
+        s->canctrl |= ctrl_op;
+    }
+
+    /* check to see if we should change the device mode */
+    if (ctrl_op != stat_op && (wakeup_happened || stat_op != CTRL_REQ_SLEEP)) {
+        uint8_t rts = 0;
+
+        trace_mcp25625_change_mode(trace_name(s), stat_op, ctrl_op);
+
+        /* write the new mode to canstat */
+        s->canstat &= ~CTRL_REQ_MASK;
+        s->canstat |= ctrl_op;
+
+        /* send pending tx if possible */
+        for (txbuff = 0; txbuff < 2; txbuff++) {
+            struct txbuff *txb = &s->txbuffs[txbuff];
+            uint8_t txbctrl = txb->data[OFF_TXBCTRL];
+
+            if (txbctrl & TXBCTRL_TXREQ) {
+                rts |= (1 << txbuff);
+            }
+        }
+        mcp25625_do_rts(s, rts);
+    }
+
+    /* mcp25625 sets canctrl.reqop to listen mode during sleep */
+    if (s->canstat & CTRL_REQ_SLEEP) {
+        s->canctrl &= ~CTRL_REQ_MASK;
+        s->canctrl |= CTRL_REQ_LISTEN;
+    }
+}
+
+static void mc25625_update_reg(MCP25625State *s, uint8_t val)
+{
+    trace_mpc25652_reg_update(trace_name(s), s->ssi_addr, *s->ssi_ptr, val);
+    *s->ssi_ptr &= ~s->ssi_writemask;
+    *s->ssi_ptr |= val & s->ssi_writemask;
+
+    if (s->ssi_ptr == &s->caninte || s->ssi_ptr == &s->canintf) {
+        mcp25625_update_irqs(s, 0x0);
+    } else if (s->ssi_ptr == &s->bfpctrl) {
+        mcp25625_update_rxbf(s, 0);
+        mcp25625_update_rxbf(s, 1);
+    } else if (s->ssi_ptr == &s->canctrl) {
+        mcp25625_update_canctrl(s, false);
+    } else if (s->ssi_ptr == &s->rxbuffs[0].data[OFF_RXBCTRL]) {
+        *s->ssi_ptr &= ~RXBCTRL_BUKT1;
+        *s->ssi_ptr |= (*s->ssi_ptr & RXBCTRL_BUKT) ? RXBCTRL_BUKT1 : 0;
+    } else {
+        unsigned txbuff;
+
+        for (txbuff = 0; txbuff <= 2; txbuff++) {
+            if (s->ssi_ptr == &s->txbuffs[txbuff].data[OFF_TXBCTRL]) {
+                if (s->canctrl & CTRL_ABAT) {
+                    *s->ssi_ptr &= ~TXBCTRL_TXREQ;
+                } else {
+                    *s->ssi_ptr &= ~TXBCTRL_ABTF;
+                    mcp25625_update_txbctrl(s, txbuff);
+                }
+                break;
+            }
+        }
+    }
+}
+
+static uint8_t mcp25625_get_txp(MCP25625State *s, int buff)
+{
+    struct txbuff *txb = &s->txbuffs[buff];
+    return txb->data[OFF_TXBCTRL] & TXBCTRL_TXP;
+}
+
+static void mcp25625_do_rts(MCP25625State *s, uint32_t tx)
+{
+    int ordered[3], ordered_used = 0, buf_place;
+    int i, curr_buf;
+
+    trace_mcp25625_do_rts(trace_name(s), tx);
+
+    if (s->canctrl & CTRL_ABAT) {
+        return;
+    }
+
+    for (curr_buf = 2; curr_buf >= 0; curr_buf--) {
+        /* skip non-requested bufs */
+        if (!(tx & (1 << curr_buf))) {
+            continue;
+        }
+
+        /* find a place in `ordered` for current buf */
+        for (buf_place = 0; buf_place < ordered_used; buf_place++) {
+            uint8_t our_txp = mcp25625_get_txp(s, curr_buf);
+            uint8_t other_txp = mcp25625_get_txp(s, ordered[buf_place]);
+
+            if (our_txp > other_txp) {
+                break;
+            }
+        }
+
+        /* make room for current buf */
+        for (i = ordered_used; i >= buf_place + 1; i--) {
+            ordered[i] = ordered[i - 1];
+        }
+
+        /* finally insert current buf in `ordered` */
+        ordered[buf_place] = curr_buf;
+        ordered_used += 1;
+    }
+
+    for (i = 0; i < ordered_used; i++) {
+        struct txbuff *txb = &s->txbuffs[ordered[i]];
+        txb->data[OFF_TXBCTRL] &= ~TXBCTRL_ABTF;
+        txb->data[OFF_TXBCTRL] |= TXBCTRL_TXREQ;
+        mcp25625_update_txbctrl(s, ordered[i]);
+    }
+}
+
+static uint32_t mcp25625_get_status(MCP25625State *s)
+{
+    uint32_t result;
+    unsigned txb;
+
+    /* lower two bits map directly to irqs for rx0/rx1 */
+    result = (s->canintf & (IRQ_RX(0) | IRQ_RX(1)));
+
+    for (txb = 0; txb < 3; txb++) {
+        if (s->canintf & IRQ_TX(txb)) {
+            result |= (1 << (3 + txb * 2));
+        }
+
+        if (s->txbuffs[txb].data[OFF_TXBCTRL] & TXBCTRL_TXREQ) {
+            result |= (1 << (2 + txb * 2));
+        }
+    }
+
+    return result;
+}
+
+static uint32_t mcp25625_get_rxstatus(MCP25625State *s)
+{
+    struct rxbuff *rxbuff = NULL;
+    uint32_t result;
+
+    /*
+     * [7:6] 0=none, 1=rxb0 full, 2=rxb1 full, 3=both full
+     * [4:3] type of frame received (rx0 priority)
+     * [2:0] filter hit for frame
+     */
+
+    result = (s->canintf & (IRQ_RX(0) | IRQ_RX(1))) << 6;
+
+    if (s->canintf & IRQ_RX(0)) {
+        rxbuff = &s->rxbuffs[0];
+    } else if (s->canintf & IRQ_RX(1)) {
+        rxbuff = &s->rxbuffs[1];
+    }
+
+    if (rxbuff) {
+        uint8_t rxbctrl = rxbuff->data[OFF_RXBCTRL];
+
+        /* type of frame */
+        if (rxbctrl & RXBCTRL_RXRTR) {
+            result |= (1 << 3);
+        }
+
+        if (rxbuff->data[OFF_RXBSIDL] & RXBSIDL_IDE) {
+            result |= (1 << 4);
+        }
+
+        /* filter hit depends on buffer */
+        if (s->canintf & IRQ_RX(0)) {
+            result |= rxbctrl & 1;
+        } else {
+            result |= rxbctrl & 7;
+            if ((result & 7) <= 1) {
+                result += 6;
+            }
+        }
+    }
+
+    return result;
+}
+
+/*
+ * spi transfer function, used to move data into or out of the device.
+ *
+ * Depending on the spi internal state, which is either awaiting a command
+ * or actioning the data from the command. The device has shortcut commands
+ * to point at various registers, or modify given registers.
+ *
+ * Note, a lot of these commands are only used here so defines aren't used
+ * for the command numbers.
+ */
+static uint32_t mcp25625_transfer8(SSIPeripheral *ss, uint32_t tx)
+{
+    MCP25625State *s = MCP25625(ss);
+    uint32_t ret = 0xffff;
+
+    trace_mcp25625_transfer8(trace_name(s), s->ssi_state, s->ssi_addr, tx);
+
+    switch (s->ssi_state) {
+    case SSI_INSTRUCTION:
+        s->ssi_write = false;
+
+        /* next bit is in datasheet order, not numerical order */
+        if (tx == (3 << 6)) {
+            mcp25625_got_reset(s);
+            s->ssi_state = SSI_WAIT;
+        } else if (tx == 3) {
+            s->ssi_state = SSI_ADDRESS;
+        } else if ((tx & 1) == 0 && (tx >= 0x90 && tx <= 0x96)) {
+            uint32_t rxptr = (tx - 0x90) >> 1;
+
+            /* read-buffer command */
+
+            s->ssi_addr = ADDR_RXB0SIDH + (rxptr >> 1) * 0x10;
+            s->ssi_addr += (rxptr & 1) ? 5 : 0;
+            s->ssi_state = SSI_RD_DATA;
+            s->ssi_rxbuff = rxptr >> 1;
+        } else if (tx == 2) {           /* write-data instruction */
+            s->ssi_state = SSI_ADDRESS;
+            s->ssi_write = true;
+        } else if (tx >= 0x40 && tx <= 0x45) {
+            uint32_t tx_off = tx - 0x40;
+
+            /* load tx-buffer instruction -> point to relevant tx buffer */
+            s->ssi_state = SSI_WR_DATA;
+            s->ssi_write = true;
+            s->ssi_addr = ADDR_TXB0SIDH + (tx_off >> 1) * 0x10;
+            s->ssi_addr += (tx_off & 1) ? 5 : 0;
+        } else if (tx >= 0x80 && tx <= 0x87) {
+            s->ssi_state = SSI_WAIT;
+            mcp25625_do_rts(s, tx);
+        } else if (tx == 0xa0) {
+            s->ssi_state = SSI_RD_STATUS;
+        } else if (tx == 0xb0) {
+            s->ssi_state = SSI_RD_RXSTATUS;
+        } else if (tx == 5) {
+            s->ssi_state = SSI_MODIFY_ADDR;
+        } else {
+            qemu_log_mask(LOG_GUEST_ERROR, "%s: spi invalid command %02x\n",
+                          trace_name(s), tx);
+
+            trace_mcp25625_invalid_cmd(trace_name(s), tx);
+            s->ssi_addr = 0x00;
+            s->ssi_state = SSI_WAIT;
+        }
+
+        ret = 0xff;
+        break;
+
+    case SSI_RD_STATUS:
+        ret = mcp25625_get_status(s);
+        break;
+
+    case SSI_RD_RXSTATUS:
+        ret = mcp25625_get_rxstatus(s);
+        break;
+
+    case SSI_WAIT:
+        ret = 0xff;
+        break;
+
+    case SSI_ADDRESS:
+        s->ssi_state = (s->ssi_write) ? SSI_WR_DATA : SSI_RD_DATA;
+        s->ssi_addr = tx;
+        ret = 0xff;
+        break;
+
+    case SSI_WR_DATA:
+        s->ssi_ptr = addr_to_reg(s, s->ssi_addr);
+        if (s->ssi_ptr) {
+            mc25625_update_reg(s, tx);
+        }
+        s->ssi_addr += 1;
+        ret = 0xff;
+        break;
+
+    case SSI_RD_DATA:
+        s->ssi_ptr = addr_to_reg(s, s->ssi_addr);
+        if (s->ssi_only_cfg_rd && !mcp25625_is_in_cfg(s)) {
+            ret = 0x00;
+        } else if (s->ssi_ptr) {
+            ret = *s->ssi_ptr;
+        } else {
+            ret = 0xff;
+        }
+        s->ssi_addr += 1;
+        break;
+
+        /* modify register - awaiting address */
+    case SSI_MODIFY_ADDR:
+        s->ssi_addr = tx;
+        s->ssi_ptr = addr_to_reg(s, tx);
+        s->ssi_state = SSI_MODIFY_MASK;
+        ret = 0xff;
+        break;
+
+        /* modify register - awaiting mask data */
+    case SSI_MODIFY_MASK:
+        s->ssi_modify_mask = tx;
+        s->ssi_state = SSI_MODIFY_DATA;
+        ret = 0xff;
+        break;
+
+    case SSI_MODIFY_DATA:
+        if (!s->ssi_can_bitmodify) {
+            s->ssi_modify_mask = 0xff;
+        }
+        if (s->ssi_ptr) {
+            tx &= s->ssi_modify_mask;
+            tx |= *s->ssi_ptr & ~s->ssi_modify_mask;
+            mc25625_update_reg(s, tx);
+        }
+
+        ret = 0xff;
+        break;
+
+    default:
+        qemu_log_mask(LOG_GUEST_ERROR, "%s: spi invalid state (ssi_state=%d)\n",
+                      trace_name(s), s->ssi_state);
+        s->ssi_state = SSI_WAIT;
+        ret = 0xff;
+    }
+
+    trace_mcp25625_transfer8_return(trace_name(s), s->ssi_state, s->ssi_addr,
+                                    tx, ret);
+    return ret;
+}
+
+#define client_to_mcp(__c) container_of(__c, MCP25625State, bus_client)
+
+static bool mcp25625_check_filter(const qemu_can_frame *frame,
+                                  struct rxmask *mask,
+                                  struct rxfilter *filt)
+{
+    uint32_t mask32, filt32, data;
+    bool frame_is_extended = !!(frame->can_id & QEMU_CAN_EFF_FLAG);
+    bool filter_is_extended = !!(filt->data[OFF_RXFSIDL] & RXFSIDL_EXIDE);
+
+    if (frame_is_extended != filter_is_extended) {
+        return false;
+    }
+
+    mask32 =
+        (mask->data[OFF_RXMSIDH] << 21)
+        | ((mask->data[OFF_RXMSIDL] & 0xE0) << 13)
+        | ((mask->data[OFF_RXMSIDL] & 0x3) << 16)
+        | (mask->data[OFF_RXMEID8] << 8)
+        | (mask->data[OFF_RXMEID0] << 0);
+
+    filt32 =
+        (filt->data[OFF_RXFSIDH] << 21)
+        | ((filt->data[OFF_RXFSIDL] & 0xE0) << 13)
+        | ((filt->data[OFF_RXFSIDL] & 0x3) << 16)
+        | (filt->data[OFF_RXFEID8] << 8)
+        | (filt->data[OFF_RXFEID0] << 0);
+
+    if (frame_is_extended) {
+        data = frame->can_id & QEMU_CAN_EFF_MASK;
+    } else {
+        /* accept data bytes by default if the frame is too short */
+        uint8_t len = (frame->can_id & QEMU_CAN_RTR_FLAG) ? 0 : frame->can_dlc;
+        uint8_t data0 = len > 0 ? frame->data[0] : filt->data[OFF_RXFEID8];
+        uint8_t data1 = len > 1 ? frame->data[1] : filt->data[OFF_RXFEID0];
+
+        data = (frame->can_id & QEMU_CAN_SFF_MASK) << 18 | data0 << 8 | data1;
+        filt32 &= ~(3 << 16);
+    }
+
+    return (~mask32 | ~(filt32 ^ data)) == 0xFFFFFFFF;
+}
+
+/*
+ * mcp25625_check_filters:
+ * for rxb0, rxmask0 and rxfilters[0..1] are used
+ * for rxb1, rxmask1 and rxfilters[2..5] are used
+ */
+static int mcp25625_check_filters(MCP25625State *s,
+                                  const qemu_can_frame *frame,
+                                  unsigned rxb)
+{
+    struct rxbuff *rxbuff = &s->rxbuffs[rxb];
+    uint8_t rxbctrl = rxbuff->data[OFF_RXBCTRL];
+    struct rxmask *mask = &s->rxmasks[rxb];
+    unsigned nr, end;
+
+    /* if we're not bothering with filters, fake hit on filter 0 */
+    if ((rxbctrl & RXBCTRL_RXM_MASK) == RXBCTRL_RXM_ANY) {
+        return 0;
+    }
+
+    /* note, these are invalid states, so just ignore */
+    if ((rxbctrl & RXBCTRL_RXM_MASK) != RXBCTRL_RXM_VALID) {
+        return -2;
+    }
+
+    if (rxb == 0) {
+        nr = 0;
+        end = 1;
+    } else {
+        nr = 2;
+        end = 5;
+    }
+
+    for (; nr <= end; nr++) {
+        if (mcp25625_check_filter(frame, mask, &s->rxfilters[nr])) {
+            return nr;
+        }
+    }
+
+    return -1;
+}
+
+static void mcp25625_rx_into_buf(MCP25625State *s,
+                                 const qemu_can_frame *frame,
+                                 unsigned buffnr, int filthit)
+{
+    struct rxbuff *rxbuff = &s->rxbuffs[buffnr];
+    qemu_canid_t e_id, sidl, id, q_id = frame->can_id;
+    unsigned len = frame->can_dlc;
+
+    trace_mcp25625_rx_buf(trace_name(s), buffnr, q_id, len);
+
+    if (len > 8)
+        return;
+
+    if (q_id & QEMU_CAN_EFF_FLAG) {
+        e_id = q_id & QEMU_CAN_EFF_MASK;
+        id = e_id >> 18;
+    } else {
+        id = q_id & QEMU_CAN_SFF_MASK;
+        e_id = 0;
+    }
+
+    sidl = (id & 0x7) << 5;
+    if (q_id & QEMU_CAN_EFF_FLAG) {
+        sidl |= RXBSIDL_IDE;
+        sidl |= (e_id >> 16) & 3;
+    } else {
+        if (q_id & QEMU_CAN_RTR_FLAG) {
+            sidl |= RXBSIDL_SRR;
+        }
+    }
+
+    rxbuff->data[OFF_RXBSIDL] = sidl;
+    rxbuff->data[OFF_RXBSIDH] = id >> 3;
+    rxbuff->data[OFF_RXBEID8] = (e_id >> 8) & 0xff;
+    rxbuff->data[OFF_RXBEID0] = e_id & 0xff;
+
+    rxbuff->data[OFF_RXBDLC] = len;
+    if ((q_id & QEMU_CAN_RTR_FLAG) && (q_id & QEMU_CAN_EFF_FLAG)) {
+        rxbuff->data[OFF_RXBDLC] |= RXBDLC_RTR;
+    }
+
+    if (buffnr == 0) {
+        rxbuff->data[OFF_RXBCTRL] &= ~((1 << 0) | RXBCTRL_RXRTR);
+
+        if (filthit > 0) {
+            rxbuff->data[OFF_RXBCTRL] |= (1 << 0);
+        }
+    } else {
+        rxbuff->data[OFF_RXBCTRL] &= ~(7 | RXBCTRL_RXRTR);
+        rxbuff->data[OFF_RXBCTRL] |= filthit;
+    }
+
+    if (q_id & QEMU_CAN_RTR_FLAG) {
+        rxbuff->data[OFF_RXBCTRL] |= RXBCTRL_RXRTR;
+    }
+
+    /* finally copy the frame data in */
+    memcpy(rxbuff->data + OFF_RXBDATA, frame->data, len);
+
+    mcp25625_update_irqs(s, IRQ_RX(buffnr));
+}
+
+#define is_full(_s, _b) (s->canintf & IRQ_RX(_b))
+#define is_bukt(_s) (s->rxbuffs[0].data[OFF_RXBCTRL] & RXBCTRL_BUKT)
+
+static inline void mcp25625_set_eflag(MCP25625State *s, unsigned flag)
+{
+    s->eflg |= flag;
+    mcp25625_update_irqs(s, IRQ_ERR);
+}
+
+static ssize_t mcp25625_can_receive(CanBusClientState *client,
+                                    const qemu_can_frame *buf,
+                                    size_t frames_cnt)
+{
+    MCP25625State *s = client_to_mcp(client);
+    int ret;
+
+    /* support receiving only one frame at a time */
+    if (frames_cnt != 1) {
+        return -1;
+    }
+
+    /* we don't support error frames or buf->flags */
+    if (buf->can_id & QEMU_CAN_ERR_FLAG || buf->flags != 0) {
+        return -1;
+    }
+
+    if (mcp25625_is_in_sleep(s)) {
+        mcp25625_update_irqs(s, IRQ_WAKE);
+        return 0;
+    }
+
+    /* initially, does rxb0 pass this */
+    ret = mcp25625_check_filters(s, buf, 0);
+    if (ret >= 0) {
+        if (is_full(s, 0)) {
+            /* if we can roll over into rxb1? */
+
+            if (is_bukt(s)) {
+                if (is_full(s, 1)) {
+                    /* generate overflow on rxb0. real hw does this */
+                    mcp25625_set_eflag(s, EFLG_RX0OVR);
+                } else {
+                    /* dump this into rxb1 */
+                    mcp25625_rx_into_buf(s, buf, 1, ret);
+                }
+            } else {
+                /* generate overflow on rxb0 */
+                mcp25625_set_eflag(s, EFLG_RX0OVR);
+            }
+        } else {
+            /* dump this into rxb0 */
+            mcp25625_rx_into_buf(s, buf, 0, ret);
+        }
+    } else {
+        /* see if rxb1 will take this */
+
+        ret = mcp25625_check_filters(s, buf, 1);
+        if (ret >= 0) {
+            if (is_full(s, 1)) {
+                /* generate overflow on rxb1 */
+                mcp25625_set_eflag(s, EFLG_RX1OVR);
+            } else {
+                /* dump into rxb1 */
+                mcp25625_rx_into_buf(s, buf, 1, ret);
+            }
+        }
+    }
+
+    return 0;
+}
+
+static bool mcp25625_can_can_receive(CanBusClientState *client)
+{
+    MCP25625State *s = client_to_mcp(client);
+
+    return !mcp25625_is_in_cfg(s) && !mcp25625_is_in_loopback(s);
+}
+
+static CanBusClientInfo mcp25625_bus_client_info = {
+    .can_receive = mcp25625_can_can_receive,
+    .receive = mcp25625_can_receive,
+};
+
+static void mcp25625_realize(SSIPeripheral *ss, Error **errp)
+{
+    MCP25625State *s = MCP25625(ss);
+    DeviceState *dev = DEVICE(ss);
+    int ret;
+
+    s->trace_name = object_get_canonical_path(OBJECT(s));
+
+    if (s->canbus) {
+        s->bus_client.info = &mcp25625_bus_client_info;
+        ret = can_bus_insert_client(s->canbus, &s->bus_client);
+        if (ret) {
+            error_setg(errp, "cannot connect mcp25625 to canbus");
+            return;
+        }
+    }
+
+    /* initialise the output pins for irq and the rxb0/rxb1 pins
+     * used to indicate buffer status if enabled. Note we don't
+     * use the tx trigger pins yet.
+     */
+    qdev_init_gpio_out_named(dev, &s->irq, "irq", 1);
+    qdev_init_gpio_out_named(dev, &s->rxb_irq[0], "rxbf0", 1);
+    qdev_init_gpio_out_named(dev, &s->rxb_irq[1], "rxbf1", 1);
+}
+
+static int mcp25625_post_load(void *op, int ver)
+{
+    MCP25625State *s = op;
+
+    /* setting ssi_ptr also resets other non-saved ssi data */
+    s->ssi_ptr = addr_to_reg(s, s->ssi_addr);
+
+    /* reset irq state */
+    s->lastirq = ~(uint32_t)0x0;
+    mcp25625_update_irqs(s, 0x00);
+
+    return 0;
+}
+
+/*
+ * Use the mcp25625_got_reset() call to reset the state, which is
+ * probably good enough for now.
+ */
+static void mcp25625_reset(DeviceState *d)
+{
+    MCP25625State *s = MCP25625(d);
+
+    mcp25625_got_reset(s);
+}
+
+static Property mcp25625_properties[] = {
+    DEFINE_PROP_LINK("canbus", MCP25625State, canbus, TYPE_CAN_BUS,
+                     CanBusState *),
+    DEFINE_PROP_END_OF_LIST(),
+};
+
+static const VMStateDescription vmstate_mcp25625_txbuff = {
+    .name = TYPE_MCP25625 "/txbuff",
+    .version_id = 1,
+    .fields = (VMStateField[]) {
+        VMSTATE_UINT8_ARRAY(data, struct txbuff, 14),
+    },
+};
+
+static const VMStateDescription vmstate_mcp25625_rxbuff = {
+    .name = TYPE_MCP25625 "/rxbuff",
+    .version_id = 1,
+    .fields = (VMStateField[]) {
+        VMSTATE_UINT8_ARRAY(data, struct rxbuff, 14),
+    },
+};
+
+static const VMStateDescription vmstate_mcp25625_rxmask = {
+    .name = TYPE_MCP25625 "/rxmask",
+    .version_id = 1,
+    .fields = (VMStateField[]) {
+        VMSTATE_UINT8_ARRAY(data, struct rxmask, 4),
+    },
+};
+
+static const VMStateDescription vmstate_mcp25625_rxfilter = {
+    .name = TYPE_MCP25625 "/rxfilter",
+    .version_id = 1,
+    .fields = (VMStateField[]) {
+        VMSTATE_UINT8_ARRAY(data, struct rxfilter, 4),
+    },
+};
+
+static const VMStateDescription vmstate_mcp25625 = {
+    .name = TYPE_MCP25625,
+    .version_id = 1,
+    .minimum_version_id = 1,
+    .post_load = mcp25625_post_load,
+    .fields = (VMStateField[]) {
+        /*
+         * think the ssi transaction state should be store here
+         * as not sure if we can suspend/migrate over this
+         */
+        VMSTATE_BOOL(ssi_write, MCP25625State),
+        VMSTATE_UINT32(ssi_state, MCP25625State),
+        VMSTATE_UINT8(ssi_addr, MCP25625State),
+        VMSTATE_UINT8(ssi_rxbuff, MCP25625State),
+        VMSTATE_UINT8(ssi_modify_mask, MCP25625State),
+        VMSTATE_UINT8(canstat, MCP25625State),
+        VMSTATE_UINT8(canctrl, MCP25625State),
+        VMSTATE_UINT8(bfpctrl, MCP25625State),
+        VMSTATE_UINT8(txrtsctrl, MCP25625State),
+        VMSTATE_UINT8(tec, MCP25625State),
+        VMSTATE_UINT8(rec, MCP25625State),
+        VMSTATE_UINT8(caninte,  MCP25625State),
+        VMSTATE_UINT8(canintf,  MCP25625State),
+        VMSTATE_UINT8(eflg,  MCP25625State),
+        VMSTATE_UINT8_ARRAY(cnfs, MCP25625State, 4),
+        VMSTATE_STRUCT_ARRAY(txbuffs, MCP25625State, 3, 1,
+                             vmstate_mcp25625_txbuff, struct txbuff),
+        VMSTATE_STRUCT_ARRAY(rxbuffs, MCP25625State, 2, 1,
+                             vmstate_mcp25625_rxbuff, struct rxbuff),
+        VMSTATE_STRUCT_ARRAY(rxmasks, MCP25625State, 2, 1,
+                             vmstate_mcp25625_rxmask, struct rxmask),
+        VMSTATE_STRUCT_ARRAY(rxfilters, MCP25625State, 6, 1,
+                             vmstate_mcp25625_rxfilter, struct rxfilter),
+    },
+};
+
+static void mcp25625_class_init(ObjectClass *klass, void *data)
+{
+    DeviceClass *dc = DEVICE_CLASS(klass);
+    SSIPeripheralClass *k = SSI_PERIPHERAL_CLASS(klass);
+
+    k->realize = mcp25625_realize;
+    k->transfer = mcp25625_transfer8;
+    k->set_cs = mcp25625_cs;
+    k->cs_polarity = SSI_CS_LOW;
+    dc->vmsd = &vmstate_mcp25625;
+    dc->desc = "Microchip MCP25625 CAN-SPI";
+    device_class_set_props(dc, mcp25625_properties);
+    dc->reset = mcp25625_reset;
+}
+
+static const TypeInfo mcp25625_info = {
+    .name           = TYPE_MCP25625,
+    .parent         = TYPE_SSI_PERIPHERAL,
+    .instance_size  = sizeof(MCP25625State),
+    .class_init     = mcp25625_class_init,
+};
+
+static void mcp25625_register_types(void)
+{
+    type_register_static(&mcp25625_info);
+}
+
+type_init(mcp25625_register_types);
diff --git a/hw/net/can/meson.build b/hw/net/can/meson.build
index 8fabbd9ee6..cfec993b52 100644
--- a/hw/net/can/meson.build
+++ b/hw/net/can/meson.build
@@ -1,3 +1,4 @@
+softmmu_ss.add(when: 'CONFIG_CAN_MCP25625', if_true: files('can_mcp25625.c'))
 softmmu_ss.add(when: 'CONFIG_CAN_SJA1000', if_true: files('can_sja1000.c'))
 softmmu_ss.add(when: 'CONFIG_CAN_PCI', if_true: files('can_kvaser_pci.c'))
 softmmu_ss.add(when: 'CONFIG_CAN_PCI', if_true: files('can_pcm3680_pci.c'))
diff --git a/hw/net/can/trace-events b/hw/net/can/trace-events
index 8346a98ab5..99dcea7f9b 100644
--- a/hw/net/can/trace-events
+++ b/hw/net/can/trace-events
@@ -1,3 +1,16 @@
+# mcp25625.c
+mcp25625_send_txb(const char *dev, unsigned buff, unsigned len) "%s: buffer %d (%d bytes)"
+mcp25625_irq_change(const char *dev, uint32_t from, uint32_t to) "%s: irq changed from 0x%" PRIx32 " to 0x%" PRIx32
+mcp25625_transfer8(const char *dev, int state, unsigned addr, uint32_t d) "%s: state %d, addr 0x%02x, data 0x%" PRIx32
+mpc25652_reg_update(const char *dev, uint32_t addr, uint8_t from, uint8_t to) "%s: update reg 0x%02x from 0x%02x to 0x%02x"
+mcp25625_transfer8_return(const char *dev, int state, unsigned addr, uint32_t d, uint32_t rv) "%s: state %d, addr 0x%02x, data 0x%" PRIx32 " -> 0x%" PRIx32
+mcp25625_invalid_cmd(const char *dev, uint32_t d) "%s: unknown command 0x%" PRIx32
+mcp25625_do_rts(const char *dev, unsigned buff) "%s: mask 0x%08x"
+mcp25625_irq_update(const char *dev, uint32_t flags) "%s: flags 0x%08x"
+mcp25625_change_mode(const char *dev, uint8_t from, uint8_t to) "%s: change mode from 0x%02x to 0x%02x"
+mcp25625_rx_clear(const char *dev, unsigned buff) "%s: clearing rx buffer %u"
+mcp25625_rx_buf(const char *dev, unsigned buff, uint32_t id, unsigned len) "%s: rx buffer %u: id 0x%" PRIx32 " len %u"
+
 # xlnx-zynqmp-can.c
 xlnx_can_update_irq(uint32_t isr, uint32_t ier, uint32_t irq) "ISR: 0x%08x IER: 0x%08x IRQ: 0x%08x"
 xlnx_can_reset(uint32_t val) "Resetting controller with value = 0x%08x"
diff --git a/include/hw/net/can_mcp25625.h b/include/hw/net/can_mcp25625.h
new file mode 100644
index 0000000000..5ec82eb4f7
--- /dev/null
+++ b/include/hw/net/can_mcp25625.h
@@ -0,0 +1,10 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * CAN device - MCP25625 chip model
+ *
+ * Copyright (c) 2022 SiFive, Inc.
+ */
+
+#define TYPE_MCP25625 "mcp25625"
+
+#define MCP25625(obj)  OBJECT_CHECK(MCP25625State, (obj), TYPE_MCP25625)
-- 
2.39.2
Re: [PATCH v2] hw/net/can: Add mcp25625 model
Posted by Pavel Pisa 1 year, 1 month ago
Hello Ben,

thanks for update.

On Thursday 16 of March 2023 13:41:13 Ben Dooks wrote:
> From: Ben Dooks <ben.dooks@sifive.com>
>
> Add support for Microchip MCP25625 SPI based CAN controller which is
> very similar to the MCP2515 (and covered by the same Linux driver).
>
> This can be added to any machine with SPI support in the machine
> model file.
>
> Example for using this when configured into a machine:
>
> 	-object can-bus,id=canbus0 \
> 	-object can-host-socketcan,id=canhost0,if=vcan0,canbus=canbus0 \
> 	-global driver=mcp25625,property=canbus,value=canbus0
>
> There is tracing support with --trace "*mcp25*"

Code looks good, I have patched actual QEMU sources and build
it successfully with your change.

I have not seen any warning.

I would like to test the mcp25625 CAN functionality.

I would prefer against some target which is already available
in QEMU and Linux kernel mainlines, so if somebody can suggest
some ARM which can connect SPI/SSI device it would be great.

I have setup /srv/nfs/debian-riscv64 chroot and used
it to prepare minimal 3 MB ramdisk.cpio with busybox
and full GLIBC and ip package.

I can run it with Debian provided RISC-V kernel
under QEMU compiled with your mcp25625 chip emulation

qemu-system-riscv64 -m 1G -M sifive_u -smp 2 \
      -initrd ramdisk.cpio \
      -kernel vmlinux-6.1.0-6-riscv64 \
      -nographic \
      -object can-bus,id=canbus0 \
      -object can-host-socketcan,id=canhost0,if=can0,canbus=canbus0 \
      -global driver=mcp25625,property=canbus,value=canbus0

I can see

/sys/bus/platform/devices/10040000.spi
/sys/bus/platform/devices/10050000.spi

I can run

  modprobe spi-sifive.ko

[   41.524160] sifive_spi 10040000.spi: mapped; irq=21, cs=1
[   41.529305] sifive_spi 10050000.spi: mapped; irq=22, cs=1

  modprobe mcp251x.ko

I can imagine to build device tree overlay and setup it from within
kernel if the device is already mapped

  cd /sys/kernel/config/device-tree/overlays
  [ -d  sifive_u-mcp25625 ] && rmdir sifive_u-mcp25625
  mkdir sifive_u-mcp25625
  cd sifive_u-mcp25625
  cat sifive_u-mcp25625.dtbo >dtbo
  echo 1 >status

which is what we do with CTU CAN FD ip on Zynq system
to run PL/FPGA update.

But from QEMU info qtree, I see that device is not mapped in QEMU...
Which is logic...

So please, can you send instruction how to proceed forward.

Do you have DTB prepared for testing or something similar?

In a longer term perspective, it would be ideal to provide
some update for documentation, how to use mcp25625 emulation

  https://www.qemu.org/docs/master/system/devices/can.html

By the way, if the Raspberry Pi emulation does not provide
right SPI emulation as you have noticed, what about BeagleBoneBlack?

Does it support SPI? It could be good target to test that mcp25625
chip emulation is portable..

Best wishes,
                Pavel
--
                Pavel Pisa
    phone:      +420 603531357
    e-mail:     pisa@cmp.felk.cvut.cz
    Department of Control Engineering FEE CVUT
    Karlovo namesti 13, 121 35, Prague 2
    university: http://control.fel.cvut.cz/
    personal:   http://cmp.felk.cvut.cz/~pisa
    projects:   https://www.openhub.net/accounts/ppisa
    CAN related:http://canbus.pages.fel.cvut.cz/
    RISC-V education: https://comparch.edu.cvut.cz/
    Open Technologies Research Education and Exchange Services
    https://gitlab.fel.cvut.cz/otrees/org/-/wikis/home
Re: [PATCH v2] hw/net/can: Add mcp25625 model
Posted by Ben Dooks 1 year, 1 month ago
On 17/03/2023 14:11, Pavel Pisa wrote:
> Hello Ben,
> 
> thanks for update.
> 
> On Thursday 16 of March 2023 13:41:13 Ben Dooks wrote:
>> From: Ben Dooks <ben.dooks@sifive.com>
>>
>> Add support for Microchip MCP25625 SPI based CAN controller which is
>> very similar to the MCP2515 (and covered by the same Linux driver).
>>
>> This can be added to any machine with SPI support in the machine
>> model file.
>>
>> Example for using this when configured into a machine:
>>
>> 	-object can-bus,id=canbus0 \
>> 	-object can-host-socketcan,id=canhost0,if=vcan0,canbus=canbus0 \
>> 	-global driver=mcp25625,property=canbus,value=canbus0
>>
>> There is tracing support with --trace "*mcp25*"
> 
> Code looks good, I have patched actual QEMU sources and build
> it successfully with your change.
> 
> I have not seen any warning.
> 
> I would like to test the mcp25625 CAN functionality.
> 
> I would prefer against some target which is already available
> in QEMU and Linux kernel mainlines, so if somebody can suggest
> some ARM which can connect SPI/SSI device it would be great.
> 
> I have setup /srv/nfs/debian-riscv64 chroot and used
> it to prepare minimal 3 MB ramdisk.cpio with busybox
> and full GLIBC and ip package.
> 
> I can run it with Debian provided RISC-V kernel
> under QEMU compiled with your mcp25625 chip emulation
> 
> qemu-system-riscv64 -m 1G -M sifive_u -smp 2 \
>        -initrd ramdisk.cpio \
>        -kernel vmlinux-6.1.0-6-riscv64 \
>        -nographic \
>        -object can-bus,id=canbus0 \
>        -object can-host-socketcan,id=canhost0,if=can0,canbus=canbus0 \
>        -global driver=mcp25625,property=canbus,value=canbus0
> 
> I can see
> 
> /sys/bus/platform/devices/10040000.spi
> /sys/bus/platform/devices/10050000.spi
> 
> I can run
> 
>    modprobe spi-sifive.ko
> 
> [   41.524160] sifive_spi 10040000.spi: mapped; irq=21, cs=1
> [   41.529305] sifive_spi 10050000.spi: mapped; irq=22, cs=1
> 
>    modprobe mcp251x.ko
> 
> I can imagine to build device tree overlay and setup it from within
> kernel if the device is already mapped
> 
>    cd /sys/kernel/config/device-tree/overlays
>    [ -d  sifive_u-mcp25625 ] && rmdir sifive_u-mcp25625
>    mkdir sifive_u-mcp25625
>    cd sifive_u-mcp25625
>    cat sifive_u-mcp25625.dtbo >dtbo
>    echo 1 >status
> 
> which is what we do with CTU CAN FD ip on Zynq system
> to run PL/FPGA update.
> 
> But from QEMU info qtree, I see that device is not mapped in QEMU...
> Which is logic...
> 
> So please, can you send instruction how to proceed forward.
> 
> Do you have DTB prepared for testing or something similar?
> 
> In a longer term perspective, it would be ideal to provide
> some update for documentation, how to use mcp25625 emulation
> 
>    https://www.qemu.org/docs/master/system/devices/can.html
> 
> By the way, if the Raspberry Pi emulation does not provide
> right SPI emulation as you have noticed, what about BeagleBoneBlack?

At the moment it seems that the as a whole qemu doesn't have a good
way of adding a generic spi device to a bus.

> Does it support SPI? It could be good target to test that mcp25625
> chip emulation is portable..

I've pushed our test branch out to:
https://gitlab.com/CodethinkLabs/qemu/-/commits/mcp25625_test

That adds an spi channel to the sifive_u machine and puts the
right dtb entry in there.

-- 
Ben Dooks				http://www.codethink.co.uk/
Senior Engineer				Codethink - Providing Genius

https://www.codethink.co.uk/privacy.html
Re: [PATCH v2] hw/net/can: Add mcp25625 model
Posted by Pavel Pisa 1 year ago
Hello Ben,

On Thursday 16 of March 2023 13:41:13 Ben Dooks wrote:
> From: Ben Dooks <ben.dooks@sifive.com>
>
> Add support for Microchip MCP25625 SPI based CAN controller which is
> very similar to the MCP2515 (and covered by the same Linux driver).
>
> This can be added to any machine with SPI support in the machine
> model file.
>
> Example for using this when configured into a machine:
>
> 	-object can-bus,id=canbus0 \
> 	-object can-host-socketcan,id=canhost0,if=vcan0,canbus=canbus0 \
> 	-global driver=mcp25625,property=canbus,value=canbus0
>
> There is tracing support with --trace "*mcp25*"
>
> Signed-off-by: Ben Dooks <ben.dooks@sifive.com>
> Co-developed-by: Nazar Kazakov <nazar.kazakov@codethink.co.uk>
> Signed-off-by: Nazar Kazakov <nazar.kazakov@codethink.co.uk>
> Co-developed-by: Lawrence Hunter <lawrence.hunter@codethink.co.uk>
> Signed-off-by: Lawrence Hunter <lawrence.hunter@codethink.co.uk>
> Reviewed-by: Frank Chang <frank.chang@sifive.com>

Tested-by: Pavel Pisa <pisa@cmp.felk.cvut.cz>
Acked-by: Pavel Pisa <pisa@cmp.felk.cvut.cz>

I have used your test branch

https://gitlab.com/CodethinkLabs/qemu/-/commits/mcp25625_test

I confirm that it contains unmodified mcp25625 model patch
applied directly to QEMU master and then additional patches
for actual integration with qemu/hw/riscv/sifive_u.c

I have used stripped down initramfs system build from
debootstraped Debian Bookworm - GLIBC, busybox, ip route,
can-utils and Linux kernel have been used. Kernel version

  Linux (none) 6.1.0-7-riscv64 #1 SMP Debian 6.1.20-1 (2023-03-19)
  riscv64 GNU/Linux  

QEMU invocation command

  qemu-system-riscv64 -m 1G -M sifive_u -smp 2 \
      -initrd ramdisk.cpio \
      -kernel vmlinux-6.1.0-7-riscv64 \
      -nographic \
      -object can-bus,id=canbus0 \
      -object can-host-socketcan,id=canhost0,if=can0,canbus=canbus0 \
      -global driver=mcp25625,property=canbus,value=canbus0

The virtual SocketCAN interface has been used on the host
side and mutual CAN dump/CAN gen has been used and worked
in the both directons.

I support inclusion of your patch in QEMU mainline
to allow keep it in sync with the rest of the projects.

But I would be happy if some integration is included as well.
Do you plan to submit your integration to qemu/hw/riscv/sifive_u.c
as well? If so, is there some plan to make mapping to SPI
configurable? May be some other developer could help with
suggestion what are the plans in SPI peripherals mapping
in QEMU.

The mapping of IRQ over SIFIVE_U_MISC_IRQ in your followup
testing patches seems to me quite ad-hock solution.
It should go through some GPIO pin on a real board
probably.

I would like to see at least one more system and
architecture to provide option to map and test
the mcp25625 model. Raspberry Pi or some other
ARM would be nice.

Anyway, I would suggest to think about integration
of CAN/CAN FD controller to SiFive SoC directly
on the CPU bus because access over SPI causes
latencies as I have already pointed to Martin Prudek's
thesis prepared for Honeywell when we tried to enhance
their choice of Raspberry Pi Matlab Simulink target
used with SPI based Microchip CAN controller

  https://dspace.cvut.cz/bitstream/handle/10467/68605/F3-DP-2017-Prudek-Martin-Dp_2017_prudek_martin.pdf

See Figure 6.27. Cumulative latency histogram of CAN messages
on page 43...

We have lot more data and tools now from our CAN latency
tester project based on our CTU CAN FD IP core.
There is some documentation in Matej Vasilevski's
thesis

  https://dspace.cvut.cz/bitstream/handle/10467/101450/F3-DP-2022-Vasilevski-Matej-vasilmat.pdf

and the followup project with continuous latency
testing on actual Linux kernel mainline and preemp-rt
will be submitted by this June.

Best wishes,

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



On Tuesday 21 of March 2023 11:49:15 Ben Dooks wrote:
> On 17/03/2023 14:11, Pavel Pisa wrote:
> > Hello Ben,
> >
> > thanks for update.
> >
> > On Thursday 16 of March 2023 13:41:13 Ben Dooks wrote:
> >> From: Ben Dooks <ben.dooks@sifive.com>
> >>
> >> Add support for Microchip MCP25625 SPI based CAN controller which is
> >> very similar to the MCP2515 (and covered by the same Linux driver).
> >>
> >> This can be added to any machine with SPI support in the machine
> >> model file.
> >>
> >> Example for using this when configured into a machine:
> >>
> >> 	-object can-bus,id=canbus0 \
> >> 	-object can-host-socketcan,id=canhost0,if=vcan0,canbus=canbus0 \
> >> 	-global driver=mcp25625,property=canbus,value=canbus0
> >>
> >> There is tracing support with --trace "*mcp25*"
> >
> > Code looks good, I have patched actual QEMU sources and build
> > it successfully with your change.
> >
> > I have not seen any warning.
> >
> > I would like to test the mcp25625 CAN functionality.
> >
> > I would prefer against some target which is already available
> > in QEMU and Linux kernel mainlines, so if somebody can suggest
> > some ARM which can connect SPI/SSI device it would be great.
> >
> > I have setup /srv/nfs/debian-riscv64 chroot and used
> > it to prepare minimal 3 MB ramdisk.cpio with busybox
> > and full GLIBC and ip package.
> >
> > I can run it with Debian provided RISC-V kernel
> > under QEMU compiled with your mcp25625 chip emulation
> >
> > qemu-system-riscv64 -m 1G -M sifive_u -smp 2 \
> >        -initrd ramdisk.cpio \
> >        -kernel vmlinux-6.1.0-6-riscv64 \
> >        -nographic \
> >        -object can-bus,id=canbus0 \
> >        -object can-host-socketcan,id=canhost0,if=can0,canbus=canbus0 \
> >        -global driver=mcp25625,property=canbus,value=canbus0
> >
> > I can see
> >
> > /sys/bus/platform/devices/10040000.spi
> > /sys/bus/platform/devices/10050000.spi
> >
> > I can run
> >
> >    modprobe spi-sifive.ko
> >
> > [   41.524160] sifive_spi 10040000.spi: mapped; irq=21, cs=1
> > [   41.529305] sifive_spi 10050000.spi: mapped; irq=22, cs=1
> >
> >    modprobe mcp251x.ko
> >
> > I can imagine to build device tree overlay and setup it from within
> > kernel if the device is already mapped
> >
> >    cd /sys/kernel/config/device-tree/overlays
> >    [ -d  sifive_u-mcp25625 ] && rmdir sifive_u-mcp25625
> >    mkdir sifive_u-mcp25625
> >    cd sifive_u-mcp25625
> >    cat sifive_u-mcp25625.dtbo >dtbo
> >    echo 1 >status
> >
> > which is what we do with CTU CAN FD ip on Zynq system
> > to run PL/FPGA update.
> >
> > But from QEMU info qtree, I see that device is not mapped in QEMU...
> > Which is logic...
> >
> > So please, can you send instruction how to proceed forward.
> >
> > Do you have DTB prepared for testing or something similar?
> >
> > In a longer term perspective, it would be ideal to provide
> > some update for documentation, how to use mcp25625 emulation
> >
> >    https://www.qemu.org/docs/master/system/devices/can.html
> >
> > By the way, if the Raspberry Pi emulation does not provide
> > right SPI emulation as you have noticed, what about BeagleBoneBlack?
>
> At the moment it seems that the as a whole qemu doesn't have a good
> way of adding a generic spi device to a bus.
>
> > Does it support SPI? It could be good target to test that mcp25625
> > chip emulation is portable..
>
> I've pushed our test branch out to:
> https://gitlab.com/CodethinkLabs/qemu/-/commits/mcp25625_test
>
> That adds an spi channel to the sifive_u machine and puts the
> right dtb entry in there.