/*****************************************************************************/

/*
 *	inode.c  --  Inode/Dentry functions for the USB device file system.
 *
 *	Copyright (C) 2000
 *          Thomas Sailer (sailer@ife.ee.ethz.ch)
 *
 *	This program is free software; you can redistribute it and/or modify
 *	it under the terms of the GNU General Public License as published by
 *	the Free Software Foundation; either version 2 of the License, or
 *	(at your option) any later version.
 *
 *	This program is distributed in the hope that it will be useful,
 *	but WITHOUT ANY WARRANTY; without even the implied warranty of
 *	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *	GNU General Public License for more details.
 *
 *	You should have received a copy of the GNU General Public License
 *	along with this program; if not, write to the Free Software
 *	Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 *  $Id: inode.c,v 1.3 2000/01/11 13:58:25 tom Exp $
 *
 *  History:
 *   0.1  04.01.2000  Created
 */

/*****************************************************************************/

#define __NO_VERSION__
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/sched.h>
#include <linux/smp_lock.h>
#include <linux/locks.h>
#include <linux/init.h>
#include <linux/proc_fs.h>
#include <asm/uaccess.h>

#include "usb.h"
#include "usbdevice_fs.h"

/* --------------------------------------------------------------------- */

static LIST_HEAD(superlist);

extern struct inode_operations usbdevfs_bus_inode_operations;

static struct inode_operations devices_inode_operations = {
        &usbdevfs_devices_fops
};

static struct inode_operations drivers_inode_operations = {
        &usbdevfs_drivers_fops
};

struct special {
	const char *name;
	struct inode_operations *iops;
	struct list_head inodes;
};

static struct special special[] = { 
	{ "devices", &devices_inode_operations,  },
	{ "drivers", &drivers_inode_operations,  }
};

#define NRSPECIAL (sizeof(special)/sizeof(special[0]))

/* --------------------------------------------------------------------- */

static int dnumber(struct dentry *dentry)
{
	const char *name;
	unsigned int s;

	if (dentry->d_name.len != 3)
		return -1;
	name = dentry->d_name.name;
	if (name[0] < '0' || name[0] > '9' ||
	    name[1] < '0' || name[1] > '9' ||
	    name[2] < '0' || name[2] > '9')
		return -1;
	s = name[0] - '0';
	s = s * 10 + name[1] - '0';
	s = s * 10 + name[2] - '0';
	return s;
}

/*
 * utility functions; should be called with the kernel lock held
 * to protect against busses/devices appearing/disappearing
 */

static void new_dev_inode(struct usb_device *dev, struct super_block *sb)
{
	struct inode *inode;
	unsigned int devnum = dev->devnum;
	unsigned int busnum = dev->bus->busnum;

	if (devnum < 1 || devnum > 127 || busnum > 255)
		return;
	inode = iget(sb, IDEVICE | (busnum << 8) | devnum);
	if (!inode) {
		printk(KERN_ERR "usbdevfs: cannot create inode for bus %u device %u\n", busnum, devnum);
		return;
	}
	inode->i_atime = inode->i_mtime = inode->i_ctime = CURRENT_TIME;
	inode->i_uid = sb->u.usbdevfs_sb.devuid;
	inode->i_gid = sb->u.usbdevfs_sb.devgid;
	inode->i_mode = sb->u.usbdevfs_sb.devmode | S_IFREG;
	inode->i_op = &usbdevfs_device_inode_operations;
	inode->i_size = sizeof(struct usb_device_descriptor);
	inode->u.usbdev_i.p.dev = dev;
	list_add_tail(&inode->u.usbdev_i.slist, &sb->u.usbdevfs_sb.ilist);
	list_add_tail(&inode->u.usbdev_i.dlist, &dev->inodes);
}

static void recurse_new_dev_inode(struct usb_device *dev, struct super_block *sb)
{
	unsigned int i;

	if (!dev)
		return;
	new_dev_inode(dev, sb);
	for (i = 0; i < dev->maxchild; i++) {
                if (!dev->children[i])
                        continue;
		recurse_new_dev_inode(dev->children[i], sb);
	}
}

static void new_bus_inode(struct usb_bus *bus, struct super_block *sb)
{
	struct inode *inode;
	unsigned int busnum = bus->busnum;

	if (busnum > 255)
		return;
	inode = iget(sb, IBUS | (busnum << 8));
	if (!inode) {
		printk(KERN_ERR "usbdevfs: cannot create inode for bus %u\n", busnum);
		return;
	}
	inode->i_atime = inode->i_mtime = inode->i_ctime = CURRENT_TIME;
	inode->i_uid = sb->u.usbdevfs_sb.busuid;
	inode->i_gid = sb->u.usbdevfs_sb.busgid;
	inode->i_mode = sb->u.usbdevfs_sb.busmode | S_IFDIR;
	inode->i_op = &usbdevfs_bus_inode_operations;
	inode->u.usbdev_i.p.bus = bus;
	list_add_tail(&inode->u.usbdev_i.slist, &sb->u.usbdevfs_sb.ilist);
	list_add_tail(&inode->u.usbdev_i.dlist, &bus->inodes);
}

static void free_inode(struct inode *inode)
{
	inode->u.usbdev_i.p.bus = NULL;
	inode->u.usbdev_i.p.dev = NULL;
	inode->i_mode &= ~S_IRWXUGO;
	inode->i_uid = inode->i_gid = 0;
	inode->i_op = NULL;
	inode->i_size = 0;
	list_del(&inode->u.usbdev_i.slist);
	INIT_LIST_HEAD(&inode->u.usbdev_i.slist);
	list_del(&inode->u.usbdev_i.dlist);
	INIT_LIST_HEAD(&inode->u.usbdev_i.dlist);
	iput(inode);
}

static struct usb_bus *usbdevfs_findbus(int busnr)
{
        struct list_head *list;
        struct usb_bus *bus;

        for (list = usb_bus_list.next; list != &usb_bus_list; list = list->next) {
                bus = list_entry(list, struct usb_bus, bus_list);
                if (bus->busnum == busnr)
                        return bus;
        }
        return NULL;
}

#if 0
static struct usb_device *finddev(struct usb_device *dev, int devnr)
{
        unsigned int i;
        struct usb_device *d2;

        if (!dev)
                return NULL;
        if (dev->devnum == devnr)
                return dev;
        for (i = 0; i < dev->maxchild; i++) {
                if (!dev->children[i])
                        continue;
                if ((d2 = finddev(dev->children[i], devnr)))
                        return d2;
        }
        return NULL;
}

static struct usb_device *usbdevfs_finddevice(struct usb_bus *bus, int devnr)
{
        return finddev(bus->root_hub, devnr);
}
#endif

/* --------------------------------------------------------------------- */

static int usbdevfs_revalidate(struct dentry *dentry, int flags)
{
	struct inode *inode = dentry->d_inode;

        if (!inode)
                return 0;
	if (ITYPE(inode->i_ino) == IBUS && !inode->u.usbdev_i.p.bus)
		return 0;
	if (ITYPE(inode->i_ino) == IDEVICE && !inode->u.usbdev_i.p.dev)
		return 0;
	return 1;
}

static struct dentry_operations usbdevfs_dentry_operations = {
        usbdevfs_revalidate,    /* d_revalidate */
        NULL,                   /* d_hash */
        NULL,                   /* d_compare */
};

static struct dentry *usbdevfs_root_lookup(struct inode *dir, struct dentry *dentry)
{
	int busnr;
	unsigned long ino = 0;
	unsigned int i;
	struct inode *inode;

	/* sanity check */
	if (dir->i_ino != IROOT)
		return ERR_PTR(-EINVAL);
	dentry->d_op = &usbdevfs_dentry_operations;
	busnr = dnumber(dentry);
	if (busnr >= 0 && busnr <= 255)
		ino = IBUS | (busnr << 8);
	if (!ino) {
		for (i = 0; i < NRSPECIAL; i++) {
			if (strlen(special[i].name) == dentry->d_name.len && 
			    !strncmp(special[i].name, dentry->d_name.name, dentry->d_name.len)) {
				ino = ISPECIAL | (i + IROOT + 1);
				break;
			}
		}
	}
	if (!ino)
		return ERR_PTR(-ENOENT);
	inode = iget(dir->i_sb, ino);
	if (!inode)
		return ERR_PTR(-EINVAL);
	if (inode && ITYPE(ino) == IBUS && inode->u.usbdev_i.p.bus == NULL) {
		iput(inode);
		inode = NULL;
	}
	d_add(dentry, inode);
	return NULL;
}

static struct dentry *usbdevfs_bus_lookup(struct inode *dir, struct dentry *dentry)
{
	struct inode *inode;
	int devnr;

	/* sanity check */
	if (ITYPE(dir->i_ino) != IBUS)
		return ERR_PTR(-EINVAL);
	dentry->d_op = &usbdevfs_dentry_operations;
	devnr = dnumber(dentry);
	if (devnr < 1 || devnr > 127)
		return ERR_PTR(-ENOENT);
	inode = iget(dir->i_sb, IDEVICE | (dir->i_ino & (0xff << 8)) | devnr);
	if (!inode)
		return ERR_PTR(-EINVAL);
	if (inode && inode->u.usbdev_i.p.dev == NULL) {
		iput(inode);
		inode = NULL;
	}
	d_add(dentry, inode);
	return NULL;
}

static int usbdevfs_root_readdir(struct file *filp, void *dirent, filldir_t filldir)
{
	struct inode *inode = filp->f_dentry->d_inode;
	unsigned long ino = inode->i_ino;
	struct special *spec;
	struct list_head *list;
	struct usb_bus *bus;
	char numbuf[8];
	unsigned int i;

	/* sanity check */
	if (ino != IROOT)
		return -EINVAL;
	i = filp->f_pos;
	switch (i) {
	case 0:
		if (filldir(dirent, ".", 1, i, IROOT) < 0)
			return 0;
		filp->f_pos++;
		i++;
		/* fall through */

	case 1:
		if (filldir(dirent, "..", 2, i, IROOT) < 0)
			return 0;
		filp->f_pos++;
		i++;
		/* fall through */

	default:
		
		while (i >= 2 && i < 2+NRSPECIAL) {
			spec = &special[filp->f_pos-2];
			if (filldir(dirent, spec->name, strlen(spec->name), i, ISPECIAL | (filp->f_pos-2+IROOT)) < 0)
				return 0;
			filp->f_pos++;
			i++;
		}
		if (i < 2+NRSPECIAL)
			return 0;
		i -= 2+NRSPECIAL;
		lock_kernel();
		for (list = usb_bus_list.next; list != &usb_bus_list; list = list->next) {
			if (i > 0) {
				i--;
				continue;
			}
			bus = list_entry(list, struct usb_bus, bus_list);
			sprintf(numbuf, "%03d", bus->busnum);
			if (filldir(dirent, numbuf, 3, filp->f_pos, IBUS | ((bus->busnum & 0xff) << 8)) < 0)
				break;
			filp->f_pos++;
		}
		unlock_kernel();
		return 0;
	}
}

static int bus_readdir(struct usb_device *dev, unsigned long ino, int pos, struct file *filp, void *dirent, filldir_t filldir)
{
	char numbuf[8];
	unsigned int i;

	if (!dev)
		return pos;
	sprintf(numbuf, "%03d", dev->devnum);
	if (pos > 0)
		pos--;
	else {
		if (filldir(dirent, numbuf, 3, filp->f_pos, ino | (dev->devnum & 0xff)) < 0)
			return -1;
		filp->f_pos++;
	}
	for (i = 0; i < dev->maxchild; i++) {
		if (!dev->children[i])
			continue;
		pos = bus_readdir(dev->children[i], ino, pos, filp, dirent, filldir);
		if (pos < 0)
			return -1;
	}
	return pos;
}

static int usbdevfs_bus_readdir(struct file *filp, void *dirent, filldir_t filldir)
{
	struct inode *inode = filp->f_dentry->d_inode;
	unsigned long ino = inode->i_ino;
	struct usb_bus *bus;

	/* sanity check */
	if (ITYPE(ino) != IBUS)
		return -EINVAL;
	switch ((unsigned int)filp->f_pos) {
	case 0:
		if (filldir(dirent, ".", 1, filp->f_pos, ino) < 0)
			return 0;
		filp->f_pos++;
		/* fall through */

	case 1:
		if (filldir(dirent, "..", 2, filp->f_pos, IROOT) < 0)
			return 0;
		filp->f_pos++;
		/* fall through */

	default:
		lock_kernel();
		bus = usbdevfs_findbus(IBUSNR(ino));
		bus_readdir(bus->root_hub, IDEVICE | ((bus->busnum & 0xff) << 8), filp->f_pos-2, filp, dirent, filldir);
		unlock_kernel();
		return 0;
	}
}

static struct file_operations usbdevfs_root_file_operations = {
	readdir: usbdevfs_root_readdir,
};

static struct inode_operations usbdevfs_root_inode_operations = {
	default_file_ops: &usbdevfs_root_file_operations,
	lookup: usbdevfs_root_lookup,
};

static struct file_operations usbdevfs_bus_file_operations = {
	readdir: usbdevfs_bus_readdir,
};

static struct inode_operations usbdevfs_bus_inode_operations = {
	default_file_ops: &usbdevfs_bus_file_operations,
	lookup: usbdevfs_bus_lookup,
};

static void usbdevfs_read_inode(struct inode *inode)
{
	struct special *spec;

	inode->i_ctime = inode->i_mtime = inode->i_atime = CURRENT_TIME;
	inode->i_mode = S_IFREG;
	inode->i_gid = inode->i_uid = 0;
	INIT_LIST_HEAD(&inode->u.usbdev_i.dlist);
	INIT_LIST_HEAD(&inode->u.usbdev_i.slist);
	inode->u.usbdev_i.p.dev = NULL;
	inode->u.usbdev_i.p.bus = NULL;
	switch (ITYPE(inode->i_ino)) {
	case ISPECIAL:
		if (inode->i_ino == IROOT) {
			inode->i_op = &usbdevfs_root_inode_operations;
			inode->i_mode = S_IFDIR | S_IRUGO | S_IXUGO;
			return;
		}
		if (inode->i_ino <= IROOT || inode->i_ino > IROOT+NRSPECIAL)
			return;
		spec = &special[inode->i_ino-(IROOT+1)];
		inode->i_op = spec->iops;
		return;

	case IDEVICE:
		return;

	case IBUS:
		return;

	default:
		return;
        }
}

static void usbdevfs_put_inode(struct inode *inode)
{
}

static void usbdevfs_put_super(struct super_block *sb)
{
	list_del(&sb->u.usbdevfs_sb.slist);
	INIT_LIST_HEAD(&sb->u.usbdevfs_sb.slist);
	while (!list_empty(&sb->u.usbdevfs_sb.ilist))
		free_inode(list_entry(sb->u.usbdevfs_sb.ilist.next, struct inode, u.usbdev_i.slist));
        MOD_DEC_USE_COUNT;
}

static int usbdevfs_statfs(struct super_block *sb, struct statfs *buf, int bufsiz)
{
        struct statfs tmp;

        tmp.f_type = USBDEVICE_SUPER_MAGIC;
        tmp.f_bsize = PAGE_SIZE/sizeof(long);   /* ??? */
        tmp.f_blocks = 0;
        tmp.f_bfree = 0;
        tmp.f_bavail = 0;
        tmp.f_files = 0;
        tmp.f_ffree = 0;
        tmp.f_namelen = NAME_MAX;
        return copy_to_user(buf, &tmp, bufsiz) ? -EFAULT : 0;
}

static struct super_operations usbdevfs_sops = { 
        usbdevfs_read_inode,
        NULL,
        usbdevfs_put_inode,
        NULL,
        NULL,
        usbdevfs_put_super,
        NULL,
        usbdevfs_statfs,
        NULL
};

struct super_block *usbdevfs_read_super(struct super_block *s, void *data, int silent)
{
        struct inode *root_inode, *inode;
	struct list_head *blist;
	struct usb_bus *bus;
	unsigned int i;
	uid_t devuid = 0, busuid = 0, listuid = 0;
	gid_t devgid = 0, busgid = 0, listgid = 0;
	umode_t devmode = S_IWUSR | S_IRUGO, busmode = S_IXUGO | S_IRUGO, listmode = S_IRUGO;
	char *curopt = NULL, *value;

	/* parse options */
	if (data)
		curopt = strtok(data, ",");
	for (; curopt; curopt = strtok(NULL, ",")) {
		if ((value = strchr(curopt, '=')) != NULL)
			*value++ = 0;
		if (!strcmp(curopt, "devuid")) {
			if (!value || !value[0])
				goto opterr;
			devuid = simple_strtoul(value, &value, 0);
			if (*value)
				goto opterr;
		}
		if (!strcmp(curopt, "devgid")) {
			if (!value || !value[0])
				goto opterr;
			devgid = simple_strtoul(value, &value, 0);
			if (*value)
				goto opterr;
		}
		if (!strcmp(curopt, "devmode")) {
			if (!value || !value[0])
				goto opterr;
			devmode = simple_strtoul(value, &value, 0) & S_IRWXUGO;
			if (*value)
				goto opterr;
		}
		if (!strcmp(curopt, "busuid")) {
			if (!value || !value[0])
				goto opterr;
			busuid = simple_strtoul(value, &value, 0);
			if (*value)
				goto opterr;
		}
		if (!strcmp(curopt, "busgid")) {
			if (!value || !value[0])
				goto opterr;
			busgid = simple_strtoul(value, &value, 0);
			if (*value)
				goto opterr;
		}
		if (!strcmp(curopt, "busmode")) {
			if (!value || !value[0])
				goto opterr;
			busmode = simple_strtoul(value, &value, 0) & S_IRWXUGO;
			if (*value)
				goto opterr;
		}
		if (!strcmp(curopt, "listuid")) {
			if (!value || !value[0])
				goto opterr;
			listuid = simple_strtoul(value, &value, 0);
			if (*value)
				goto opterr;
		}
		if (!strcmp(curopt, "listgid")) {
			if (!value || !value[0])
				goto opterr;
			listgid = simple_strtoul(value, &value, 0);
			if (*value)
				goto opterr;
		}
		if (!strcmp(curopt, "listmode")) {
			if (!value || !value[0])
				goto opterr;
			listmode = simple_strtoul(value, &value, 0) & S_IRWXUGO;
			if (*value)
				goto opterr;
		}
	}
	/* fill superblock */
        MOD_INC_USE_COUNT;
        lock_super(s);
        s->s_blocksize = 1024;
        s->s_blocksize_bits = 10;
        s->s_magic = USBDEVICE_SUPER_MAGIC;
        s->s_op = &usbdevfs_sops;
	INIT_LIST_HEAD(&s->u.usbdevfs_sb.slist);
	INIT_LIST_HEAD(&s->u.usbdevfs_sb.ilist);
	s->u.usbdevfs_sb.devuid = devuid;
	s->u.usbdevfs_sb.devgid = devgid;
	s->u.usbdevfs_sb.devmode = devmode;
	s->u.usbdevfs_sb.busuid = busuid;
	s->u.usbdevfs_sb.busgid = busgid;
	s->u.usbdevfs_sb.busmode = busmode;
	root_inode = iget(s, IROOT);
        if (!root_inode)
                goto out_no_root;
        s->s_root = d_alloc_root(root_inode);
        if (!s->s_root)
                goto out_no_root;
	list_add_tail(&s->u.usbdevfs_sb.slist, &superlist);
	unlock_super(s);
	for (i = 0; i < NRSPECIAL; i++) {
		if (!(inode = iget(s, IROOT+1+i)))
			continue;
		inode->i_uid = listuid;
		inode->i_gid = listgid;
		inode->i_mode = listmode | S_IFREG;
		list_add_tail(&inode->u.usbdev_i.slist, &s->u.usbdevfs_sb.ilist);
		list_add_tail(&inode->u.usbdev_i.dlist, &special[i].inodes);
	}
	lock_kernel();
	for (blist = usb_bus_list.next; blist != &usb_bus_list; blist = blist->next) {
		bus = list_entry(blist, struct usb_bus, bus_list);
		new_bus_inode(bus, s);
		recurse_new_dev_inode(bus->root_hub, s);
	}
	unlock_kernel();
        return s;

 out_no_root:
        printk("usbdevfs_read_super: get root inode failed\n");
        iput(root_inode);
        s->s_dev = 0;
        unlock_super(s);
	MOD_DEC_USE_COUNT;
        return NULL;

 opterr:
        printk(KERN_WARNING "usbdevfs: mount parameter error\n");
	s->s_dev = 0;
	return NULL;
}

static struct file_system_type usbdevice_fs_type = {
        "usbdevfs",
        0,
        usbdevfs_read_super,
        NULL
};

/* --------------------------------------------------------------------- */

void usbdevfs_add_bus(struct usb_bus *bus)
{
	struct list_head *slist;

	lock_kernel();
	for (slist = superlist.next; slist != &superlist; slist = slist->next)
		new_bus_inode(bus, list_entry(slist, struct super_block, u.usbdevfs_sb.slist));
	unlock_kernel();
	usbdevfs_conn_disc_event();
}

void usbdevfs_remove_bus(struct usb_bus *bus)
{
	lock_kernel();
	while (!list_empty(&bus->inodes))
		free_inode(list_entry(bus->inodes.next, struct inode, u.usbdev_i.dlist));
	unlock_kernel();
	usbdevfs_conn_disc_event();
}

void usbdevfs_add_device(struct usb_device *dev)
{
	struct list_head *slist;

	lock_kernel();
	for (slist = superlist.next; slist != &superlist; slist = slist->next)
		new_dev_inode(dev, list_entry(slist, struct super_block, u.usbdevfs_sb.slist));
	unlock_kernel();
	usbdevfs_conn_disc_event();
}

void usbdevfs_remove_device(struct usb_device *dev)
{
	struct dev_state *ds;
	struct siginfo sinfo;

	lock_kernel();
	while (!list_empty(&dev->inodes))
		free_inode(list_entry(dev->inodes.next, struct inode, u.usbdev_i.dlist));
	while (!list_empty(&dev->filelist)) {
		ds = list_entry(dev->filelist.next, struct dev_state, list);
		list_del(&ds->list);
		INIT_LIST_HEAD(&ds->list);
		down_write(&ds->devsem);
		ds->dev = NULL;
		up_write(&ds->devsem);
		if (ds->discsignr) {
			sinfo.si_signo = SIGPIPE;
			sinfo.si_errno = EPIPE;
			sinfo.si_code = SI_ASYNCIO;
			sinfo.si_addr = ds->disccontext;
			send_sig_info(ds->discsignr, &sinfo, ds->disctask);
		}
	}
	unlock_kernel();
	usbdevfs_conn_disc_event();
}

/* --------------------------------------------------------------------- */

static struct proc_dir_entry *usbdir = NULL;

int __init usbdevfs_init(void)
{
	int ret;

	for (ret = 0; ret < NRSPECIAL; ret++) {
		INIT_LIST_HEAD(&special[ret].inodes);
	}
	if ((ret = usb_register(&usbdevfs_driver)))
		return ret;
	if ((ret = register_filesystem(&usbdevice_fs_type)))
		usb_deregister(&usbdevfs_driver);
	/* create mount point for usbdevfs */
	usbdir = proc_mkdir("usb", proc_bus);
	return ret;
}

void __exit usbdevfs_cleanup(void)
{
	usb_deregister(&usbdevfs_driver);
	unregister_filesystem(&usbdevice_fs_type);
        if (usbdir)
                remove_proc_entry("usb", proc_bus);
}

#if 0
module_init(usbdevfs_init);
module_exit(usbdevfs_cleanup);
#endif
