[LWN Logo]
[LWN.net]
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