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
- Lines: 1136
- Date:
Wed Nov 21 09:59:11 2001
- Orig file:
v2.4.14/linux/drivers/hotplug/pci_hotplug_core.c
- Orig date:
Wed Dec 31 16:00:00 1969
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)