cregit-Linux how code gets into the kernel

Release 4.9 fs/char_dev.c

Directory: fs
/*
 *  linux/fs/char_dev.c
 *
 *  Copyright (C) 1991, 1992  Linus Torvalds
 */

#include <linux/init.h>
#include <linux/fs.h>
#include <linux/kdev_t.h>
#include <linux/slab.h>
#include <linux/string.h>

#include <linux/major.h>
#include <linux/errno.h>
#include <linux/module.h>
#include <linux/seq_file.h>

#include <linux/kobject.h>
#include <linux/kobj_map.h>
#include <linux/cdev.h>
#include <linux/mutex.h>
#include <linux/backing-dev.h>
#include <linux/tty.h>

#include "internal.h"


static struct kobj_map *cdev_map;

static DEFINE_MUTEX(chrdevs_lock);


static struct char_device_struct {
	
struct char_device_struct *next;
	
unsigned int major;
	
unsigned int baseminor;
	
int minorct;
	
char name[64];
	
struct cdev *cdev;		/* will die */
} 
*chrdevs[CHRDEV_MAJOR_HASH_SIZE];

/* index in the above */

static inline int major_to_index(unsigned major) { return major % CHRDEV_MAJOR_HASH_SIZE; }

Contributors

PersonTokensPropCommitsCommitProp
andrew mortonandrew morton1386.67%133.33%
joe kortyjoe korty16.67%133.33%
yang zhangyang zhang16.67%133.33%
Total15100.00%3100.00%

#ifdef CONFIG_PROC_FS
void chrdev_show(struct seq_file *f, off_t offset) { struct char_device_struct *cd; if (offset < CHRDEV_MAJOR_HASH_SIZE) { mutex_lock(&chrdevs_lock); for (cd = chrdevs[offset]; cd; cd = cd->next) seq_printf(f, "%3d %s\n", cd->major, cd->name); mutex_unlock(&chrdevs_lock); } }

Contributors

PersonTokensPropCommitsCommitProp
joe kortyjoe korty2738.57%120.00%
andrew mortonandrew morton2028.57%120.00%
brian gerstbrian gerst1521.43%120.00%
neil hormanneil horman710.00%120.00%
jes sorensenjes sorensen11.43%120.00%
Total70100.00%5100.00%

#endif /* CONFIG_PROC_FS */ /* * Register a single major with a specified minor range. * * If major == 0 this functions will dynamically allocate a major and return * its number. * * If major > 0 this function will attempt to reserve the passed range of * minors and will return zero on success. * * Returns a -ve errno on failure. */
static struct char_device_struct * __register_chrdev_region(unsigned int major, unsigned int baseminor, int minorct, const char *name) { struct char_device_struct *cd, **cp; int ret = 0; int i; cd = kzalloc(sizeof(struct char_device_struct), GFP_KERNEL); if (cd == NULL) return ERR_PTR(-ENOMEM); mutex_lock(&chrdevs_lock); /* temporary */ if (major == 0) { for (i = ARRAY_SIZE(chrdevs)-1; i > 0; i--) { if (chrdevs[i] == NULL) break; } if (i < CHRDEV_MAJOR_DYN_END) pr_warn("CHRDEV \"%s\" major number %d goes below the dynamic allocation range\n", name, i); if (i == 0) { ret = -EBUSY; goto out; } major = i; } cd->major = major; cd->baseminor = baseminor; cd->minorct = minorct; strlcpy(cd->name, name, sizeof(cd->name)); i = major_to_index(major); for (cp = &chrdevs[i]; *cp; cp = &(*cp)->next) if ((*cp)->major > major || ((*cp)->major == major && (((*cp)->baseminor >= baseminor) || ((*cp)->baseminor + (*cp)->minorct > baseminor)))) break; /* Check for overlapping minor ranges. */ if (*cp && (*cp)->major == major) { int old_min = (*cp)->baseminor; int old_max = (*cp)->baseminor + (*cp)->minorct - 1; int new_min = baseminor; int new_max = baseminor + minorct - 1; /* New driver overlaps from the left. */ if (new_max >= old_min && new_max <= old_max) { ret = -EBUSY; goto out; } /* New driver overlaps from the right. */ if (new_min <= old_max && new_min >= old_min) { ret = -EBUSY; goto out; } } cd->next = *cp; *cp = cd; mutex_unlock(&chrdevs_lock); return cd; out: mutex_unlock(&chrdevs_lock); kfree(cd); return ERR_PTR(ret); }

Contributors

PersonTokensPropCommitsCommitProp
andrew mortonandrew morton20851.11%325.00%
amos waterlandamos waterland9122.36%18.33%
brian gerstbrian gerst5613.76%18.33%
al viroal viro225.41%18.33%
linus walleijlinus walleij143.44%18.33%
cyrill gorcunovcyrill gorcunov71.72%18.33%
neil hormanneil horman40.98%18.33%
jes sorensenjes sorensen30.74%18.33%
oliver neukumoliver neukum10.25%18.33%
fengguang wufengguang wu10.25%18.33%
Total407100.00%12100.00%


static struct char_device_struct * __unregister_chrdev_region(unsigned major, unsigned baseminor, int minorct) { struct char_device_struct *cd = NULL, **cp; int i = major_to_index(major); mutex_lock(&chrdevs_lock); for (cp = &chrdevs[i]; *cp; cp = &(*cp)->next) if ((*cp)->major == major && (*cp)->baseminor == baseminor && (*cp)->minorct == minorct) break; if (*cp) { cd = *cp; *cp = cd->next; } mutex_unlock(&chrdevs_lock); return cd; }

Contributors

PersonTokensPropCommitsCommitProp
andrew mortonandrew morton7863.41%240.00%
al viroal viro2822.76%120.00%
brian gerstbrian gerst1512.20%120.00%
jes sorensenjes sorensen21.63%120.00%
Total123100.00%5100.00%

/** * register_chrdev_region() - register a range of device numbers * @from: the first in the desired range of device numbers; must include * the major number. * @count: the number of consecutive device numbers required * @name: the name of the device or driver. * * Return value is zero on success, a negative error code on failure. */
int register_chrdev_region(dev_t from, unsigned count, const char *name) { struct char_device_struct *cd; dev_t to = from + count; dev_t n, next; for (n = from; n < to; n = next) { next = MKDEV(MAJOR(n)+1, 0); if (next > to) next = to; cd = __register_chrdev_region(MAJOR(n), MINOR(n), next - n, name); if (IS_ERR(cd)) goto fail; } return 0; fail: to = n; for (n = from; n < to; n = next) { next = MKDEV(MAJOR(n)+1, 0); kfree(__unregister_chrdev_region(MAJOR(n), MINOR(n), next - n)); } return PTR_ERR(cd); }

Contributors

PersonTokensPropCommitsCommitProp
al viroal viro16094.67%125.00%
brian gerstbrian gerst63.55%125.00%
andrew mortonandrew morton21.18%125.00%
stephen hemmingerstephen hemminger10.59%125.00%
Total169100.00%4100.00%

/** * alloc_chrdev_region() - register a range of char device numbers * @dev: output parameter for first assigned number * @baseminor: first of the requested range of minor numbers * @count: the number of minor numbers required * @name: the name of the associated device or driver * * Allocates a range of char device numbers. The major number will be * chosen dynamically, and returned (along with the first minor number) * in @dev. Returns zero or a negative error code. */
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name) { struct char_device_struct *cd; cd = __register_chrdev_region(0, baseminor, count, name); if (IS_ERR(cd)) return PTR_ERR(cd); *dev = MKDEV(cd->major, cd->baseminor); return 0; }

Contributors

PersonTokensPropCommitsCommitProp
al viroal viro5175.00%120.00%
andrew mortonandrew morton811.76%120.00%
paul fulghumpaul fulghum45.88%120.00%
brian gerstbrian gerst45.88%120.00%
stephen hemmingerstephen hemminger11.47%120.00%
Total68100.00%5100.00%

/** * __register_chrdev() - create and register a cdev occupying a range of minors * @major: major device number or 0 for dynamic allocation * @baseminor: first of the requested range of minor numbers * @count: the number of minor numbers required * @name: name of this range of devices * @fops: file operations associated with this devices * * If @major == 0 this functions will dynamically allocate a major and return * its number. * * If @major > 0 this function will attempt to reserve a device with the given * major number and will return zero on success. * * Returns a -ve errno on failure. * * The name of this device has nothing to do with the name of the device in * /dev. It only helps to keep track of the different owners of devices. If * your module name has only one type of devices it's ok to use e.g. the name * of the module here. */
int __register_chrdev(unsigned int major, unsigned int baseminor, unsigned int count, const char *name, const struct file_operations *fops) { struct char_device_struct *cd; struct cdev *cdev; int err = -ENOMEM; cd = __register_chrdev_region(major, baseminor, count, name); if (IS_ERR(cd)) return PTR_ERR(cd); cdev = cdev_alloc(); if (!cdev) goto out2; cdev->owner = fops->owner; cdev->ops = fops; kobject_set_name(&cdev->kobj, "%s", name); err = cdev_add(cdev, MKDEV(cd->major, baseminor), count); if (err) goto out; cd->cdev = cdev; return major ? 0 : cd->major; out: kobject_put(&cdev->kobj); out2: kfree(__unregister_chrdev_region(cd->major, baseminor, count)); return err; }

Contributors

PersonTokensPropCommitsCommitProp
al viroal viro15486.52%450.00%
tejun heotejun heo158.43%112.50%
stephen hemmingerstephen hemminger42.25%112.50%
brian gerstbrian gerst42.25%112.50%
arjan van de venarjan van de ven10.56%112.50%
Total178100.00%8100.00%

/** * unregister_chrdev_region() - unregister a range of device numbers * @from: the first in the range of numbers to unregister * @count: the number of device numbers to unregister * * This function will unregister a range of @count device numbers, * starting with @from. The caller should normally be the one who * allocated those numbers in the first place... */
void unregister_chrdev_region(dev_t from, unsigned count) { dev_t to = from + count; dev_t n, next; for (n = from; n < to; n = next) { next = MKDEV(MAJOR(n)+1, 0); if (next > to) next = to; kfree(__unregister_chrdev_region(MAJOR(n), MINOR(n), next - n)); } }

Contributors

PersonTokensPropCommitsCommitProp
al viroal viro83100.00%2100.00%
Total83100.00%2100.00%

/** * __unregister_chrdev - unregister and destroy a cdev * @major: major device number * @baseminor: first of the range of minor numbers * @count: the number of minor numbers this cdev is occupying * @name: name of this range of devices * * Unregister and destroy the cdev occupying the region described by * @major, @baseminor and @count. This function undoes what * __register_chrdev() did. */
void __unregister_chrdev(unsigned int major, unsigned int baseminor, unsigned int count, const char *name) { struct char_device_struct *cd; cd = __unregister_chrdev_region(major, baseminor, count); if (cd && cd->cdev) cdev_del(cd->cdev); kfree(cd); }

Contributors

PersonTokensPropCommitsCommitProp
al viroal viro2950.00%240.00%
andrew mortonandrew morton1729.31%120.00%
tejun heotejun heo1118.97%120.00%
akinobu mitaakinobu mita11.72%120.00%
Total58100.00%5100.00%

static DEFINE_SPINLOCK(cdev_lock);
static struct kobject *cdev_get(struct cdev *p) { struct module *owner = p->owner; struct kobject *kobj; if (owner && !try_module_get(owner)) return NULL; kobj = kobject_get(&p->kobj); if (!kobj) module_put(owner); return kobj; }

Contributors

PersonTokensPropCommitsCommitProp
arjan van de venarjan van de ven63100.00%1100.00%
Total63100.00%1100.00%


void cdev_put(struct cdev *p) { if (p) { struct module *owner = p->owner; kobject_put(&p->kobj); module_put(owner); } }

Contributors

PersonTokensPropCommitsCommitProp
arjan van de venarjan van de ven2976.32%150.00%
brian kingbrian king923.68%150.00%
Total38100.00%2100.00%

/* * Called every time a character special file is opened */
static int chrdev_open(struct inode *inode, struct file *filp) { const struct file_operations *fops; struct cdev *p; struct cdev *new = NULL; int ret = 0; spin_lock(&cdev_lock); p = inode->i_cdev; if (!p) { struct kobject *kobj; int idx; spin_unlock(&cdev_lock); kobj = kobj_lookup(cdev_map, inode->i_rdev, &idx); if (!kobj) return -ENXIO; new = container_of(kobj, struct cdev, kobj); spin_lock(&cdev_lock); /* Check i_cdev again in case somebody beat us to it while we dropped the lock. */ p = inode->i_cdev; if (!p) { inode->i_cdev = p = new; list_add(&inode->i_devices, &p->list); new = NULL; } else if (!cdev_get(p)) ret = -ENXIO; } else if (!cdev_get(p)) ret = -ENXIO; spin_unlock(&cdev_lock); cdev_put(new); if (ret) return ret; ret = -ENXIO; fops = fops_get(p->ops); if (!fops) goto out_cdev_put; replace_fops(filp, fops); if (filp->f_op->open) { ret = filp->f_op->open(inode, filp); if (ret) goto out_cdev_put; } return 0; out_cdev_put: cdev_put(p); return ret; }

Contributors

PersonTokensPropCommitsCommitProp
al viroal viro18869.63%228.57%
brian gerstbrian gerst5921.85%114.29%
christoph hellwigchristoph hellwig186.67%114.29%
andrew mortonandrew morton31.11%114.29%
cheng renquancheng renquan10.37%114.29%
jonathan corbetjonathan corbet10.37%114.29%
Total270100.00%7100.00%


void cd_forget(struct inode *inode) { spin_lock(&cdev_lock); list_del_init(&inode->i_devices); inode->i_cdev = NULL; inode->i_mapping = &inode->i_data; spin_unlock(&cdev_lock); }

Contributors

PersonTokensPropCommitsCommitProp
al viroal viro3680.00%150.00%
dan williamsdan williams920.00%150.00%
Total45100.00%2100.00%


static void cdev_purge(struct cdev *cdev) { spin_lock(&cdev_lock); while (!list_empty(&cdev->list)) { struct inode *inode; inode = container_of(cdev->list.next, struct inode, i_devices); list_del_init(&inode->i_devices); inode->i_cdev = NULL; } spin_unlock(&cdev_lock); }

Contributors

PersonTokensPropCommitsCommitProp
al viroal viro7098.59%150.00%
adrian bunkadrian bunk11.41%150.00%
Total71100.00%2100.00%

/* * Dummy default file-operations: the only thing this does * is contain the open that then fills in the correct operations * depending on the special file... */ const struct file_operations def_chr_fops = { .open = chrdev_open, .llseek = noop_llseek, };
static struct kobject *exact_match(dev_t dev, int *part, void *data) { struct cdev *p = data; return &p->kobj; }

Contributors

PersonTokensPropCommitsCommitProp
al viroal viro32100.00%1100.00%
Total32100.00%1100.00%


static int exact_lock(dev_t dev, void *data) { struct cdev *p = data; return cdev_get(p) ? 0 : -1; }

Contributors

PersonTokensPropCommitsCommitProp
al viroal viro31100.00%1100.00%
Total31100.00%1100.00%

/** * cdev_add() - add a char device to the system * @p: the cdev structure for the device * @dev: the first device number for which this device is responsible * @count: the number of consecutive minor numbers corresponding to this * device * * cdev_add() adds the device represented by @p to the system, making it * live immediately. A negative error code is returned on failure. */
int cdev_add(struct cdev *p, dev_t dev, unsigned count) { int error; p->dev = dev; p->count = count; error = kobj_map(cdev_map, dev, count, NULL, exact_match, exact_lock, p); if (error) return error; kobject_get(p->kobj.parent); return 0; }

Contributors

PersonTokensPropCommitsCommitProp
al viroal viro3652.17%250.00%
dmitry torokhovdmitry torokhov2434.78%125.00%
jonathan corbetjonathan corbet913.04%125.00%
Total69100.00%4100.00%


static void cdev_unmap(dev_t dev, unsigned count) { kobj_unmap(cdev_map, dev, count); }

Contributors

PersonTokensPropCommitsCommitProp
al viroal viro2095.24%150.00%
jonathan corbetjonathan corbet14.76%150.00%
Total21100.00%2100.00%

/** * cdev_del() - remove a cdev from the system * @p: the cdev structure to be removed * * cdev_del() removes @p from the system, possibly freeing the structure * itself. */
void cdev_del(struct cdev *p) { cdev_unmap(p->dev, p->count); kobject_put(&p->kobj); }

Contributors

PersonTokensPropCommitsCommitProp
al viroal viro1862.07%266.67%
jonathan corbetjonathan corbet1137.93%133.33%
Total29100.00%3100.00%


static void cdev_default_release(struct kobject *kobj) { struct cdev *p = container_of(kobj, struct cdev, kobj); struct kobject *parent = kobj->parent; cdev_purge(p); kobject_put(parent); }

Contributors

PersonTokensPropCommitsCommitProp
al viroal viro3168.89%150.00%
dmitry torokhovdmitry torokhov1431.11%150.00%
Total45100.00%2100.00%


static void cdev_dynamic_release(struct kobject *kobj) { struct cdev *p = container_of(kobj, struct cdev, kobj); struct kobject *parent = kobj->parent; cdev_purge(p); kfree(p); kobject_put(parent); }

Contributors

PersonTokensPropCommitsCommitProp
al viroal viro3672.00%266.67%
dmitry torokhovdmitry torokhov1428.00%133.33%
Total50100.00%3100.00%

static struct kobj_type ktype_cdev_default = { .release = cdev_default_release, }; static struct kobj_type ktype_cdev_dynamic = { .release = cdev_dynamic_release, }; /** * cdev_alloc() - allocate a cdev structure * * Allocates and returns a cdev structure, or NULL on failure. */
struct cdev *cdev_alloc(void) { struct cdev *p = kzalloc(sizeof(struct cdev), GFP_KERNEL); if (p) { INIT_LIST_HEAD(&p->list); kobject_init(&p->kobj, &ktype_cdev_dynamic); } return p; }

Contributors

PersonTokensPropCommitsCommitProp
al viroal viro4890.57%240.00%
greg kroah-hartmangreg kroah-hartman47.55%240.00%
oliver neukumoliver neukum11.89%120.00%
Total53100.00%5100.00%

/** * cdev_init() - initialize a cdev structure * @cdev: the structure to initialize * @fops: the file_operations for this device * * Initializes @cdev, remembering @fops, making it ready to add to the * system with cdev_add(). */
void cdev_init(struct cdev *cdev, const struct file_operations *fops) { memset(cdev, 0, sizeof *cdev); INIT_LIST_HEAD(&cdev->list); kobject_init(&cdev->kobj, &ktype_cdev_default); cdev->ops = fops; }

Contributors

PersonTokensPropCommitsCommitProp
al viroal viro3669.23%233.33%
roland dreierroland dreier1121.15%116.67%
greg kroah-hartmangreg kroah-hartman47.69%233.33%
arjan van de venarjan van de ven11.92%116.67%
Total52100.00%6100.00%


static struct kobject *base_probe(dev_t dev, int *part, void *data) { if (request_module("char-major-%d-%d", MAJOR(dev), MINOR(dev)) > 0) /* Make old-style 2.4 aliases work */ request_module("char-major-%d", MAJOR(dev)); return NULL; }

Contributors

PersonTokensPropCommitsCommitProp
al viroal viro3057.69%125.00%
andrew mortonandrew morton2140.38%250.00%
randy dunlaprandy dunlap11.92%125.00%
Total52100.00%4100.00%


void __init chrdev_init(void) { cdev_map = kobj_map_init(base_probe, &chrdevs_lock); }

Contributors

PersonTokensPropCommitsCommitProp
al viroal viro1688.89%133.33%
linus torvaldslinus torvalds15.56%133.33%
greg kroah-hartmangreg kroah-hartman15.56%133.33%
Total18100.00%3100.00%

/* Let modules do char dev stuff */ EXPORT_SYMBOL(register_chrdev_region); EXPORT_SYMBOL(unregister_chrdev_region); EXPORT_SYMBOL(alloc_chrdev_region); EXPORT_SYMBOL(cdev_init); EXPORT_SYMBOL(cdev_alloc); EXPORT_SYMBOL(cdev_del); EXPORT_SYMBOL(cdev_add); EXPORT_SYMBOL(__register_chrdev); EXPORT_SYMBOL(__unregister_chrdev);

Overall Contributors

PersonTokensPropCommitsCommitProp
al viroal viro120051.68%58.62%
andrew mortonandrew morton44118.99%915.52%
brian gerstbrian gerst1928.27%11.72%
arjan van de venarjan van de ven954.09%35.17%
amos waterlandamos waterland913.92%11.72%
dmitry torokhovdmitry torokhov522.24%11.72%
joe kortyjoe korty381.64%11.72%
tejun heotejun heo301.29%11.72%
jonathan corbetjonathan corbet281.21%46.90%
christoph hellwigchristoph hellwig210.90%23.45%
neil hormanneil horman140.60%11.72%
linus walleijlinus walleij140.60%11.72%
roland dreierroland dreier110.47%11.72%
linus torvaldslinus torvalds100.43%35.17%
jes sorensenjes sorensen100.43%11.72%
greg kroah-hartmangreg kroah-hartman90.39%35.17%
dan williamsdan williams90.39%11.72%
david howellsdavid howells90.39%35.17%
brian kingbrian king90.39%11.72%
cyrill gorcunovcyrill gorcunov70.30%11.72%
thomas gleixnerthomas gleixner70.30%11.72%
stephen hemmingerstephen hemminger60.26%23.45%
arnd bergmannarnd bergmann50.22%11.72%
paul fulghumpaul fulghum40.17%11.72%
oliver neukumoliver neukum20.09%11.72%
cheng renquancheng renquan10.04%11.72%
yang zhangyang zhang10.04%11.72%
fengguang wufengguang wu10.04%11.72%
akinobu mitaakinobu mita10.04%11.72%
partha pratim mukherjeepartha pratim mukherjee10.04%11.72%
andries brouwerandries brouwer10.04%11.72%
adrian bunkadrian bunk10.04%11.72%
randy dunlaprandy dunlap10.04%11.72%
Total2322100.00%58100.00%
Directory: fs