linuxsocketcan

Is it possible to have two tx queues in Linux socketcan with two different priorities?


I am playing around with can on Linux and I can send and receive frames using cansend: https://github.com/linux-can/can-utils/blob/master/cansend.c

I was wondering if there is a mechanism to prioritize certain Messages on TX using socketcan?

For Example in CanOpen it is common practice to have two tx message buffers one high priority for emergency and sync and one for all the other traffic. Most Can Controllers can configure multiple tx message buffers which work like a ringbuffer.

The goal is that when the low priority queue is filled up and you implement a sync producer the sync frame gets on the bus as fast as possible. Meaning when there are 20 frames in the non priority queue and a packet is written to the priority queue the controller finishes transmitting the current loaded frame and continues sending the prioritized queue until its empty and then continues pulling packets from the non prioritized queue.


Solution

  • Thanks to @Hasturkun

    AFAICT, CAN messages would be prioritized based on their message ID (and that seems to be up to the specific driver/hw by default). I did find a couple of relevant links about setting up a queue discipline, though, rtime.felk.cvut.cz/can/socketcan-qdisc-final.pdf and forum.opencyphal.org/t/queue-disciplines-and-linux-socketcan/… . The default qdisc pfifo_fast supports 3 priority bands (which you can apparently get at with SO_PRIORITY on a per socket basis for a raw socket).

    This is exactly the mechanism to do this.

    The only confusion i had was that: Some where was mentioned the priority is 0 to 7. 0 means best and 7 means lowest. This is not the priority meant to put in SO_PRIORITY. There is a Priority map:

    $ tc qdisc show dev can1
    qdisc pfifo_fast 0: root refcnt 2 bands 3 priomap 1 2 2 2 1 2 0 0 1 1 1 1 1 1 1 1
    

    So according to this map it is read like this:

    SO_PRIO | qdisc PRIO
    0       | 1
    1       | 2
    2       | 2
    3       | 2
    4       | 1
    5       | 2
    6       | 0
    7       | 0
    8       | 1
    ..      | 1
    13      | 1
    

    So this is how i configured my can Interface:

    sudo ip link set can1 type can bitrate 125000
    sudo ip link set up can1
    

    Here is an example code that worked to illustrate that the high_fd is prioretised over the low_fd:

    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <unistd.h>
    
    #include <net/if.h>
    #include <sys/ioctl.h>
    #include <sys/socket.h>
    
    #include <linux/can.h>
    #include <linux/can/raw.h>
    
    static const unsigned char dlc2len[] = {0, 1, 2, 3, 4, 5, 6, 7,
                        8, 12, 16, 20, 24, 32, 48, 64};
    
    /* get data length from raw data length code (DLC) */
    unsigned char can_fd_dlc2len(unsigned char dlc)
    {
        return dlc2len[dlc & 0x0F];
    }
    
    static const unsigned char len2dlc[] = {0, 1, 2, 3, 4, 5, 6, 7, 8,      /* 0 - 8 */
                        9, 9, 9, 9,             /* 9 - 12 */
                        10, 10, 10, 10,             /* 13 - 16 */
                        11, 11, 11, 11,             /* 17 - 20 */
                        12, 12, 12, 12,             /* 21 - 24 */
                        13, 13, 13, 13, 13, 13, 13, 13,     /* 25 - 32 */
                        14, 14, 14, 14, 14, 14, 14, 14,     /* 33 - 40 */
                        14, 14, 14, 14, 14, 14, 14, 14,     /* 41 - 48 */
                        15, 15, 15, 15, 15, 15, 15, 15,     /* 49 - 56 */
                        15, 15, 15, 15, 15, 15, 15, 15};    /* 57 - 64 */
    
    /* map the sanitized data length to an appropriate data length code */
    unsigned char can_fd_len2dlc(unsigned char len)
    {
        if (len > 64)
            return 0xF;
    
        return len2dlc[len];
    }
    
    int main(int argc, char **argv)
    {
        int mtuSize = CAN_MTU;
        int low_fd, high_fd;
        struct sockaddr_can low_addr, high_addr;
        struct ifreq ifr = {
            .ifr_name = "can1",
            .ifr_ifindex = if_nametoindex(ifr.ifr_name)
        };
        struct can_frame frame = {
            .can_id = 0x80,
            .len = 4,
            .len8_dlc = 4,
            .data = {0xba, 0xd0, 0x00, 0x00}
        };
    
        if (!ifr.ifr_ifindex) {
            perror("wrong ifr_ifindex");
            return 1;
        }
    
        /* open socket */
        if ((low_fd = socket(PF_CAN, SOCK_RAW, CAN_RAW)) < 0) {
            perror("can't create low_fd");
            return 1;
        }
        /* open socket high prio */
        if ((high_fd = socket(PF_CAN, SOCK_RAW, CAN_RAW)) < 0) {
            perror("can't create high_fd");
            return 1;
        }
    
        memset(&low_addr, 0, sizeof(low_addr));
        low_addr.can_family = AF_CAN;
        low_addr.can_ifindex = ifr.ifr_ifindex;
    
        memset(&high_addr, 0, sizeof(high_addr));
        high_addr.can_family = AF_CAN;
        high_addr.can_ifindex = ifr.ifr_ifindex;
    
        frame.len = can_fd_dlc2len(can_fd_len2dlc(frame.len));
    
        /* disable default receive filter on this RAW socket */
        /* This is obsolete as we do not read from the socket at all, but for */
        /* this reason we can remove the receive list in the Kernel to save a */
        /* little (really a very little!) CPU usage.                          */
        setsockopt(low_fd, SOL_CAN_RAW, CAN_RAW_FILTER, NULL, 0);
        if (bind(low_fd, (struct sockaddr *)&low_addr, sizeof(low_addr)) < 0) {
            perror("error binding low_fd");
            return 1;
        }
    
        setsockopt(high_fd, SOL_CAN_RAW, CAN_RAW_FILTER, NULL, 0);
        if (bind(high_fd, (struct sockaddr *)&high_addr, sizeof(high_addr)) < 0) {
            perror("error binding high_fd");
            return 1;
        }
    
        /* set priorities */
        int low_prio = 2;
        setsockopt(low_fd, SOL_SOCKET, SO_PRIORITY, &low_prio, sizeof(low_prio));
    
        int high_prio = 0;
        setsockopt(high_fd, SOL_SOCKET, SO_PRIORITY, &high_prio, sizeof(high_prio));
    
        for(int i=0;i<24;i++) {
            /* send frame */
            //printf("Packet: %d\r\n", i);
            if (write(low_fd, &frame, sizeof(frame)) != CAN_MTU) {
                perror("low write error packet");
                printf("Error on packet: %d\r\n", i);
                return -2;
            } else {
                frame.data[2]++;
            }
            usleep(10);
        }
        frame.can_id = 0x33;
        frame.data[3] = 0x33;
        if (write(high_fd, &frame, sizeof(frame)) != CAN_MTU) {
            perror("high write error");
        }
    
        close(low_fd);
        close(high_fd);
    
        return 0;
    }
    

    This is the output on the Bus when prio_low is 2 => 2 and prio_high is 0 => 1

    CAN6: 18137.742 <- sff 080 (4)  ba d0 00 00
    CAN6:    1.717 <- sff 080 (4)  ba d0 01 00
    CAN6:    1.720 <- sff 080 (4)  ba d0 02 00
    CAN6:    1.739 <- sff 080 (4)  ba d0 03 00
    CAN6:    1.718 <- sff 080 (4)  ba d0 04 00
    CAN6:    1.720 <- sff 080 (4)  ba d0 05 00
    CAN6:    1.738 <- sff 080 (4)  ba d0 06 00
    CAN6:    1.720 <- sff 080 (4)  ba d0 07 00
    CAN6:    1.741 <- sff 080 (4)  ba d0 08 00
    CAN6:    1.701 <- sff 080 (4)  ba d0 09 00
    CAN6:    1.720 <- sff 080 (4)  ba d0 0a 00
    CAN6:    1.698 <- sff 080 (4)  ba d0 0b 00
    CAN6:    1.739 <- sff 080 (4)  ba d0 0c 00
    CAN6:    1.699 <- sff 080 (4)  ba d0 0d 00
    CAN6:    1.718 <- sff 080 (4)  ba d0 0e 00
    CAN6:    1.678 <- sff 033 (4)  ba d0 18 33 <-- Prioritised
    CAN6:    1.699 <- sff 080 (4)  ba d0 0f 00
    CAN6:    1.739 <- sff 080 (4)  ba d0 10 00
    CAN6:    1.699 <- sff 080 (4)  ba d0 11 00
    CAN6:    1.739 <- sff 080 (4)  ba d0 12 00
    CAN6:    1.720 <- sff 080 (4)  ba d0 13 00
    CAN6:    1.739 <- sff 080 (4)  ba d0 14 00
    CAN6:    1.718 <- sff 080 (4)  ba d0 15 00
    CAN6:    1.718 <- sff 080 (4)  ba d0 16 00
    CAN6:    1.699 <- sff 080 (4)  ba d0 17 00
    

    This is the output if prio low is 7 => 0 and prio high is 0 => 1:

    CAN6: 1027753.429 <- sff 080 (4)  ba d0 00 00
    CAN6:    1.718 <- sff 080 (4)  ba d0 01 00
    CAN6:    1.720 <- sff 080 (4)  ba d0 02 00
    CAN6:    1.741 <- sff 080 (4)  ba d0 03 00
    CAN6:    1.720 <- sff 080 (4)  ba d0 04 00
    CAN6:    1.722 <- sff 080 (4)  ba d0 05 00
    CAN6:    1.742 <- sff 080 (4)  ba d0 06 00
    CAN6:    1.722 <- sff 080 (4)  ba d0 07 00
    CAN6:    1.742 <- sff 080 (4)  ba d0 08 00
    CAN6:    1.701 <- sff 080 (4)  ba d0 09 00
    CAN6:    1.720 <- sff 080 (4)  ba d0 0a 00
    CAN6:    1.701 <- sff 080 (4)  ba d0 0b 00
    CAN6:    1.741 <- sff 080 (4)  ba d0 0c 00
    CAN6:    1.701 <- sff 080 (4)  ba d0 0d 00
    CAN6:    1.723 <- sff 080 (4)  ba d0 0e 00
    CAN6:    1.699 <- sff 080 (4)  ba d0 0f 00
    CAN6:    1.739 <- sff 080 (4)  ba d0 10 00
    CAN6:    1.698 <- sff 080 (4)  ba d0 11 00
    CAN6:    1.739 <- sff 080 (4)  ba d0 12 00
    CAN6:    1.720 <- sff 080 (4)  ba d0 13 00
    CAN6:    1.742 <- sff 080 (4)  ba d0 14 00
    CAN6:    1.720 <- sff 080 (4)  ba d0 15 00
    CAN6:    1.722 <- sff 080 (4)  ba d0 16 00
    CAN6:    1.699 <- sff 080 (4)  ba d0 17 00
    CAN6:   47.819 <- sff 033 (4)  ba d0 18 33 <-- Not Prioritised