blob: 7598b3bdbb7d952372b90d5c5a3c9a4d261975d5 [file] [log] [blame]
/*****************************************************************************/
/*
* 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/config.h>
#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 <linux/usb.h>
#include <linux/usbdevice_fs.h>
#include <asm/uaccess.h>
static struct inode_operations usbdevfs_bus_inode_operations;
static struct file_operations usbdevfs_bus_file_operations;
/* --------------------------------------------------------------------- */
/*
* This list of superblocks is still used,
* but since usbdevfs became FS_SINGLE
* there is only one super_block.
*/
static LIST_HEAD(superlist);
struct special {
const char *name;
struct file_operations *fops;
struct inode *inode;
struct list_head inodes;
};
static struct special special[] = {
{ "devices", &usbdevfs_devices_fops, },
{ "drivers", &usbdevfs_drivers_fops, }
};
#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_fop = &usbdevfs_device_file_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->i_fop = &usbdevfs_bus_file_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_size = 0;
list_del(&inode->u.usbdev_i.slist);
list_del(&inode->u.usbdev_i.dlist);
iput(inode);
}
static int parse_options(struct super_block *s, char *data)
{
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])
return -EINVAL;
devuid = simple_strtoul(value, &value, 0);
if (*value)
return -EINVAL;
}
if (!strcmp(curopt, "devgid")) {
if (!value || !value[0])
return -EINVAL;
devgid = simple_strtoul(value, &value, 0);
if (*value)
return -EINVAL;
}
if (!strcmp(curopt, "devmode")) {
if (!value || !value[0])
return -EINVAL;
devmode = simple_strtoul(value, &value, 0) & S_IRWXUGO;
if (*value)
return -EINVAL;
}
if (!strcmp(curopt, "busuid")) {
if (!value || !value[0])
return -EINVAL;
busuid = simple_strtoul(value, &value, 0);
if (*value)
return -EINVAL;
}
if (!strcmp(curopt, "busgid")) {
if (!value || !value[0])
return -EINVAL;
busgid = simple_strtoul(value, &value, 0);
if (*value)
return -EINVAL;
}
if (!strcmp(curopt, "busmode")) {
if (!value || !value[0])
return -EINVAL;
busmode = simple_strtoul(value, &value, 0) & S_IRWXUGO;
if (*value)
return -EINVAL;
}
if (!strcmp(curopt, "listuid")) {
if (!value || !value[0])
return -EINVAL;
listuid = simple_strtoul(value, &value, 0);
if (*value)
return -EINVAL;
}
if (!strcmp(curopt, "listgid")) {
if (!value || !value[0])
return -EINVAL;
listgid = simple_strtoul(value, &value, 0);
if (*value)
return -EINVAL;
}
if (!strcmp(curopt, "listmode")) {
if (!value || !value[0])
return -EINVAL;
listmode = simple_strtoul(value, &value, 0) & S_IRWXUGO;
if (*value)
return -EINVAL;
}
}
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;
s->u.usbdevfs_sb.listuid = listuid;
s->u.usbdevfs_sb.listgid = listgid;
s->u.usbdevfs_sb.listmode = listmode;
return 0;
}
static struct usb_bus *usbdevfs_findbus(int busnr)
{
struct list_head *list;
struct usb_bus *bus;
down (&usb_bus_list_lock);
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) {
up (&usb_bus_list_lock);
return bus;
}
}
up (&usb_bus_list_lock);
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 = {
d_revalidate: usbdevfs_revalidate,
};
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, DT_DIR) < 0)
return 0;
filp->f_pos++;
i++;
/* fall through */
case 1:
if (filldir(dirent, "..", 2, i, IROOT, DT_DIR) < 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), DT_UNKNOWN) < 0)
return 0;
filp->f_pos++;
i++;
}
if (i < 2+NRSPECIAL)
return 0;
i -= 2+NRSPECIAL;
down (&usb_bus_list_lock);
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), DT_UNKNOWN) < 0)
break;
filp->f_pos++;
}
up (&usb_bus_list_lock);
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), DT_UNKNOWN) < 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, DT_DIR) < 0)
return 0;
filp->f_pos++;
/* fall through */
case 1:
if (filldir(dirent, "..", 2, filp->f_pos, IROOT, DT_DIR) < 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 = {
lookup: usbdevfs_root_lookup,
};
static struct file_operations usbdevfs_bus_file_operations = {
readdir: usbdevfs_bus_readdir,
};
static struct inode_operations usbdevfs_bus_inode_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;
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_fop = &usbdevfs_root_file_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_fop = spec->fops;
return;
case IDEVICE:
return;
case IBUS:
return;
default:
return;
}
}
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));
}
static int usbdevfs_statfs(struct super_block *sb, struct statfs *buf)
{
buf->f_type = USBDEVICE_SUPER_MAGIC;
buf->f_bsize = PAGE_SIZE/sizeof(long); /* ??? */
buf->f_bfree = 0;
buf->f_bavail = 0;
buf->f_ffree = 0;
buf->f_namelen = NAME_MAX;
return 0;
}
static int usbdevfs_remount(struct super_block *s, int *flags, char *data)
{
struct list_head *ilist = s->u.usbdevfs_sb.ilist.next;
struct inode *inode;
int ret;
if ((ret = parse_options(s, data))) {
printk(KERN_WARNING "usbdevfs: remount parameter error\n");
return ret;
}
for (; ilist != &s->u.usbdevfs_sb.ilist; ilist = ilist->next) {
inode = list_entry(ilist, struct inode, u.usbdev_i.slist);
switch (ITYPE(inode->i_ino)) {
case ISPECIAL :
inode->i_uid = s->u.usbdevfs_sb.listuid;
inode->i_gid = s->u.usbdevfs_sb.listgid;
inode->i_mode = s->u.usbdevfs_sb.listmode | S_IFREG;
break;
case IBUS :
inode->i_uid = s->u.usbdevfs_sb.busuid;
inode->i_gid = s->u.usbdevfs_sb.busgid;
inode->i_mode = s->u.usbdevfs_sb.busmode | S_IFDIR;
break;
case IDEVICE :
inode->i_uid = s->u.usbdevfs_sb.devuid;
inode->i_gid = s->u.usbdevfs_sb.devgid;
inode->i_mode = s->u.usbdevfs_sb.devmode | S_IFREG;
break;
}
}
return 0;
}
static struct super_operations usbdevfs_sops = {
read_inode: usbdevfs_read_inode,
put_super: usbdevfs_put_super,
statfs: usbdevfs_statfs,
remount_fs: usbdevfs_remount,
};
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;
if (parse_options(s, data)) {
printk(KERN_WARNING "usbdevfs: mount parameter error\n");
return NULL;
}
/* fill superblock */
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);
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;
lock_kernel();
list_add_tail(&s->u.usbdevfs_sb.slist, &superlist);
for (i = 0; i < NRSPECIAL; i++) {
if (!(inode = iget(s, IROOT+1+i)))
continue;
inode->i_uid = s->u.usbdevfs_sb.listuid;
inode->i_gid = s->u.usbdevfs_sb.listgid;
inode->i_mode = s->u.usbdevfs_sb.listmode | S_IFREG;
special[i].inode = inode;
list_add_tail(&inode->u.usbdev_i.slist, &s->u.usbdevfs_sb.ilist);
list_add_tail(&inode->u.usbdev_i.dlist, &special[i].inodes);
}
down (&usb_bus_list_lock);
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);
}
up (&usb_bus_list_lock);
unlock_kernel();
return s;
out_no_root:
printk("usbdevfs_read_super: get root inode failed\n");
iput(root_inode);
return NULL;
}
/*
* The usbdevfs name is now deprecated (as of 2.4.19).
* It will be removed when the 2.7.x development cycle is started.
* You have been warned :)
*/
static DECLARE_FSTYPE(usbdevice_fs_type, "usbdevfs", usbdevfs_read_super, FS_SINGLE);
static DECLARE_FSTYPE(usbfs_type, "usbfs", usbdevfs_read_super, FS_SINGLE);
/* --------------------------------------------------------------------- */
static void update_special_inodes (void)
{
int i;
for (i = 0; i < NRSPECIAL; i++) {
struct inode *inode = special[i].inode;
if (inode)
inode->i_atime = inode->i_mtime = inode->i_ctime = CURRENT_TIME;
}
}
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));
update_special_inodes();
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));
update_special_inodes();
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));
update_special_inodes();
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);
}
}
update_special_inodes();
unlock_kernel();
usbdevfs_conn_disc_event();
}
/* --------------------------------------------------------------------- */
#ifdef CONFIG_PROC_FS
static struct proc_dir_entry *usbdir = NULL;
#endif
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);
return ret;
}
if ((ret = register_filesystem(&usbfs_type))) {
usb_deregister(&usbdevfs_driver);
unregister_filesystem(&usbdevice_fs_type);
return ret;
}
#ifdef CONFIG_PROC_FS
/* create mount point for usbdevfs */
usbdir = proc_mkdir("usb", proc_bus);
#endif
return ret;
}
void __exit usbdevfs_cleanup(void)
{
usb_deregister(&usbdevfs_driver);
unregister_filesystem(&usbdevice_fs_type);
unregister_filesystem(&usbfs_type);
#ifdef CONFIG_PROC_FS
if (usbdir)
remove_proc_entry("usb", proc_bus);
#endif
}
#if 0
module_init(usbdevfs_init);
module_exit(usbdevfs_cleanup);
#endif