Linux字符设备驱动(一) - 框架

字符设备是Linux三大设备之一(另外两种是块设备,网络设备),字符设备就是字节流形式通讯的I/O设备,绝大部分设备都是字符设备,常见的字符设备包括鼠标、键盘、显示器、串口等等,当我们执行ls -l /dev的时候,就能看到大量的设备文件,c就是字符设备,b就是块设备,网络设备没有对应的设备文件。编写一个外部模块的字符设备驱动,除了要实现编写一个模块所需要的代码之外,还需要编写作为一个字符设备的代码。

一,关键数据结构

1,cdev

cdev可以理解为char device,用来抽象一个字符设备。

//kernel\include\linux\cdev.h

struct cdev {
    struct kobject kobj; //表示一个内核对象
    struct module *owner; //指向该模块的指针
    const struct file_operations *ops; //指向文件操作的指针,包括open、read、write等操作接口
    struct list_head list; //用于将该设备加入到内核模块链表中
    dev_t dev; //设备号,由主设备号和次设备号构成
    unsigned int count; //表示有多少个同类型设备,也间接表示设备号的范围
} __randomize_layout; //一个编译器指令,用于随机化结构体的布局,以增加安全性

2, file_operations

主要用来描述文件操作的各种接口,Linux一切接文件的思想,内核想要操作哪个文件,都需要通过这些接口来实现。

//kernel\include\linux\fs.h

struct file_operations {
    struct module *owner;
    loff_t (*llseek) (struct file *, loff_t, int); //改变文件读写指针位置的函数
    ssize_t (*read) (struct file *, char __user *, size_t, loff_t *); //读取文件的函数
    ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *); //写入文件的函数
    ssize_t (*read_iter) (struct kiocb *, struct iov_iter *);
    ssize_t (*write_iter) (struct kiocb *, struct iov_iter *);
    int (*iopoll)(struct kiocb *kiocb, bool spin);
    int (*iterate) (struct file *, struct dir_context *);
    int (*iterate_shared) (struct file *, struct dir_context *);
    __poll_t (*poll) (struct file *, struct poll_table_struct *); //询问文件是否可被非阻塞读写
    long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
    long (*compat_ioctl) (struct file *, unsigned int, unsigned long);
    int (*mmap) (struct file *, struct vm_area_struct *);
    unsigned long mmap_supported_flags;
    int (*open) (struct inode *, struct file *); //打开文件的函数
    int (*flush) (struct file *, fl_owner_t id); //刷新文件的函数,通常在关闭文件时调用
    int (*release) (struct inode *, struct file *); //关闭文件的函数
    int (*fsync) (struct file *, loff_t, loff_t, int datasync); //将文件数据同步写入磁盘的函数
    int (*fasync) (int, struct file *, int);
    int (*lock) (struct file *, int, struct file_lock *);
    ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
    unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);
    int (*check_flags)(int);
    int (*flock) (struct file *, int, struct file_lock *);
    ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);
    ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);
    int (*setlease)(struct file *, long, struct file_lock **, void **);
    long (*fallocate)(struct file *file, int mode, loff_t offset,
              loff_t len);
    void (*show_fdinfo)(struct seq_file *m, struct file *f);
#ifndef CONFIG_MMU
    unsigned (*mmap_capabilities)(struct file *);
#endif
    ssize_t (*copy_file_range)(struct file *, loff_t, struct file *,
            loff_t, size_t, unsigned int);
    loff_t (*remap_file_range)(struct file *file_in, loff_t pos_in,
                   struct file *file_out, loff_t pos_out,
                   loff_t len, unsigned int remap_flags);
    int (*fadvise)(struct file *, loff_t, loff_t, int);

    ANDROID_KABI_RESERVE(1);
    ANDROID_KABI_RESERVE(2);
    ANDROID_KABI_RESERVE(3);
    ANDROID_KABI_RESERVE(4);
} __randomize_layout;

3, dev_t

表示字符设备对应的设备号,其中包括主设备号和次设备号。

//kernel\include\linux\types.h

typedef u32 __kernel_dev_t;
typedef __kernel_dev_t        dev_t;

4,数据结构之间的关系

字符设备驱动程序的数据结构以及API的关系图:

二,设备号管理

1,设备号的概念

每一类字符设备都有一个唯一的设备号,其中设备号又分为主设备号和次设备号,那么这两个分别作用是什么呢?

  • 主设备号:用于标识设备的类型,

  • 次设备号:用于区分同类型的不同设备

简单来说,主设备号用于区分是IIC设备还是SPI设备,而次设备号用于区分IIC设备下,具体哪一个设备,是MPU6050还是EEPROM。

2,设备号的分配

了解了设备号的概念,Linux中设备号有那么多,那么我们该如何去使用正确的设备号呢?

设备号的分配方式有两种,一种是动态分配,另一种是静态分配,也可以理解为一种是内核自动分配,一种是手动分配。

静态分配函数:

int register_chrdev_region(dev_t from, unsigned count, const char *name);

* from:表示已知的一个设备号
* count:表示连续设备编号的个数,(同类型的设备有多少个)
* name:表示设备或者驱动的名称

以from设备号开始,连续分配count个同类型的设备号。

动态分配函数:

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

* dev:设备号的指针,用于存放分配的设备号的值
* baseminor:次设备号开始分配的起始值
* count:表示连续设备编号的个数,(同类型的设备有多少个)
* name:表示设备或者驱动的名称

从baseminor次设备号开始,连续分配count个同类型的设备号,并自动分配一个主设备号,将主、次组成的设备号信息赋值给*dev。

这两个函数最大的区别在于:

  • register_chrdev_region:调用前,已预先定义好了主设备号和次设备号,调用该接口后,会将自定义的设备号登记加入子系统中,方便系统追踪系统设备号的使用情况。

  • alloc_chrdev_region:调用前,未定义主设备号和次设备号;调用后,主设备号以0来表示,以自动分配,并且将自动分配的设备号,同样加入到子系统中,方便系统追踪系统设备号的使用情况。

这两个函数的共同点在于:

系统维护了一个数组列表,用来登记所有的已使用的设备号信息,这两个接口归根到底也是将其设备号信息,登记到系统维护的设备号列表中,以免后续冲突使用。

在Linux中,我们可以通过cat /proc/devices命令,查看所有已登记的设备号列表。

lynkco:/ # cat /proc/devices
Character devices:
  1 mem
  5 /dev/tty
  5 /dev/console
  5 /dev/ptmx
10 misc
13 input
21 sg
81 video4linux
89 i2c
... ...
Block devices:
  1 ramdisk
  7 loop
  8 sd
65 sd
66 sd
67 sd
... ...

3,设备号的注销

设备号作为一种系统资源,当所对应的设备卸载时,当然也要将其所占用的设备号归还给系统,无论时静态分配,还是动态分配,最终都是调用下面函数来注销的。

void unregister_chrdev_region(dev_t from, unsigned count);

* from:表示已知的一个设备号
* count:表示连续设备编号的个数,(同类型的设备有多少个)

要注销from主设备号下的连续count个设备。

4,设备号的获取

设备号的管理很简单,在关键数据结构中,我们看到设备号的类型是dev_t,也就是u32类型表示的一个数值。

其中主设备号和次设备号的分界线,由MINORBITS宏定义指定:

#define MINORBITS   20

也就是主设备号占用高12bit,次设备号占用低20bit

并且,内核还提供了相关API接口,来获取主设备号和次设备号,以及生成设备号的接口,如下:

#define MINORBITS    20
#define MINORMASK    ((1U << MINORBITS) - 1)

#define MAJOR(dev)    ((unsigned int) ((dev) >> MINORBITS))
#define MINOR(dev)    ((unsigned int) ((dev) & MINORMASK))
#define MKDEV(ma,mi)    (((ma) << MINORBITS) | (mi))

以上,通过移位操作,来实现主次设备号的获取。

5,通用代码实现

#define CUSTOM_DEVICE_NUM 0
#define DEVICE_NUM 1
#device DEVICE_NAME "XXXXXX"
static dev_t global_custom_major = CUSTOM_DEVICE_NUM;

static int __init xxx_init(void)
{
     dev_t custom_device_number= MKDEV(global_custom_major, 0);  //  custom device number
     /* device number register*/
     if (global_custom_major) {
         ret = register_chrdev_region(custom_device_number, DEVICE_NUM, DEVICE_NAME);
     } else {
         ret = alloc_chrdev_region(&custom_device_number, 0, DEVICE_NUM, DEVICE_NAME);
         global_custom_major = MAJOR(custom_device_number);
     }
}

static void __exit xxx_exit(void)
{
     unregister_chrdev_region(MKDEV(global_mem_major, 0), DEVICE_NUM);
}
module_init(xxx_init);
module_exit(xxx_exit);

该函数实现了设备号的分配,如果主设备号为0,则采用动态配分的方式,否则采用静态分配的方式。

三,字符设备的管理

1,字符设备初始化

void cdev_init(struct cdev *cdev, const struct file_operations *fops);

* cdev:一个字符设备对象,也就是我们创建好的字符设备
* fops:该字符设备的文件处理接口

初始化一个字符设备,并且将所对应的文件处理指针与字符设备绑定起来。

2,字符设备注册

int cdev_add(struct cdev *p, dev_t dev, unsigned count);

* p:指向要添加的字符设备
* dev:字符设备所使用的设备号
* count:要添加的设备数量

初始化一个字符设备,并且将所对应的文件处理指针与字符设备绑定起来。

3,字符设备注销

void cdev_del(struct cdev *p);

* p:指向字符设备对象的指针

从系统中移除该字符设备驱动。

4,文件操作接口的实现

因为在Linux中,一切皆文件的思想,所以每一个字符设备,也都有一个文件节点来对应。

我们在初始化字符设备的时候,会将struct file_operations的对象与字符设备进行绑定,其作用是来处理该字符设备的open、read、write等操作。

我们要做的就是去实现我们需要的函数接口,如:

//kernel\drivers\watchdog\ath79_wdt.c

static const struct file_operations ath79_wdt_fops = {
    .owner        = THIS_MODULE,
    .llseek        = no_llseek,
    .write        = ath79_wdt_write,
    .unlocked_ioctl    = ath79_wdt_ioctl,
    .compat_ioctl    = compat_ptr_ioctl,
    .open        = ath79_wdt_open,
    .release    = ath79_wdt_release,
};

5,实现ioctl

ioctl是Linux专门为用户层控制设备设计的系统调用接口,这个接口具有极大的灵活性,我们的设备打算让用户通过哪些命令实现哪些功能,都可以通过它来实现,ioctl在操作方法集中对应的函数指针是long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);,其中的命令和参数完全由驱动指定,通常命令会写在一个头文件中以供应用层和驱动层遵守同样的通信协议。

 5.1 Linux建议如下所示的方式定义ioctl()命令
设备类型 序列号 方向   数据尺寸 
8bit     8bit  2bit  13/14bit
  • 设备类型字段为一个幻数,可以是0~0xff之间的数,内核中的"ioctl-number.txt"给出了一个推荐的和已经被使用的幻数(但是已经好久没人维护了),新设备驱动定义幻数的时候要避免与其冲突。

  • 序列号字段表示当前命令是整个ioctl命令中的第几个,从1开始计数。

  • 方向字段为2bit,表示数据的传输方向,可能的值是:_IOC_NONE,_IOC_READ,_IOC_WRITE和_IOC_READ|_IOC_WRITE。

  • 数据尺寸字段表示涉及的用户数据的大小,这个成员的宽度依赖于体系结构,通常是13或14位。

内核还定义了_IO(),_IOR(),_IOW(),_IOWR()这4个宏来辅助生成这种格式的命令。这几个宏的作用是根据传入的type(设备类型字段),nr(序列号字段)和size(数据长度字段)和方向字段移位组合生成命令码。

内核中还预定义了一些I/O控制命令,如果某设备驱动中包含了与预定义命令一样的命令码,这些命令会被当做预定义命令被内核处理而不是被设备驱动处理,有如下4种:

  • FIOCLEX:即file ioctl close on exec 对文件设置专用的标志,通知内核当exec()系统带哦用发生时自动关闭打开的文件

  • FIONCLEX:即file ioctl not close on exec,清除由FIOCLEX设置的标志

  • FIOQSIZE:获得一个文件或目录的大小,当用于设备文件时,返回一个ENOTTY错误

  • FIONBIO:即file ioctl non-blocking I/O 这个调用修改flip->f_flags中的O_NONBLOCK标志

5.2 unlocked_ioctl与compat_ioctl

    compat_ioctl:支持64bit的driver必须要实现的ioctl,当有32bit的userspace application call 64bit kernel的IOCTL的时候,这个callback会被调用到。如果没有实现compat_ioctl,那么32位的用户程序在64位的kernel上执行ioctl时会返回错误:Not a typewriter

    如果是64位的用户程序运行在64位的kernel上,调用的是unlocked_ioctl,如果是32位的APP运行在32位的kernel上,调用的也是unlocked_ioctl。

5.3 实例
//kernel\include\uapi\linux\watchdog.h
#define    WDIOC_GETSUPPORT    _IOR(WATCHDOG_IOCTL_BASE, 0, struct watchdog_info)
#define    WDIOC_GETSTATUS        _IOR(WATCHDOG_IOCTL_BASE, 1, int)
#define    WDIOC_GETBOOTSTATUS    _IOR(WATCHDOG_IOCTL_BASE, 2, int)
#define    WDIOC_GETTEMP        _IOR(WATCHDOG_IOCTL_BASE, 3, int)
#define    WDIOC_SETOPTIONS    _IOR(WATCHDOG_IOCTL_BASE, 4, int)
#define    WDIOC_KEEPALIVE        _IOR(WATCHDOG_IOCTL_BASE, 5, int)
#define    WDIOC_SETTIMEOUT        _IOWR(WATCHDOG_IOCTL_BASE, 6, int)
#define    WDIOC_GETTIMEOUT        _IOR(WATCHDOG_IOCTL_BASE, 7, int)
#define    WDIOC_SETPRETIMEOUT    _IOWR(WATCHDOG_IOCTL_BASE, 8, int)
#define    WDIOC_GETPRETIMEOUT    _IOR(WATCHDOG_IOCTL_BASE, 9, int)
#define    WDIOC_GETTIMELEFT    _IOR(WATCHDOG_IOCTL_BASE, 10, int)

//kernel\drivers\watchdog\ath79_wdt.c
static long ath79_wdt_ioctl(struct file *file, unsigned int cmd,
                unsigned long arg)
{
    void __user *argp = (void __user *)arg;
    int __user *p = argp;
    int err;
    int t;

    switch (cmd) {
    case WDIOC_GETSUPPORT:
        err = copy_to_user(argp, &ath79_wdt_info,
                   sizeof(ath79_wdt_info)) ? -EFAULT : 0;
        break;

    case WDIOC_GETSTATUS:
        err = put_user(0, p);
        break;

    case WDIOC_GETBOOTSTATUS:
        err = put_user(boot_status, p);
        break;

    case WDIOC_KEEPALIVE:
        ath79_wdt_keepalive();
        err = 0;
        break;

    case WDIOC_SETTIMEOUT:
        err = get_user(t, p);
        if (err)
            break;

        err = ath79_wdt_set_timeout(t);
        if (err)
            break;
        fallthrough;

    case WDIOC_GETTIMEOUT:
        err = put_user(timeout, p);
        break;

    default:
        err = -ENOTTY;
        break;
    }

    return err;
}

四,创建设备文件

插入的设备模块,我们就可以使用cat /proc/devices命令查看当前系统注册的设备,但是我们还没有创建相应的设备文件,用户也就不能通过文件访问这个设备。设备文件的inode应该是包含了这个设备的设备号,操作方法集指针等信息,这样我们就可以通过设备文件找到相应的inode进而访问设备。

创建设备文件的方法有两种,手动创建或自动创建。

1,手动创建设备文件

使用mknod /dev/xxx 设备类型 主设备号 次设备号的命令创建,所以首先需要使用cat /proc/devices查看设备的主设备号并通过源码找到设备的次设备号,需要注意的是,理论上设备文件可以放置在任何文件加夹,但是放到"/dev"才符合Linux的设备管理机制,这里面的devtmpfs是专门设计用来管理设备文件的文件系统。设备文件创建好之后就会和创建时指定的设备绑定,即使设备已经被卸载了,如要删除设备文件,只需要像删除普通文件一样rm即可。理论上模块名(lsmod),设备名(/proc/devices),设备文件名(/dev)并没有什么关系,完全可以不一样,但是原则上还是建议将三者进行统一,便于管理。

2,自动创建设备文件

除了使用蹩脚的手动创建设备节点的方式,我们还可以在设备源码中使用相应的措施使设备一旦被加载就自动创建设备文件,自动创建设备文件需要我们在编译内核的时候或制作根文件系统的时候就好相应的配置:

Device Drivers ---> 
    Generic Driver Options ---> 
        [*]Maintain a devtmpfs filesystem to mount at /dev 
        [*] Automount devtmpfs at /dev,after the kernel mounted the rootfs

或者

制作根文件系统的启动脚本写入

mount -t sysfs none sysfs /sys mdev -s //udev也行

有了这些准备,只需要导出相应的设备信息到"/sys"就可以按照我们的要求自动创建设备文件。内核给我们提供了相关的API

#define class_create(owner, name)        \
({                        \
    static struct lock_class_key __key;    \
    __class_create(owner, name, &__key);    \
})

struct device *device_create(struct class *class, struct device *parent,
                 dev_t devt, void *drvdata, const char *fmt, ...);


void class_destroy(struct class *cls); 
void device_destroy(struct class *cls, dev_t devt);

有了这几个函数,我们就可以在设备的xxx_init()和xxx_exit()中分别填写以下的代码就可以实现自动的创建删除设备文件

/* 在/sys中导出设备类信息 */
cls = class_create(THIS_MODULE,DEV_NAME);

/* 在cls指向的类中创建一组(个)设备文件 */
for(i= minor;i<(minor+cnt);i++){
    devp = device_create(cls,NULL,MKDEV(major,i),NULL,"%s%d",DEV_NAME,i);
}
/* 在cls指向的类中删除一组(个)设备文件 */
for(i= minor;i<(minor+cnt);i++){
    device_destroy(cls,MKDEV(major,i));
}

/* 在/sys中删除设备类信息 */
class_destroy(cls);                //一定要先卸载device再卸载class

五,注册字符设备实例

kernel\include\uapi\linux\hidraw.h

#define HIDRAW_FIRST_MINOR 0
#define HIDRAW_MAX_DEVICES 64

static int hidraw_major;
static struct cdev hidraw_cdev;
static struct class *hidraw_class;


/* ioctl interface */
#define HIDIOCGRDESCSIZE    _IOR('H', 0x01, int)
#define HIDIOCGRDESC        _IOR('H', 0x02, struct hidraw_report_descriptor)
#define HIDIOCGRAWINFO        _IOR('H', 0x03, struct hidraw_devinfo)
#define HIDIOCGRAWNAME(len)     _IOC(_IOC_READ, 'H', 0x04, len)
#define HIDIOCGRAWPHYS(len)     _IOC(_IOC_READ, 'H', 0x05, len)
/* The first byte of SFEATURE and GFEATURE is the report number */
#define HIDIOCSFEATURE(len)    _IOC(_IOC_WRITE|_IOC_READ, 'H', 0x06, len)
#define HIDIOCGFEATURE(len)    _IOC(_IOC_WRITE|_IOC_READ, 'H', 0x07, len)
#define HIDIOCGRAWUNIQ(len)     _IOC(_IOC_READ, 'H', 0x08, len)

kernel\drivers\hid\hidraw.c

static int hidraw_open(struct inode *inode, struct file *file)
{
    file->private_data = list;
}

static ssize_t hidraw_read(struct file *file, char __user *buffer, size_t count, loff_t *ppos)
{
    struct hidraw_list *list = file->private_data;

    if (list->buffer[list->tail].value) {
        if (copy_to_user(buffer, list->buffer[list->tail].value, len)) {
            ret = -EFAULT;
            goto out;
        }
        ret = len;
    }
}

static ssize_t hidraw_write(struct file *file, const char __user *buffer, size_t count, loff_t *ppos)
{
    buf = memdup_user(buffer, count);
}

static __poll_t hidraw_poll(struct file *file, poll_table *wait)
{
    struct hidraw_list *list = file->private_data;
    __poll_t mask = EPOLLOUT | EPOLLWRNORM; /* hidraw is always writable */

    poll_wait(file, &list->hidraw->wait, wait);
    if (list->head != list->tail)
        mask |= EPOLLIN | EPOLLRDNORM;
    if (!list->hidraw->exist)
        mask |= EPOLLERR | EPOLLHUP;
    return mask;
}

static long hidraw_ioctl(struct file *file, unsigned int cmd,
                            unsigned long arg)
{
    struct inode *inode = file_inode(file);
    unsigned int minor = iminor(inode);
    long ret = 0;
    struct hidraw *dev;
    void __user *user_arg = (void __user*) arg;

    switch (cmd) {
        case HIDIOCGRDESCSIZE:
            if (put_user(dev->hid->rsize, (int __user *)arg))
                ret = -EFAULT;
            break;

        case HIDIOCGRDESC:
            {
                __u32 len;

                if (get_user(len, (int __user *)arg))
                    ret = -EFAULT;
                else if (len > HID_MAX_DESCRIPTOR_SIZE - 1)
                    ret = -EINVAL;
                else if (copy_to_user(user_arg + offsetof(
                    struct hidraw_report_descriptor,
                    value[0]),
                    dev->hid->rdesc,
                    min(dev->hid->rsize, len)))
                    ret = -EFAULT;
                break;
            }
        case HIDIOCGRAWINFO:
            {
                struct hidraw_devinfo dinfo;

                dinfo.bustype = dev->hid->bus;
                dinfo.vendor = dev->hid->vendor;
                dinfo.product = dev->hid->product;
                if (copy_to_user(user_arg, &dinfo, sizeof(dinfo)))
                    ret = -EFAULT;
                break;
            }
        default:
            {
                struct hid_device *hid = dev->hid;
                if (_IOC_TYPE(cmd) != 'H') {
                    ret = -EINVAL;
                    break;
                }

                if (_IOC_NR(cmd) == _IOC_NR(HIDIOCSFEATURE(0))) {
                    int len = _IOC_SIZE(cmd);
                    ret = hidraw_send_report(file, user_arg, len, HID_FEATURE_REPORT);
                    break;
                }
                if (_IOC_NR(cmd) == _IOC_NR(HIDIOCGFEATURE(0))) {
                    int len = _IOC_SIZE(cmd);
                    ret = hidraw_get_report(file, user_arg, len, HID_FEATURE_REPORT);
                    break;
                }

                /* Begin Read-only ioctls. */
                if (_IOC_DIR(cmd) != _IOC_READ) {
                    ret = -EINVAL;
                    break;
                }

                if (_IOC_NR(cmd) == _IOC_NR(HIDIOCGRAWNAME(0))) {
                    int len = strlen(hid->name) + 1;
                    if (len > _IOC_SIZE(cmd))
                        len = _IOC_SIZE(cmd);
                    ret = copy_to_user(user_arg, hid->name, len) ?
                        -EFAULT : len;
                    break;
                }

                if (_IOC_NR(cmd) == _IOC_NR(HIDIOCGRAWPHYS(0))) {
                    int len = strlen(hid->phys) + 1;
                    if (len > _IOC_SIZE(cmd))
                        len = _IOC_SIZE(cmd);
                    ret = copy_to_user(user_arg, hid->phys, len) ?
                        -EFAULT : len;
                    break;
                }

                if (_IOC_NR(cmd) == _IOC_NR(HIDIOCGRAWUNIQ(0))) {
                    int len = strlen(hid->uniq) + 1;
                    if (len > _IOC_SIZE(cmd))
                        len = _IOC_SIZE(cmd);
                    ret = copy_to_user(user_arg, hid->uniq, len) ?
                        -EFAULT : len;
                    break;
                }
            }

        ret = -ENOTTY;
    }
out:
    mutex_unlock(&minors_lock);
    return ret;
}

static const struct file_operations hidraw_ops = {
    .owner =        THIS_MODULE,
    .read =         hidraw_read,
    .write =        hidraw_write,
    .poll =         hidraw_poll,
    .open =         hidraw_open,
    .release =      hidraw_release,
    .unlocked_ioctl = hidraw_ioctl,
    .fasync =    hidraw_fasync,
    .compat_ioctl   = compat_ptr_ioctl,
    .llseek =    noop_llseek,
};

int hidraw_connect(struct hid_device *hid)
{
    int minor, result;
    struct hidraw *dev;
    ... ...

    for (minor = 0; minor < HIDRAW_MAX_DEVICES; minor++) {
        if (hidraw_table[minor])
            continue;
        hidraw_table[minor] = dev;
        result = 0;
        break;
    }

    dev->dev = device_create(hidraw_class, &hid->dev, MKDEV(hidraw_major, minor),
                 NULL, "%s%d", "hidraw", minor);
}
EXPORT_SYMBOL_GPL(hidraw_connect);

int __init hidraw_init(void)
{
    int result;
    dev_t dev_id;

    result = alloc_chrdev_region(&dev_id, HIDRAW_FIRST_MINOR,
            HIDRAW_MAX_DEVICES, "hidraw");
    if (result < 0) {
        pr_warn("can't get major number\n");
        goto out;
    }

    hidraw_major = MAJOR(dev_id);


    hidraw_class = class_create(THIS_MODULE, "hidraw");
    if (IS_ERR(hidraw_class)) {
        result = PTR_ERR(hidraw_class);
        goto error_cdev;
    }

    cdev_init(&hidraw_cdev, &hidraw_ops);
    result = cdev_add(&hidraw_cdev, dev_id, HIDRAW_MAX_DEVICES);
    if (result < 0)
        goto error_class;

    pr_info("raw HID events driver (C) Jiri Kosina\n");
out:
    return result;

error_class:
    class_destroy(hidraw_class);
error_cdev:
    unregister_chrdev_region(dev_id, HIDRAW_MAX_DEVICES);
    goto out;
}

void hidraw_exit(void)
{
    dev_t dev_id = MKDEV(hidraw_major, 0);

    cdev_del(&hidraw_cdev);
    class_destroy(hidraw_class);
    unregister_chrdev_region(dev_id, HIDRAW_MAX_DEVICES);
}

参考链接:

https://www.cnblogs.com/xiaojiang1025/p/6181833.html

一文秒懂|Linux字符设备驱动 - 知乎

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/595795.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

2024.05.06作业

自由发挥应用场景&#xff0c;实现登录界面。 要求&#xff1a;尽量每行代码都有注释。 #include "yuanshen.h"yuanshen::yuanshen(QWidget *parent): QWidget(parent) {//窗口相关设置this->resize(1600,910);this->setFixedSize(1600,910);//窗口标题this-…

管理能力学习笔记九:授权的常见误区和如何有效授权

授权的常见误区 误区一&#xff1a;随意授权 管理者在授权工作时&#xff0c;需要依据下属的能力、经验、意愿问最自己&#xff1a;这项工作适合授权给Ta做吗&#xff1f;如果没有&#xff0c;可以通过哪些方法进行培训呢&#xff1f; 误区二&#xff1a;缺乏信任 心理暗示…

腾讯崛起!2024年最赚钱的电商平台,竟然来自视频号

大家好&#xff0c;我是电商月月 说到卖货平台&#xff0c;这两年“抖音”绝对是所有人心里最赚钱的电商平台 抖音小店的商家利用抖店后台的“精选联盟”引流&#xff0c;不用自己直播&#xff0c;也能靠直播卖货赚的盆满钵满 于是好多平台都效仿抖店的直播卖货形式来获取更…

BGP的选路 :

前提条件 &#xff1a; 丢弃所有不可用的路由信息。 属性的名称 传播范围 默认值 评判标准 PV&#xff08;优选值&#xff09; 不传播 0&#xff08;0-65535&#xff09; 越大越优 LP&#xff08;本地优先级&#xff09; IBGP对等体 之间 100 越大越优 AS_PATH …

中间件研发之Springboot自定义starter

Spring Boot Starter是一种简化Spring Boot应用开发的机制&#xff0c;它可以通过引入一些预定义的依赖和配置&#xff0c;让我们快速地集成某些功能模块&#xff0c;而无需繁琐地编写代码和配置文件。Spring Boot官方提供了很多常用的Starter&#xff0c;例如spring-boot-star…

PMO全面指南:一文读懂PMO的功能、职责、类型、构建

多年来&#xff0c;PMO 的概念在多个行业和类型的组织中越来越受欢迎。一开始&#xff0c;只有大型跨国公司才熟悉它&#xff0c;但后来&#xff0c;许多中小型公司开始采用 PMO 来进行高效的项目管理并实现其战略目标。 根据Statista的数据&#xff0c;目前有80%的组织设有至…

企业网站 | 被攻击时该怎么办?

前言 每天&#xff0c;数以千计的网站被黑客入侵。发生这种情况时&#xff0c;被入侵网站可用于从网络钓鱼页面到SEO垃圾邮件或者其它内容。如果您拥有一个小型网站&#xff0c;很容易相信黑客不会对它感兴趣。不幸的是&#xff0c;通常情况并非如此。 黑客入侵网站的动机与所…

09_电子设计教程基础篇(电阻)

文章目录 前言一、电阻原理二、电阻种类1.固定电阻1、材料工艺1、线绕电阻2、非线绕电阻1、实心电阻1、有机实心电阻2、无机实心电阻 2、薄膜电阻&#xff08;常用&#xff09;1、碳膜电阻2、合成碳膜电阻3、金属膜电阻4、金属氧化膜电阻5、玻璃釉膜电阻 3、厚膜电阻&#xff0…

segformer部分错误

亲测有用 1、TypeError: FormatCode() got an unexpected keyword argument ‘verify‘ mmcv中出现TypeError: FormatCode() got an unexpected keyword argument ‘verify‘-CSDN博客 pip install yapf0.40.0 2、“EncoderDecoder: ‘mit_b1 is not in the backbone regist…

达梦数据库导入数据问题

进行数据导入的时候遇到了导入数据问题 第一个问题&#xff1a; 该工具不能解析此文件&#xff0c;请使用更高版本的工具 这个是因为版本有点低&#xff0c;需要下载最新的达梦数据库 第二个问题&#xff1a; &#xff08;1&#xff09;本地编码&#xff1a;PG_GBK, 导入文…

美特CRM upload.jsp 文件上传致RCE漏洞复现(CNVD-2023-06971)

0x01 产品简介 MetaCRM是一款智能平台化CRM软件,通过提升企业管理和协同办公,全面提高企业管理水平和运营效率,帮助企业实现卓越管理。美特软件开创性地在CRM领域中引入用户级产品平台MetaCRM V5/V6,多年来一直在持续地为客户创造价值,大幅提升了用户需求满足度与使用的满意…

21 内核开发-临界区及临界区代码段判断

内核开发-临界区判断 目录 内核开发-临界区判断 1.定义 2.临界区实现机制 3.使用互斥锁实现临界区的示例 4.怎么识别是临界区代码 5.总结 1.定义 临界区是计算机系统中的一段代码&#xff0c;在任何时刻只能被一个线程执行。临界区的目的是防止多个线程同时访问共享资源…

Make3D数据集相关介绍

一、参考资料 Make3d数据集使用方法 二、相关介绍 1. 简介 Make3D 数据集的每帧图像的深度值均由激光雷达进行采集&#xff0c;相较于 Kinect 相机采集的深度信息&#xff0c;该测距仪可以得到室外图像更加精确的深度信息&#xff0c;而且测距范围更大&#xff0c;与普通的…

【stm32笔记】DSP库调用

参考&#xff1a;DSP库调用 , __CC_ARM,__TARGET_FPU_VFP, __FPU_PRESENT1U, ARM_MATH_CM4把需要的库复制出来单独用&#xff0c;方便移植

websevere服务器从零搭建到上线(三)|IO多路复用小总结和服务器的基础框架

文章目录 epollselect和poll的优缺点epoll的原理以及优势epoll 好的网络服务器设计Reactor模型图解Reactor muduo库的Multiple Reactors模型 epoll select和poll的优缺点 1、单个进程能够监视的文件描述符的数量存在最大限制&#xff0c;通常是1024&#xff0c;当然可以更改数…

什么是X电容和Y电容?

先补充个知识&#xff1a; 一、什么是差模信号和共模信号 差模信号&#xff1a;大小相等&#xff0c;方向相反的交流信号&#xff1b;双端输入时&#xff0c;两个信号的相位相差180度 共模信号&#xff1a;大小相等。方向相同。双端输入时&#xff0c;两个信号相同。 二、安规…

小程序如何重启

用户在使用小程序的过程中&#xff0c;有时候会碰到一些问题。比如小程序数据不加载、卡顿、崩溃或者出现其他异常情况。这时候&#xff0c;最简单的办法就是重启小程序。但是很多客户不知道如何重启小程序&#xff0c;下面就具体介绍小程序重新启动的几种方法。 1. 强制关闭&…

CWDM、DWDM、MWDM、LWDM:快速了解光波复用技术

在现代光纤通信领域&#xff0c;波分复用&#xff08;WDM&#xff09;技术作为一项先进的创新脱颖而出。它通过将多个不同波长和速率的光信号汇聚到一根光纤中来有效地传输数据。本文将深入探讨几种关键的 WDM 技术&#xff08;CWDM、DWDM、MWDM 和 LWDM&#xff09;&#xff0…

流量分析。

流量分析 在Wireshak抓包可以看到正常的执行流程如下&#xff1a; ● Client向Server发起Load data local infile请求 ● Server返回需要读取的文件路径 ● Client读取文件内容并发送给Server ● PS&#xff1a;在本机上启动服务端与客户端&#xff0c;启动wireshark 抓包&…

根据相同的key 取出数组中最后一个值

数组中有很多对象 , 需根据当前页面的值current 和 数组中的key对比 拿到返回值 数据结构如下 之前写法 const clickedItem routeList.find(item > item.key current) // current是当前页 用reduce遍历数组返回最后一个值 const clickedItem routeList.reduce((lastIte…