[ 驱动移植 ] [ 3 ] Linux设备驱动

注册字符设备

主要解决内核驱动和外部设备匹配问题。

字符设备基本操作

总体架构图:

应用程序 -> 虚拟文件系统 -> 字符设备文件 -> 字符设备驱动 -> 字符设备

上面是字符设备(比如我们最常见的点灯、按键、IIC、SPI,LCD 等等都是字符设备)与 linux 交互的整体流程。

字符设备 read / write

mychar.c

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/types.h>
#include <linux/uaccess.h>

// 定义3个全局变量,分别用来表示:
// 主设备号、次设备号、设备数
int major = 11;
int minor = 0;
int mychar_num = 1;

// 定义一个 struct cdev 的全局变量
// 来表示本设备
struct cdev mydev;

char* kernelbuff[1024];

int mychar_open(struct inode *pnode, struct file *pfile) {
    printk("mychar_open is called\n");
    return 0;
}

int mychar_close(struct inode *pnode, struct file *pfile) {
    printk("mychar_close is called\n");
    return 0;
}

ssize_t mychar_read(struct file *filp, char __user *pbuf, size_t count, loff_t *ppos) {
    printk("mychar_read called\n");
    if (*ppos > 1024 || count > 1024) {
        return 0;
    }
    if ((*ppos + count) > 1024) {
        count = 1024 - *ppos;
    }
    copy_to_user(pbuf, kernelbuff + *ppos, 1024);
    *ppos += count;
    return count;
}

ssize_t mychar_write(struct file *filp, const char __user *pbuf, size_t count, loff_t *ppos) {
    printk("mychar_write called\n");
    if (((*ppos) > sizeof(kernelbuff)) || (count > sizeof(kernelbuff))) {
        printk("Error: Allocating more than kernel buffer size");
        return -EFBIG;
    }
    if ((*ppos + count) > sizeof(kernelbuff)) {
        printk("Error: Allocating more than kernel buffer size");
        return -EFBIG;
    }
    copy_from_user(kernelbuff + *ppos, pbuf, 50);
    *ppos += count;
    return 1024;
}

// 定义一个 struct file_operations 结构体变量,
// 其 owner 成员设置成 THIS_MODULE
struct file_operations myops = {
    .owner = THIS_MODULE,
    .open = mychar_open,
    .release = mychar_close,
    .read = mychar_read,
    .write = mychar_write,
};

int __init mychar_init(void) {
    int ret = 0;
    dev_t devno = MKDEV(major, minor);

    // 申请设备号
    ret = register_chrdev_region(devno, mychar_num, "mychar");
    if (ret) {
        ret = alloc_chrdev_region(&devno, minor, mychar_num, "mychar");
        if (ret) {
            printk("get devno failed\n");
            return -1;
        }
        major = MAJOR(devno);
    }

    // 给struct cdev对象指定操作函数集
    cdev_init(&mydev, &myops);

    // 将struct cdev对象添加到内核对应的数据结构中
    mydev.owner = THIS_MODULE;
    cdev_add(&mydev, devno, mychar_num);
    return 0;
}

void __exit mychar_exit(void) {
    dev_t devno = MKDEV(major, minor);
    // 从内核中移除 struct cdev
    cdev_del(&mydev);
    // 注销设备号
    unregister_chrdev_region(devno, mychar_num);
}

MODULE_LICENSE("GPL");
module_init(mychar_init);
module_exit(mychar_exit);

testmychar_app.c

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <fcntl.h>

int main() {
    int fd;
    char buff[500];

    fd = open("/dev/chardev", O_RDWR);
    write(fd, "Hello World", 13);

    read(fd, buff, 500);
    printf("Reading data from kernel: \t");
    puts(buff);

    return 0;
}

执行报如下错误:

Reading data from kernel:       eading data from kernel:

*** stack smashing detected ***: <unknown> terminated

已放弃 (核心已转储)

错误原因:copy_to_user(pbuf, kernelbuff + *ppos, 1024)

ppos:对于需要位置提示器控制的设备操作有用,用来指示读取的起始位置,读完后也需要变更位置提示器的提示位置。在该例中用不到。

报错原因是因为用户空间只分配 500 字节空间,而 copy_to_user 需要 1024 字节空间,所以会报错。


字符设备 ioctl

功能:对相应设备做指定控制操作(各种属性的设置获取等等)

IOCTL is referred to as Input and Output Control, which is used to talk to device drivers. This system call is available in most driver categories. The major use of this is in case of handling some specific operations of a device for which the kernel does not have a system call by default.

long mychar_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) {
    if (_IOC_TYPE(cmd) != TEST_IOC) {
        return -1;
    }
    printk("CODE_NR: %d\n", _IOC_NR(cmd));

    switch(cmd) {
        case TEST_1:
            printk("use ioctl successful\n");  // 不带参数
            break;
        case TEST_2:
            printk("----CMD: %d----arg: %d\n", (int)cmd, (int)arg);     // 带参数
            break;
        default:
            break;
    }

    return 0;
}


    // Test1, 不带参数
    ret = ioctl(fd, TEST_1);
    if (ret == -1) {
        perror("ioctl error");
        return -2;
    }

    // Test2, 带额外参数
    int ioctl_arg = 16;
    ret = ioctl(fd, TEST_2, ioctl_arg);
    if (ret == -1) {
        perror("ioctl error");
        return -2;
    }

image.png