Add FPGA character device driver for linux kernel

The driver in Linux is located between the operating system and the hardware. It is the link between the operating system and the hardware. Today we will add a simple FPGA character driver to Linux.

I. Introduction to Linux Character Driver

Drivers in Linux are divided into character drivers, block device drivers, and network device drivers. The character device driver is one of the simpler ones. A character device refers to a device that can only read and write one byte by one byte, and cannot read a certain data in the device randomly. The data must be read in order.

II. Steps to add character driver

Insert picture description here
Insert picture description here
For more C/C++Linux video materials, please add qun: 832218493 or vx to follow the Zero Sound Academy for free!

1. Get the character device number.
In linux, we can view various devices and their corresponding device numbers through the cat /proc/devices command. In the kernel, we usually use the following two functions to get the device number

int register_chrdev_region(dev_t first, unsigned int count, char *name);
/* 参数:
    dev_t first - 要申请的设备号(起始)
    unsigned int count - 要申请的设备号数量
    const char *name - 设备名
   返回值:
    成功:0
    失败:负数
   需要事先知道哪个设备号没有被占用!!!
*/
int alloc_chrdev_region(dev_t *dev,unsigned int firstminor,unsigned int count,char *name);
/* 参数:
    dev_t *dev - 用于保存分配到的第一个设备号(起始)
    unsigned int firstminor - 起始次设备号
    unsigned int count - 要分配设备号的数量
    char *name - 设备名
   返回值:
    成功:0
    失败:负数(绝对值是错误码)
   不需要事先知道哪个设备号被占用,系统会根据实际情况自行分配
*/

2. Register file operations with the cdev structure.
In the Linux kernel code, the description of character devices is usually done by struct cdev. One of the members is a pointer to file_operations. We want to operate the hardware like ordinary files in the future. Device, you need to complete the .open, .read, .write, .release, .llseek functions in file_operations, and map them to specific hardware device operations, as shown in the following code

static const struct file_operations fpga_fops = {
    
    
    .read        = fpga_read,
    .write        = fpga_write,
    .llseek = fpga_llseek,
    .open        = fpga_open,
    .release    = fpga_release,
};

After defining the file operation, you need to register the file operation to the cdev structure and then you need to register the device driver with the kernel. The code is as follows:

III. FPGA address mapping and read and write communication

In the character driver I wrote, I used the ioremap() function to implement address mapping. Here is what you need to pay attention to: You must determine the actual address according to the FPGA enable pin and address pin of your core board. Blind copy; copy_to_user() and copy_from_user() functions to implement FPGA read and write operations, see the code for details:

#include <linux/init.h>
#include <linux/fs.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/cdev.h>
#include <linux/errno.h>
#include <linux/device.h>

#include <asm/io.h>
#include <asm/uaccess.h>


#define FPGA_BASE_ADDR 0xF0000000
#define FPGA_SIZE 0x4000000
#define DEV_NAME "fpga"

struct cdev fpga_dev;
dev_t fpga_no;
struct class *fpga_class;


static char *fpga_addr = NULL;

static int fpga_open(struct inode *ino, struct file *filp);
static int fpga_release(struct inode *ino, struct file *filp);
static loff_t fpga_llseek(struct file *, loff_t, int);
static ssize_t fpga_read(struct file *, char __user *, size_t, loff_t *);
static ssize_t fpga_write(struct file *, const char __user *, size_t, loff_t *);

static const struct file_operations fpga_fops = {
    
    
    .read        = fpga_read,
    .write        = fpga_write,
    .llseek = fpga_llseek,
    .open        = fpga_open,
    .release    = fpga_release,
};


static int fpga_open(struct inode *ino, struct file *filp)
{
    
    
    return 0;
}
static int fpga_release(struct inode *ino, struct file *filp)
{
    
    
    return 0;
}
static loff_t fpga_llseek(struct file *file, loff_t off, int whence)
{
    
    
    if(fpga_addr)
        iounmap(fpga_addr);
    if(off <= FPGA_SIZE)
        fpga_addr=ioremap(FPGA_BASE_ADDR+off,FPGA_SIZE-off);
    return off;
}
static ssize_t fpga_read(struct file *file, char __user *buf, size_t count,
            loff_t *ppos)
{
    
    
    printk("user data: %x", buf[0]);
    printk("kern data: %x", fpga_addr[0]);
    if(copy_to_user(buf, fpga_addr, count))
        return -EFAULT;
    
    return count;
}

static ssize_t fpga_write(struct file *file, const char __user *buf,
                        size_t count, loff_t *ppos)
{
    
    
    printk("user data: %x", buf[0]);
    printk("kern data: %x", fpga_addr[0]);
    if(copy_from_user(fpga_addr, buf, count))
        return -EFAULT;
    printk("kern data: %x", fpga_addr[0]);
    return count;
}
static int __init fpga_init(void)
{
    
    
    int ret;
    ret = alloc_chrdev_region(&fpga_no, 0, 1, "fpga"); /* device num you get, from where to allocate, num of this kind of device, device name */
    if(ret)
    {
    
    
        printk("alloc_chrdev_region failed!\n");
        unregister_chrdev_region(fpga_no, 1); /* first device number, num of this kind of device */
        return ret;
    }
    else
    {
    
    
        printk("alloc_chrdev_region success!\n");
    }

    cdev_init(&fpga_dev, &fpga_fops); /* initialize your device settings */

    ret = cdev_add(&fpga_dev, fpga_no, 1); /* register your device to the kernel */
    if(ret)
    {
    
    
        printk("fpga dev add fail.\n");
        unregister_chrdev_region(fpga_no, 1);
        return ret;
    }
    else
    {
    
    
        printk("fpga dev add success!\n");
    }
    /* ==================add module to /dev/fpga=================== */
    fpga_class = class_create(THIS_MODULE, "fpga");
    device_create(fpga_class, NULL, fpga_no, NULL, "fpga");
    /* ==================address mapping ===================== */
    fpga_addr = ioremap(FPGA_BASE_ADDR, FPGA_SIZE);
    if(fpga_addr)
    {
    
    
        printk("ioremap success~\n");
    }
    else
    {
    
    
        unregister_chrdev_region(fpga_no, 1);
        return -ENODEV;
    }
    return 0;
}
static void __exit fpga_exit(void)
{
    
    
    if(fpga_addr)
        iounmap(fpga_addr);
    cdev_del(&fpga_dev);
    unregister_chrdev_region(fpga_no, 1);
    printk("fpga dev exit success!\n");
}
module_init(fpga_init);

Compile the above code into the kernel, you can use cat /proc/devices to see the fpga device and its device number, and you can see the fpga in the /dev/ directory~~~

Guess you like

Origin blog.csdn.net/lingshengxueyuan/article/details/108552547