On Mon, 29 Jun 2026 at 13:01, Peter Maydell <[email protected]> wrote:
>
> From: Matyáš Bobek <[email protected]>
>
> Added the FlexCAN2 emulator implementation core, with
> CAN_FLEXCAN Kconfig flag and MAINTAINERS entry.
>
> FlexCAN2 version can be found in i.MX6 SoCs and others.
>
> More information about the implementation can be found in [1].
>
> Some macro and struct defintions were borrowed from the Linux kernel.
> The original authors agreed with relicensing them to GPL-2.0-or-later on
> the qemu-devel mailing list.
>
> [1] 
> http://dspace.cvut.cz/bitstream/handle/10467/122654/F3-BP-2025-Bobek-Matyas-BP_Bobek_FlexCAN_final_4.pdf
>

Hi; Coverity points out some issues with this change; details below.


> +/**
> + * flexcan_fifo_pop() - Pop message from FIFO and update IRQs
> + * @s: FlexCAN device pointer
> + *
> + * Does not require the queue to be non-empty.
> + */
> +static void flexcan_fifo_pop(FlexcanState *s)
> +{
> +    if (s->regs.fifo.mb_back.can_ctrl != 0) {
> +        /* move queue elements forward */
> +        memmove(&s->regs.fifo.mb_back, &s->regs.fifo.mbs_queue[0],
> +                sizeof(s->regs.fifo.mbs_queue));

CID 1662971: Coverity doesn't like this because mb_back is
only 1 element long and you're copying more than that.

We seem to use these fields only in this function -- did we
really need to specify them as two separate fields rather
than one array so that it's a bit clearer that we're just
copying within what is effectively a single 6 element array?

> +
> +        /* clear the first-in slot */
> +        memset(&s->regs.mbs[FLEXCAN_FIFO_DEPTH - 1], 0,
> +               sizeof(FlexcanRegsMessageBuffer));
> +
> +        trace_flexcan_fifo_pop(DEVICE(s)->canonical_path, 1,
> +                               s->regs.fifo.mb_back.can_ctrl != 0);
> +    } else {
> +        trace_flexcan_fifo_pop(DEVICE(s)->canonical_path, 0, 0);
> +    }
> +
> +    if (s->regs.fifo.mb_back.can_ctrl != 0) {
> +        flexcan_irq_iflag_set(s, I_FIFO_AVAILABLE);
> +    } else {
> +        flexcan_irq_iflag_clear(s, I_FIFO_AVAILABLE);
> +    }
> +}
> +



> +static enum FlexcanRx flexcan_mb_rx(FlexcanState *s, const qemu_can_frame 
> *buf)
> +{
> +    int last_not_free_to_receive_mbid = -1;

CID 1662790:

Initially last_not_free_to_receive_mbid is -1.

> +    bool last_not_free_to_receive_locked = false;
> +
> +    FlexcanRegsMessageBuffer *first_mb = flexcan_get_first_message_buffer(s);
> +    FlexcanRegsMessageBuffer *last_mb = flexcan_get_last_enabled_mailbox(s);
> +
> +    for (FlexcanRegsMessageBuffer *mb = first_mb;
> +         mb <= last_mb; mb++) {
> +        int mbid = mb - s->regs.mbs;

In this loop mbid must always be positive, because the bounds on mb
mean it's always somewhere inside the s->regs.mbs array.

> +        enum FlexcanCheck r = flexcan_mb_rx_check_mb(s, buf, mbid);
> +        if (r == FLEXCAN_CHECK_MB_MATCH) {
> +            flexcan_mb_move_in(s, buf, mb);
> +            flexcan_irq_iflag_set(s, mbid);
> +            return FLEXCAN_RX_SEARCH_ACCEPT;
> +        }
> +
> +        if (r == FLEXCAN_CHECK_MB_MATCH_NON_FREE) {
> +            last_not_free_to_receive_mbid = mbid;
> +            last_not_free_to_receive_locked = false;
> +        } else if (r == FLEXCAN_CHECK_MB_MATCH_LOCKED) {
> +            /*
> +             * message buffer is locked,
> +             * we can move in the message after it's unlocked
> +             */
> +            last_not_free_to_receive_mbid = mbid;
> +            last_not_free_to_receive_locked = true;
> +        }

In the loop body the only places where we assign to
last_not_free_to_receive_mbid are where we set it to mbid.

> +    }
> +
> +    if (last_not_free_to_receive_mbid >= -1) {

So when can this condition be false ?

> +        if (last_not_free_to_receive_locked) {
> +            /*
> +             * copy to temporary mailbox (SMB)
> +             * it will be moved in when the mailbox is unlocked
> +             */
> +            s->regs.rx_smb0.can_ctrl =
> +                s->regs.mbs[last_not_free_to_receive_mbid].can_id;
> +            flexcan_mb_move_in(s, buf, &s->regs.rx_smb0);
> +            s->smb_target_mbidx = last_not_free_to_receive_mbid;
> +            return FLEXCAN_RX_SEARCH_ACCEPT;
> +        }
> +
> +        if (s->regs.mcr & FLEXCAN_MCR_IRMQ) {
> +            flexcan_mb_move_in(s, buf,
> +                               &s->regs.mbs[last_not_free_to_receive_mbid]);

Here we use last_not_free_to_receive_mbid as an index into mbs,
but it could be -1, which will underrun the array.

Should the condition above have been ">= 0" ?

> +            flexcan_irq_iflag_set(s, last_not_free_to_receive_mbid);
> +            return FLEXCAN_RX_SEARCH_ACCEPT;
> +        }
> +    }

> +
> +    return FLEXCAN_RX_SEARCH_RETRY;
> +}



> +/* ========== I/O handling ========== */
> +static void flexcan_mem_write(void *opaque, hwaddr addr, uint64_t val,
> +                              unsigned size)
> +{
> +    FlexcanState *s = opaque;
> +    uint32_t write_mask = ((const uint32_t *)
> +        &flexcan_regs_write_mask)[addr / 4];
> +    uint32_t old_value = s->regs_raw[addr / 4];
> +
> +    /*
> +     * 0 for bits that can "only be written in Freeze mode as it is blocked
> +     * by hardware in other modes"
> +     */
> +    const uint32_t freeze_mask_mcr = 0xDF54CC80;
> +    const uint32_t freeze_mask_ctrl1 = 0x0000E740;
> +
> +    flexcan_trace_mem_op(s, addr, val, size, true);
> +    switch (addr) {
> +    case offsetof(FlexcanRegs, mcr):
> +        if (!(s->regs.mcr & FLEXCAN_MCR_FRZ_ACK)) {
> +            write_mask &= freeze_mask_mcr;
> +        }
> +        s->regs.mcr = (val & write_mask) | (old_value & ~write_mask);
> +        flexcan_set_mcr(s, old_value);
> +        break;
> +    case offsetof(FlexcanRegs, ctrl):
> +        if (!(s->regs.mcr & FLEXCAN_MCR_FRZ_ACK)) {
> +            write_mask &= freeze_mask_ctrl1;
> +        }
> +        s->regs.ctrl = (val & write_mask) | (old_value & ~write_mask);
> +        break;
> +    case offsetof(FlexcanRegs, iflag1):
> +        s->regs.iflag1 &= ~val;
> +        if ((s->regs.mcr & FLEXCAN_MCR_FEN) &&
> +            (val & FLEXCAN_IFLAG_RX_FIFO_AVAILABLE)) {
> +            flexcan_fifo_pop(s);
> +        }
> +        break;
> +    case offsetof(FlexcanRegs, iflag2):
> +        s->regs.iflag2 &= ~val;
> +        break;
> +    case offsetof(FlexcanRegs, ctrl2):
> +        QEMU_FALLTHROUGH;
> +    case offsetof(FlexcanRegs, ecr):
> +        QEMU_FALLTHROUGH;
> +    case offsetof(FlexcanRegs, rxmgmask):
> +        QEMU_FALLTHROUGH;
> +    case offsetof(FlexcanRegs, rx14mask):
> +        QEMU_FALLTHROUGH;
> +    case offsetof(FlexcanRegs, rx15mask):
> +        QEMU_FALLTHROUGH;
> +    case offsetof(FlexcanRegs, rxfgmask):
> +        QEMU_FALLTHROUGH;
> +    case offsetof(FlexcanRegs, rximr[0]) ... offsetof(FlexcanRegs, 
> rximr[63]):
> +        /* these registers can only be written in freeze mode */
> +        if (!(s->regs.mcr & FLEXCAN_MCR_FRZ_ACK)) {
> +            break;
> +        }
> +        QEMU_FALLTHROUGH;
> +    default:
> +        s->regs_raw[addr / 4] = (val & write_mask) | (old_value & 
> ~write_mask);
> +
> +        if (addr >= offsetof(FlexcanRegs, mb) &&
> +            addr < offsetof(FlexcanRegs, _reserved4)) {
> +            /* access to mailbox */
> +            int mbid = (addr - offsetof(FlexcanRegs, mb)) /
> +                            sizeof(FlexcanRegsMessageBuffer);

CID 1662974: This bounds check isn't sufficient to ensure that
mbid is within the bounds of s->regs.mbs[], which flexcan_mb_write()
will access. This is the same as 1662975 below; see that for detail.

> +
> +            if (s->locked_mbidx == mbid) {
> +                flexcan_mb_unlock(s);
> +            }
> +
> +            /* check for invalid writes into FIFO region */
> +            if (s->regs.mcr & FLEXCAN_MCR_FEN && mbid < FLEXCAN_FIFO_DEPTH) {
> +                qemu_log_mask(LOG_GUEST_ERROR,
> +                              "%s: Invalid write to Rx-FIFO structure",
> +                              DEVICE(s)->canonical_path);
> +                return;
> +            }
> +
> +            /* run mailbox processing function on write to control word */
> +            if ((addr & 0xF) == 0) {
> +                flexcan_mb_write(s, mbid);
> +            }
> +        }
> +        break;
> +    }
> +
> +    flexcan_irq_update(s);
> +}
> +
> +static uint64_t flexcan_mem_read(void *opqaue, hwaddr addr, unsigned size)
> +{
> +    FlexcanState *s = opqaue;
> +    uint32_t rv = s->regs_raw[addr >> 2];
> +
> +    if (addr >= offsetof(FlexcanRegs, mb) &&
> +        addr < offsetof(FlexcanRegs, _reserved4)) {
> +        /* reading from mailbox */
> +        hwaddr offset = addr - offsetof(FlexcanRegs, mb);
> +        int mbid = offset / sizeof(FlexcanRegsMessageBuffer);
> +
> +        if (addr % 16 == 0 && s->locked_mbidx != mbid) {
> +            /* reading control word locks the mailbox */
> +            flexcan_mb_unlock(s);
> +            flexcan_mb_lock(s, mbid);
> +            flexcan_irq_update(s);
> +            rv = s->regs.mbs[mbid].can_ctrl & ~FLEXCAN_MB_CNT_NOT_SRV;

CID 1662972, 1662975:

Here we guard the bounds on addr based on the struct layout,
where the relevant part is:

    union {                      /* 0x80 - not affected by soft reset */
        uint32_t mb[sizeof(FlexcanRegsMessageBuffer) * FLEXCAN_MAILBOX_COUNT];
        FlexcanRegsMessageBuffer mbs[FLEXCAN_MAILBOX_COUNT];
        FlexcanRegsRXFifo fifo;
    };
    uint32_t _reserved4[256];    /* 0x480 */

So we know that 'addr' is inside the union. However, 'mbs' is
not the smallest member of the union, because 'mbs' is 64 * 16 == 1024
bytes, but mb is 64 * 16 * 4 == 14436 bytes, and fifo is
(6 * 16 + 128 * 4) == 608 bytes. So because we only bounded
'mbid' based on the total size of the union, it can be larger
than the size of the mb[] array.

What is the intention here?

As an aside, using unions like this is rather error-prone, because
it is an easy way to introduce endianness bugs. This one happens to
work because all the fields in all the sub-structs end up being
uint32_t, but for instance if there was a uint16_t field in there
somewhere that would be an endianness bug.


> +
> +    } else if (addr == offsetof(FlexcanRegs, timer)) {
> +        flexcan_mb_unlock(s);
> +        flexcan_irq_update(s);
> +        rv = flexcan_get_timestamp(s, false);
> +    }
> +
> +    flexcan_trace_mem_op(s, addr, rv, size, false);
> +    return rv;
> +}



> +#define FLEXCAN_GENMASK(h, l) (((~(uint32_t)0) >> (31 - (h) + (l))) << (l))

Please don't roll your own mask creation macros. We have
MAKE_64BIT_MASK() in bitops.h.

> +/* FLEXCAN CRC Register (CRCR) bits */
> +#define FLEXCAN_CRCR_MBCRC_MASK         FLEXCAN_GENMASK(22, 16)
> +#define FLEXCAN_CRCR_MBCRC(x)           (((x) & FLEXCAN_CRCR_MBCRC_MASK) << 
> 16)

CID 1662973: FLEXCAN_CRCR_MBCRC_MASK is 0x7F0000. That means
that (((x) & FLEXCAN_CRCR_MBCRC_MASK) << 16) will always be 0,
because the field gets shifted off the top of the 32-bit type.

I suspect you meant here
(((x) << 16) & FLEXCAN_CRCR_MBCRC_MASK)

The standard QEMU FIELD macros let you use FIELD_DP32() which
avoids this kind of bug entirely.

thanks
-- PMM

Reply via email to