/*****************************************************************************/ /* * 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.1.1.1 2003/06/23 22:18:35 jharrell Exp $ * * History: * 0.1 04.01.2000 Created */ /*****************************************************************************/ #define __NO_VERSION__ #include #include #include #include #include #include #include #include #include #include #include /* --------------------------------------------------------------------- */ /* * 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); 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 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; 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_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; 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); return s; out_no_root: printk("usbdevfs_read_super: get root inode failed\n"); iput(root_inode); return NULL; } static DECLARE_FSTYPE(usbdevice_fs_type, "usbdevfs", 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; } #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); #ifdef CONFIG_PROC_FS if (usbdir) remove_proc_entry("usb", proc_bus); #endif } #if 0 module_init(usbdevfs_init); module_exit(usbdevfs_cleanup); #endif