patch-2.4.15 linux/drivers/hotplug/pci_hotplug_core.c

Next file: linux/drivers/hotplug/pci_hotplug_util.c
Previous file: linux/drivers/hotplug/pci_hotplug.h
Back to the patch index
Back to the overall index

diff -u --recursive --new-file v2.4.14/linux/drivers/hotplug/pci_hotplug_core.c linux/drivers/hotplug/pci_hotplug_core.c
@@ -0,0 +1,1135 @@
+/*
+ * PCI HotPlug Controller Core
+ *
+ * Copyright (c) 2001 Greg Kroah-Hartman (greg@kroah.com)
+ * Copyright (c) 2001 IBM Corp.
+ *
+ * All rights reserved.
+ *
+ * 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, GOOD TITLE or
+ * NON INFRINGEMENT.  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.
+ *
+ * Send feedback to <greg@kroah.com>
+ *
+ */
+
+#include <linux/config.h>
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/types.h>
+#include <linux/list.h>
+#include <linux/pagemap.h>
+#include <linux/slab.h>
+#include <linux/smp_lock.h>
+#include <linux/init.h>
+#include <linux/pci.h>
+#include <asm/uaccess.h>
+#include "pci_hotplug.h"
+
+
+#if !defined(CONFIG_HOTPLUG_PCI_MODULE)
+	#define MY_NAME	"pci_hotplug"
+#else
+	#define MY_NAME	THIS_MODULE->name
+#endif
+
+#define dbg(fmt, arg...) do { if (debug) printk(KERN_DEBUG "%s: "__FUNCTION__": " fmt , MY_NAME , ## arg); } while (0)
+#define err(format, arg...) printk(KERN_ERR "%s: " format , MY_NAME , ## arg)
+#define info(format, arg...) printk(KERN_INFO "%s: " format , MY_NAME , ## arg)
+#define warn(format, arg...) printk(KERN_WARNING "%s: " format , MY_NAME , ## arg)
+
+
+/* local variables */
+static int debug;
+
+#define DRIVER_VERSION	"0.3"
+#define DRIVER_AUTHOR	"Greg Kroah-Hartman <greg@kroah.com>"
+#define DRIVER_DESC	"PCI Hot Plug PCI Core"
+
+
+//////////////////////////////////////////////////////////////////
+
+/* Random magic number */
+#define PCIHPFS_MAGIC 0x52454541
+
+struct hotplug_slot_core {
+	struct dentry	*dir_dentry;
+	struct dentry	*power_dentry;
+	struct dentry	*attention_dentry;
+	struct dentry	*latch_dentry;
+	struct dentry	*adapter_dentry;
+	struct dentry	*test_dentry;
+};
+
+static struct super_operations pcihpfs_ops;
+static struct address_space_operations pcihpfs_aops;
+static struct file_operations pcihpfs_dir_operations;
+static struct file_operations default_file_operations;
+static struct inode_operations pcihpfs_dir_inode_operations;
+static struct vfsmount *pcihpfs_mount;	/* one of the mounts of our fs for reference counting */
+static int pcihpfs_mount_count;		/* times we have mounted our fs */
+static spinlock_t mount_lock;		/* protects our mount_count */
+static spinlock_t list_lock;
+
+LIST_HEAD(pci_hotplug_slot_list);
+
+
+static int pcihpfs_statfs (struct super_block *sb, struct statfs *buf)
+{
+	buf->f_type = PCIHPFS_MAGIC;
+	buf->f_bsize = PAGE_CACHE_SIZE;
+	buf->f_namelen = 255;
+	return 0;
+}
+
+static struct dentry *pcihpfs_lookup (struct inode *dir, struct dentry *dentry)
+{
+	d_add(dentry, NULL);
+	return NULL;
+}
+
+static struct inode *pcihpfs_get_inode (struct super_block *sb, int mode, int dev)
+{
+	struct inode *inode = new_inode(sb);
+
+	if (inode) {
+		inode->i_mode = mode;
+		inode->i_uid = current->fsuid;
+		inode->i_gid = current->fsgid;
+		inode->i_blksize = PAGE_CACHE_SIZE;
+		inode->i_blocks = 0;
+		inode->i_rdev = NODEV;
+		inode->i_mapping->a_ops = &pcihpfs_aops;
+		inode->i_atime = inode->i_mtime = inode->i_ctime = CURRENT_TIME;
+		switch (mode & S_IFMT) {
+		default:
+			init_special_inode(inode, mode, dev);
+			break;
+		case S_IFREG:
+			inode->i_fop = &default_file_operations;
+			break;
+		case S_IFDIR:
+			inode->i_op = &pcihpfs_dir_inode_operations;
+			inode->i_fop = &pcihpfs_dir_operations;
+			break;
+		}
+	}
+	return inode; 
+}
+
+static int pcihpfs_mknod (struct inode *dir, struct dentry *dentry, int mode, int dev)
+{
+	struct inode *inode = pcihpfs_get_inode(dir->i_sb, mode, dev);
+	int error = -ENOSPC;
+
+	if (inode) {
+		d_instantiate(dentry, inode);
+		dget(dentry);
+		error = 0;
+	}
+	return error;
+}
+
+static int pcihpfs_mkdir (struct inode *dir, struct dentry *dentry, int mode)
+{
+	return pcihpfs_mknod (dir, dentry, mode | S_IFDIR, 0);
+}
+
+static int pcihpfs_create (struct inode *dir, struct dentry *dentry, int mode)
+{
+ 	return pcihpfs_mknod (dir, dentry, mode | S_IFREG, 0);
+}
+
+static int pcihpfs_link (struct dentry *old_dentry, struct inode *dir,
+			 struct dentry *dentry)
+{
+	struct inode *inode = old_dentry->d_inode;
+
+	if(S_ISDIR(inode->i_mode))
+		return -EPERM;
+
+	inode->i_nlink++;
+	atomic_inc(&inode->i_count);
+ 	dget(dentry);
+	d_instantiate(dentry, inode);
+	return 0;
+}
+
+static inline int pcihpfs_positive (struct dentry *dentry)
+{
+	return dentry->d_inode && !d_unhashed(dentry);
+}
+
+static int pcihpfs_empty (struct dentry *dentry)
+{
+	struct list_head *list;
+
+	spin_lock(&dcache_lock);
+
+	list_for_each(list, &dentry->d_subdirs) {
+		struct dentry *de = list_entry(list, struct dentry, d_child);
+		if (pcihpfs_positive(de)) {
+			spin_unlock(&dcache_lock);
+			return 0;
+		}
+	}
+
+	spin_unlock(&dcache_lock);
+	return 1;
+}
+
+static int pcihpfs_unlink (struct inode *dir, struct dentry *dentry)
+{
+	int error = -ENOTEMPTY;
+
+	if (pcihpfs_empty(dentry)) {
+		struct inode *inode = dentry->d_inode;
+
+		inode->i_nlink--;
+		dput(dentry);
+		error = 0;
+	}
+	return error;
+}
+
+static int pcihpfs_rename (struct inode *old_dir, struct dentry *old_dentry,
+			   struct inode *new_dir, struct dentry *new_dentry)
+{
+	int error = -ENOTEMPTY;
+
+	if (pcihpfs_empty(new_dentry)) {
+		struct inode *inode = new_dentry->d_inode;
+		if (inode) {
+			inode->i_nlink--;
+			dput(new_dentry);
+		}
+		error = 0;
+	}
+	return error;
+}
+
+#define pcihpfs_rmdir pcihpfs_unlink
+
+/* default file operations */
+static ssize_t default_read_file (struct file *file, char *buf, size_t count, loff_t *ppos)
+{
+	dbg ("\n");
+	return 0;
+}
+
+static ssize_t default_write_file (struct file *file, const char *buf, size_t count, loff_t *ppos)
+{
+	dbg ("\n");
+	return count;
+}
+
+static loff_t default_file_lseek (struct file *file, loff_t offset, int orig)
+{
+	loff_t retval = -EINVAL;
+
+	switch(orig) {
+	case 0:
+		if (offset > 0) {
+			file->f_pos = offset;
+			retval = file->f_pos;
+		} 
+		break;
+	case 1:
+		if ((offset + file->f_pos) > 0) {
+			file->f_pos += offset;
+			retval = file->f_pos;
+		} 
+		break;
+	default:
+		break;
+	}
+	return retval;
+}
+
+static int default_open (struct inode *inode, struct file *filp)
+{
+	if (inode->u.generic_ip)
+		filp->private_data = inode->u.generic_ip;
+
+	return 0;
+}
+
+static int default_sync_file (struct file *file, struct dentry *dentry, int datasync)
+{
+	return 0;
+}
+
+static struct address_space_operations pcihpfs_aops = {
+};
+
+static struct file_operations pcihpfs_dir_operations = {
+	read:		generic_read_dir,
+	readdir:	dcache_readdir,
+	fsync:		default_sync_file,
+};
+
+static struct file_operations default_file_operations = {
+	read:		default_read_file,
+	write:		default_write_file,
+	open:		default_open,
+	llseek:		default_file_lseek,
+	fsync:		default_sync_file,
+	mmap:		generic_file_mmap,
+};
+
+/* file ops for the "power" files */
+static ssize_t power_read_file (struct file *file, char *buf, size_t count, loff_t *offset);
+static ssize_t power_write_file (struct file *file, const char *buf, size_t count, loff_t *ppos);
+static struct file_operations power_file_operations = {
+	read:		power_read_file,
+	write:		power_write_file,
+	open:		default_open,
+	llseek:		default_file_lseek,
+	fsync:		default_sync_file,
+	mmap:		generic_file_mmap,
+};
+
+/* file ops for the "attention" files */
+static ssize_t attention_read_file (struct file *file, char *buf, size_t count, loff_t *offset);
+static ssize_t attention_write_file (struct file *file, const char *buf, size_t count, loff_t *ppos);
+static struct file_operations attention_file_operations = {
+	read:		attention_read_file,
+	write:		attention_write_file,
+	open:		default_open,
+	llseek:		default_file_lseek,
+	fsync:		default_sync_file,
+	mmap:		generic_file_mmap,
+};
+
+/* file ops for the "latch" files */
+static ssize_t latch_read_file (struct file *file, char *buf, size_t count, loff_t *offset);
+static struct file_operations latch_file_operations = {
+	read:		latch_read_file,
+	write:		default_write_file,
+	open:		default_open,
+	llseek:		default_file_lseek,
+	fsync:		default_sync_file,
+	mmap:		generic_file_mmap,
+};
+
+/* file ops for the "presence" files */
+static ssize_t presence_read_file (struct file *file, char *buf, size_t count, loff_t *offset);
+static struct file_operations presence_file_operations = {
+	read:		presence_read_file,
+	write:		default_write_file,
+	open:		default_open,
+	llseek:		default_file_lseek,
+	fsync:		default_sync_file,
+	mmap:		generic_file_mmap,
+};
+
+/* file ops for the "test" files */
+static ssize_t test_write_file (struct file *file, const char *buf, size_t count, loff_t *ppos);
+static struct file_operations test_file_operations = {
+	read:		default_read_file,
+	write:		test_write_file,
+	open:		default_open,
+	llseek:		default_file_lseek,
+	fsync:		default_sync_file,
+	mmap:		generic_file_mmap,
+};
+
+static struct inode_operations pcihpfs_dir_inode_operations = {
+	create:		pcihpfs_create,
+	lookup:		pcihpfs_lookup,
+	link:		pcihpfs_link,
+	unlink:		pcihpfs_unlink,
+	mkdir:		pcihpfs_mkdir,
+	rmdir:		pcihpfs_rmdir,
+	mknod:		pcihpfs_mknod,
+	rename:		pcihpfs_rename,
+};
+
+static struct super_operations pcihpfs_ops = {
+	statfs:		pcihpfs_statfs,
+	put_inode:	force_delete,
+};
+
+static struct super_block *pcihpfs_read_super (struct super_block *sb, void *data, int silent)
+{
+	struct inode *inode;
+	struct dentry *root;
+
+	sb->s_blocksize = PAGE_CACHE_SIZE;
+	sb->s_blocksize_bits = PAGE_CACHE_SHIFT;
+	sb->s_magic = PCIHPFS_MAGIC;
+	sb->s_op = &pcihpfs_ops;
+	inode = pcihpfs_get_inode(sb, S_IFDIR | 0755, 0);
+
+	if (!inode) {
+		dbg("%s: could not get inode!\n",__FUNCTION__);
+		return NULL;
+	}
+
+	root = d_alloc_root(inode);
+	if (!root) {
+		dbg("%s: could not get root dentry!\n",__FUNCTION__);
+		iput(inode);
+		return NULL;
+	}
+	sb->s_root = root;
+	return sb;
+}
+
+static DECLARE_FSTYPE(pcihpfs_fs_type, "pcihpfs", pcihpfs_read_super, FS_SINGLE | FS_LITTER);
+
+static int get_mount (void)
+{
+	struct vfsmount *mnt;
+
+	spin_lock (&mount_lock);
+	if (pcihpfs_mount) {
+		mntget(pcihpfs_mount);
+		++pcihpfs_mount_count;
+		spin_unlock (&mount_lock);
+		goto go_ahead;
+	}
+
+	spin_unlock (&mount_lock);
+	mnt = kern_mount (&pcihpfs_fs_type);
+	if (IS_ERR(mnt)) {
+		err ("could not mount the fs...erroring out!\n");
+		return -ENODEV;
+	}
+	spin_lock (&mount_lock);
+	if (!pcihpfs_mount) {
+		pcihpfs_mount = mnt;
+		++pcihpfs_mount_count;
+		spin_unlock (&mount_lock);
+		goto go_ahead;
+	}
+	mntget(pcihpfs_mount);
+	++pcihpfs_mount_count;
+	spin_unlock (&mount_lock);
+	mntput(mnt);
+
+go_ahead:
+	dbg("pcihpfs_mount_count = %d\n", pcihpfs_mount_count);
+	return 0;
+}
+
+static void remove_mount (void)
+{
+	struct vfsmount *mnt;
+
+	spin_lock (&mount_lock);
+	mnt = pcihpfs_mount;
+	--pcihpfs_mount_count;
+	if (!pcihpfs_mount_count)
+		pcihpfs_mount = NULL;
+
+	spin_unlock (&mount_lock);
+	mntput(mnt);
+	dbg("pcihpfs_mount_count = %d\n", pcihpfs_mount_count);
+}
+
+
+/**
+ * pcihpfs_create_by_name - create a file, given a name
+ * @name:	name of file
+ * @mode:	type of file
+ * @parent:	dentry of directory to create it in
+ * @dentry:	resulting dentry of file
+ *
+ * There is a bit of overhead in creating a file - basically, we 
+ * have to hash the name of the file, then look it up. This will
+ * prevent files of the same name. 
+ * We then call the proper vfs_ function to take care of all the 
+ * file creation details. 
+ * This function handles both regular files and directories.
+ */
+static int pcihpfs_create_by_name (const char *name, mode_t mode,
+				   struct dentry *parent, struct dentry **dentry)
+{
+	struct dentry *d = NULL;
+	struct qstr qstr;
+	int error;
+
+	/* If the parent is not specified, we create it in the root.
+	 * We need the root dentry to do this, which is in the super 
+	 * block. A pointer to that is in the struct vfsmount that we
+	 * have around.
+	 */
+	if (!parent ) {
+		if (pcihpfs_mount && pcihpfs_mount->mnt_sb) {
+			parent = pcihpfs_mount->mnt_sb->s_root;
+		}
+	}
+
+	if (!parent) {
+		dbg("Ah! can not find a parent!\n");
+		return -EFAULT;
+	}
+
+	*dentry = NULL;
+	qstr.name = name;
+	qstr.len = strlen(name);
+ 	qstr.hash = full_name_hash(name,qstr.len);
+
+	parent = dget(parent);
+
+	down(&parent->d_inode->i_sem);
+
+	d = lookup_hash(&qstr,parent);
+
+	error = PTR_ERR(d);
+	if (!IS_ERR(d)) {
+		switch(mode & S_IFMT) {
+		case 0: 
+		case S_IFREG:
+			error = vfs_create(parent->d_inode,d,mode);
+			break;
+		case S_IFDIR:
+			error = vfs_mkdir(parent->d_inode,d,mode);
+			break;
+		default:
+			err("cannot create special files\n");
+		}
+		*dentry = d;
+	}
+	up(&parent->d_inode->i_sem);
+
+	dput(parent);
+	return error;
+}
+
+static struct dentry *fs_create_file (const char *name, mode_t mode,
+				      struct dentry *parent, void *data,
+				      struct file_operations *fops)
+{
+	struct dentry *dentry;
+	int error;
+
+	dbg("creating file '%s'\n",name);
+
+	error = pcihpfs_create_by_name(name,mode,parent,&dentry);
+	if (error) {
+		dentry = NULL;
+	} else {
+		if (dentry->d_inode) {
+			if (data)
+				dentry->d_inode->u.generic_ip = data;
+			if (fops)
+			dentry->d_inode->i_fop = fops;
+		}
+	}
+
+	return dentry;
+}
+
+static void fs_remove_file (struct dentry *dentry)
+{
+	struct dentry *parent = dentry->d_parent;
+	
+	if (!parent || !parent->d_inode)
+		return;
+
+	down(&parent->d_inode->i_sem);
+	if (pcihpfs_positive(dentry)) {
+		if (dentry->d_inode) {
+			if (S_ISDIR(dentry->d_inode->i_mode))
+				vfs_rmdir(parent->d_inode,dentry);
+			else
+				vfs_unlink(parent->d_inode,dentry);
+		}
+
+		dput(dentry);
+	}
+	up(&parent->d_inode->i_sem);
+}
+
+#define GET_STATUS(name)	\
+static int get_##name##_status (struct hotplug_slot *slot, u8 *value)	\
+{									\
+	struct hotplug_slot_ops *ops = slot->ops;			\
+	int retval = 0;							\
+	if (ops->owner)							\
+		__MOD_INC_USE_COUNT(ops->owner);			\
+	if (ops->get_##name##_status)					\
+		retval = ops->get_##name##_status (slot, value);	\
+	else								\
+		*value = slot->info->name##_status;			\
+	if (ops->owner)							\
+		__MOD_DEC_USE_COUNT(ops->owner);			\
+	return retval;							\
+}
+
+GET_STATUS(power)
+GET_STATUS(attention)
+GET_STATUS(latch)
+GET_STATUS(adapter)
+
+static ssize_t power_read_file (struct file *file, char *buf, size_t count, loff_t *offset)
+{
+	struct hotplug_slot *slot = file->private_data;
+	unsigned char *page;
+	int retval;
+	int len;
+	u8 value;
+
+	dbg(" count = %d, offset = %lld\n", count, *offset);
+
+	if (*offset < 0)
+		return -EINVAL;
+	if (count <= 0)
+		return 0;
+	if (*offset != 0)
+		return 0;
+
+	if (slot == NULL) {
+		dbg("slot == NULL???\n");
+		return -ENODEV;
+	}
+
+	page = (unsigned char *)__get_free_page(GFP_KERNEL);
+	if (!page)
+		return -ENOMEM;
+
+	retval = get_power_status (slot, &value);
+	if (retval)
+		goto exit;
+	len = sprintf (page, "%d\n", value);
+
+	if (copy_to_user (buf, page, len)) {
+		retval = -EFAULT;
+		goto exit;
+	}
+	*offset += len;
+	retval = len;
+
+exit:
+	free_page((unsigned long)page);
+	return retval;
+}
+
+static ssize_t power_write_file (struct file *file, const char *ubuff, size_t count, loff_t *offset)
+{
+	struct hotplug_slot *slot = file->private_data;
+	char *buff;
+	unsigned long lpower;
+	u8 power;
+	int retval = 0;
+
+	if (*offset < 0)
+		return -EINVAL;
+	if (count <= 0)
+		return 0;
+	if (*offset != 0)
+		return 0;
+
+	if (slot == NULL) {
+		dbg("slot == NULL???\n");
+		return -ENODEV;
+	}
+
+	buff = kmalloc (count + 1, GFP_KERNEL);
+	if (!buff)
+		return -ENOMEM;
+	memset (buff, 0x00, count + 1);
+ 
+	if (copy_from_user ((void *)buff, (void *)ubuff, count)) {
+		retval = -EFAULT;
+		goto exit;
+	}
+	
+	lpower = simple_strtoul (buff, NULL, 10);
+	power = (u8)(lpower & 0xff);
+	dbg ("power = %d\n", power);
+
+	switch (power) {
+		case 0:
+			if (!slot->ops->disable_slot)
+				break;
+			if (slot->ops->owner)
+				__MOD_INC_USE_COUNT(slot->ops->owner);
+			retval = slot->ops->disable_slot(slot);
+			if (slot->ops->owner)
+				__MOD_DEC_USE_COUNT(slot->ops->owner);
+			break;
+
+		case 1:
+			if (!slot->ops->enable_slot)
+				break;
+			if (slot->ops->owner)
+				__MOD_INC_USE_COUNT(slot->ops->owner);
+			retval = slot->ops->enable_slot(slot);
+			if (slot->ops->owner)
+				__MOD_DEC_USE_COUNT(slot->ops->owner);
+			break;
+
+		default:
+			err ("Illegal value specified for power\n");
+			retval = -EFAULT;
+	}
+
+exit:	
+	kfree (buff);
+
+	if (retval)
+		return retval;
+	return count;
+}
+
+static ssize_t attention_read_file (struct file *file, char *buf, size_t count, loff_t *offset)
+{
+	struct hotplug_slot *slot = file->private_data;
+	unsigned char *page;
+	int retval;
+	int len;
+	u8 value;
+
+	dbg("count = %d, offset = %lld\n", count, *offset);
+
+	if (*offset < 0)
+		return -EINVAL;
+	if (count <= 0)
+		return 0;
+	if (*offset != 0)
+		return 0;
+
+	if (slot == NULL) {
+		dbg("slot == NULL???\n");
+		return -ENODEV;
+	}
+
+	page = (unsigned char *)__get_free_page(GFP_KERNEL);
+	if (!page)
+		return -ENOMEM;
+
+	retval = get_attention_status (slot, &value);
+	if (retval)
+		goto exit;
+	len = sprintf (page, "%d\n", value);
+
+	if (copy_to_user (buf, page, len)) {
+		retval = -EFAULT;
+		goto exit;
+	}
+	*offset += len;
+	retval = len;
+
+exit:
+	free_page((unsigned long)page);
+	return retval;
+}
+
+static ssize_t attention_write_file (struct file *file, const char *ubuff, size_t count, loff_t *offset)
+{
+	struct hotplug_slot *slot = file->private_data;
+	char *buff;
+	unsigned long lattention;
+	u8 attention;
+	int retval = 0;
+
+	if (*offset < 0)
+		return -EINVAL;
+	if (count <= 0)
+		return 0;
+	if (*offset != 0)
+		return 0;
+
+	if (slot == NULL) {
+		dbg("slot == NULL???\n");
+		return -ENODEV;
+	}
+
+	buff = kmalloc (count + 1, GFP_KERNEL);
+	if (!buff)
+		return -ENOMEM;
+	memset (buff, 0x00, count + 1);
+
+	if (copy_from_user ((void *)buff, (void *)ubuff, count)) {
+		retval = -EFAULT;
+		goto exit;
+	}
+	
+	lattention = simple_strtoul (buff, NULL, 10);
+	attention = (u8)(lattention & 0xff);
+	dbg (" - attention = %d\n", attention);
+
+	if (slot->ops->set_attention_status) {
+		if (slot->ops->owner)
+			__MOD_INC_USE_COUNT(slot->ops->owner);
+		retval = slot->ops->set_attention_status(slot, attention);
+		if (slot->ops->owner)
+			__MOD_DEC_USE_COUNT(slot->ops->owner);
+	}
+
+exit:	
+	kfree (buff);
+
+	if (retval)
+		return retval;
+	return count;
+}
+
+static ssize_t latch_read_file (struct file *file, char *buf, size_t count, loff_t *offset)
+{
+	struct hotplug_slot *slot = file->private_data;
+	unsigned char *page;
+	int retval;
+	int len;
+	u8 value;
+
+	dbg("count = %d, offset = %lld\n", count, *offset);
+
+	if (*offset < 0)
+		return -EINVAL;
+	if (count <= 0)
+		return 0;
+	if (*offset != 0)
+		return 0;
+
+	if (slot == NULL) {
+		dbg("slot == NULL???\n");
+		return -ENODEV;
+	}
+
+	page = (unsigned char *)__get_free_page(GFP_KERNEL);
+	if (!page)
+		return -ENOMEM;
+
+	retval = get_latch_status (slot, &value);
+	if (retval)
+		goto exit;
+	len = sprintf (page, "%d\n", value);
+
+	if (copy_to_user (buf, page, len)) {
+		retval = -EFAULT;
+		goto exit;
+	}
+	*offset += len;
+	retval = len;
+
+exit:
+	free_page((unsigned long)page);
+	return retval;
+}
+
+
+static ssize_t presence_read_file (struct file *file, char *buf, size_t count, loff_t *offset)
+{
+	struct hotplug_slot *slot = file->private_data;
+	unsigned char *page;
+	int retval;
+	int len;
+	u8 value;
+
+	dbg("count = %d, offset = %lld\n", count, *offset);
+
+	if (*offset < 0)
+		return -EINVAL;
+	if (count <= 0)
+		return 0;
+	if (*offset != 0)
+		return 0;
+
+	if (slot == NULL) {
+		dbg("slot == NULL???\n");
+		return -ENODEV;
+	}
+
+	page = (unsigned char *)__get_free_page(GFP_KERNEL);
+	if (!page)
+		return -ENOMEM;
+
+	retval = get_adapter_status (slot, &value);
+	if (retval)
+		goto exit;
+	len = sprintf (page, "%d\n", value);
+
+	if (copy_to_user (buf, page, len)) {
+		retval = -EFAULT;
+		goto exit;
+	}
+	*offset += len;
+	retval = len;
+
+exit:
+	free_page((unsigned long)page);
+	return retval;
+}
+
+static ssize_t test_write_file (struct file *file, const char *ubuff, size_t count, loff_t *offset)
+{
+	struct hotplug_slot *slot = file->private_data;
+	char *buff;
+	unsigned long ltest;
+	u32 test;
+	int retval = 0;
+
+	if (*offset < 0)
+		return -EINVAL;
+	if (count <= 0)
+		return 0;
+	if (*offset != 0)
+		return 0;
+
+	if (slot == NULL) {
+		dbg("slot == NULL???\n");
+		return -ENODEV;
+	}
+
+	buff = kmalloc (count + 1, GFP_KERNEL);
+	if (!buff)
+		return -ENOMEM;
+	memset (buff, 0x00, count + 1);
+
+	if (copy_from_user ((void *)buff, (void *)ubuff, count)) {
+		retval = -EFAULT;
+		goto exit;
+	}
+	
+	ltest = simple_strtoul (buff, NULL, 10);
+	test = (u32)(ltest & 0xffffffff);
+	dbg ("test = %d\n", test);
+
+	if (slot->ops->hardware_test) {
+		if (slot->ops->owner)
+			__MOD_INC_USE_COUNT(slot->ops->owner);
+		retval = slot->ops->hardware_test(slot, test);
+		if (slot->ops->owner)
+			__MOD_DEC_USE_COUNT(slot->ops->owner);
+	}
+
+exit:	
+	kfree (buff);
+
+	if (retval)
+		return retval;
+	return count;
+}
+
+static int fs_add_slot (struct hotplug_slot *slot)
+{
+	struct hotplug_slot_core *core = slot->core_priv;
+	int result;
+
+	result = get_mount();
+	if (result)
+		return result;
+
+	core->dir_dentry = fs_create_file (slot->name,
+					   S_IFDIR | S_IXUGO | S_IRUGO,
+					   NULL, NULL, NULL);
+	if (core->dir_dentry != NULL) {
+		core->power_dentry = fs_create_file ("power",
+						     S_IFREG | S_IRUGO | S_IWUSR,
+						     core->dir_dentry, slot,
+						     &power_file_operations);
+
+		core->attention_dentry = fs_create_file ("attention",
+							 S_IFREG | S_IRUGO | S_IWUSR,
+							 core->dir_dentry, slot,
+							 &attention_file_operations);
+
+		core->latch_dentry = fs_create_file ("latch",
+						     S_IFREG | S_IRUGO,
+						     core->dir_dentry, slot,
+						     &latch_file_operations);
+
+		core->adapter_dentry = fs_create_file ("adapter",
+						       S_IFREG | S_IRUGO,
+						       core->dir_dentry, slot,
+						       &presence_file_operations);
+
+		core->test_dentry = fs_create_file ("test",
+						    S_IFREG | S_IRUGO | S_IWUSR,
+						    core->dir_dentry, slot,
+						    &test_file_operations);
+	}
+	return 0;
+}
+
+static void fs_remove_slot (struct hotplug_slot *slot)
+{
+	struct hotplug_slot_core *core = slot->core_priv;
+
+	if (core->dir_dentry) {
+		if (core->power_dentry)
+			fs_remove_file (core->power_dentry);
+		if (core->attention_dentry)
+			fs_remove_file (core->attention_dentry);
+		if (core->latch_dentry)
+			fs_remove_file (core->latch_dentry);
+		if (core->adapter_dentry)
+			fs_remove_file (core->adapter_dentry);
+		if (core->test_dentry)
+			fs_remove_file (core->test_dentry);
+		fs_remove_file (core->dir_dentry);
+	}
+
+	remove_mount();
+}
+
+static struct hotplug_slot *get_slot_from_name (const char *name)
+{
+	struct hotplug_slot *slot;
+	struct list_head *tmp;
+
+	list_for_each (tmp, &pci_hotplug_slot_list) {
+		slot = list_entry (tmp, struct hotplug_slot, slot_list);
+		if (strcmp(slot->name, name) == 0)
+			return slot;
+	}
+	return NULL;
+}
+
+/**
+ * pci_hp_register - register a hotplug_slot with the PCI hotplug subsystem
+ * @slot: pointer to the &struct hotplug_slot to register
+ *
+ * Registers a hotplug slot with the pci hotplug subsystem, which will allow
+ * userspace interaction to the slot.
+ *
+ * Returns 0 if successful, anything else for an error.
+ */
+int pci_hp_register (struct hotplug_slot *slot)
+{
+	struct hotplug_slot_core *core;
+	int result;
+
+	if (slot == NULL)
+		return -ENODEV;
+	if ((slot->info == NULL) || (slot->ops == NULL))
+		return -EFAULT;
+
+	core = kmalloc (sizeof (struct hotplug_slot_core), GFP_KERNEL);
+	if (!core)
+		return -ENOMEM;
+
+	/* make sure we have not already registered this slot */
+	spin_lock (&list_lock);
+	if (get_slot_from_name (slot->name) != NULL) {
+		spin_unlock (&list_lock);
+		kfree (core);
+		return -EFAULT;
+	}
+
+	slot->core_priv = core;
+
+	list_add (&slot->slot_list, &pci_hotplug_slot_list);
+	spin_unlock (&list_lock);
+
+	result = fs_add_slot (slot);
+	dbg ("Added slot %s to the list\n", slot->name);
+	return result;
+}
+
+/**
+ * pci_hp_deregister - deregister a hotplug_slot with the PCI hotplug subsystem
+ * @slot: pointer to the &struct hotplug_slot to deregister
+ *
+ * The @slot must have been registered with the pci hotplug subsystem
+ * previously with a call to pci_hp_register().
+ *
+ * Returns 0 if successful, anything else for an error.
+ */
+int pci_hp_deregister (struct hotplug_slot *slot)
+{
+	struct hotplug_slot *temp;
+
+	if (slot == NULL)
+		return -ENODEV;
+
+	/* make sure we have this slot in our list before trying to delete it */
+	spin_lock (&list_lock);
+	temp = get_slot_from_name (slot->name);
+	if (temp != slot) {
+		spin_unlock (&list_lock);
+		return -ENODEV;
+	}
+
+	list_del (&slot->slot_list);
+	spin_unlock (&list_lock);
+
+	fs_remove_slot (slot);
+	kfree(slot->core_priv);
+	dbg ("Removed slot %s from the list\n", slot->name);
+	return 0;
+}
+
+/**
+ * pci_hp_change_slot_info - changes the slot's information structure in the core
+ * @name: the name of the slot whose info has changed
+ * @info: pointer to the info copy into the slot's info structure
+ *
+ * A slot with @name must have been registered with the pci 
+ * hotplug subsystem previously with a call to pci_hp_register().
+ *
+ * Returns 0 if successful, anything else for an error.
+ */
+int pci_hp_change_slot_info (const char *name, struct hotplug_slot_info *info)
+{
+	struct hotplug_slot *temp;
+
+	if (info == NULL)
+		return -ENODEV;
+
+	spin_lock (&list_lock);
+	temp = get_slot_from_name (name);
+	if (temp == NULL) {
+		spin_unlock (&list_lock);
+		return -ENODEV;
+	}
+
+	memcpy (temp->info, info, sizeof (struct hotplug_slot_info));
+	spin_unlock (&list_lock);
+	return 0;
+}
+
+static int __init pci_hotplug_init (void)
+{
+	int result;
+
+	spin_lock_init(&mount_lock);
+	spin_lock_init(&list_lock);
+
+	dbg("registering filesystem.\n");
+	result = register_filesystem(&pcihpfs_fs_type);
+	if (result) {
+		err("register_filesystem failed with %d\n", result);
+		goto exit;
+	}
+
+	info (DRIVER_DESC " version: " DRIVER_VERSION "\n");
+
+exit:
+	return result;
+}
+
+static void __exit pci_hotplug_exit (void)
+{
+	unregister_filesystem(&pcihpfs_fs_type);
+}
+
+module_init(pci_hotplug_init);
+module_exit(pci_hotplug_exit);
+
+MODULE_AUTHOR(DRIVER_AUTHOR);
+MODULE_DESCRIPTION(DRIVER_DESC);
+MODULE_LICENSE("GPL");
+MODULE_PARM(debug, "i");
+MODULE_PARM_DESC(debug, "Debugging mode enabled or not");
+
+EXPORT_SYMBOL_GPL(pci_hp_register);
+EXPORT_SYMBOL_GPL(pci_hp_deregister);
+EXPORT_SYMBOL_GPL(pci_hp_change_slot_info);
+

FUNET's LINUX-ADM group, linux-adm@nic.funet.fi
TCL-scripts by Sam Shen (who was at: slshen@lbl.gov)