[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
Re: [Qemu-devel] [PATCH V4 3/7] CAN bus SJA1000 chip register level emul
From: |
Philippe Mathieu-Daudé |
Subject: |
Re: [Qemu-devel] [PATCH V4 3/7] CAN bus SJA1000 chip register level emulation for QEMU |
Date: |
Mon, 15 Jan 2018 00:03:38 -0300 |
User-agent: |
Mozilla/5.0 (X11; Linux x86_64; rv:52.0) Gecko/20100101 Thunderbird/52.5.2 |
On 01/14/2018 05:14 PM, address@hidden wrote:
> From: Pavel Pisa <address@hidden>
>
> The core SJA1000 support is independent of following
> patches which map SJA1000 chip to PCI boards.
>
> The work is based on Jin Yang GSoC 2013 work funded
> by Google and mentored in frame of RTEMS project GSoC
> slot donated to QEMU.
>
> Rewritten for QEMU-2.0+ versions and architecture cleanup
> by Pavel Pisa (Czech Technical University in Prague).
>
> Signed-off-by: Pavel Pisa <address@hidden>
> ---
> default-configs/pci.mak | 1 +
> hw/can/Makefile.objs | 1 +
> hw/can/can_sja1000.c | 1013
> +++++++++++++++++++++++++++++++++++++++++++++++
> hw/can/can_sja1000.h | 167 ++++++++
> 4 files changed, 1182 insertions(+)
> create mode 100644 hw/can/can_sja1000.c
> create mode 100644 hw/can/can_sja1000.h
>
> diff --git a/default-configs/pci.mak b/default-configs/pci.mak
> index bbe11887a1..979b649fe5 100644
> --- a/default-configs/pci.mak
> +++ b/default-configs/pci.mak
> @@ -32,6 +32,7 @@ CONFIG_SERIAL=y
> CONFIG_SERIAL_ISA=y
> CONFIG_SERIAL_PCI=y
> CONFIG_CAN_CORE=y
> +CONFIG_CAN_SJA1000=y
> CONFIG_IPACK=y
> CONFIG_WDT_IB6300ESB=y
> CONFIG_PCI_TESTDEV=y
> diff --git a/hw/can/Makefile.objs b/hw/can/Makefile.objs
> index f999085f7a..3c4bf3bfc1 100644
> --- a/hw/can/Makefile.objs
> +++ b/hw/can/Makefile.objs
> @@ -7,4 +7,5 @@ common-obj-y += can_socketcan.o
> else
> common-obj-y += can_host_stub.o
> endif
> +common-obj-$(CONFIG_CAN_SJA1000) += can_sja1000.o
> endif
> diff --git a/hw/can/can_sja1000.c b/hw/can/can_sja1000.c
> new file mode 100644
> index 0000000000..7f7a6ea244
> --- /dev/null
> +++ b/hw/can/can_sja1000.c
> @@ -0,0 +1,1013 @@
> +/*
> + * CAN device - SJA1000 chip emulation for QEMU
> + *
> + * Copyright (c) 2013-2014 Jin Yang
> + * Copyright (c) 2014-2018 Pavel Pisa
> + *
> + * Initial development supported by Google GSoC 2013 from RTEMS project slot
> + *
> + * Permission is hereby granted, free of charge, to any person obtaining a
> copy
> + * of this software and associated documentation files (the "Software"), to
> deal
> + * in the Software without restriction, including without limitation the
> rights
> + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
> + * copies of the Software, and to permit persons to whom the Software is
> + * furnished to do so, subject to the following conditions:
> + *
> + * The above copyright notice and this permission notice shall be included in
> + * all copies or substantial portions of the Software.
> + *
> + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
> + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
> + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
> + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
> + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
> FROM,
> + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
> + * THE SOFTWARE.
> + */
> +#include "qemu/osdep.h"
> +#include "qemu/log.h"
> +#include "chardev/char.h"
> +#include "hw/hw.h"
> +#include "can/can_emu.h"
> +
> +#include "can_sja1000.h"
> +
> +#ifndef DEBUG_FILTER
> +#define DEBUG_FILTER 0
> +#endif /*DEBUG_FILTER*/
> +
> +#ifndef DEBUG_CAN
> +#define DEBUG_CAN 0
> +#endif /*DEBUG_CAN*/
> +
> +#define DPRINTF(fmt, ...) \
> + do { \
> + if (DEBUG_CAN) { \
> + qemu_log("[cansja]: " fmt , ## __VA_ARGS__); \
> + } \
> + } while (0)
> +
> +static void can_sja_software_reset(CanSJA1000State *s)
> +{
> + s->mode &= ~0x31;
> + s->mode |= 0x01;
> + s->status_pel &= ~0x37;
> + s->status_pel |= 0x34;
> +
> + s->rxbuf_start = 0x00;
> + s->rxmsg_cnt = 0x00;
> + s->rx_cnt = 0x00;
> +}
> +
> +void can_sja_hardware_reset(CanSJA1000State *s)
> +{
> + /* Reset by hardware, p10 */
> + s->mode = 0x01;
> + s->status_pel = 0x3c;
> + s->interrupt_pel = 0x00;
> + s->clock = 0x00;
> + s->rxbuf_start = 0x00;
> + s->rxmsg_cnt = 0x00;
> + s->rx_cnt = 0x00;
> +
> + s->control = 0x01;
> + s->status_bas = 0x0c;
> + s->interrupt_bas = 0x00;
> +
> + s->irq_lower(s->irq_opaque);
> +}
> +
> +static
> +void can_sja_single_filter(struct qemu_can_filter *filter,
> + const uint8_t *acr, const uint8_t *amr, int extended)
> +{
> + if (extended) {
> + filter->can_id = (uint32_t)acr[0] << 21;
> + filter->can_id |= (uint32_t)acr[1] << 13;
> + filter->can_id |= (uint32_t)acr[2] << 5;
> + filter->can_id |= (uint32_t)acr[3] >> 3;
> + if (acr[3] & 4) {
> + filter->can_id |= QEMU_CAN_RTR_FLAG;
> + }
I hope we inline that later...
> +
> + filter->can_mask = (uint32_t)amr[0] << 21;
> + filter->can_mask |= (uint32_t)amr[1] << 13;
> + filter->can_mask |= (uint32_t)amr[2] << 5;
> + filter->can_mask |= (uint32_t)amr[3] >> 3;
> + filter->can_mask = ~filter->can_mask & QEMU_CAN_EFF_MASK;
> + if (!(amr[3] & 4)) {
> + filter->can_mask |= QEMU_CAN_RTR_FLAG;
> + }
> + } else {
> + filter->can_id = (uint32_t)acr[0] << 3;
> + filter->can_id |= (uint32_t)acr[1] >> 5;
> + if (acr[1] & 0x10) {
> + filter->can_id |= QEMU_CAN_RTR_FLAG;
> + }
ditto.
> +
> + filter->can_mask = (uint32_t)amr[0] << 3;
> + filter->can_mask |= (uint32_t)amr[1] << 5;
> + filter->can_mask = ~filter->can_mask & QEMU_CAN_SFF_MASK;
> + if (!(amr[1] & 4)) {
> + filter->can_mask |= QEMU_CAN_RTR_FLAG;
> + }
> + }
> +}
> +
> +static
> +void can_sja_dual_filter(struct qemu_can_filter *filter,
> + const uint8_t *acr, const uint8_t *amr, int extended)
> +{
> + if (extended) {
> + filter->can_id = (uint32_t)acr[0] << 21;
> + filter->can_id |= (uint32_t)acr[1] << 13;
> +
> + filter->can_mask = (uint32_t)amr[0] << 21;
> + filter->can_mask |= (uint32_t)amr[1] << 13;
> + filter->can_mask = ~filter->can_mask & QEMU_CAN_EFF_MASK & ~0x1fff;
> + } else {
> + filter->can_id = (uint32_t)acr[0] << 3;
> + filter->can_id |= (uint32_t)acr[1] >> 5;
> + if (acr[1] & 0x10) {
> + filter->can_id |= QEMU_CAN_RTR_FLAG;
> + }
> +
> + filter->can_mask = (uint32_t)amr[0] << 3;
> + filter->can_mask |= (uint32_t)amr[1] >> 5;
> + filter->can_mask = ~filter->can_mask & QEMU_CAN_SFF_MASK;
> + if (!(amr[1] & 0x10)) {
> + filter->can_mask |= QEMU_CAN_RTR_FLAG;
> + }
> + }
> +}
> +
> +/* Details in DS-p22, what we need to do here is to test the data. */
> +static
> +int can_sja_accept_filter(CanSJA1000State *s,
> + const qemu_can_frame *frame)
> +{
> +
> + struct qemu_can_filter filter;
> +
> + if (s->clock & 0x80) { /* PeliCAN Mode */
> + if (s->mode & (1 << 3)) { /* Single mode. */
> + if (frame->can_id & QEMU_CAN_EFF_FLAG) { /* EFF */
> + can_sja_single_filter(&filter,
> + s->code_mask + 0, s->code_mask + 4, 1);
> +
> + if (!can_bus_filter_match(&filter, frame->can_id)) {
> + return 0;
> + }
> + } else { /* SFF */
> + can_sja_single_filter(&filter,
> + s->code_mask + 0, s->code_mask + 4, 0);
> +
> + if (!can_bus_filter_match(&filter, frame->can_id)) {
> + return 0;
> + }
> +
> + if (frame->can_id & QEMU_CAN_RTR_FLAG) { /* RTR */
> + return 1;
> + }
> +
> + if (frame->can_dlc == 0) {
> + return 1;
> + }
> +
> + if ((frame->data[0] & ~(s->code_mask[6])) !=
> + (s->code_mask[2] & ~(s->code_mask[6]))) {
> + return 0;
> + }
> +
> + if (frame->can_dlc < 2) {
> + return 1;
> + }
> +
> + if ((frame->data[1] & ~(s->code_mask[7])) ==
> + (s->code_mask[3] & ~(s->code_mask[7]))) {
> + return 1;
> + }
> +
> + return 0;
> + }
> + } else { /* Dual mode */
> + if (frame->can_id & QEMU_CAN_EFF_FLAG) { /* EFF */
> + can_sja_dual_filter(&filter,
> + s->code_mask + 0, s->code_mask + 4, 1);
> +
> + if (can_bus_filter_match(&filter, frame->can_id)) {
> + return 1;
> + }
> +
> + can_sja_dual_filter(&filter,
> + s->code_mask + 2, s->code_mask + 6, 1);
> +
> + if (can_bus_filter_match(&filter, frame->can_id)) {
> + return 1;
> + }
> +
> + return 0;
> + } else {
> + can_sja_dual_filter(&filter,
> + s->code_mask + 0, s->code_mask + 4, 0);
> +
> + if (can_bus_filter_match(&filter, frame->can_id)) {
> + uint8_t expect;
> + uint8_t mask;
> + expect = s->code_mask[1] << 4;
> + expect |= s->code_mask[3] & 0x0f;
> +
> + mask = s->code_mask[5] << 4;
> + mask |= s->code_mask[7] & 0x0f;
> + mask = ~mask & 0xff;
> +
> + if ((frame->data[0] & mask) ==
> + (expect & mask)) {
> + return 1;
> + }
> + }
> +
> + can_sja_dual_filter(&filter,
> + s->code_mask + 2, s->code_mask + 6, 0);
> +
> + if (can_bus_filter_match(&filter, frame->can_id)) {
> + return 1;
> + }
> +
> + return 0;
> + }
> + }
> + }
> +
> + return 1;
> +}
> +
> +static void can_display_msg(const char *prefix, const qemu_can_frame *msg)
> +{
> + int i;
> +
> + qemu_log_lock();
> + qemu_log("%s%03X [%01d] %s %s",
> + prefix,
> + msg->can_id & QEMU_CAN_EFF_MASK,
> + msg->can_dlc,
> + msg->can_id & QEMU_CAN_EFF_FLAG ? "EFF" : "SFF",
> + msg->can_id & QEMU_CAN_RTR_FLAG ? "RTR" : "DAT");
> +
> + for (i = 0; i < msg->can_dlc; i++) {
> + qemu_log(" %02X", msg->data[i]);
> + }
> + qemu_log("\n");
> + qemu_log_flush();
> + qemu_log_unlock();
> +}
> +
> +static void buff2frame_pel(const uint8_t *buff, qemu_can_frame *frame)
> +{
> + uint8_t i;
> +
> + frame->can_id = 0;
> + if (buff[0] & 0x40) { /* RTR */
> + frame->can_id = QEMU_CAN_RTR_FLAG;
> + }
> + frame->can_dlc = buff[0] & 0x0f;
> +
> + if (buff[0] & 0x80) { /* Extended */
> + frame->can_id |= QEMU_CAN_EFF_FLAG;
> + frame->can_id |= buff[1] << 21; /* ID.28~ID.21 */
> + frame->can_id |= buff[2] << 13; /* ID.20~ID.13 */
> + frame->can_id |= buff[3] << 5;
> + frame->can_id |= buff[4] >> 3;
> + for (i = 0; i < frame->can_dlc; i++) {
> + frame->data[i] = buff[5 + i];
> + }
> + for (; i < 8; i++) {
> + frame->data[i] = 0;
> + }
> + } else {
> + frame->can_id |= buff[1] << 3;
> + frame->can_id |= buff[2] >> 5;
> + for (i = 0; i < frame->can_dlc; i++) {
> + frame->data[i] = buff[3 + i];
> + }
> + for (; i < 8; i++) {
> + frame->data[i] = 0;
> + }
> + }
> +}
> +
> +
> +static void buff2frame_bas(const uint8_t *buff, qemu_can_frame *frame)
> +{
> + uint8_t i;
> +
> + frame->can_id = ((buff[0] << 3) & (0xff << 3)) + ((buff[1] >> 5) & 0x07);
> + if (buff[1] & 0x10) { /* RTR */
> + frame->can_id = QEMU_CAN_RTR_FLAG;
> + }
> + frame->can_dlc = buff[1] & 0x0f;
> +
> + for (i = 0; i < frame->can_dlc; i++) {
> + frame->data[i] = buff[2 + i];
> + }
> + for (; i < 8; i++) {
> + frame->data[i] = 0;
> + }
> +}
> +
> +
> +static int frame2buff_pel(const qemu_can_frame *frame, uint8_t *buff)
> +{
> + int i;
> +
> + if (frame->can_id & QEMU_CAN_ERR_FLAG) { /* error frame, NOT support
> now. */
> + return -1;
> + }
> +
> + buff[0] = 0x0f & frame->can_dlc; /* DLC */
> + if (frame->can_id & QEMU_CAN_RTR_FLAG) { /* RTR */
> + buff[0] |= (1 << 6);
> + }
> + if (frame->can_id & QEMU_CAN_EFF_FLAG) { /* EFF */
> + buff[0] |= (1 << 7);
> + buff[1] = extract32(frame->can_id, 21, 8); /* ID.28~ID.21 */
> + buff[2] = extract32(frame->can_id, 13, 8); /* ID.20~ID.13 */
> + buff[3] = extract32(frame->can_id, 5, 8); /* ID.12~ID.05 */
> + buff[4] = extract32(frame->can_id, 0, 5) << 3; /* ID.04~ID.00,xxx */
> + for (i = 0; i < frame->can_dlc; i++) {
> + buff[5 + i] = frame->data[i];
> + }
> + return frame->can_dlc + 5;
> + } else { /* SFF */
> + buff[1] = extract32(frame->can_id, 3, 8); /* ID.10~ID.03 */
> + buff[2] = extract32(frame->can_id, 0, 3) << 5; /* ID.02~ID.00,xxxxx
> */
> + for (i = 0; i < frame->can_dlc; i++) {
> + buff[3 + i] = frame->data[i];
> + }
> +
> + return frame->can_dlc + 3;
> + }
> +
> + return -1;
> +}
> +
> +static int frame2buff_bas(const qemu_can_frame *frame, uint8_t *buff)
> +{
> + int i;
> +
> + /*
> + * EFF, no support for BasicMode
> + * No use for Error frames now,
> + * they could be used in future to update SJA1000 error state
> + */
> + if ((frame->can_id & QEMU_CAN_EFF_FLAG) ||
> + (frame->can_id & QEMU_CAN_ERR_FLAG)) {
> + return -1;
> + }
> +
> + buff[0] = extract32(frame->can_id, 3, 8); /* ID.10~ID.03 */
> + buff[1] = extract32(frame->can_id, 0, 3) << 5; /* ID.02~ID.00,xxxxx */
> + if (frame->can_id & QEMU_CAN_RTR_FLAG) { /* RTR */
> + buff[1] |= (1 << 4);
> + }
> + buff[1] |= frame->can_dlc & 0x0f;
> + for (i = 0; i < frame->can_dlc; i++) {
> + buff[2 + i] = frame->data[i];
> + }
> +
> + return frame->can_dlc + 2;
> +}
> +
> +void can_sja_mem_write(CanSJA1000State *s, hwaddr addr, uint64_t val,
> + unsigned size)
> +{
> + qemu_can_frame frame;
> + uint32_t tmp;
> + uint8_t tmp8, count;
> +
> +
> + DPRINTF("write 0x%02llx addr 0x%02x\n",
> + (unsigned long long)val, (unsigned int)addr);
> +
> + if (addr > CAN_SJA_MEM_SIZE) {
> + return ;
> + }
> +
> + if (s->clock & 0x80) { /* PeliCAN Mode */
> + switch (addr) {
> + case SJA_MOD: /* Mode register */
> + s->mode = 0x1f & val;
> + if ((s->mode & 0x01) && ((val & 0x01) == 0)) {
> + /* Go to operation mode from reset mode. */
> + if (s->mode & (1 << 3)) { /* Single mode. */
> + /* For EFF */
> + can_sja_single_filter(&s->filter[0],
> + s->code_mask + 0, s->code_mask + 4, 1);
> +
> + /* For SFF */
> + can_sja_single_filter(&s->filter[1],
> + s->code_mask + 0, s->code_mask + 4, 0);
> +
> + can_bus_client_set_filters(&s->bus_client, s->filter, 2);
> + } else { /* Dual mode */
> + /* For EFF */
> + can_sja_dual_filter(&s->filter[0],
> + s->code_mask + 0, s->code_mask + 4, 1);
> +
> + can_sja_dual_filter(&s->filter[1],
> + s->code_mask + 2, s->code_mask + 6, 1);
> +
> + /* For SFF */
> + can_sja_dual_filter(&s->filter[2],
> + s->code_mask + 0, s->code_mask + 4, 0);
> +
> + can_sja_dual_filter(&s->filter[3],
> + s->code_mask + 2, s->code_mask + 6, 0);
> +
> + can_bus_client_set_filters(&s->bus_client, s->filter, 4);
> + }
> +
> + s->rxmsg_cnt = 0;
> + s->rx_cnt = 0;
> + }
> + break;
> +
> + case SJA_CMR: /* Command register. */
> + if (0x01 & val) { /* Send transmission request. */
> + buff2frame_pel(s->tx_buff, &frame);
> + if (DEBUG_FILTER) {
> + can_display_msg("[cansja]: Tx request " , &frame);
> + }
> +
> + /*
> + * Clear transmission complete status,
> + * and Transmit Buffer Status.
> + * write to the backends.
> + */
> + s->status_pel &= ~(3 << 2);
> +
> + can_bus_client_send(&s->bus_client, &frame, 1);
> +
> + /*
> + * Set transmission complete status
> + * and Transmit Buffer Status.
> + */
> + s->status_pel |= (3 << 2);
> +
> + /* Clear transmit status. */
> + s->status_pel &= ~(1 << 5);
> + s->interrupt_pel |= 0x02;
> + if (s->interrupt_en & 0x02) {
> + s->irq_raise(s->irq_opaque);
> + }
> + }
> + if (0x04 & val) { /* Release Receive Buffer */
> + if (s->rxmsg_cnt <= 0) {
> + break;
> + }
> +
> + tmp8 = s->rx_buff[s->rxbuf_start]; count = 0;
> + if (tmp8 & (1 << 7)) { /* EFF */
> + count += 2;
> + }
> + count += 3;
> + if (!(tmp8 & (1 << 6))) { /* DATA */
> + count += (tmp8 & 0x0f);
> + }
> +
> + if (DEBUG_FILTER) {
> + qemu_log("[cansja]: message released from "
> + "Rx FIFO cnt=%d, count=%d\n", s->rx_cnt, count);
> + }
> +
> + s->rxbuf_start += count;
> + s->rxbuf_start %= SJA_RCV_BUF_LEN;
> +
> + s->rx_cnt -= count;
> + s->rxmsg_cnt--;
> + if (s->rxmsg_cnt == 0) {
> + s->status_pel &= ~(1 << 0);
> + s->interrupt_pel &= ~(1 << 0);
> + }
> + if ((s->interrupt_en & 0x01) && (s->interrupt_pel == 0)) {
> + /* no other interrupts. */
> + s->irq_lower(s->irq_opaque);
> + }
> + }
> + if (0x08 & val) { /* Clear data overrun */
> + s->status_pel &= ~(1 << 1);
> + s->interrupt_pel &= ~(1 << 3);
> + if ((s->interrupt_en & 0x80) && (s->interrupt_pel == 0)) {
> + /* no other interrupts. */
> + s->irq_lower(s->irq_opaque);
> + }
> + }
> + break;
> + case SJA_SR: /* Status register */
> + case SJA_IR: /* Interrupt register */
> + break; /* Do nothing */
> + case SJA_IER: /* Interrupt enable register */
> + s->interrupt_en = val;
> + break;
> + case 16: /* RX frame information addr16-28. */
> + s->status_pel |= (1 << 5); /* Set transmit status. */
> + case 17:
> + case 18:
> + case 19:
> + case 20:
> + case 21:
> + case 22:
> + case 23:
> + case 24:
> + case 25:
> + case 26:
> + case 27:
> + case 28:
case 17 ... 28:
> + if (s->mode & 0x01) { /* Reset mode */
> + if (addr < 24) {
> + s->code_mask[addr - 16] = val;
> + }
> + } else { /* Operation mode */
> + s->tx_buff[addr - 16] = val; /* Store to TX buffer directly.
> */
> + }
> + break;
> + case SJA_CDR:
> + s->clock = val;
> + break;
> + }
> + } else { /* Basic Mode */
> + switch (addr) {
> + case SJA_BCAN_CTR: /* Control register, addr 0 */
> + if ((s->control & 0x01) && ((val & 0x01) == 0)) {
> + /* Go to operation mode from reset mode. */
> + s->filter[0].can_id = (s->code << 3) & (0xff << 3);
> + tmp = (~(s->mask << 3)) & (0xff << 3);
> + tmp |= QEMU_CAN_EFF_FLAG; /* Only Basic CAN Frame. */
> + s->filter[0].can_mask = tmp;
> + can_bus_client_set_filters(&s->bus_client, s->filter, 1);
> +
> + s->rxmsg_cnt = 0;
> + s->rx_cnt = 0;
> + } else if (!(s->control & 0x01) && !(val & 0x01)) {
> + can_sja_software_reset(s);
> + }
> +
> + s->control = 0x1f & val;
> + break;
> + case SJA_BCAN_CMR: /* Command register, addr 1 */
> + if (0x01 & val) { /* Send transmission request. */
> + buff2frame_bas(s->tx_buff, &frame);
> + if (DEBUG_FILTER) {
> + can_display_msg("[cansja]: Tx request " , &frame);
> + }
> +
> + /*
> + * Clear transmission complete status,
> + * and Transmit Buffer Status.
> + */
> + s->status_bas &= ~(3 << 2);
> +
> + /* write to the backends. */
> + can_bus_client_send(&s->bus_client, &frame, 1);
> +
> + /*
> + * Set transmission complete status,
> + * and Transmit Buffer Status.
> + */
> + s->status_bas |= (3 << 2);
> +
> + /* Clear transmit status. */
> + s->status_bas &= ~(1 << 5);
> + s->interrupt_bas |= 0x02;
> + if (s->control & 0x04) {
> + s->irq_raise(s->irq_opaque);
> + }
> + }
> + if (0x04 & val) { /* Release Receive Buffer */
> + if (s->rxmsg_cnt <= 0) {
> + break;
> + }
> +
> + qemu_mutex_lock(&s->rx_lock);
> + tmp8 = s->rx_buff[(s->rxbuf_start + 1) % SJA_RCV_BUF_LEN];
> + count = 2 + (tmp8 & 0x0f);
> +
> + if (DEBUG_FILTER) {
> + qemu_log("[cansja]: message released from "
> + "Rx FIFO cnt=%d, count=%d\n", s->rx_cnt, count);
> + }
> +
> + s->rxbuf_start += count;
> + s->rxbuf_start %= SJA_RCV_BUF_LEN;
> + s->rx_cnt -= count;
> + s->rxmsg_cnt--;
> + qemu_mutex_unlock(&s->rx_lock);
> +
> + if (s->rxmsg_cnt == 0) {
> + s->status_bas &= ~(1 << 0);
> + s->interrupt_bas &= ~(1 << 0);
> + }
> + if ((s->control & 0x02) && (s->interrupt_bas == 0)) {
> + /* no other interrupts. */
> + s->irq_lower(s->irq_opaque);
> + }
> + }
> + if (0x08 & val) { /* Clear data overrun */
> + s->status_bas &= ~(1 << 1);
> + s->interrupt_bas &= ~(1 << 3);
> + if ((s->control & 0x10) && (s->interrupt_bas == 0)) {
> + /* no other interrupts. */
> + s->irq_lower(s->irq_opaque);
> + }
> + }
> + break;
> + case 4:
> + s->code = val;
> + break;
> + case 5:
> + s->mask = val;
> + break;
> + case 10:
> + s->status_bas |= (1 << 5); /* Set transmit status. */
> + case 11:
> + case 12:
> + case 13:
> + case 14:
> + case 15:
> + case 16:
> + case 17:
> + case 18:
> + case 19:
case 11 ... 19:
> + if ((s->control & 0x01) == 0) { /* Operation mode */
> + s->tx_buff[addr - 10] = val; /* Store to TX buffer directly.
> */
> + }
> + break;
> + case SJA_CDR:
> + s->clock = val;
> + break;
> + }
> + }
> +}
> +
> +uint64_t can_sja_mem_read(CanSJA1000State *s, hwaddr addr, unsigned size)
> +{
> + uint64_t temp = 0;
> +
> + DPRINTF("read addr 0x%02x ...\n", (unsigned int)addr);
> +
> + if (addr > CAN_SJA_MEM_SIZE) {
> + return 0;
> + }
> +
> + if (s->clock & 0x80) { /* PeliCAN Mode */
> + switch (addr) {
> + case SJA_MOD: /* Mode register, addr 0 */
> + temp = s->mode;
> + break;
> + case SJA_CMR: /* Command register, addr 1 */
> + temp = 0x00; /* Command register, cannot be read. */
> + break;
> + case SJA_SR: /* Status register, addr 2 */
> + temp = s->status_pel;
> + break;
> + case SJA_IR: /* Interrupt register, addr 3 */
> + temp = s->interrupt_pel;
> + s->interrupt_pel = 0;
> + if (s->rxmsg_cnt) {
> + s->interrupt_pel |= (1 << 0); /* Receive interrupt. */
> + break;
> + }
> + s->irq_lower(s->irq_opaque);
> + break;
> + case SJA_IER: /* Interrupt enable register, addr 4 */
> + temp = s->interrupt_en;
> + break;
> + case 5: /* Reserved */
> + case 6: /* Bus timing 0, hardware related, not support now. */
> + case 7: /* Bus timing 1, hardware related, not support now. */
> + case 8: /*
> + * Output control register, hardware related,
> + * not supported for now.
> + */
> + case 9: /* Test. */
> + case 10: /* Reserved */
> + case 11:
> + case 12:
> + case 13:
> + case 14:
> + case 15:
> + temp = 0x00;
> + break;
> +
> + case 16:
> + case 17:
> + case 18:
> + case 19:
> + case 20:
> + case 21:
> + case 22:
> + case 23:
> + case 24:
> + case 25:
> + case 26:
> + case 27:
> + case 28:
> + if (s->mode & 0x01) { /* Reset mode */
> + if (addr < 24) {
> + temp = s->code_mask[addr - 16];
> + } else {
> + temp = 0x00;
> + }
> + } else { /* Operation mode */
> + temp = s->rx_buff[(s->rxbuf_start + addr - 16) %
> + SJA_RCV_BUF_LEN];
> + }
> + break;
> + case SJA_CDR:
> + temp = s->clock;
> + break;
> + default:
> + temp = 0xff;
> + }
> + } else { /* Basic Mode */
> + switch (addr) {
> + case SJA_BCAN_CTR: /* Control register, addr 0 */
> + temp = s->control;
> + break;
> + case SJA_BCAN_SR: /* Status register, addr 2 */
> + temp = s->status_bas;
> + break;
> + case SJA_BCAN_IR: /* Interrupt register, addr 3 */
> + temp = s->interrupt_bas;
> + s->interrupt_bas = 0;
> + if (s->rxmsg_cnt) {
> + s->interrupt_bas |= (1 << 0); /* Receive interrupt. */
> + break;
> + }
> + s->irq_lower(s->irq_opaque);
> + break;
> + case 4:
> + temp = s->code;
> + break;
> + case 5:
> + temp = s->mask;
> + break;
> + case 20:
> + case 21:
> + case 22:
> + case 23:
> + case 24:
> + case 25:
> + case 26:
> + case 27:
> + case 28:
> + case 29:
> + temp = s->rx_buff[(s->rxbuf_start + addr - 20) %
> SJA_RCV_BUF_LEN];
> + break;
> + case 31:
> + temp = s->clock;
> + break;
> + default:
> + temp = 0xff;
> + break;
> + }
> + }
> + DPRINTF("read addr 0x%02x, %d bytes, content 0x%02lx\n",
> + (int)addr, size, (long unsigned int)temp);
> +
> + return temp;
> +}
> +
> +int can_sja_can_receive(CanBusClientState *client)
> +{
> + CanSJA1000State *s = container_of(client, CanSJA1000State, bus_client);
> +
> + if (s->clock & 0x80) { /* PeliCAN Mode */
> + if (s->mode & 0x01) { /* reset mode. */
> + return 0;
> + }
> + } else { /* BasicCAN mode */
> + if (s->control & 0x01) {
> + return 0;
> + }
> + }
> +
> + return 1; /* always return 1, when operation mode */
> +}
> +
> +ssize_t can_sja_receive(CanBusClientState *client, const qemu_can_frame
> *frames,
> + size_t frames_cnt)
> +{
> + CanSJA1000State *s = container_of(client, CanSJA1000State, bus_client);
> + static uint8_t rcv[SJA_MSG_MAX_LEN];
> + int i;
> + int ret = -1;
> + const qemu_can_frame *frame = frames;
> +
> + if (frames_cnt <= 0) {
> + return 0;
> + }
> + if (DEBUG_FILTER) {
> + can_display_msg("[cansja]: receive ", frame);
> + }
> +
> + qemu_mutex_lock(&s->rx_lock); /* Just do it quickly :) */
> + if (s->clock & 0x80) { /* PeliCAN Mode */
> +
> + /* the CAN controller is receiving a message */
> + s->status_pel |= (1 << 4);
> +
> + if (can_sja_accept_filter(s, frame) == 0) {
> + s->status_pel &= ~(1 << 4);
> + if (DEBUG_FILTER) {
> + qemu_log("[cansja]: filter rejects message\n");
> + }
> + goto fail;
> + }
> +
> + ret = frame2buff_pel(frame, rcv);
> + if (ret < 0) {
> + s->status_pel &= ~(1 << 4);
> + if (DEBUG_FILTER) {
> + qemu_log("[cansja]: message store failed\n");
> + }
> + goto fail; /* maybe not support now. */
> + }
> +
> + if (s->rx_cnt + ret > SJA_RCV_BUF_LEN) { /* Data overrun. */
> + s->status_pel |= (1 << 1); /* Overrun status */
> + s->interrupt_pel |= (1 << 3);
> + if (s->interrupt_en & (1 << 3)) { /* Overrun interrupt enable */
> + s->irq_raise(s->irq_opaque);
> + }
> + s->status_pel &= ~(1 << 4);
> + if (DEBUG_FILTER) {
> + qemu_log("[cansja]: receive FIFO overrun\n");
> + }
> + goto fail;
> + }
> + s->rx_cnt += ret;
> + s->rxmsg_cnt++;
> + if (DEBUG_FILTER) {
> + qemu_log("[cansja]: message stored in receive FIFO\n");
> + }
> +
> + for (i = 0; i < ret; i++) {
> + s->rx_buff[(s->rx_ptr++) % SJA_RCV_BUF_LEN] = rcv[i];
> + }
> + s->rx_ptr %= SJA_RCV_BUF_LEN; /* update the pointer. */
> +
> + s->status_pel |= 0x01; /* Set the Receive Buffer Status. DS-p23 */
> + s->interrupt_pel |= 0x01;
> + s->status_pel &= ~(1 << 4);
> + s->status_pel |= (1 << 0);
> + if (s->interrupt_en & 0x01) { /* Receive Interrupt enable. */
> + s->irq_raise(s->irq_opaque);
> + }
> + } else { /* BasicCAN mode */
> +
> + /* the CAN controller is receiving a message */
> + s->status_bas |= (1 << 4);
> +
> + ret = frame2buff_bas(frame, rcv);
> + if (ret < 0) {
> + s->status_bas &= ~(1 << 4);
> + if (DEBUG_FILTER) {
> + qemu_log("[cansja]: message store failed\n");
> + }
> + goto fail; /* maybe not support now. */
> + }
> +
> + if (s->rx_cnt + ret > SJA_RCV_BUF_LEN) { /* Data overrun. */
> + s->status_bas |= (1 << 1); /* Overrun status */
> + s->status_bas &= ~(1 << 4);
> + s->interrupt_bas |= (1 << 3);
> + if (s->control & (1 << 4)) { /* Overrun interrupt enable */
> + s->irq_raise(s->irq_opaque);
> + }
> + if (DEBUG_FILTER) {
> + qemu_log("[cansja]: receive FIFO overrun\n");
> + }
> + goto fail;
> + }
> + s->rx_cnt += ret;
> + s->rxmsg_cnt++;
> +
> + if (DEBUG_FILTER) {
> + qemu_log("[cansja]: message stored\n");
> + }
> +
> + for (i = 0; i < ret; i++) {
> + s->rx_buff[(s->rx_ptr++) % SJA_RCV_BUF_LEN] = rcv[i];
> + }
> + s->rx_ptr %= SJA_RCV_BUF_LEN; /* update the pointer. */
> +
> + s->status_bas |= 0x01; /* Set the Receive Buffer Status. DS-p15 */
> + s->status_bas &= ~(1 << 4);
> + s->interrupt_bas |= 0x01;
> + if (s->control & 0x02) { /* Receive Interrupt enable. */
> + s->irq_raise(s->irq_opaque);
> + }
> + }
> + ret = 1;
> +fail:
> + qemu_mutex_unlock(&s->rx_lock);
> +
> + return ret;
> +}
> +
> +static CanBusClientInfo can_sja_bus_client_info = {
> + .can_receive = can_sja_can_receive,
> + .receive = can_sja_receive,
> + .cleanup = NULL,
> + .poll = NULL
> +};
> +
> +
> +int can_sja_connect_to_bus(CanSJA1000State *s, CanBusState *bus)
> +{
> + s->bus_client.info = &can_sja_bus_client_info;
> +
> + if (can_bus_insert_client(bus, &s->bus_client) < 0) {
> + return -1;
> + }
> +
> + return 0;
> +}
> +
> +void can_sja_disconnect(CanSJA1000State *s)
> +{
> + can_bus_remove_client(&s->bus_client);
> +}
> +
> +int can_sja_init(CanSJA1000State *s, CanSJAIrqRaiseLower *irq_raise,
> + CanSJAIrqRaiseLower *irq_lower, void *irq_opaque)
> +{
> + qemu_mutex_init(&s->rx_lock);
> +
> + s->irq_raise = irq_raise;
> + s->irq_lower = irq_lower;
> + s->irq_opaque = irq_opaque;
> +
> + s->irq_lower(s->irq_opaque);
> +
> + can_sja_hardware_reset(s);
> +
> + return 0;
> +}
> +
> +void can_sja_exit(CanSJA1000State *s)
> +{
> + qemu_mutex_destroy(&s->rx_lock);
> +}
> +
> +const VMStateDescription vmstate_qemu_can_filter = {
> + .name = "qemu_can_filter",
> + .version_id = 1,
> + .minimum_version_id = 1,
> + .minimum_version_id_old = 1,
> + .fields = (VMStateField[]) {
> + VMSTATE_UINT32(can_id, qemu_can_filter),
> + VMSTATE_UINT32(can_mask, qemu_can_filter),
> + VMSTATE_END_OF_LIST()
> + }
> +};
> +
> +/* VMState is needed for live migration of QEMU images */
> +const VMStateDescription vmstate_can_sja = {
> + .name = "can_sja",
> + .version_id = 1,
> + .minimum_version_id = 1,
> + .minimum_version_id_old = 1,
> + .fields = (VMStateField[]) {
> + VMSTATE_UINT8(mode, CanSJA1000State),
> +
> + VMSTATE_UINT8(status_pel, CanSJA1000State),
> + VMSTATE_UINT8(interrupt_pel, CanSJA1000State),
> + VMSTATE_UINT8(interrupt_en, CanSJA1000State),
> + VMSTATE_UINT8(rxmsg_cnt, CanSJA1000State),
> + VMSTATE_UINT8(rxbuf_start, CanSJA1000State),
> + VMSTATE_UINT8(clock, CanSJA1000State),
> +
> + VMSTATE_BUFFER(code_mask, CanSJA1000State),
> + VMSTATE_BUFFER(tx_buff, CanSJA1000State),
> +
> + VMSTATE_BUFFER(rx_buff, CanSJA1000State),
> +
> + VMSTATE_UINT32(rx_ptr, CanSJA1000State),
> + VMSTATE_UINT32(rx_cnt, CanSJA1000State),
> +
> + VMSTATE_UINT8(control, CanSJA1000State),
> +
> + VMSTATE_UINT8(status_bas, CanSJA1000State),
> + VMSTATE_UINT8(interrupt_bas, CanSJA1000State),
> + VMSTATE_UINT8(code, CanSJA1000State),
> + VMSTATE_UINT8(mask, CanSJA1000State),
> +
> + VMSTATE_STRUCT_ARRAY(filter, CanSJA1000State, 4, 0,
> + vmstate_qemu_can_filter, qemu_can_filter),
> +
> +
> + VMSTATE_END_OF_LIST()
> + }
> +};
> diff --git a/hw/can/can_sja1000.h b/hw/can/can_sja1000.h
> new file mode 100644
> index 0000000000..6f2cfbcb72
> --- /dev/null
> +++ b/hw/can/can_sja1000.h
> @@ -0,0 +1,167 @@
> +/*
> + * CAN device - SJA1000 chip emulation for QEMU
> + *
> + * Copyright (c) 2013-2014 Jin Yang
> + * Copyright (c) 2014-2018 Pavel Pisa
> + *
> + * Initial development supported by Google GSoC 2013 from RTEMS project slot
> + *
> + * Permission is hereby granted, free of charge, to any person obtaining a
> copy
> + * of this software and associated documentation files (the "Software"), to
> deal
> + * in the Software without restriction, including without limitation the
> rights
> + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
> + * copies of the Software, and to permit persons to whom the Software is
> + * furnished to do so, subject to the following conditions:
> + *
> + * The above copyright notice and this permission notice shall be included in
> + * all copies or substantial portions of the Software.
> + *
> + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
> + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
> + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
> + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
> + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
> FROM,
> + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
> + * THE SOFTWARE.
> + */
> +#ifndef HW_CAN_SJA1000_H
> +#define HW_CAN_SJA1000_H
> +
> +#include "can/can_emu.h"
> +
> +#define CAN_SJA_MEM_SIZE 128
> +
> +/* The max size for a message buffer, EFF and DLC=8, DS-p39 */
> +#define SJA_MSG_MAX_LEN 13
> +/* The receive buffer size. */
> +#define SJA_RCV_BUF_LEN 64
> +
> +typedef void (CanSJAIrqRaiseLower)(void *opaque);
> +
> +typedef struct CanSJA1000State {
> + /* PeliCAN state and registers sorted by address */
> + uint8_t mode; /* 0 .. Mode register, DS-p26 */
> + /* 1 .. Command register */
> + uint8_t status_pel; /* 2 .. Status register, p15 */
> + uint8_t interrupt_pel; /* 3 .. Interrupt register */
> + uint8_t interrupt_en; /* 4 .. Interrupt Enable register */
> + uint8_t rxmsg_cnt; /* 29 .. RX message counter. DS-p49 */
> + uint8_t rxbuf_start; /* 30 .. RX buffer start address, DS-p49
> */
> + uint8_t clock; /* 31 .. Clock Divider register, DS-p55 */
> +
> + uint8_t code_mask[8]; /* 16~23 */
> + uint8_t tx_buff[13]; /* 96~108 .. transmit buffer */
> + /* 10~19 .. transmit buffer for BasicCAN
> */
> +
> + uint8_t rx_buff[SJA_RCV_BUF_LEN]; /* 32~95 .. 64bytes Rx FIFO */
> + uint32_t rx_ptr; /* Count by bytes. */
> + uint32_t rx_cnt; /* Count by bytes. */
> +
> + /* PeliCAN state and registers sorted by address */
> + uint8_t control; /* 0 .. Control register */
> + /* 1 .. Command register */
> + uint8_t status_bas; /* 2 .. Status register */
> + uint8_t interrupt_bas; /* 3 .. Interrupt register */
> + uint8_t code; /* 4 .. Acceptance code register */
> + uint8_t mask; /* 5 .. Acceptance mask register */
> +
> + qemu_can_filter filter[4];
> +
> + QemuMutex rx_lock;
> + CanSJAIrqRaiseLower *irq_raise;
> + CanSJAIrqRaiseLower *irq_lower;
> + void *irq_opaque;
> + CanBusClientState bus_client;
> +} CanSJA1000State;
> +
> +/* PeliCAN mode */
> +enum SJA1000_PeliCAN_regs {
> + SJA_MOD = 0x00,
> +/* Command register */
why not keep in one line?
SJA_MOD = 0x00, /* Command */
SJA_CMR = 0x01, /* Status */
> + SJA_CMR = 0x01,
> +/* Status register */
> + SJA_SR = 0x02,
> +/* Interrupt register */
> + SJA_IR = 0x03,
> +/* Interrupt Enable */
> + SJA_IER = 0x04,
> +/* Bus Timing register 0 */
> + SJA_BTR0 = 0x06,
> +/* Bus Timing register 1 */
> + SJA_BTR1 = 0x07,
> +/* Output Control register */
> + SJA_OCR = 0x08,
> +/* Arbitration Lost Capture */
> + SJA_ALC = 0x0b,
> +/* Error Code Capture */
> + SJA_ECC = 0x0c,
> +/* Error Warning Limit */
> + SJA_EWLR = 0x0d,
> +/* RX Error Counter */
> + SJA_RXERR = 0x0e,
> +/* TX Error Counter */
> + SJA_TXERR0 = 0x0e,
> + SJA_TXERR1 = 0x0f,
> +/* Rx Message Counter (number of msgs. in RX FIFO */
> + SJA_RMC = 0x1d,
> +/* Rx Buffer Start Addr. (address of current MSG) */
> + SJA_RBSA = 0x1e,
> +/* Transmit Buffer (write) Receive Buffer (read) Frame Information */
> + SJA_FRM = 0x10,
> +/*
> + * ID bytes (11 bits in 0 and 1 for standard message or
> + * 16 bits in 0,1 and 13 bits in 2,3 for extended message)
> + */
> + SJA_ID0 = 0x11, SJA_ID1 = 0x12,
> +/* ID cont. for extended frames */
> + SJA_ID2 = 0x13, SJA_ID3 = 0x14,
> +/* Data start standard frame */
> + SJA_DATS = 0x13,
> +/* Data start extended frame */
> + SJA_DATE = 0x15,
> +/* Acceptance Code (4 bytes) in RESET mode */
> + SJA_ACR0 = 0x10,
> +/* Acceptance Mask (4 bytes) in RESET mode */
> + SJA_AMR0 = 0x14,
> +/* 4 bytes */
> + SJA_PeliCAN_AC_LEN = 4,
> +/* Clock Divider */
> + SJA_CDR = 0x1f
> +};
> +
> +
> +/* PeliCAN mode */
> +enum SJA1000_BasicCAN_regs {
> + SJA_BCAN_CTR = 0x00,
> +/* Command register */
> + SJA_BCAN_CMR = 0x01,
> +/* Status register */
> + SJA_BCAN_SR = 0x02,
> +/* Interrupt register */
> + SJA_BCAN_IR = 0x03
> +};
> +
> +void can_sja_hardware_reset(CanSJA1000State *s);
> +
> +void can_sja_mem_write(CanSJA1000State *s, hwaddr addr, uint64_t val,
> + unsigned size);
> +
> +uint64_t can_sja_mem_read(CanSJA1000State *s, hwaddr addr, unsigned size);
> +
> +int can_sja_connect_to_bus(CanSJA1000State *s, CanBusState *bus);
> +
> +void can_sja_disconnect(CanSJA1000State *s);
> +
> +int can_sja_init(CanSJA1000State *s, CanSJAIrqRaiseLower *irq_raise,
> + CanSJAIrqRaiseLower *irq_lower, void *irq_opaque);
> +
> +void can_sja_exit(CanSJA1000State *s);
> +
> +int can_sja_can_receive(CanBusClientState *client);
> +
> +ssize_t can_sja_receive(CanBusClientState *client,
> + const qemu_can_frame *frames, size_t frames_cnt);
> +
> +extern const VMStateDescription vmstate_can_sja;
> +
> +#endif
>
- Re: [Qemu-devel] [PATCH V4 2/7] CAN bus support to connect bust to Linux host SocketCAN interface., (continued)
- Re: [Qemu-devel] [PATCH V4 2/7] CAN bus support to connect bust to Linux host SocketCAN interface., Philippe Mathieu-Daudé, 2018/01/14
- Re: [Qemu-devel] [PATCH V4 2/7] CAN bus support to connect bust to Linux host SocketCAN interface., Pavel Pisa, 2018/01/15
- Re: [Qemu-devel] [PATCH V4 2/7] CAN bus support to connect bust to Linux host SocketCAN interface., Philippe Mathieu-Daudé, 2018/01/15
- Re: [Qemu-devel] [PATCH V4 2/7] CAN bus support to connect bust to Linux host SocketCAN interface., Pavel Pisa, 2018/01/19
- Re: [Qemu-devel] [PATCH V4 2/7] CAN bus support to connect bust to Linux host SocketCAN interface., Philippe Mathieu-Daudé, 2018/01/19
- Re: [Qemu-devel] [PATCH V4 2/7] CAN bus support to connect bust to Linux host SocketCAN interface., Stefan Hajnoczi, 2018/01/22
- Re: [Qemu-devel] [PATCH V4 2/7] CAN bus support to connect bust to Linux host SocketCAN interface., Daniel P. Berrange, 2018/01/19
Re: [Qemu-devel] [PATCH V4 2/7] CAN bus support to connect bust to Linux host SocketCAN interface., Philippe Mathieu-Daudé, 2018/01/19
[Qemu-devel] [PATCH V4 3/7] CAN bus SJA1000 chip register level emulation for QEMU, pisa, 2018/01/14
- Re: [Qemu-devel] [PATCH V4 3/7] CAN bus SJA1000 chip register level emulation for QEMU,
Philippe Mathieu-Daudé <=
[Qemu-devel] [PATCH V4 4/7] CAN bus Kvaser PCI CAN-S (single SJA1000 channel) emulation added., pisa, 2018/01/14
[Qemu-devel] [PATCH V4 5/7] QEMU CAN bus emulation documentation, pisa, 2018/01/14
[Qemu-devel] [PATCH V4 6/7] CAN bus PCM-3680I PCI (dual SJA1000 channel) emulation added., pisa, 2018/01/14
[Qemu-devel] [PATCH V4 7/7] CAN bus MIOe-3680 PCI (dual SJA1000 channel) emulation added., pisa, 2018/01/14
Re: [Qemu-devel] [PATCH V4 0/7] CAN bus support for QEMU (SJA1000 PCI so far), Philippe Mathieu-Daudé, 2018/01/22