字符设备驱动

一、什么是字符设备

二、设备号

#include <linux/kdev_t.h>
#include <linux/fs.h>

// 主设备号区分驱动,次设备号区分设备实例
// 例:ttySAC0(major=204, minor=0)、ttySAC1(major=204, minor=1)

MAJOR(dev_t dev);
MINOR(dev_t dev);
MKDEV(int major, int minor);

int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name);
int register_chrdev_region(dev_t dev, unsigned count, const char *name);
void unregister_chrdev_region(dev_t dev, unsigned count);

三、核心结构 file_operations

static const struct file_operations my_fops = {
    .owner          = THIS_MODULE,
    .llseek         = my_llseek,
    .read           = my_read,
    .write          = my_write,
    .unlocked_ioctl = my_ioctl,
    .mmap           = my_mmap,
    .open           = my_open,
    .release        = my_release,
    .fsync          = my_fsync,
    .poll           = my_poll,
};

四、初始化与注册

static dev_t devno;
static struct cdev my_cdev;
static struct class *my_class;
static struct device *my_device;

static int __init my_init(void)
{
    int ret;

    // 1. 分配设备号
    ret = alloc_chrdev_region(&devno, 0, 1, "mydev");
    if (ret) return ret;

    // 2. 初始化 cdev
    cdev_init(&my_cdev, &my_fops);
    my_cdev.owner = THIS_MODULE;

    // 3. 添加到内核
    ret = cdev_add(&my_cdev, devno, 1);
    if (ret) goto err_region;

    // 4. 创建 class 和 device(自动创建设备节点)
    my_class = class_create(THIS_MODULE, "mydev");
    if (IS_ERR(my_class)) { ret = PTR_ERR(my_class); goto err_cdev; }

    my_device = device_create(my_class, NULL, devno, NULL, "mydev");
    if (IS_ERR(my_device)) { ret = PTR_ERR(my_device); goto err_class; }

    pr_info("loaded: major=%d minor=%d\n", MAJOR(devno), MINOR(devno));
    return 0;

err_class:
    class_destroy(my_class);
err_cdev:
    cdev_del(&my_cdev);
err_region:
    unregister_chrdev_region(devno, 1);
    return ret;
}

static void __exit my_exit(void)
{
    device_destroy(my_class, devno);
    class_destroy(my_class);
    cdev_del(&my_cdev);
    unregister_chrdev_region(devno, 1);
}

五、关键回调

1. open / release

static int my_open(struct inode *inode, struct file *filp)
{
    // 从 inode 获取 cdev 数据
    // filp->private_data = ...
    return 0;
}

static int my_release(struct inode *inode, struct file *filp)
{
    return 0;
}

2. read / write

static ssize_t my_read(struct file *filp, char __user *buf, size_t count, loff_t *ppos)
{
    // 1. 检查数据是否就绪
    // 2. 从设备或缓冲区读取
    // 3. copy_to_user(buf, kbuf, n)
    return 实际读取字节数;   // 0=EOF, -EFAULT, -EINVAL 等
}

static ssize_t my_write(struct file *filp, const char __user *buf, size_t count, loff_t *ppos)
{
    // 1. copy_from_user(kbuf, buf, n)
    // 2. 写入设备或缓冲区
    return 实际写入字节数;
}

注意:
- 一次 read 不一定读完(特别是管道/网络),但字符设备一般可以一次读满
- 写入缓冲要考虑并发

3. ioctl(推荐 unlocked_ioctl)

static long my_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
    // 必须检查 cmd 合法性、arg 指针
    if (_IOC_TYPE(cmd) != MY_IOC_MAGIC) return -ENOTTY;
    if (_IOC_NR(cmd) > MY_IOC_MAXNR) return -ENOTTY;

    switch (cmd) {
    case MY_GET_VAL:
        return put_user(val, (int __user *)arg);
    case MY_SET_VAL:
        if (get_user(val, (int __user *)arg)) return -EFAULT;
        return 0;
    }
    return -ENOTTY;
}

4. poll

static __poll_t my_poll(struct file *filp, struct poll_table_struct *pt)
{
    __poll_t mask = 0;
    poll_wait(filp, &my_waitq, pt);
    if (data_available)
        mask |= EPOLLIN | EPOLLRDNORM;
    if (space_available)
        mask |= EPOLLOUT | EPOLLWRNORM;
    return mask;
}

// 用户态
struct pollfd pfd = { .fd = fd, .events = POLLIN };
poll(&pfd, 1, -1);

5. mmap

static int my_mmap(struct file *filp, struct vm_area_struct *vma)
{
    // 把设备内存映射到用户态
    return remap_pfn_range(vma, vma->vm_start,
                           vmalloc_to_pfn(buf),
                           vma->vm_end - vma->vm_start,
                           vma->vm_page_prot);
}

6. llseek

static loff_t my_llseek(struct file *filp, loff_t off, int whence)
{
    loff_t newpos;
    switch (whence) {
    case SEEK_SET: newpos = off; break;
    case SEEK_CUR: newpos = filp->f_pos + off; break;
    case SEEK_END: newpos = size + off; break;
    default: return -EINVAL;
    }
    if (newpos < 0) return -EINVAL;
    filp->f_pos = newpos;
    return newpos;
}

六、阻塞与非阻塞

// 等待队列
static DECLARE_WAIT_QUEUE_HEAD(my_waitq);

// 读
static ssize_t my_read(...)
{
    if (wait_event_interruptible(my_waitq, data_available))
        return -ERESTARTSYS;
    // ...
}

// 中断中唤醒
wake_up_interruptible(&my_waitq);

// 非阻塞
if (filp->f_flags & O_NONBLOCK)
    return -EAGAIN;

七、设备私有数据

struct my_dev {
    void __iomem *base;
    int irq;
    wait_queue_head_t waitq;
    struct mutex lock;
    // ...
};

static int my_open(struct inode *inode, struct file *filp)
{
    struct my_dev *dev = container_of(inode->i_cdev, struct my_dev, cdev);
    filp->private_data = dev;
    return 0;
}

八、多个设备实例

alloc_chrdev_region(&devno, 0, NUM_DEVS, "mydev");
for (i = 0; i < NUM_DEVS; i++) {
    cdev_init(&devs[i].cdev, &my_fops);
    cdev_add(&devs[i].cdev, MKDEV(MAJOR(devno), MINOR(devno)+i), 1);
    device_create(cls, NULL, MKDEV(MAJOR(devno), MINOR(devno)+i), NULL, "mydev%d", i);
}

九、devtmpfs / mdev

十、测试命令

cat /proc/devices | grep mydev
ls -l /dev/mydev
cat /dev/mydev
echo "test" > /dev/mydev
strace -e read,write,open,ioctl cat /dev/mydev