From: Alexander Viro <viro@math.psu.edu> To: Linus Torvalds <torvalds@transmeta.com> Subject: [RFC] per-driver filesystems made easy Date: Sat, 11 May 2002 16:53:53 -0400 (EDT) Cc: Patrick Mochel <mochel@osdl.org>, Jeff Garzik <jgarzik@mandrakesoft.com>, linux-fsdevel@vger.kernel.org OK, I've been promising that for quite a while and here's the first demo - PCI driver exporting a filesystem with minimal PITA. It's a toy driver - PCI side is completely trivial (I wanted to get temperature readings from VIA-based motherboard without screwing with lm-sensors). I've never done PCI drivers before and didn't want to mess with anything more complex than that. It exports a small tree (it's trivial to extend adding more readings, etc., but again, that's not the point). Right now all it got is 3 files - readings from 3 sensors. You say # mount -t devvia pci0:7.4 /mnt and you've got /mnt/temp{1,2,3}. The first half of that guy is the next batch of stuff for libfs.c - generic helpers. Quite a few of them are already present in some form in the tree - e.g. nfsd_fill_super() is pretty much a special case of simple_fill_super(), and single_open()/single_release() will cut down a lot of junk in fs/proc/proc_misc.c. Really new thing here is pci_get_sb(). All filesystem glue in driver is boiled down to this: ------------------------------------------------------------------- static struct tree_descr via_files[] = { [Via_Temp1] = {"temp1", &temp_ops, S_IRUGO}, [Via_Temp2] = {"temp2", &temp_ops, S_IRUGO}, [Via_Temp3] = {"temp3", &temp_ops, S_IRUGO}, /* last one */ {""} }; enum {VIA_MAGIC = 0x756961}; static struct super_block *via_get_sb(struct file_system_type *type, int flags, char *dev_name, void *data) { return pci_get_sb(type, flags, dev_name, &via_sensors_driver, via_files, VIA_MAGIC); } static struct file_system_type via_fs_type = { owner: THIS_MODULE, name: "devvia", get_sb: via_get_sb, kill_sb: kill_litter_super, }; ------------------------------------------------------------------- less than a screenful. It describes the tree (name/file_operations/permissions) and it that's pretty much it (VIA_MAGIC should go into inexistent devvia_fs.h - it's simply an arbitrary value to put into ->f_type upon statfs(2)). We have to provide file_operations for our files, obviously - be they seq_file based, pagecache based, whatever, but gluing a simple fs out of them is trivial now. And IMO it beats messing with procfs. The thing build and works here - anyone with VT82C686 (Apollo) is welcome to play with it. Enjoy. #include <linux/version.h> #include <linux/module.h> #include <linux/pci.h> #include <linux/slab.h> #include <linux/pagemap.h> #include <linux/seq_file.h> /* completely generic */ struct tree_descr { char *name; struct file_operations *ops; int mode; }; int simple_fill_super(struct super_block *s, int magic, struct tree_descr *files) { static struct super_operations s_ops = {statfs:simple_statfs}; struct inode *inode; struct dentry *root; struct dentry *dentry; int i; s->s_blocksize = PAGE_CACHE_SIZE; s->s_blocksize_bits = PAGE_CACHE_SHIFT; s->s_magic = magic; s->s_op = &s_ops; inode = new_inode(s); if (!inode) return -ENOMEM; inode->i_mode = S_IFDIR | 0755; inode->i_uid = inode->i_gid = 0; inode->i_blksize = PAGE_CACHE_SIZE; inode->i_blocks = 0; inode->i_atime = inode->i_mtime = inode->i_ctime = CURRENT_TIME; inode->i_op = &simple_dir_inode_operations; inode->i_fop = &simple_dir_operations; root = d_alloc_root(inode); if (!root) { iput(inode); return -ENOMEM; } for (i = 0; !files->name || files->name[0]; i++, files++) { struct qstr name; if (!files->name) continue; name.name = files->name; name.len = strlen(name.name); name.hash = full_name_hash(name.name, name.len); dentry = d_alloc(root, &name); if (!dentry) goto out; inode = new_inode(s); if (!inode) goto out; inode->i_mode = S_IFREG | files->mode; inode->i_uid = inode->i_gid = 0; inode->i_blksize = PAGE_CACHE_SIZE; inode->i_blocks = 0; inode->i_atime = inode->i_mtime = inode->i_ctime = CURRENT_TIME; inode->i_fop = files->ops; inode->i_ino = i; d_add(dentry, inode); } s->s_root = root; return 0; out: d_genocide(root); dput(root); return -ENOMEM; } static void *single_start(struct seq_file *p, loff_t *pos) { return NULL + (*pos == 0); } static void *single_next(struct seq_file *p, void *v, loff_t *pos) { ++*pos; return NULL; } static void single_stop(struct seq_file *p, void *v) { } int single_open(struct file *file, int (*show)(struct seq_file *, void*), void *data) { struct seq_operations *op = kmalloc(sizeof(*op), GFP_KERNEL); int res = -ENOMEM; if (op) { op->start = single_start; op->next = single_next; op->stop = single_stop; op->show = show; res = seq_open(file, op); if (!res) ((struct seq_file *)file->private_data)->private = data; else kfree(op); } return res; } int single_release(struct inode *inode, struct file *file) { struct seq_operations *op = ((struct seq_file *)file->private_data)->op; int res = seq_release(inode, file); kfree(op); return res; } /* common for pci */ static int pci_sb_set(struct super_block *s, void *dev) { s->u.generic_sbp = dev; return set_anon_super(s, dev); } static inline struct pci_dev *PCI_SB(struct super_block *s) { return (struct pci_dev *)s->u.generic_sbp; } static int pci_sb_test(struct super_block *s, void *dev) { return PCI_SB(s) == dev; } struct super_block * pci_get_sb(struct file_system_type *type, int flags, char *dev_name, struct pci_driver *driver, struct tree_descr *files, int magic) { unsigned bus, dev, func; struct pci_dev *pci_dev; struct super_block *s; int error; if (!dev_name) return ERR_PTR(-EINVAL); if (sscanf(dev_name, "pci%x:%x.%x", &bus, &dev, &func) != 3) return ERR_PTR(-EINVAL); pci_dev = pci_find_slot(bus, PCI_DEVFN(dev, func)); if (!pci_dev || pci_dev->driver != driver) return ERR_PTR(-ENODEV); s = sget(type, pci_sb_test, pci_sb_set, pci_dev); if (IS_ERR(s) || s->s_root) return s; s->s_flags = flags; error = simple_fill_super(s, magic, files); if (error) goto fail; s->s_flags |= MS_ACTIVE; return s; fail: up_write(&s->s_umount); deactivate_super(s); return ERR_PTR(error); } /******************************** driver itself ******************************/ enum { Via_Temp1 = 1, Via_Temp2, Via_Temp3, }; struct via_data { unsigned long base; unsigned long size; }; static u8 reg(struct via_data *s, int n) { return inb(s->base + n); } static int show_temp1(struct seq_file *m, void *v) { struct via_data *s = m->private; seq_printf(m, "%d\n", (reg(s, 0x20) << 2) + ((reg(s, 0x4b) & 0xc0) >> 6)); return 0; } static int show_temp2(struct seq_file *m, void *v) { struct via_data *s = m->private; seq_printf(m, "%d\n", (reg(s, 0x21) << 2) + ((reg(s, 0x49) & 0x30) >> 4)); return 0; } static int show_temp3(struct seq_file *m, void *v) { struct via_data *s = m->private; seq_printf(m, "%d\n", (reg(s, 0x1f) << 2) + ((reg(s, 0x49) & 0x60) >> 6)); return 0; } static int (*show_temp[])(struct seq_file *, void*) = { [Via_Temp1] = show_temp1, [Via_Temp2] = show_temp2, [Via_Temp3] = show_temp3, }; static int temp_open(struct inode *inode, struct file *file) { struct pci_dev *dev = PCI_SB(file->f_vfsmnt->mnt_sb); int index = file->f_dentry->d_inode->i_ino; return single_open(file, show_temp[index], pci_get_drvdata(dev)); } static struct file_operations temp_ops = { open: temp_open, read: seq_read, llseek: seq_lseek, release: single_release }; static int __devinit via_sensors_probe(struct pci_dev *dev, const struct pci_device_id *ent) { unsigned char monitor_control; int error = -ENOMEM; struct via_data *via = kmalloc(sizeof(struct via_data), GFP_KERNEL); if (!via) goto out; error = -ENODEV; if (pci_enable_device(dev) < 0) goto out1; if (!(pci_resource_flags(dev, 8) & IORESOURCE_IO)) goto out2; via->base = pci_resource_start(dev, 8); via->size = pci_resource_len(dev, 8); if (!via->base || via->size < 0x4c) goto out2; pci_read_config_byte(dev, 0x74, &monitor_control); if (!(monitor_control & 1)) goto out2; error = -EBUSY; if (!request_region(via->base, via->size, "via_sensors")) goto out2; pci_set_drvdata(dev, via); return 0; out2: pci_disable_device(dev); out1: kfree(via); out: return error; } static void __devexit via_sensors_remove(struct pci_dev *dev) { struct via_data *via = pci_get_drvdata(dev); pci_set_drvdata(dev, NULL); release_region(via->base, via->size); pci_disable_device(dev); kfree(via); } static struct pci_device_id via_sensors_tbl[] __devinitdata = { {PCI_VENDOR_ID_VIA, PCI_DEVICE_ID_VIA_82C686_4, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, {0,} }; MODULE_DEVICE_TABLE(pci, via_sensors_tbl); static struct pci_driver via_sensors_driver = { name: "via_sensors", probe: via_sensors_probe, remove: via_sensors_remove, id_table: via_sensors_tbl, }; /* * Description of fs tree. */ static struct tree_descr via_files[] = { [Via_Temp1] = {"temp1", &temp_ops, S_IRUGO}, [Via_Temp2] = {"temp2", &temp_ops, S_IRUGO}, [Via_Temp3] = {"temp3", &temp_ops, S_IRUGO}, /* last one */ {""} }; enum {VIA_MAGIC = 0x756961}; static struct super_block *via_get_sb(struct file_system_type *type, int flags, char *dev_name, void *data) { return pci_get_sb(type, flags, dev_name, &via_sensors_driver, via_files, VIA_MAGIC); } static struct file_system_type via_fs_type = { owner: THIS_MODULE, name: "devvia", get_sb: via_get_sb, kill_sb: kill_litter_super, }; static int __init via_sensors_init(void) { int err = pci_module_init(&via_sensors_driver); if (!err) { err = register_filesystem(&via_fs_type); if (err) pci_unregister_driver(&via_sensors_driver); } return err; } static void __exit via_sensors_exit(void) { unregister_filesystem(&via_fs_type); pci_unregister_driver(&via_sensors_driver); } module_init(via_sensors_init); module_exit(via_sensors_exit); MODULE_LICENSE("GPL"); - To unsubscribe from this list: send the line "unsubscribe linux-fsdevel" in the body of a message to majordomo@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html