devices.c

Jan Bobrowski (jb@wizard.ae.krakow.pl)
Fri, 14 May 1999 11:49:46 +0200 (MET DST)


File fs/devices.c contains separate functions for block and character
devices. This redundancy can be removed by defining device_class structure
which may hold all differences between block and char device classes.

This is replacement for devices.c file from 2.3.0 kernel. It contains
old functions and structures for compatibility.

Jan Bobrowski
jb@wizard.ae.krakow.pl

--------------------------
/*
* linux/fs/devices.c
*
* (C) 1993 Matthias Urlichs -- collected common code and tables.
*
* Copyright (C) 1991, 1992 Linus Torvalds
*
* Added kerneld support: Jacques Gelinas and Bjorn Ekwall
* (changed to kmod)
*/

#include <linux/config.h>
#include <linux/fs.h>
#include <linux/major.h>
#include <linux/string.h>
#include <linux/sched.h>
#include <linux/stat.h>
#include <linux/fcntl.h>
#include <linux/errno.h>
#ifdef CONFIG_KMOD
#include <linux/kmod.h>

#include <linux/tty.h>

/* serial module kmod load support */
struct tty_driver *get_tty_driver(kdev_t device);
#define isa_tty_dev(ma) (ma == TTY_MAJOR || ma == TTYAUX_MAJOR)
#define need_serial(ma,mi) (get_tty_driver(MKDEV(ma,mi)) == NULL)
#endif

struct device_struct {
const char * name;
struct file_operations * fops;
};

static struct device_struct chrdevs[MAX_CHRDEV] = {
{ NULL, NULL },
};

static struct device_struct blkdevs[MAX_BLKDEV] = {
{ NULL, NULL },
};

struct device_class {
struct device_struct* table;
int max;
unsigned mask; /* Used to mask out block minor numbers */
const char* name_mangle; /* String to use to build the module name */
const char* name_unknown;
};

static struct device_class char_class = {
chrdevs,
MAX_CHRDEV,
~0,
"char-major-%d",
"unknown-char"
};

static struct device_class block_class = {
blkdevs,
MAX_BLKDEV,
~MINORMASK,
"block-major-%d",
"unknown-block"
};

static char * devname(struct device_class* dc, kdev_t dev);

static char* list_devices(char* ptr, char* text, struct device_class* dc)
{
int i;

ptr += sprintf(ptr,text);
for(i=0;i<dc->max;i++) {
if(dc->table[i].fops) {
ptr += sprintf(ptr, "%3d %s\n", i, dc->table[i].name);
}
}
return ptr;
}

int get_device_list(char * page)
{
char* ptr = page;

ptr = list_devices(ptr,"Character devices:\n",&char_class);
ptr = list_devices(ptr,"\nBlock devices:\n",&block_class);
return ptr - page;
}

/*
Return the function table of a device.
Load the driver if needed.
*/
static struct file_operations * get_fops(struct device_class* dc,unsigned devn)
{
struct file_operations *ret = NULL;
unsigned int major = MAJOR(devn);

if (major < dc->max) {
struct device_struct* dev=&dc->table[major];
#ifdef CONFIG_KMOD
/*
* I do get request for device 0. I have no idea why. It happen
* at shutdown time for one. Without the following test, the
* kernel will happily trigger a request_module() which will
* trigger kmod and modprobe for nothing (since there
* is no device with major number == 0. And furthermore
* it locks the reboot process :-(
*
* Jacques Gelinas (jacques@solucorp.qc.ca)
*
* A. Haritsis <ah@doc.ic.ac.uk>: fix for serial module
* though we need the minor here to check if serial dev,
* we pass only the normal major char dev to kmod
* as there is no other loadable dev on these majors
*/
if ((isa_tty_dev(major) && need_serial(major,MINOR(devn))) ||
(major != 0 && !dev->fops)) {
char name[20];
sprintf(name, dc->name_mangle, major);
request_module(name);
}
#endif
ret = dev->fops;
}
return ret;
}

int register_device(
struct device_class* dc,
unsigned int major,
const char * name,
struct file_operations *fops )
{
struct device_struct* table = dc->table;
int max = dc->max;
int ret;

if (major == 0) {
for (major = max; --major > 0;) {
if (table[major].fops == NULL) {
ret = major;
goto out_set;
}
}
return -EBUSY;
}
if (major >= max)
return -EINVAL;
if (table[major].fops && table[major].fops != fops)
return -EBUSY;
ret = 0;
out_set:
table[major].name = name;
table[major].fops = fops;
return ret;
}

int unregister_device(struct device_class* dc, unsigned int major, const char * name)
{
struct device_struct* table = dc->table;

if (major >= dc->max)
return -EINVAL;
if (!table[major].fops)
return -EINVAL;
if (strcmp(table[major].name, name))
return -EINVAL;
table[major].name = NULL;
table[major].fops = NULL;
return 0;
}

/*
* This routine checks whether a removable media has been changed,
* and invalidates all buffer-cache-entries in that case. This
* is a relatively slow routine, so we have to try to minimize using
* it. Thus it is called only upon a 'mount' or 'open'. This
* is the best way of combining speed and utility, I think.
* People changing diskettes in the middle of an operation deserve
* to loose :-)
*/
int check_disk_change(kdev_t dev)
{
int i;
struct file_operations * fops;
struct super_block * sb;
int ret = 0;

i = MAJOR(dev);
if (i >= MAX_BLKDEV || (fops = blkdevs[i].fops) == NULL)
goto out;
if (fops->check_media_change == NULL)
goto out;
if (!fops->check_media_change(dev))
goto out;

printk(KERN_DEBUG "VFS: Disk change detected on device %s\n",
devname(&block_class,dev));

sb = get_super(dev);
if (sb && invalidate_inodes(sb))
printk("VFS: busy inodes on changed media.\n");

invalidate_buffers(dev);

if (fops->revalidate)
fops->revalidate(dev);
ret++;
out:
return ret;
}

/*
* Called every time a device special file is opened
*/

int device_open(struct inode * inode, struct file * filp)
{
int ret = -ENODEV;
umode_t mode = inode->i_mode;
struct device_class* dc;

if (S_ISCHR(mode))
dc = &char_class;
else if (S_ISBLK(mode))
dc = &block_class;
else {
printk(KERN_DEBUG "device_open: bad mode (%o)\n", mode);
return ret;
}

filp->f_op = get_fops(dc, inode->i_rdev & dc->mask);
if (filp->f_op != NULL) {
ret = 0;
if (filp->f_op->open != NULL)
ret = filp->f_op->open(inode,filp);
}
return ret;
}

/*
* 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...
*/
struct file_operations def_dev_fops = {
NULL, /* lseek */
NULL, /* read */
NULL, /* write */
NULL, /* readdir */
NULL, /* poll */
NULL, /* ioctl */
NULL, /* mmap */
device_open, /* open */
NULL, /* flush */
NULL, /* release */
};

struct inode_operations device_inode_operations = {
&def_dev_fops, /* default file operations */
NULL, /* create */
NULL, /* lookup */
NULL, /* link */
NULL, /* unlink */
NULL, /* symlink */
NULL, /* mkdir */
NULL, /* rmdir */
NULL, /* mknod */
NULL, /* rename */
NULL, /* readlink */
NULL, /* readpage */
NULL, /* writepage */
NULL, /* bmap */
NULL, /* truncate */
NULL /* permission */
};

int blkdev_release(struct inode * inode)
{
struct file_operations *fops;

fops = get_fops(&block_class, inode->i_rdev & block_class.mask);
if (fops && fops->release)
return fops->release(inode,NULL);
return 0;
}

/*
* Print device name (in decimal, hexadecimal or symbolic)
* Note: returns pointer to static data!
*/
char * kdevname(kdev_t dev)
{
static char buffer[32];
sprintf(buffer, "%02x:%02x", MAJOR(dev), MINOR(dev));
return buffer;
}

static char * devname(struct device_class* dc, kdev_t dev)
{
static char buffer[32];
const char * name = dc->table[MAJOR(dev)].name;

if (!name)
name = dc->name_unknown;

sprintf(buffer, "%s(%d,%d)", name, MAJOR(dev), MINOR(dev));
return buffer;
}

void init_special_inode(struct inode *inode, umode_t mode, int rdev)
{
inode->i_mode = mode;
inode->i_op = NULL;
if (S_ISCHR(mode) || S_ISBLK(mode)) { /* how about S_ISDEV() */
inode->i_op = &device_inode_operations;
inode->i_rdev = to_kdev_t(rdev);
} else if (S_ISFIFO(mode))
init_fifo(inode);
else if (S_ISSOCK(mode))
;
else
printk(KERN_DEBUG "init_special_inode: bogus imode (%o)\n", mode);
}

/* COMPATIBILITY */

struct file_operations * get_blkfops(unsigned int major)
{
return get_fops(&block_class,MKDEV(major,0));
}

struct file_operations * get_chrfops(unsigned int major, unsigned int minor)
{
return get_fops(&char_class,MKDEV(major,minor));
}

int register_chrdev(unsigned int major, const char * name, struct file_operations *fops)
{
return register_device(&char_class,major,name,fops);
}

int register_blkdev(unsigned int major, const char * name, struct file_operations *fops)
{
return register_device(&block_class,major,name,fops);
}

int unregister_chrdev(unsigned int major, const char * name)
{
return unregister_device(&char_class,major,name);
}

int unregister_blkdev(unsigned int major, const char * name)
{
return unregister_device(&block_class,major,name);
}

char * bdevname(kdev_t dev)
{
return devname(&block_class,dev);
}

char * cdevname(kdev_t dev)
{
return devname(&char_class,dev);
}

int blkdev_open(struct inode * inode, struct file * filp)
{
inode->i_mode = S_IFBLK;
return device_open(inode,filp);
}

struct inode_operations blkdev_inode_operations = {
&def_dev_fops, /* default file operations */
};

struct inode_operations chrdev_inode_operations = {
&def_dev_fops, /* default file operations */
};

-
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@vger.rutgers.edu
Please read the FAQ at http://www.tux.org/lkml/