How to write a simple Linux device driver?

前端 未结 4 1909
温柔的废话
温柔的废话 2020-12-22 15:24

I need to write an SPI Linux character device driver for omap4 from scratch. I know some basics of writing device drivers. But, I don\'t know how to start writing platform s

4条回答
  •  一个人的身影
    2020-12-22 16:27

    file_operations minimal runnable example

    This example does not interact with any hardware, but it illustrates the simpler file_operations kernel API with debugfs.

    Kernel module fops.c:

    #include  /* copy_from_user, copy_to_user */
    #include 
    #include  /* EFAULT */
    #include  /* file_operations */
    #include  /* min */
    #include 
    #include  /* printk */
    #include  /* S_IRUSR */
    
    static struct dentry *debugfs_file;
    static char data[] = {'a', 'b', 'c', 'd'};
    
    static int open(struct inode *inode, struct file *filp)
    {
        pr_info("open\n");
        return 0;
    }
    
    /* @param[in,out] off: gives the initial position into the buffer.
     *      We must increment this by the ammount of bytes read.
     *      Then when userland reads the same file descriptor again,
     *      we start from that point instead.
     * */
    static ssize_t read(struct file *filp, char __user *buf, size_t len, loff_t *off)
    {
        ssize_t ret;
    
        pr_info("read\n");
        pr_info("len = %zu\n", len);
        pr_info("off = %lld\n", (long long)*off);
        if (sizeof(data) <= *off) {
            ret = 0;
        } else {
            ret = min(len, sizeof(data) - (size_t)*off);
            if (copy_to_user(buf, data + *off, ret)) {
                ret = -EFAULT;
            } else {
                *off += ret;
            }
        }
        pr_info("buf = %.*s\n", (int)len, buf);
        pr_info("ret = %lld\n", (long long)ret);
        return ret;
    }
    
    /* Similar to read, but with one notable difference:
     * we must return ENOSPC if the user tries to write more
     * than the size of our buffer. Otherwise, Bash > just
     * keeps trying to write to it infinitely. */
    static ssize_t write(struct file *filp, const char __user *buf, size_t len, loff_t *off)
    {
        ssize_t ret;
    
        pr_info("write\n");
        pr_info("len = %zu\n", len);
        pr_info("off = %lld\n", (long long)*off);
        if (sizeof(data) <= *off) {
            ret = 0;
        } else {
            if (sizeof(data) - (size_t)*off < len) {
                ret = -ENOSPC;
            } else {
                if (copy_from_user(data + *off, buf, len)) {
                    ret = -EFAULT;
                } else {
                    ret = len;
                    pr_info("buf = %.*s\n", (int)len, data + *off);
                    *off += ret;
                }
            }
        }
        pr_info("ret = %lld\n", (long long)ret);
        return ret;
    }
    
    /*
    Called on the last close:
    http://stackoverflow.com/questions/11393674/why-is-the-close-function-is-called-release-in-struct-file-operations-in-the-l
    */
    static int release(struct inode *inode, struct file *filp)
    {
        pr_info("release\n");
        return 0;
    }
    
    static loff_t llseek(struct file *filp, loff_t off, int whence)
    {
        loff_t newpos;
    
        pr_info("llseek\n");
        pr_info("off = %lld\n", (long long)off);
        pr_info("whence = %lld\n", (long long)whence);
        switch(whence) {
            case SEEK_SET:
                newpos = off;
                break;
            case SEEK_CUR:
                newpos = filp->f_pos + off;
                break;
            case SEEK_END:
                newpos = sizeof(data) + off;
                break;
            default:
                return -EINVAL;
        }
        if (newpos < 0) return -EINVAL;
        filp->f_pos = newpos;
        pr_info("newpos = %lld\n", (long long)newpos);
        return newpos;
    }
    
    static const struct file_operations fops = {
        /* Prevents rmmod while fops are running.
         * Try removing this for poll, which waits a lot. */
        .owner = THIS_MODULE,
        .llseek = llseek,
        .open = open,
        .read = read,
        .release = release,
        .write = write,
    };
    
    static int myinit(void)
    {
        debugfs_file = debugfs_create_file("lkmc_fops", S_IRUSR | S_IWUSR, NULL, NULL, &fops);
        return 0;
    }
    
    static void myexit(void)
    {
        debugfs_remove_recursive(debugfs_file);
    }
    
    module_init(myinit)
    module_exit(myexit)
    MODULE_LICENSE("GPL");
    

    Userland shell test program:

    #!/bin/sh
    
    mount -t debugfs none /sys/kernel/debug
    
    insmod /fops.ko
    cd /sys/kernel/debug/lkmc_fops
    
    ## Basic read.
    cat f
    # => abcd
    # dmesg => open
    # dmesg => read
    # dmesg => len = [0-9]+
    # dmesg => close
    
    ## Basic write
    
    printf '01' >f
    # dmesg => open
    # dmesg => write
    # dmesg => len = 1
    # dmesg => buf = a
    # dmesg => close
    
    cat f
    # => 01cd
    # dmesg => open
    # dmesg => read
    # dmesg => len = [0-9]+
    # dmesg => close
    
    ## ENOSPC
    printf '1234' >f
    printf '12345' >f
    echo "$?"
    # => 8
    cat f
    # => 1234
    
    ## seek
    printf '1234' >f
    printf 'z' | dd bs=1 of=f seek=2
    cat f
    # => 12z4
    

    You should also write a C program that runs those tests if it is not clear to you what system calls are being called for each of those commands. (or you could also use strace and find out :-)).

    The other file_operations are a bit more involved, here are some further examples:

    • ioctl
    • poll
    • mmap

    Start with software models of simplified hardware in emulators

    Actual device hardware development is "hard" because:

    • you can't always get your hand on a given hardware easily
    • hardware APIs may be complicated
    • it is hard to see what is the internal state of the hardware

    Emulators like QEMU allow us to overcome all those difficulties, by simulating simplified hardware simulation in software.

    QEMU for example, has a built-in educational PCI device called edu, which I explained further at: How to add a new device in QEMU source code? and is a good way to get started with device drivers. I've made a simple driver for it available here.

    You can then put printf's or use GDB on QEMU just as for any other program, and see exactly what is going on.

    There is also an OPAM SPI model for you specific use case: https://github.com/qemu/qemu/blob/v2.7.0/hw/ssi/omap_spi.c

提交回复
热议问题