![[LWN Logo]](/images/lcorner.png) |
|
![[LWN.net]](/images/Included.png) |
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