• QEMU CAN总线


    QEMU CAN总线

    在我的博客《qemu常用参数选项说明》中我介绍的一些常用的qemu参数配置,而对于嵌入式开发往往还会涉及到更多形形色色的系统总线和硬件,本文来讲述下使用qemu can总线的用法。

    qemu参数配置

    qemu支持can模拟的硬件不多,如果是官网下载的qemu我这里推荐使用xlnx zynqmp这个board,但是本文这里直接选择我的系列博客《基于qemu-riscv从0开始构建嵌入式linux系统》中自制的quard-star板,目前开源在githubgitee上的代码均已支持CAN模拟。

    qemu启动参数如下:

    -M quard-star,canbus=canbus0
    -object can-bus,id=canbus0 
    -object can-host-socketcan,id=socketcan0,if=vcan0,canbus=canbus0
    
    • 1
    • 2
    • 3

    这里参数设置是将host主机上的can设备与qemu模拟的can设备相连接,如果我们host上没有can设备,则需要创建虚拟的vcan,此处vcan0即为我们需要创建的vcan。

    vcan0创建

    创建config_vcan.sh脚本,内容如下:

    #!/bin/bash
    set -e
    
    MODE=\
    "config_vcan | \
    release_vcan"
    
    USER_NAME=$(whoami)
    USAGE="usage $0 [$MODE] [] "
    
    if [ $# == 2 ] ; then
    	CAN_NAME=$1
    else
        CAN_NAME=vcan0
    fi
    
    config_vcan()
    {
        modprobe vcan
        ip link add dev $CAN_NAME type vcan
        ip link set up $CAN_NAME
    }
    
    release_vcan()
    {
        ip link set down $CAN_NAME
        ip link delete dev $CAN_NAME
    }
    
    case "$1" in
    config_vcan)
        config_vcan
    	;;
    release_vcan)
        release_vcan
    	;;
    --help)
    	echo $USAGE
    	exit 0
    	;;
    *)
    	echo $USAGE
    	exit 1	
    	;;
    esac
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45

    执行sudo ./config_vcan.sh config_vcan就可以成功创建vcan0,如果你想删除这个vcan则只需sudo ./config_vcan.sh release_vcan。

    这里稍微解释下,vcan是linux kernel内的一个driver,提供vcan的设备,因此只需要加载该驱动,就可以使用ip link相关命令来配置vcan设备,如同真实物理can设备一样。

    完成vcan配置后,输入ifconfig,一切正常就会显示存在以下设备,此时我们就可以启动qemu了。

    vcan0: flags=193<UP,RUNNING,NOARP>  mtu 72
            unspec 00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00  txqueuelen 1000  (未指定)
            RX packets 0  bytes 0 (0.0 B)
            RX errors 0  dropped 0  overruns 0  frame 0
            TX packets 0  bytes 0 (0.0 B)
            TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    qemu xlnx-can ip fix

    原本此时我们配置好驱动就可以在qemu内的linux系统上使用can总线了,当然你可以裸机自己手写can驱动进行开发,但是我这里实测时发现直接使用linux kerenl的xlnx-can驱动,设备枚举一切正常,但数据会产生错误,经过debug分析最终确认我使用的qemu 7.0.0(截止发文前目前qemu上游mainline依然存在该问题)版本的xlnx-can ip模拟代码可能存在问题,因此这里需要手动修复(如果你使用quard-star则不需要,因为我已经修复它了)。这里描述问题如下:

    在测试host上使用vcan与guest上的can通信,结果发现数据包字节序似乎不正确,因此对比kernel内的驱动代码以及qemu的代码发现xlnx的can格式和socket can格式是不匹配的,需要转换,这里阅读《ug1085-zynq-ultrascale-trm.pdf》的Table 20‐3: CAN Message Format就能发现这个问题,奇怪的是qemu-7.0.0/hw/net/can/xlnx-zynqmp-can.c内实现的似乎没有转换xlnx CAN Message Format而是把Socket CAN Format直接转发了,因此导致了错误。我这里添加或修改以下函数,问题得到解决(这里我没有向qemu上游发送patch,因为我不确定是否真实的zynq硬件情况)。

    #define XCAN_IDR_IDE_MASK		0x00080000U
    #define XCAN_IDR_ID1_MASK		0xFFE00000U
    #define XCAN_IDR_ID2_MASK		0x0007FFFEU
    #define XCAN_IDR_RTR_MASK		0x00000001U
    #define XCAN_IDR_SRR_MASK		0x00100000U
    #define XCAN_IDR_ID1_SHIFT		21
    #define XCAN_IDR_ID2_SHIFT		1
    #define CAN_SFF_ID_BITS		    11
    #define CAN_EFF_ID_BITS		    29
    
    static uint32_t id_xcan2can(uint32_t id)
    {
        uint32_t ret_id = 0; 
        /* Change Xilinx CAN ID format to socketCAN ID format */
        if (id & XCAN_IDR_IDE_MASK) {
            /* The received frame is an Extended format frame */
            ret_id = (id & XCAN_IDR_ID1_MASK) >> 3;
            ret_id |= (id & XCAN_IDR_ID2_MASK) >>
                    XCAN_IDR_ID2_SHIFT;
            ret_id |= QEMU_CAN_EFF_FLAG;
            if (id & XCAN_IDR_RTR_MASK)
                ret_id |= QEMU_CAN_RTR_FLAG;
        } else {
            /* The received frame is a standard format frame */
            ret_id = (id & XCAN_IDR_ID1_MASK) >>
                    XCAN_IDR_ID1_SHIFT;
            if (id & XCAN_IDR_SRR_MASK)
                ret_id |= QEMU_CAN_RTR_FLAG;
        }
        return ret_id;
    }
    
    static uint32_t id_can2xcan(uint32_t id)
    {
        uint32_t ret_id = 0;
        if (id & QEMU_CAN_EFF_FLAG) {
            /* Extended CAN ID format */
            ret_id = ((id & QEMU_CAN_EFF_MASK) << XCAN_IDR_ID2_SHIFT) &
                XCAN_IDR_ID2_MASK;
            ret_id |= (((id & QEMU_CAN_EFF_MASK) >>
                (CAN_EFF_ID_BITS - CAN_SFF_ID_BITS)) <<
                XCAN_IDR_ID1_SHIFT) & XCAN_IDR_ID1_MASK;
            ret_id |= XCAN_IDR_IDE_MASK | XCAN_IDR_SRR_MASK;
            if (id & QEMU_CAN_RTR_FLAG)
                ret_id |= XCAN_IDR_RTR_MASK;
        } else {
            /* Standard CAN ID format */
            ret_id = ((id & QEMU_CAN_SFF_MASK) << XCAN_IDR_ID1_SHIFT) &
                XCAN_IDR_ID1_MASK;
            if (id & QEMU_CAN_RTR_FLAG)
                ret_id |= XCAN_IDR_SRR_MASK;
        }
        return ret_id;
    }
    
    static void generate_frame(qemu_can_frame *frame, uint32_t *data)
    {
        frame->can_id = id_xcan2can(data[0]);
        frame->can_dlc = FIELD_EX32(data[1], TXFIFO_DLC, DLC);
    
        frame->data[0] = FIELD_EX32(data[2], TXFIFO_DATA1, DB0);
        frame->data[1] = FIELD_EX32(data[2], TXFIFO_DATA1, DB1);
        frame->data[2] = FIELD_EX32(data[2], TXFIFO_DATA1, DB2);
        frame->data[3] = FIELD_EX32(data[2], TXFIFO_DATA1, DB3);
    
        frame->data[4] = FIELD_EX32(data[3], TXFIFO_DATA2, DB4);
        frame->data[5] = FIELD_EX32(data[3], TXFIFO_DATA2, DB5);
        frame->data[6] = FIELD_EX32(data[3], TXFIFO_DATA2, DB6);
        frame->data[7] = FIELD_EX32(data[3], TXFIFO_DATA2, DB7);
    }
    
    static void update_rx_fifo(XlnxZynqMPCANState *s, const qemu_can_frame *frame)
    {
        bool filter_pass = false;
        uint16_t timestamp = 0;
    
        /* If no filter is enabled. Message will be stored in FIFO. */
        if (!((ARRAY_FIELD_EX32(s->regs, AFR, UAF1)) |
        (ARRAY_FIELD_EX32(s->regs, AFR, UAF2)) |
        (ARRAY_FIELD_EX32(s->regs, AFR, UAF3)) |
        (ARRAY_FIELD_EX32(s->regs, AFR, UAF4)))) {
            filter_pass = true;
        }
    
        /*
        * Messages that pass any of the acceptance filters will be stored in
        * the RX FIFO.
        */
        if (ARRAY_FIELD_EX32(s->regs, AFR, UAF1)) {
            uint32_t id_masked = s->regs[R_AFMR1] & frame->can_id;
            uint32_t filter_id_masked = s->regs[R_AFMR1] & s->regs[R_AFIR1];
    
            if (filter_id_masked == id_masked) {
                filter_pass = true;
            }
        }
    
        if (ARRAY_FIELD_EX32(s->regs, AFR, UAF2)) {
            uint32_t id_masked = s->regs[R_AFMR2] & frame->can_id;
            uint32_t filter_id_masked = s->regs[R_AFMR2] & s->regs[R_AFIR2];
    
            if (filter_id_masked == id_masked) {
                filter_pass = true;
            }
        }
    
        if (ARRAY_FIELD_EX32(s->regs, AFR, UAF3)) {
            uint32_t id_masked = s->regs[R_AFMR3] & frame->can_id;
            uint32_t filter_id_masked = s->regs[R_AFMR3] & s->regs[R_AFIR3];
    
            if (filter_id_masked == id_masked) {
                filter_pass = true;
            }
        }
    
        if (ARRAY_FIELD_EX32(s->regs, AFR, UAF4)) {
            uint32_t id_masked = s->regs[R_AFMR4] & frame->can_id;
            uint32_t filter_id_masked = s->regs[R_AFMR4] & s->regs[R_AFIR4];
    
            if (filter_id_masked == id_masked) {
                filter_pass = true;
            }
        }
    
        if (!filter_pass) {
            trace_xlnx_can_rx_fifo_filter_reject(frame->can_id, frame->can_dlc);
            return;
        }
    
        /* Store the message in fifo if it passed through any of the filters. */
        if (filter_pass && frame->can_dlc <= MAX_DLC) {
    
            if (fifo32_is_full(&s->rx_fifo)) {
                ARRAY_FIELD_DP32(s->regs, INTERRUPT_STATUS_REGISTER, RXOFLW, 1);
            } else {
                timestamp = CAN_TIMER_MAX - ptimer_get_count(s->can_timer);
    
                fifo32_push(&s->rx_fifo, id_can2xcan(frame->can_id));
    
                fifo32_push(&s->rx_fifo, deposit32(0, R_RXFIFO_DLC_DLC_SHIFT,
                                                R_RXFIFO_DLC_DLC_LENGTH,
                                                frame->can_dlc) |
                                        deposit32(0, R_RXFIFO_DLC_RXT_SHIFT,
                                                R_RXFIFO_DLC_RXT_LENGTH,
                                                timestamp));
    
                /* First 32 bit of the data. */
                fifo32_push(&s->rx_fifo, deposit32(0, R_TXFIFO_DATA1_DB0_SHIFT,
                                                R_TXFIFO_DATA1_DB0_LENGTH,
                                                frame->data[0]) |
                                        deposit32(0, R_TXFIFO_DATA1_DB1_SHIFT,
                                                R_TXFIFO_DATA1_DB1_LENGTH,
                                                frame->data[1]) |
                                        deposit32(0, R_TXFIFO_DATA1_DB2_SHIFT,
                                                R_TXFIFO_DATA1_DB2_LENGTH,
                                                frame->data[2]) |
                                        deposit32(0, R_TXFIFO_DATA1_DB3_SHIFT,
                                                R_TXFIFO_DATA1_DB3_LENGTH,
                                                frame->data[3]));
                /* Last 32 bit of the data. */
                fifo32_push(&s->rx_fifo, deposit32(0, R_TXFIFO_DATA2_DB4_SHIFT,
                                                R_TXFIFO_DATA2_DB4_LENGTH,
                                                frame->data[4]) |
                                        deposit32(0, R_TXFIFO_DATA2_DB5_SHIFT,
                                                R_TXFIFO_DATA2_DB5_LENGTH,
                                                frame->data[5]) |
                                        deposit32(0, R_TXFIFO_DATA2_DB6_SHIFT,
                                                R_TXFIFO_DATA2_DB6_LENGTH,
                                                frame->data[6]) |
                                        deposit32(0, R_TXFIFO_DATA2_DB7_SHIFT,
                                                R_TXFIFO_DATA2_DB7_LENGTH,
                                                frame->data[7]));
    
                ARRAY_FIELD_DP32(s->regs, INTERRUPT_STATUS_REGISTER, RXOK, 1);
                trace_xlnx_can_rx_data(frame->can_id, frame->can_dlc,
                                    frame->data[0], frame->data[1],
                                    frame->data[2], frame->data[3],
                                    frame->data[4], frame->data[5],
                                    frame->data[6], frame->data[7]);
            }
    
            can_update_irq(s);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177
    • 178
    • 179
    • 180
    • 181
    • 182
    • 183
    • 184

    运行qemu

    配置即将在qemu内运行的系统设备树

    can: can@1000c000 {
        compatible = "xlnx,zynq-can-1.0";
        status = "disabled";
        clock-names = "can_clk", "pclk";
        clocks = <&can_clk>, <&pclk>;
        reg = <0x0 0x1000c000 0x0 0x1000>;
        interrupt-parent = <&plic>;
        interrupts = <23>;
        tx-fifo-depth = <0x40>;
        rx-fifo-depth = <0x40>;
      };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    进入系统can初始化成功,然后我们通过终端配置can设备

    /sbin/ip link set can0 type can bitrate 1000000 sample-point 0.750
    /sbin/ifconfig can0 up
    
    • 1
    • 2

    在qemu内系统输入ifconfig,一切正常则会显示

    can0      Link encap:UNSPEC  HWaddr 00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00  
              UP RUNNING NOARP  MTU:16  Metric:1
              RX packets:0 errors:0 dropped:0 overruns:0 frame:0
              TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
              collisions:0 txqueuelen:10 
              RX bytes:0 (0.0 B)  TX bytes:0 (0.0 B)
              Interrupt:41 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    现在让我们使用can-utils工具包进行调试,我们在host和guest上均安装该工具,guest上自己需要交叉编译,host可以用apt安装。

    此时先在host执行

    candump vcan0
    
    • 1

    然后在guest执行

    cansend can0 1807EC0B#1122334455667788
    cansend can0 5A1#11.22.33.44.55.66.77.88
    
    • 1
    • 2

    查看host上输出

      vcan0  1807EC0B  [08]  11 22 33 44 55 66 77 88
      vcan0       5A1  [08]  11 22 33 44 55 66 77 88
    
    • 1
    • 2

    交换host和guest上的命令,可以得到同样的结果,到这里can设备在qemu上的模拟就完成了,此时你可以使用标准的linux网络框架通过can总线在host和guest上交换数据了。当然嵌入式工程师也可以不使用linux kernel的驱动,通过裸机开发驱动发送数据到host上。

  • 相关阅读:
    7、迁移学习
    第137篇 荷兰拍卖
    皕杰报表之隐藏处理
    辅助驾驶功能开发-功能规范篇(23)-2-Mobileye NOP功能规范
    Leetcode算法入门与数组丨1. 数据结构与算法简介
    10亿美元,微软收购AT&T旗下广告业务
    【Spring】bean的创建生命周期
    centos7部署Wordpress
    大数据-之LibrA数据库系统告警处理(ALM-25005 Nscd服务异常)
    springboot基于协同过滤算法的书籍推荐毕业设计源码101555
  • 原文地址:https://blog.csdn.net/weixin_39871788/article/details/126174921