1.misc驱动简介

  • misc中文名就是杂项设备杂散设备,因为现在的硬件设备多种多样,有好些设备不好对他们进行一个单独的分类,所以就将这些设备全部归属于杂散设备,也就是misc设备,例如像adc、buzzer等这些设备一般都归属于misc中。所有的misc类设备都是字符设备,也就是misc类是字符设备中分出来的一个小类。
  • misc类设备在应用层的操作接口:/dev/xxx,设备类对应在/sys/class/misc
  • misc类设备有自己的一套驱动框架,所以我们写一个misc设备的驱动直接利用的是内核中提供的驱动程序框架来实现的。misc驱动框架是对内核提供的原始的字符设备注册接口的一个类层次的封装,很多典型的字符设备都可以归于misc设备,都可以利用misc提供的驱动框架来编写驱动代码,通过misc驱动框架来进行管理。

2.misc驱动框架源码分析

在内核源码中,misc驱动框架源码的实现在:driver/char/misc.c,相应的头文件在:include/linux/miscdevice.h
如果我们添加自己的misc类设备,那么驱动源文件最好在driver/misc这个目录下
先看miscdevice.h的源代码:

/* SPDX-License-Identifier: GPL-2.0 */
#ifndef _LINUX_MISCDEVICE_H
#define _LINUX_MISCDEVICE_H
#include <linux/major.h>
#include <linux/list.h>
#include <linux/types.h>
#include <linux/device.h>

/*
 *    These allocations are managed by device@lanana.org. If you use an
 *    entry that is not in assigned your entry may well be moved and
 *    reassigned, or set dynamic if a fixed value is not justified.
 */

#define PSMOUSE_MINOR        1
#define MS_BUSMOUSE_MINOR    2    /* unused */
#define ATIXL_BUSMOUSE_MINOR    3    /* unused */
/*#define AMIGAMOUSE_MINOR    4    FIXME OBSOLETE */
#define ATARIMOUSE_MINOR    5    /* unused */
#define SUN_MOUSE_MINOR        6    /* unused */
#define APOLLO_MOUSE_MINOR    7    /* unused */
#define PC110PAD_MINOR        9    /* unused */
/*#define ADB_MOUSE_MINOR    10    FIXME OBSOLETE */
#define WATCHDOG_MINOR        130    /* Watchdog timer     */
#define TEMP_MINOR        131    /* Temperature Sensor */
#define APM_MINOR_DEV        134
#define RTC_MINOR        135
#define EFI_RTC_MINOR        136    /* EFI Time services */
#define VHCI_MINOR        137
#define SUN_OPENPROM_MINOR    139
#define DMAPI_MINOR        140    /* unused */
#define NVRAM_MINOR        144
#define SGI_MMTIMER        153
#define STORE_QUEUE_MINOR    155    /* unused */
#define I2O_MINOR        166
#define AGPGART_MINOR        175
#define HWRNG_MINOR        183
#define MICROCODE_MINOR        184
#define IRNET_MINOR        187
#define D7S_MINOR        193
#define VFIO_MINOR        196
#define TUN_MINOR        200
#define CUSE_MINOR        203
#define MWAVE_MINOR        219    /* ACP/Mwave Modem */
#define MPT_MINOR        220
#define MPT2SAS_MINOR        221
#define MPT3SAS_MINOR        222
#define UINPUT_MINOR        223
#define MISC_MCELOG_MINOR    227
#define HPET_MINOR        228
#define FUSE_MINOR        229
#define KVM_MINOR        232
#define BTRFS_MINOR        234
#define AUTOFS_MINOR        235
#define MAPPER_CTRL_MINOR    236
#define LOOP_CTRL_MINOR        237
#define VHOST_NET_MINOR        238
#define UHID_MINOR        239
#define USERIO_MINOR        240
#define VHOST_VSOCK_MINOR    241
#define RFKILL_MINOR        242
#define MISC_DYNAMIC_MINOR    255

struct device;
struct attribute_group;

struct miscdevice  {
    int minor;  // 次设备号
    const char *name;   //设备名称
    const struct file_operations *fops; // 文件操作结构体
    struct list_head list;  // 作为一个链表结点挂接到misc设备维护的一个链表头上去
    struct device *parent;  // 次设备的父设备
    struct device *this_device; // 本设备的device结构体指针
    const struct attribute_group **groups;
    const char *nodename;
    umode_t mode;
};

extern int misc_register(struct miscdevice *misc);
extern void misc_deregister(struct miscdevice *misc);

/*
 * Helper macro for drivers that don't do anything special in the initcall.
 * This helps in eleminating of boilerplate code.
 */
#define builtin_misc_device(__misc_device) \
    builtin_driver(__misc_device, misc_register)

/*
 * Helper macro for drivers that don't do anything special in module init / exit
 * call. This helps in eleminating of boilerplate code.
 */
#define module_misc_device(__misc_device) \
    module_driver(__misc_device, misc_register, misc_deregister)

#define MODULE_ALIAS_MISCDEV(minor)                \
    MODULE_ALIAS("char-major-" __stringify(MISC_MAJOR)    \
    "-" __stringify(minor))
#endif

下面是misc驱动框架的源码实现,misc.c


#include <linux/module.h>

#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/miscdevice.h>
#include <linux/kernel.h>
#include <linux/major.h>
#include <linux/mutex.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/stat.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/tty.h>
#include <linux/kmod.h>
#include <linux/gfp.h>

/*
 * Head entry for the doubly linked miscdevice list
 */
static LIST_HEAD(misc_list);
static DEFINE_MUTEX(misc_mtx);

/*
 * Assigned numbers, used for dynamic minors
 */
#define DYNAMIC_MINORS 64 /* like dynamic majors */
static DECLARE_BITMAP(misc_minors, DYNAMIC_MINORS);

#ifdef CONFIG_PROC_FS
static void *misc_seq_start(struct seq_file *seq, loff_t *pos)
{
    mutex_lock(&misc_mtx);
    return seq_list_start(&misc_list, *pos);
}

static void *misc_seq_next(struct seq_file *seq, void *v, loff_t *pos)
{
    return seq_list_next(v, &misc_list, pos);
}

static void misc_seq_stop(struct seq_file *seq, void *v)
{
    mutex_unlock(&misc_mtx);
}

static int misc_seq_show(struct seq_file *seq, void *v)
{
    const struct miscdevice *p = list_entry(v, struct miscdevice, list);

    seq_printf(seq, "%3i %s\n", p->minor, p->name ? p->name : "");
    return 0;
}


static const struct seq_operations misc_seq_ops = {
    .start = misc_seq_start,
    .next  = misc_seq_next,
    .stop  = misc_seq_stop,
    .show  = misc_seq_show,
};
#endif

static int misc_open(struct inode *inode, struct file *file)
{
    int minor = iminor(inode);
    struct miscdevice *c;
    int err = -ENODEV;
    const struct file_operations *new_fops = NULL;

    mutex_lock(&misc_mtx);

    list_for_each_entry(c, &misc_list, list) {
        if (c->minor == minor) {
            new_fops = fops_get(c->fops);
            break;
        }
    }

    if (!new_fops) {
        mutex_unlock(&misc_mtx);
        request_module("char-major-%d-%d", MISC_MAJOR, minor);
        mutex_lock(&misc_mtx);

        list_for_each_entry(c, &misc_list, list) {
            if (c->minor == minor) {
                new_fops = fops_get(c->fops);
                break;
            }
        }
        if (!new_fops)
            goto fail;
    }

    /*
     * Place the miscdevice in the file's
     * private_data so it can be used by the
     * file operations, including f_op->open below
     */
    file->private_data = c;

    err = 0;
    replace_fops(file, new_fops);
    if (file->f_op->open)
        err = file->f_op->open(inode, file);
fail:
    mutex_unlock(&misc_mtx);
    return err;
}

static struct class *misc_class;

static const struct file_operations misc_fops = {
    .owner        = THIS_MODULE,
    .open        = misc_open,
    .llseek        = noop_llseek,
};

/**
 *    misc_register    -    register a miscellaneous device
 *    @misc: device structure
 *
 *    Register a miscellaneous device with the kernel. If the minor
 *    number is set to %MISC_DYNAMIC_MINOR a minor number is assigned
 *    and placed in the minor field of the structure. For other cases
 *    the minor number requested is used.
 *
 *    The structure passed is linked into the kernel and may not be
 *    destroyed until it has been unregistered. By default, an open()
 *    syscall to the device sets file->private_data to point to the
 *    structure. Drivers don't need open in fops for this.
 *
 *    A zero is returned on success and a negative errno code for
 *    failure.
 */

int misc_register(struct miscdevice *misc)
{
    dev_t dev;
    int err = 0;
    bool is_dynamic = (misc->minor == MISC_DYNAMIC_MINOR);

    INIT_LIST_HEAD(&misc->list);

    mutex_lock(&misc_mtx);

    if (is_dynamic) {
        int i = find_first_zero_bit(misc_minors, DYNAMIC_MINORS);

        if (i >= DYNAMIC_MINORS) {
            err = -EBUSY;
            goto out;
        }
        misc->minor = DYNAMIC_MINORS - i - 1;
        set_bit(i, misc_minors);
    } else {
        struct miscdevice *c;

        list_for_each_entry(c, &misc_list, list) {
            if (c->minor == misc->minor) {
                err = -EBUSY;
                goto out;
            }
        }
    }

    dev = MKDEV(MISC_MAJOR, misc->minor);

    misc->this_device =
        device_create_with_groups(misc_class, misc->parent, dev,
                      misc, misc->groups, "%s", misc->name);
    if (IS_ERR(misc->this_device)) {
        if (is_dynamic) {
            int i = DYNAMIC_MINORS - misc->minor - 1;

            if (i < DYNAMIC_MINORS && i >= 0)
                clear_bit(i, misc_minors);
            misc->minor = MISC_DYNAMIC_MINOR;
        }
        err = PTR_ERR(misc->this_device);
        goto out;
    }

    /*
     * Add it to the front, so that later devices can "override"
     * earlier defaults
     */
    list_add(&misc->list, &misc_list);
 out:
    mutex_unlock(&misc_mtx);
    return err;
}
EXPORT_SYMBOL(misc_register);

/**
 *    misc_deregister - unregister a miscellaneous device
 *    @misc: device to unregister
 *
 *    Unregister a miscellaneous device that was previously
 *    successfully registered with misc_register().
 */

void misc_deregister(struct miscdevice *misc)
{
    int i = DYNAMIC_MINORS - misc->minor - 1;

    if (WARN_ON(list_empty(&misc->list)))
        return;

    mutex_lock(&misc_mtx);
    list_del(&misc->list);
    device_destroy(misc_class, MKDEV(MISC_MAJOR, misc->minor));
    if (i < DYNAMIC_MINORS && i >= 0)
        clear_bit(i, misc_minors);
    mutex_unlock(&misc_mtx);
}
EXPORT_SYMBOL(misc_deregister);

static char *misc_devnode(struct device *dev, umode_t *mode)
{
    struct miscdevice *c = dev_get_drvdata(dev);

    if (mode && c->mode)
        *mode = c->mode;
    if (c->nodename)
        return kstrdup(c->nodename, GFP_KERNEL);
    return NULL;
}

// misc_init()函数是misc驱动框架模块注册时的一个初始化函数,只有进行了初始化,我们才能够利用misc提供的框架来进行编写misc设备驱动程序和管理misc类设备。
static int __init misc_init(void)
{
    int err;
    struct proc_dir_entry *ret;

    ret = proc_create_seq("misc", 0, NULL, &misc_seq_ops);
    // 在sys文件系统下创建misc设备类
    misc_class = class_create(THIS_MODULE, "misc");
    err = PTR_ERR(misc_class);
    if (IS_ERR(misc_class))
        goto fail_remove;

    err = -EIO;
    // 注册misc字符设备,主设备是10
    if (register_chrdev(MISC_MAJOR, "misc", &misc_fops))
        goto fail_printk;
    misc_class->devnode = misc_devnode;
    return 0;

fail_printk:
    pr_err("unable to get major %d for misc devices\n", MISC_MAJOR);
    class_destroy(misc_class);
fail_remove:
    if (ret)
        remove_proc_entry("misc", NULL);
    return err;
}
subsys_initcall(misc_init);