/* * PCI HotPlug Controller Core * * Copyright (c) 2001-2002 Greg Kroah-Hartman (greg@kroah.com) * Copyright (c) 2001-2002 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> * * Filesystem portion based on work done by Pat Mochel on ddfs/driverfs * */ #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/mount.h> #include <linux/namei.h> #include <linux/pci.h> #include <linux/dnotify.h> #include <linux/proc_fs.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: %s: " fmt , MY_NAME , __FUNCTION__ , ## 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.5" #define DRIVER_AUTHOR "Greg Kroah-Hartman <greg@kroah.com>, Scott Murray <scottm@somanetworks.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; struct dentry *max_bus_speed_dentry; struct dentry *cur_bus_speed_dentry; }; static struct super_operations pcihpfs_ops; 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; static LIST_HEAD(pci_hotplug_slot_list); /* these strings match up with the values in pci_bus_speed */ static char *pci_bus_speed_strings[] = { "33 MHz PCI", /* 0x00 */ "66 MHz PCI", /* 0x01 */ "66 MHz PCIX", /* 0x02 */ "100 MHz PCIX", /* 0x03 */ "133 MHz PCIX", /* 0x04 */ NULL, /* 0x05 */ NULL, /* 0x06 */ NULL, /* 0x07 */ NULL, /* 0x08 */ "66 MHz PCIX 266", /* 0x09 */ "100 MHz PCIX 266", /* 0x0a */ "133 MHz PCIX 266", /* 0x0b */ NULL, /* 0x0c */ NULL, /* 0x0d */ NULL, /* 0x0e */ NULL, /* 0x0f */ NULL, /* 0x10 */ "66 MHz PCIX 533", /* 0x11 */ "100 MHz PCIX 533", /* 0x12 */ "133 MHz PCIX 533", /* 0x13 */ }; #ifdef CONFIG_PROC_FS extern struct proc_dir_entry *proc_bus_pci_dir; static struct proc_dir_entry *slotdir = NULL; static const char *slotdir_name = "slots"; #endif #ifdef CONFIG_HOTPLUG_PCI_CPCI extern int cpci_hotplug_init(int debug); extern void cpci_hotplug_exit(void); #else static inline int cpci_hotplug_init(int debug) { return 0; } static inline void cpci_hotplug_exit(void) { } #endif static struct inode *pcihpfs_get_inode (struct super_block *sb, int mode, dev_t 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_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 = &simple_dir_operations; /* directory inodes start off with i_nlink == 2 (for "." entry) */ inode->i_nlink++; break; } } return inode; } /* SMP-safe */ static int pcihpfs_mknod (struct inode *dir, struct dentry *dentry, int mode, dev_t 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 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; lock_kernel(); inode->i_nlink--; unlock_kernel(); dput(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; lock_kernel(); 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; } unlock_kernel(); 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 struct file_operations default_file_operations = { .read = default_read_file, .write = default_write_file, .open = default_open, .llseek = default_file_lseek, }; /* 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, }; /* 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, }; /* 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, }; /* 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, }; /* file ops for the "max bus speed" files */ static ssize_t max_bus_speed_read_file (struct file *file, char *buf, size_t count, loff_t *offset); static struct file_operations max_bus_speed_file_operations = { .read = max_bus_speed_read_file, .write = default_write_file, .open = default_open, .llseek = default_file_lseek, }; /* file ops for the "current bus speed" files */ static ssize_t cur_bus_speed_read_file (struct file *file, char *buf, size_t count, loff_t *offset); static struct file_operations cur_bus_speed_file_operations = { .read = cur_bus_speed_read_file, .write = default_write_file, .open = default_open, .llseek = default_file_lseek, }; /* 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, }; static struct inode_operations pcihpfs_dir_inode_operations = { .create = pcihpfs_create, .lookup = simple_lookup, .unlink = pcihpfs_unlink, .mkdir = pcihpfs_mkdir, .rmdir = pcihpfs_rmdir, .mknod = pcihpfs_mknod, }; static struct super_operations pcihpfs_ops = { .statfs = simple_statfs, .drop_inode = generic_delete_inode, }; static int pcihpfs_fill_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 -ENOMEM; } root = d_alloc_root(inode); if (!root) { dbg("%s: could not get root dentry!\n",__FUNCTION__); iput(inode); return -ENOMEM; } sb->s_root = root; return 0; } static struct super_block *pcihpfs_get_sb(struct file_system_type *fs_type, int flags, char *dev_name, void *data) { return get_sb_single(fs_type, flags, data, pcihpfs_fill_super); } static struct file_system_type pcihpfs_type = { .owner = THIS_MODULE, .name = "pcihpfs", .get_sb = pcihpfs_get_sb, .kill_sb = kill_litter_super, }; 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_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 -EINVAL; } *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,type) \ static int get_##name (struct hotplug_slot *slot, type *value) \ { \ struct hotplug_slot_ops *ops = slot->ops; \ int retval = 0; \ if (ops->owner) \ __MOD_INC_USE_COUNT(ops->owner); \ if (ops->get_##name) \ retval = ops->get_##name (slot, value); \ else \ *value = slot->info->name; \ if (ops->owner) \ __MOD_DEC_USE_COUNT(ops->owner); \ return retval; \ } GET_STATUS(power_status, u8) GET_STATUS(attention_status, u8) GET_STATUS(latch_status, u8) GET_STATUS(adapter_status, u8) GET_STATUS(max_bus_speed, enum pci_bus_speed) GET_STATUS(cur_bus_speed, enum pci_bus_speed) 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 || count > 16384) 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 = -EINVAL; } 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 || count > 16384) 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 char *unknown_speed = "Unknown bus speed"; static ssize_t max_bus_speed_read_file (struct file *file, char *buf, size_t count, loff_t *offset) { struct hotplug_slot *slot = file->private_data; unsigned char *page; char *speed_string; int retval; int len = 0; enum pci_bus_speed 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_max_bus_speed (slot, &value); if (retval) goto exit; if (value == PCI_SPEED_UNKNOWN) speed_string = unknown_speed; else speed_string = pci_bus_speed_strings[value]; len = sprintf (page, "%s\n", speed_string); 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 cur_bus_speed_read_file (struct file *file, char *buf, size_t count, loff_t *offset) { struct hotplug_slot *slot = file->private_data; unsigned char *page; char *speed_string; int retval; int len = 0; enum pci_bus_speed 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_cur_bus_speed (slot, &value); if (retval) goto exit; if (value == PCI_SPEED_UNKNOWN) speed_string = unknown_speed; else speed_string = pci_bus_speed_strings[value]; len = sprintf (page, "%s\n", speed_string); 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 || count > 16384) 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) { if ((slot->ops->enable_slot) || (slot->ops->disable_slot) || (slot->ops->get_power_status)) core->power_dentry = fs_create_file ("power", S_IFREG | S_IRUGO | S_IWUSR, core->dir_dentry, slot, &power_file_operations); if ((slot->ops->set_attention_status) || (slot->ops->get_attention_status)) core->attention_dentry = fs_create_file ("attention", S_IFREG | S_IRUGO | S_IWUSR, core->dir_dentry, slot, &attention_file_operations); if (slot->ops->get_latch_status) core->latch_dentry = fs_create_file ("latch", S_IFREG | S_IRUGO, core->dir_dentry, slot, &latch_file_operations); if (slot->ops->get_adapter_status) core->adapter_dentry = fs_create_file ("adapter", S_IFREG | S_IRUGO, core->dir_dentry, slot, &presence_file_operations); if (slot->ops->get_max_bus_speed) core->max_bus_speed_dentry = fs_create_file ("max_bus_speed", S_IFREG | S_IRUGO, core->dir_dentry, slot, &max_bus_speed_file_operations); if (slot->ops->get_cur_bus_speed) core->cur_bus_speed_dentry = fs_create_file ("cur_bus_speed", S_IFREG | S_IRUGO, core->dir_dentry, slot, &cur_bus_speed_file_operations); if (slot->ops->hardware_test) 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->max_bus_speed_dentry) fs_remove_file (core->max_bus_speed_dentry); if (core->cur_bus_speed_dentry) fs_remove_file (core->cur_bus_speed_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 -EINVAL; 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 -EINVAL; } memset (core, 0, sizeof (struct hotplug_slot_core)); 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; } static inline void update_dentry_inode_time (struct dentry *dentry) { struct inode *inode = dentry->d_inode; if (inode) { inode->i_mtime = CURRENT_TIME; dnotify_parent(dentry, DN_MODIFY); } } /** * 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; struct hotplug_slot_core *core; if (info == NULL) return -ENODEV; spin_lock (&list_lock); temp = get_slot_from_name (name); if (temp == NULL) { spin_unlock (&list_lock); return -ENODEV; } /* * check all fields in the info structure, and update timestamps * for the files referring to the fields that have now changed. */ core = temp->core_priv; if ((core->power_dentry) && (temp->info->power_status != info->power_status)) update_dentry_inode_time (core->power_dentry); if ((core->attention_dentry) && (temp->info->attention_status != info->attention_status)) update_dentry_inode_time (core->attention_dentry); if ((core->latch_dentry) && (temp->info->latch_status != info->latch_status)) update_dentry_inode_time (core->latch_dentry); if ((core->adapter_dentry) && (temp->info->adapter_status != info->adapter_status)) update_dentry_inode_time (core->adapter_dentry); if ((core->cur_bus_speed_dentry) && (temp->info->cur_bus_speed != info->cur_bus_speed)) update_dentry_inode_time (core->cur_bus_speed_dentry); 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_type); if (result) { err("register_filesystem failed with %d\n", result); goto exit; } result = cpci_hotplug_init(debug); if (result) { err ("cpci_hotplug_init with error %d\n", result); goto error_fs; } #ifdef CONFIG_PROC_FS /* create mount point for pcihpfs */ slotdir = proc_mkdir(slotdir_name, proc_bus_pci_dir); #endif info (DRIVER_DESC " version: " DRIVER_VERSION "\n"); goto exit; error_fs: unregister_filesystem(&pcihpfs_type); exit: return result; } static void __exit pci_hotplug_exit (void) { cpci_hotplug_exit(); unregister_filesystem(&pcihpfs_type); #ifdef CONFIG_PROC_FS if (slotdir) remove_proc_entry(slotdir_name, proc_bus_pci_dir); #endif } 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);