Search code examples
clinuxkernel-module

Linux Kernel Module Using SOCK_RAW for CAN Interface Not Receiving Data


I'm trying to develop a Linux kernel module that is intended to read data from a CAN (Controller Area Network) interface using a raw socket (SOCK_RAW). Beware this is my first time working with a kernel module, so i guess something obvious is wrong, but i just can't figure out what it is. As a starting point for this i looked at the implementation of candump, and was able to break the code down a little to have a working copy of in which runs in user-space.

The module successfully creates the socket, binds to the CAN interface, and enters a loop to read data. However, the sock_recvmsg function appears to block indefinitely and never returns, even though I've confirmed that data is being sent to the CAN interface (can1) by running the candump can1 command in user space.

Here's an overview of the relevant portions of my kernel module code:

#include <linux/init.h>
#include <linux/module.h>
MODULE_LICENSE("GPL");
#include <linux/kernel.h>
#include <linux/kthread.h>
#include <linux/sched.h>
#include <uapi/linux/sched/types.h>
#include <linux/delay.h>

#include <linux/net.h>
#include <linux/can.h>
#include <linux/ioctl.h>
#include <linux/can/dev.h>
#include <linux/can/raw.h>
#include <linux/can/core.h>
#include <linux/netdevice.h>

struct task_struct *task;
#define THREAD_NAME "canopener_thread"

int canopener_thread(void *data)
{
    struct task_struct *TSK;
    struct net_device *net_dev;
    struct sockaddr_can addr;
    int err;
    bool found_can;
    int canfd_on = 1;
    char* canif;
    struct socket* can_sock;
    sockptr_t sock_ptr;
    /* Code below returns 88, user-space test code with struct timeval and struct timespec actually returns 72.
    char ctrlmsg[CMSG_SPACE(sizeof(struct old_timeval32)) +
    CMSG_SPACE(3 * sizeof(struct old_timespec32)) +
    CMSG_SPACE(sizeof(__u32))];*/
    char ctrlmsg[72];
    struct iovec iov;
    struct msghdr msg;
    struct canfd_frame frame;
    int nbytes;

    can_sock = NULL;
    found_can = false;
    canif = "can1";
    TSK = current;
    sched_set_fifo(TSK);
    allow_signal(SIGKILL);

    printk(KERN_INFO "Searching CAN interface: %s\n", canif);
    memset(&addr, 0, sizeof(addr));
    addr.can_family = AF_CAN;
    for_each_netdev(&init_net, net_dev) {
        if (strcmp(net_dev->name,canif) == 0) {
        printk(KERN_INFO "Found CAN interface: %s (ifindex: %d)\n",
                net_dev->name, net_dev->ifindex);
        addr.can_ifindex = net_dev->ifindex;
        found_can = true;
        break;
        }
    }
    if(!found_can){
        printk(KERN_ERR "Failed to find CAN device!\n");
        return err;
    }

    printk(KERN_INFO "sock_create!\n");
    err = sock_create(PF_CAN, SOCK_RAW, CAN_RAW, &can_sock);
    printk(KERN_INFO "sock_create done!\n");
    if (err < 0) {
        printk(KERN_ERR "Failed to create CAN socket: %d\n", err);
        return err;
    }

    sock_ptr.kernel = &canfd_on;
    sock_ptr.is_kernel = 1;
    sock_setsockopt(can_sock, SOL_CAN_RAW, CAN_RAW_FD_FRAMES, sock_ptr, sizeof(sockptr_t));

    can_sock->ops->bind(can_sock, (struct sockaddr *)&addr, sizeof(addr));
    if (err < 0) {
        printk(KERN_ERR "Failed to bind CAN socket: %d\n", err);
        return err;
    }


    iov.iov_base = &frame;
    iov.iov_len = sizeof(frame);
    msg.msg_name = &addr;
    msg.msg_control = ctrlmsg;

    while(!kthread_should_stop()) {
        iov_iter_init(&msg.msg_iter, READ, &iov, 1, sizeof(frame));
        msg.msg_namelen = sizeof(addr);
        msg.msg_controllen = sizeof(ctrlmsg);
        msg.msg_flags = 0;

        printk(KERN_INFO "Reading...\n");
        nbytes = sock_recvmsg(can_sock, &msg, 0);
        printk(KERN_INFO "Done reading %d...\n", nbytes);
    }

    sock_release(can_sock);

    printk("canopener_thread exiting\n");
    return 0;
}


static int __init modcanopener_init(void)
{
    printk(KERN_INFO "canopener thread: starting...\n");
    task = kthread_run(canopener_thread, NULL, THREAD_NAME);

    printk(KERN_INFO "canopener thread: starting done.\n");
    return 0;
}

static void __exit modcanopener_exit(void)
{
    printk(KERN_INFO "canopener thread: stopping...\n");
    kthread_stop(task);
    printk(KERN_INFO "canopener thread: stopping done.\n");
}

module_init(modcanopener_init);
module_exit(modcanopener_exit);

I've carefully checked error handling, and there are no apparent issues with socket creation, binding, or thread scheduling. However, the sock_recvmsg function never seems to return.

The Kernel version i'm using is 6.1.21-v8+, and everything is running on a raspberry pi 4.

I tried loading the module using "sudo insmod modcanopener_thread.ko". The output from dmesg --follow is as follows

    [  448.319191] canopener thread: starting...
    [  448.319538] canopener thread: starting done.
    [  448.319606] Searching CAN interface: can1
    [  448.319627] Found CAN interface: can1 (ifindex: 5)
    [  448.319641] sock_create!
    [  448.319695] sock_create done!
    [  448.319728] Reading...

I also checked the ifindex of 5 is correct. I'm unsure whether the iov_iter_init is correct, but i tried modifying it in several ways to ensure that at least anything would be read, but failed to do so.

Any suggestions or insights into what might be causing the sock_recvmsg function to block indefinitely would be greatly appreciated. Thank you!


Solution

  • So, with some trial and error i was able to change my code in a way that works. I'm unsure why it suddenly works, but i will still provide the code for everybody who might run into this. Feel free to comment any improvements that might still be made. First the working code:

    struct kvec v;
    err = sock_create(PF_CAN, SOCK_RAW, CAN_RAW, &can_sock);
    printk(KERN_INFO "sock_create done!\n");
    if (err < 0) {
        printk(KERN_ERR "Failed to create CAN socket: %d\n", err);
        return err;
    }
    
    err = can_sock->ops->setsockopt(can_sock, SOL_CAN_RAW, CAN_RAW_FD_FRAMES, KERNEL_SOCKPTR(&canfd_on), sizeof(int));
    if (err < 0) {
        printk(KERN_ERR "Failed to sock_setsockopt CAN_RAW_FD_FRAMES: %d\n", err);
        return err;
    }
    
    err = can_sock->ops->bind(can_sock, (struct sockaddr *)&addr, sizeof(addr));
    if (err < 0) {
        printk(KERN_ERR "Failed to bind CAN socket: %d\n", err);
        return err;
    }
    
    
    v.iov_base = &frame;
    v.iov_len = sizeof(struct canfd_frame);
    
    msg.msg_name = &addr;
    msg.msg_control = ctrlmsg;
    
    while(!kthread_should_stop()) {
        msg.msg_namelen = sizeof(addr);
        msg.msg_controllen = sizeof(ctrlmsg);
        
        printk(KERN_INFO "Reading...\n");        
        nbytes = kernel_recvmsg(can_sock,&msg, &v, 1, sizeof(struct canfd_frame), 0);
        printk(KERN_INFO "Done reading %d...\n", nbytes);
    }
    

    Steps i did:

    • change sock_setsockopt to can_sock->ops->setsockopt
    • instead of using sizeof(sockptr_t) for the setsockopt i used the size of the option, which in this case was an int. Also i created the sockptr using the define KERNEL_SOCKPTR.
    • After the setsockopt change, i got a -EFAULT error from recvmsg. To fix this, i replaced the iovec with a kvec, and suddenly everything started working.

    Hope this might help someone some day. If anyone knows why the iovec did not work, i'd be interested to know.