/*
       *  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>
      #include <linux/module.h>
      #include <linux/smp_lock.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 rwlock_t chrdevs_lock = RW_LOCK_UNLOCKED;
      static struct device_struct chrdevs[MAX_CHRDEV];
      
      extern int get_blkdev_list(char *);
      
  43  int get_device_list(char * page)
      {
      	int i;
      	int len;
      
      	len = sprintf(page, "Character devices:\n");
      	read_lock(&chrdevs_lock);
  50  	for (i = 0; i < MAX_CHRDEV ; i++) {
  51  		if (chrdevs[i].fops) {
      			len += sprintf(page+len, "%3d %s\n", i, chrdevs[i].name);
      		}
      	}
  55  	read_unlock(&chrdevs_lock);
      	len += get_blkdev_list(page+len);
  57  	return len;
      }
      
      /*
      	Return the function table of a device.
      	Load the driver if needed.
      	Increment the reference count of module in question.
      */
  65  static struct file_operations * get_chrfops(unsigned int major, unsigned int minor)
      {
      	struct file_operations *ret = NULL;
      
  69  	if (!major || major >= MAX_CHRDEV)
  70  		return NULL;
      
      	read_lock(&chrdevs_lock);
      	ret = fops_get(chrdevs[major].fops);
  74  	read_unlock(&chrdevs_lock);
      #ifdef CONFIG_KMOD
      	if (ret && isa_tty_dev(major)) {
      		lock_kernel();
      		if (need_serial(major,minor)) {
      			/* Force request_module anyway, but what for? */
      			fops_put(ret);
      			ret = NULL;
      		}
      		unlock_kernel();
      	}
      	if (!ret) {
      		char name[20];
      		sprintf(name, "char-major-%d", major);
      		request_module(name);
      
      		read_lock(&chrdevs_lock);
      		ret = fops_get(chrdevs[major].fops);
      		read_unlock(&chrdevs_lock);
      	}
      #endif
  95  	return ret;
      }
      
  98  int register_chrdev(unsigned int major, const char * name, struct file_operations *fops)
      {
 100  	if (major == 0) {
      		write_lock(&chrdevs_lock);
 102  		for (major = MAX_CHRDEV-1; major > 0; major--) {
 103  			if (chrdevs[major].fops == NULL) {
      				chrdevs[major].name = name;
      				chrdevs[major].fops = fops;
 106  				write_unlock(&chrdevs_lock);
 107  				return major;
      			}
      		}
 110  		write_unlock(&chrdevs_lock);
 111  		return -EBUSY;
      	}
 113  	if (major >= MAX_CHRDEV)
 114  		return -EINVAL;
      	write_lock(&chrdevs_lock);
 116  	if (chrdevs[major].fops && chrdevs[major].fops != fops) {
 117  		write_unlock(&chrdevs_lock);
 118  		return -EBUSY;
      	}
      	chrdevs[major].name = name;
      	chrdevs[major].fops = fops;
 122  	write_unlock(&chrdevs_lock);
 123  	return 0;
      }
      
 126  int unregister_chrdev(unsigned int major, const char * name)
      {
 128  	if (major >= MAX_CHRDEV)
 129  		return -EINVAL;
      	write_lock(&chrdevs_lock);
 131  	if (!chrdevs[major].fops || strcmp(chrdevs[major].name, name)) {
 132  		write_unlock(&chrdevs_lock);
 133  		return -EINVAL;
      	}
      	chrdevs[major].name = NULL;
      	chrdevs[major].fops = NULL;
 137  	write_unlock(&chrdevs_lock);
 138  	return 0;
      }
      
      /*
       * Called every time a character special file is opened
       */
 144  int chrdev_open(struct inode * inode, struct file * filp)
      {
      	int ret = -ENODEV;
      
      	filp->f_op = get_chrfops(MAJOR(inode->i_rdev), MINOR(inode->i_rdev));
 149  	if (filp->f_op) {
      		ret = 0;
 151  		if (filp->f_op->open != NULL) {
 152  			lock_kernel();
      			ret = filp->f_op->open(inode,filp);
 154  			unlock_kernel();
      		}
      	}
 157  	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...
       */
      static struct file_operations def_chr_fops = {
      	open:		chrdev_open,
      };
      
      /*
       * Print device name (in decimal, hexadecimal or symbolic)
       * Note: returns pointer to static data!
       */
 173  const char * kdevname(kdev_t dev)
      {
      	static char buffer[32];
      	sprintf(buffer, "%02x:%02x", MAJOR(dev), MINOR(dev));
 177  	return buffer;
      }
      
 180  const char * cdevname(kdev_t dev)
      {
      	static char buffer[32];
      	const char * name = chrdevs[MAJOR(dev)].name;
      
 185  	if (!name)
      		name = "unknown-char";
      	sprintf(buffer, "%s(%d,%d)", name, MAJOR(dev), MINOR(dev));
 188  	return buffer;
      }
        
 191  static int sock_no_open(struct inode *irrelevant, struct file *dontcare)
      {
 193  	return -ENXIO;
      }
      
      static struct file_operations bad_sock_fops = {
      	open:		sock_no_open
      };
      
 200  void init_special_inode(struct inode *inode, umode_t mode, int rdev)
      {
      	inode->i_mode = mode;
 203  	if (S_ISCHR(mode)) {
      		inode->i_fop = &def_chr_fops;
      		inode->i_rdev = to_kdev_t(rdev);
 206  	} else if (S_ISBLK(mode)) {
      		inode->i_fop = &def_blk_fops;
      		inode->i_rdev = to_kdev_t(rdev);
      		inode->i_bdev = bdget(rdev);
 210  	} else if (S_ISFIFO(mode))
      		inode->i_fop = &def_fifo_fops;
 212  	else if (S_ISSOCK(mode))
      		inode->i_fop = &bad_sock_fops;
 214  	else
      		printk(KERN_DEBUG "init_special_inode: bogus imode (%o)\n", mode);
      }