diff options
author | Timothy Pearson <tpearson@raptorengineering.com> | 2017-08-23 14:45:25 -0500 |
---|---|---|
committer | Timothy Pearson <tpearson@raptorengineering.com> | 2017-08-23 14:45:25 -0500 |
commit | fcbb27b0ec6dcbc5a5108cb8fb19eae64593d204 (patch) | |
tree | 22962a4387943edc841c72a4e636a068c66d58fd /drivers/pci | |
download | ast2050-linux-kernel-fcbb27b0ec6dcbc5a5108cb8fb19eae64593d204.zip ast2050-linux-kernel-fcbb27b0ec6dcbc5a5108cb8fb19eae64593d204.tar.gz |
Initial import of modified Linux 2.6.28 tree
Original upstream URL:
git://git.kernel.org/pub/scm/linux/kernel/git/stable/linux-stable.git | branch linux-2.6.28.y
Diffstat (limited to 'drivers/pci')
94 files changed, 51273 insertions, 0 deletions
diff --git a/drivers/pci/.gitignore b/drivers/pci/.gitignore new file mode 100644 index 0000000..f297ca8 --- /dev/null +++ b/drivers/pci/.gitignore @@ -0,0 +1,4 @@ +classlist.h +devlist.h +gen-devlist + diff --git a/drivers/pci/Kconfig b/drivers/pci/Kconfig new file mode 100644 index 0000000..e1ca425 --- /dev/null +++ b/drivers/pci/Kconfig @@ -0,0 +1,52 @@ +# +# PCI configuration +# +config ARCH_SUPPORTS_MSI + bool + default n + +config PCI_MSI + bool "Message Signaled Interrupts (MSI and MSI-X)" + depends on PCI + depends on ARCH_SUPPORTS_MSI + help + This allows device drivers to enable MSI (Message Signaled + Interrupts). Message Signaled Interrupts enable a device to + generate an interrupt using an inbound Memory Write on its + PCI bus instead of asserting a device IRQ pin. + + Use of PCI MSI interrupts can be disabled at kernel boot time + by using the 'pci=nomsi' option. This disables MSI for the + entire system. + + If you don't know what to do here, say N. + +config PCI_LEGACY + bool "Enable deprecated pci_find_* API" + depends on PCI + default y + help + Say Y here if you want to include support for the deprecated + pci_find_slot() and pci_find_device() APIs. Most drivers have + been converted over to using the proper hotplug APIs, so this + option serves to include/exclude only a few drivers that are + still using this API. + +config PCI_DEBUG + bool "PCI Debugging" + depends on PCI && DEBUG_KERNEL + help + Say Y here if you want the PCI core to produce a bunch of debug + messages to the system log. Select this if you are having a + problem with PCI support and want to see more of what is going on. + + When in doubt, say N. + +config HT_IRQ + bool "Interrupts on hypertransport devices" + default y + depends on PCI && X86_LOCAL_APIC && X86_IO_APIC + help + This allows native hypertransport devices to use interrupts. + + If unsure say Y. diff --git a/drivers/pci/Makefile b/drivers/pci/Makefile new file mode 100644 index 0000000..af3bfe2 --- /dev/null +++ b/drivers/pci/Makefile @@ -0,0 +1,58 @@ +# +# Makefile for the PCI bus specific drivers. +# + +obj-y += access.o bus.o probe.o remove.o pci.o quirks.o slot.o \ + pci-driver.o search.o pci-sysfs.o rom.o setup-res.o \ + irq.o +obj-$(CONFIG_PROC_FS) += proc.o + +# Build PCI Express stuff if needed +obj-$(CONFIG_PCIEPORTBUS) += pcie/ + +obj-$(CONFIG_HOTPLUG) += hotplug.o + +# Build the PCI Hotplug drivers if we were asked to +obj-$(CONFIG_HOTPLUG_PCI) += hotplug/ +ifdef CONFIG_HOTPLUG_PCI +obj-y += hotplug-pci.o +endif + +# Build the PCI MSI interrupt support +obj-$(CONFIG_PCI_MSI) += msi.o + +# Build the Hypertransport interrupt support +obj-$(CONFIG_HT_IRQ) += htirq.o + +# Build Intel IOMMU support +obj-$(CONFIG_DMAR) += dmar.o iova.o intel-iommu.o + +obj-$(CONFIG_INTR_REMAP) += dmar.o intr_remapping.o + +# +# Some architectures use the generic PCI setup functions +# +obj-$(CONFIG_X86) += setup-bus.o +obj-$(CONFIG_ALPHA) += setup-bus.o setup-irq.o +obj-$(CONFIG_ARM) += setup-bus.o setup-irq.o +obj-$(CONFIG_PARISC) += setup-bus.o +obj-$(CONFIG_SUPERH) += setup-bus.o setup-irq.o +obj-$(CONFIG_PPC32) += setup-irq.o +obj-$(CONFIG_PPC) += setup-bus.o +obj-$(CONFIG_MIPS) += setup-bus.o setup-irq.o +obj-$(CONFIG_X86_VISWS) += setup-irq.o +obj-$(CONFIG_MN10300) += setup-bus.o + +# +# ACPI Related PCI FW Functions +# +obj-$(CONFIG_ACPI) += pci-acpi.o + +# Cardbus & CompactPCI use setup-bus +obj-$(CONFIG_HOTPLUG) += setup-bus.o + +obj-$(CONFIG_PCI_SYSCALL) += syscall.o + +ifeq ($(CONFIG_PCI_DEBUG),y) +EXTRA_CFLAGS += -DDEBUG +endif diff --git a/drivers/pci/access.c b/drivers/pci/access.c new file mode 100644 index 0000000..39bb96b --- /dev/null +++ b/drivers/pci/access.c @@ -0,0 +1,333 @@ +#include <linux/delay.h> +#include <linux/pci.h> +#include <linux/module.h> +#include <linux/sched.h> +#include <linux/ioport.h> +#include <linux/wait.h> + +#include "pci.h" + +/* + * This interrupt-safe spinlock protects all accesses to PCI + * configuration space. + */ + +static DEFINE_SPINLOCK(pci_lock); + +/* + * Wrappers for all PCI configuration access functions. They just check + * alignment, do locking and call the low-level functions pointed to + * by pci_dev->ops. + */ + +#define PCI_byte_BAD 0 +#define PCI_word_BAD (pos & 1) +#define PCI_dword_BAD (pos & 3) + +#define PCI_OP_READ(size,type,len) \ +int pci_bus_read_config_##size \ + (struct pci_bus *bus, unsigned int devfn, int pos, type *value) \ +{ \ + int res; \ + unsigned long flags; \ + u32 data = 0; \ + if (PCI_##size##_BAD) return PCIBIOS_BAD_REGISTER_NUMBER; \ + spin_lock_irqsave(&pci_lock, flags); \ + res = bus->ops->read(bus, devfn, pos, len, &data); \ + *value = (type)data; \ + spin_unlock_irqrestore(&pci_lock, flags); \ + return res; \ +} + +#define PCI_OP_WRITE(size,type,len) \ +int pci_bus_write_config_##size \ + (struct pci_bus *bus, unsigned int devfn, int pos, type value) \ +{ \ + int res; \ + unsigned long flags; \ + if (PCI_##size##_BAD) return PCIBIOS_BAD_REGISTER_NUMBER; \ + spin_lock_irqsave(&pci_lock, flags); \ + res = bus->ops->write(bus, devfn, pos, len, value); \ + spin_unlock_irqrestore(&pci_lock, flags); \ + return res; \ +} + +PCI_OP_READ(byte, u8, 1) +PCI_OP_READ(word, u16, 2) +PCI_OP_READ(dword, u32, 4) +PCI_OP_WRITE(byte, u8, 1) +PCI_OP_WRITE(word, u16, 2) +PCI_OP_WRITE(dword, u32, 4) + +EXPORT_SYMBOL(pci_bus_read_config_byte); +EXPORT_SYMBOL(pci_bus_read_config_word); +EXPORT_SYMBOL(pci_bus_read_config_dword); +EXPORT_SYMBOL(pci_bus_write_config_byte); +EXPORT_SYMBOL(pci_bus_write_config_word); +EXPORT_SYMBOL(pci_bus_write_config_dword); + +/* + * The following routines are to prevent the user from accessing PCI config + * space when it's unsafe to do so. Some devices require this during BIST and + * we're required to prevent it during D-state transitions. + * + * We have a bit per device to indicate it's blocked and a global wait queue + * for callers to sleep on until devices are unblocked. + */ +static DECLARE_WAIT_QUEUE_HEAD(pci_ucfg_wait); + +static noinline void pci_wait_ucfg(struct pci_dev *dev) +{ + DECLARE_WAITQUEUE(wait, current); + + __add_wait_queue(&pci_ucfg_wait, &wait); + do { + set_current_state(TASK_UNINTERRUPTIBLE); + spin_unlock_irq(&pci_lock); + schedule(); + spin_lock_irq(&pci_lock); + } while (dev->block_ucfg_access); + __remove_wait_queue(&pci_ucfg_wait, &wait); +} + +#define PCI_USER_READ_CONFIG(size,type) \ +int pci_user_read_config_##size \ + (struct pci_dev *dev, int pos, type *val) \ +{ \ + int ret = 0; \ + u32 data = -1; \ + if (PCI_##size##_BAD) return PCIBIOS_BAD_REGISTER_NUMBER; \ + spin_lock_irq(&pci_lock); \ + if (unlikely(dev->block_ucfg_access)) pci_wait_ucfg(dev); \ + ret = dev->bus->ops->read(dev->bus, dev->devfn, \ + pos, sizeof(type), &data); \ + spin_unlock_irq(&pci_lock); \ + *val = (type)data; \ + return ret; \ +} + +#define PCI_USER_WRITE_CONFIG(size,type) \ +int pci_user_write_config_##size \ + (struct pci_dev *dev, int pos, type val) \ +{ \ + int ret = -EIO; \ + if (PCI_##size##_BAD) return PCIBIOS_BAD_REGISTER_NUMBER; \ + spin_lock_irq(&pci_lock); \ + if (unlikely(dev->block_ucfg_access)) pci_wait_ucfg(dev); \ + ret = dev->bus->ops->write(dev->bus, dev->devfn, \ + pos, sizeof(type), val); \ + spin_unlock_irq(&pci_lock); \ + return ret; \ +} + +PCI_USER_READ_CONFIG(byte, u8) +PCI_USER_READ_CONFIG(word, u16) +PCI_USER_READ_CONFIG(dword, u32) +PCI_USER_WRITE_CONFIG(byte, u8) +PCI_USER_WRITE_CONFIG(word, u16) +PCI_USER_WRITE_CONFIG(dword, u32) + +/* VPD access through PCI 2.2+ VPD capability */ + +#define PCI_VPD_PCI22_SIZE (PCI_VPD_ADDR_MASK + 1) + +struct pci_vpd_pci22 { + struct pci_vpd base; + spinlock_t lock; /* controls access to hardware and the flags */ + u8 cap; + bool busy; + bool flag; /* value of F bit to wait for */ +}; + +/* Wait for last operation to complete */ +static int pci_vpd_pci22_wait(struct pci_dev *dev) +{ + struct pci_vpd_pci22 *vpd = + container_of(dev->vpd, struct pci_vpd_pci22, base); + u16 flag, status; + int wait; + int ret; + + if (!vpd->busy) + return 0; + + flag = vpd->flag ? PCI_VPD_ADDR_F : 0; + wait = vpd->flag ? 10 : 1000; /* read: 100 us; write: 10 ms */ + for (;;) { + ret = pci_user_read_config_word(dev, + vpd->cap + PCI_VPD_ADDR, + &status); + if (ret < 0) + return ret; + if ((status & PCI_VPD_ADDR_F) == flag) { + vpd->busy = false; + return 0; + } + if (wait-- == 0) + return -ETIMEDOUT; + udelay(10); + } +} + +static int pci_vpd_pci22_read(struct pci_dev *dev, int pos, int size, + char *buf) +{ + struct pci_vpd_pci22 *vpd = + container_of(dev->vpd, struct pci_vpd_pci22, base); + u32 val; + int ret; + int begin, end, i; + + if (pos < 0 || pos > vpd->base.len || size > vpd->base.len - pos) + return -EINVAL; + if (size == 0) + return 0; + + spin_lock_irq(&vpd->lock); + ret = pci_vpd_pci22_wait(dev); + if (ret < 0) + goto out; + ret = pci_user_write_config_word(dev, vpd->cap + PCI_VPD_ADDR, + pos & ~3); + if (ret < 0) + goto out; + vpd->busy = true; + vpd->flag = 1; + ret = pci_vpd_pci22_wait(dev); + if (ret < 0) + goto out; + ret = pci_user_read_config_dword(dev, vpd->cap + PCI_VPD_DATA, + &val); +out: + spin_unlock_irq(&vpd->lock); + if (ret < 0) + return ret; + + /* Convert to bytes */ + begin = pos & 3; + end = min(4, begin + size); + for (i = 0; i < end; ++i) { + if (i >= begin) + *buf++ = val; + val >>= 8; + } + return end - begin; +} + +static int pci_vpd_pci22_write(struct pci_dev *dev, int pos, int size, + const char *buf) +{ + struct pci_vpd_pci22 *vpd = + container_of(dev->vpd, struct pci_vpd_pci22, base); + u32 val; + int ret; + + if (pos < 0 || pos > vpd->base.len || pos & 3 || + size > vpd->base.len - pos || size < 4) + return -EINVAL; + + val = (u8) *buf++; + val |= ((u8) *buf++) << 8; + val |= ((u8) *buf++) << 16; + val |= ((u32)(u8) *buf++) << 24; + + spin_lock_irq(&vpd->lock); + ret = pci_vpd_pci22_wait(dev); + if (ret < 0) + goto out; + ret = pci_user_write_config_dword(dev, vpd->cap + PCI_VPD_DATA, + val); + if (ret < 0) + goto out; + ret = pci_user_write_config_word(dev, vpd->cap + PCI_VPD_ADDR, + pos | PCI_VPD_ADDR_F); + if (ret < 0) + goto out; + vpd->busy = true; + vpd->flag = 0; + ret = pci_vpd_pci22_wait(dev); +out: + spin_unlock_irq(&vpd->lock); + if (ret < 0) + return ret; + + return 4; +} + +static void pci_vpd_pci22_release(struct pci_dev *dev) +{ + kfree(container_of(dev->vpd, struct pci_vpd_pci22, base)); +} + +static struct pci_vpd_ops pci_vpd_pci22_ops = { + .read = pci_vpd_pci22_read, + .write = pci_vpd_pci22_write, + .release = pci_vpd_pci22_release, +}; + +int pci_vpd_pci22_init(struct pci_dev *dev) +{ + struct pci_vpd_pci22 *vpd; + u8 cap; + + cap = pci_find_capability(dev, PCI_CAP_ID_VPD); + if (!cap) + return -ENODEV; + vpd = kzalloc(sizeof(*vpd), GFP_ATOMIC); + if (!vpd) + return -ENOMEM; + + vpd->base.len = PCI_VPD_PCI22_SIZE; + vpd->base.ops = &pci_vpd_pci22_ops; + spin_lock_init(&vpd->lock); + vpd->cap = cap; + vpd->busy = false; + dev->vpd = &vpd->base; + return 0; +} + +/** + * pci_block_user_cfg_access - Block userspace PCI config reads/writes + * @dev: pci device struct + * + * When user access is blocked, any reads or writes to config space will + * sleep until access is unblocked again. We don't allow nesting of + * block/unblock calls. + */ +void pci_block_user_cfg_access(struct pci_dev *dev) +{ + unsigned long flags; + int was_blocked; + + spin_lock_irqsave(&pci_lock, flags); + was_blocked = dev->block_ucfg_access; + dev->block_ucfg_access = 1; + spin_unlock_irqrestore(&pci_lock, flags); + + /* If we BUG() inside the pci_lock, we're guaranteed to hose + * the machine */ + BUG_ON(was_blocked); +} +EXPORT_SYMBOL_GPL(pci_block_user_cfg_access); + +/** + * pci_unblock_user_cfg_access - Unblock userspace PCI config reads/writes + * @dev: pci device struct + * + * This function allows userspace PCI config accesses to resume. + */ +void pci_unblock_user_cfg_access(struct pci_dev *dev) +{ + unsigned long flags; + + spin_lock_irqsave(&pci_lock, flags); + + /* This indicates a problem in the caller, but we don't need + * to kill them, unlike a double-block above. */ + WARN_ON(!dev->block_ucfg_access); + + dev->block_ucfg_access = 0; + wake_up_all(&pci_ucfg_wait); + spin_unlock_irqrestore(&pci_lock, flags); +} +EXPORT_SYMBOL_GPL(pci_unblock_user_cfg_access); diff --git a/drivers/pci/bus.c b/drivers/pci/bus.c new file mode 100644 index 0000000..999cc40 --- /dev/null +++ b/drivers/pci/bus.c @@ -0,0 +1,226 @@ +/* + * drivers/pci/bus.c + * + * From setup-res.c, by: + * Dave Rusling (david.rusling@reo.mts.dec.com) + * David Mosberger (davidm@cs.arizona.edu) + * David Miller (davem@redhat.com) + * Ivan Kokshaysky (ink@jurassic.park.msu.ru) + */ +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/pci.h> +#include <linux/errno.h> +#include <linux/ioport.h> +#include <linux/proc_fs.h> +#include <linux/init.h> + +#include "pci.h" + +/** + * pci_bus_alloc_resource - allocate a resource from a parent bus + * @bus: PCI bus + * @res: resource to allocate + * @size: size of resource to allocate + * @align: alignment of resource to allocate + * @min: minimum /proc/iomem address to allocate + * @type_mask: IORESOURCE_* type flags + * @alignf: resource alignment function + * @alignf_data: data argument for resource alignment function + * + * Given the PCI bus a device resides on, the size, minimum address, + * alignment and type, try to find an acceptable resource allocation + * for a specific device resource. + */ +int +pci_bus_alloc_resource(struct pci_bus *bus, struct resource *res, + resource_size_t size, resource_size_t align, + resource_size_t min, unsigned int type_mask, + void (*alignf)(void *, struct resource *, resource_size_t, + resource_size_t), + void *alignf_data) +{ + int i, ret = -ENOMEM; + + type_mask |= IORESOURCE_IO | IORESOURCE_MEM; + + for (i = 0; i < PCI_BUS_NUM_RESOURCES; i++) { + struct resource *r = bus->resource[i]; + if (!r) + continue; + + /* type_mask must match */ + if ((res->flags ^ r->flags) & type_mask) + continue; + + /* We cannot allocate a non-prefetching resource + from a pre-fetching area */ + if ((r->flags & IORESOURCE_PREFETCH) && + !(res->flags & IORESOURCE_PREFETCH)) + continue; + + /* Ok, try it out.. */ + ret = allocate_resource(r, res, size, + r->start ? : min, + -1, align, + alignf, alignf_data); + if (ret == 0) + break; + } + return ret; +} + +/** + * add a single device + * @dev: device to add + * + * This adds a single pci device to the global + * device list and adds sysfs and procfs entries + */ +int pci_bus_add_device(struct pci_dev *dev) +{ + int retval; + retval = device_add(&dev->dev); + if (retval) + return retval; + + dev->is_added = 1; + pci_proc_attach_device(dev); + pci_create_sysfs_dev_files(dev); + return 0; +} + +/** + * pci_bus_add_devices - insert newly discovered PCI devices + * @bus: bus to check for new devices + * + * Add newly discovered PCI devices (which are on the bus->devices + * list) to the global PCI device list, add the sysfs and procfs + * entries. Where a bridge is found, add the discovered bus to + * the parents list of child buses, and recurse (breadth-first + * to be compatible with 2.4) + * + * Call hotplug for each new devices. + */ +void pci_bus_add_devices(struct pci_bus *bus) +{ + struct pci_dev *dev; + struct pci_bus *child_bus; + int retval; + + list_for_each_entry(dev, &bus->devices, bus_list) { + /* Skip already-added devices */ + if (dev->is_added) + continue; + retval = pci_bus_add_device(dev); + if (retval) + dev_err(&dev->dev, "Error adding device, continuing\n"); + } + + list_for_each_entry(dev, &bus->devices, bus_list) { + BUG_ON(!dev->is_added); + + /* + * If there is an unattached subordinate bus, attach + * it and then scan for unattached PCI devices. + */ + if (dev->subordinate) { + if (list_empty(&dev->subordinate->node)) { + down_write(&pci_bus_sem); + list_add_tail(&dev->subordinate->node, + &dev->bus->children); + up_write(&pci_bus_sem); + } + pci_bus_add_devices(dev->subordinate); + + /* register the bus with sysfs as the parent is now + * properly registered. */ + child_bus = dev->subordinate; + if (child_bus->is_added) + continue; + child_bus->dev.parent = child_bus->bridge; + retval = device_register(&child_bus->dev); + if (retval) + dev_err(&dev->dev, "Error registering pci_bus," + " continuing...\n"); + else { + child_bus->is_added = 1; + retval = device_create_file(&child_bus->dev, + &dev_attr_cpuaffinity); + } + if (retval) + dev_err(&dev->dev, "Error creating cpuaffinity" + " file, continuing...\n"); + + retval = device_create_file(&child_bus->dev, + &dev_attr_cpulistaffinity); + if (retval) + dev_err(&dev->dev, + "Error creating cpulistaffinity" + " file, continuing...\n"); + } + } +} + +void pci_enable_bridges(struct pci_bus *bus) +{ + struct pci_dev *dev; + int retval; + + list_for_each_entry(dev, &bus->devices, bus_list) { + if (dev->subordinate) { + retval = pci_enable_device(dev); + pci_set_master(dev); + pci_enable_bridges(dev->subordinate); + } + } +} + +/** pci_walk_bus - walk devices on/under bus, calling callback. + * @top bus whose devices should be walked + * @cb callback to be called for each device found + * @userdata arbitrary pointer to be passed to callback. + * + * Walk the given bus, including any bridged devices + * on buses under this bus. Call the provided callback + * on each device found. + */ +void pci_walk_bus(struct pci_bus *top, void (*cb)(struct pci_dev *, void *), + void *userdata) +{ + struct pci_dev *dev; + struct pci_bus *bus; + struct list_head *next; + + bus = top; + down_read(&pci_bus_sem); + next = top->devices.next; + for (;;) { + if (next == &bus->devices) { + /* end of this bus, go up or finish */ + if (bus == top) + break; + next = bus->self->bus_list.next; + bus = bus->self->bus; + continue; + } + dev = list_entry(next, struct pci_dev, bus_list); + if (dev->subordinate) { + /* this is a pci-pci bridge, do its devices next */ + next = dev->subordinate->devices.next; + bus = dev->subordinate; + } else + next = dev->bus_list.next; + + /* Run device routines with the device locked */ + down(&dev->dev.sem); + cb(dev, userdata); + up(&dev->dev.sem); + } + up_read(&pci_bus_sem); +} + +EXPORT_SYMBOL(pci_bus_alloc_resource); +EXPORT_SYMBOL_GPL(pci_bus_add_device); +EXPORT_SYMBOL(pci_bus_add_devices); +EXPORT_SYMBOL(pci_enable_bridges); diff --git a/drivers/pci/dmar.c b/drivers/pci/dmar.c new file mode 100644 index 0000000..691b3ad --- /dev/null +++ b/drivers/pci/dmar.c @@ -0,0 +1,762 @@ +/* + * Copyright (c) 2006, Intel Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. 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., 59 Temple + * Place - Suite 330, Boston, MA 02111-1307 USA. + * + * Copyright (C) 2006-2008 Intel Corporation + * Author: Ashok Raj <ashok.raj@intel.com> + * Author: Shaohua Li <shaohua.li@intel.com> + * Author: Anil S Keshavamurthy <anil.s.keshavamurthy@intel.com> + * + * This file implements early detection/parsing of Remapping Devices + * reported to OS through BIOS via DMA remapping reporting (DMAR) ACPI + * tables. + * + * These routines are used by both DMA-remapping and Interrupt-remapping + */ + +#include <linux/pci.h> +#include <linux/dmar.h> +#include <linux/iova.h> +#include <linux/intel-iommu.h> +#include <linux/timer.h> + +#undef PREFIX +#define PREFIX "DMAR:" + +/* No locks are needed as DMA remapping hardware unit + * list is constructed at boot time and hotplug of + * these units are not supported by the architecture. + */ +LIST_HEAD(dmar_drhd_units); + +static struct acpi_table_header * __initdata dmar_tbl; + +static void __init dmar_register_drhd_unit(struct dmar_drhd_unit *drhd) +{ + /* + * add INCLUDE_ALL at the tail, so scan the list will find it at + * the very end. + */ + if (drhd->include_all) + list_add_tail(&drhd->list, &dmar_drhd_units); + else + list_add(&drhd->list, &dmar_drhd_units); +} + +static int __init dmar_parse_one_dev_scope(struct acpi_dmar_device_scope *scope, + struct pci_dev **dev, u16 segment) +{ + struct pci_bus *bus; + struct pci_dev *pdev = NULL; + struct acpi_dmar_pci_path *path; + int count; + + bus = pci_find_bus(segment, scope->bus); + path = (struct acpi_dmar_pci_path *)(scope + 1); + count = (scope->length - sizeof(struct acpi_dmar_device_scope)) + / sizeof(struct acpi_dmar_pci_path); + + while (count) { + if (pdev) + pci_dev_put(pdev); + /* + * Some BIOSes list non-exist devices in DMAR table, just + * ignore it + */ + if (!bus) { + printk(KERN_WARNING + PREFIX "Device scope bus [%d] not found\n", + scope->bus); + break; + } + pdev = pci_get_slot(bus, PCI_DEVFN(path->dev, path->fn)); + if (!pdev) { + printk(KERN_WARNING PREFIX + "Device scope device [%04x:%02x:%02x.%02x] not found\n", + segment, bus->number, path->dev, path->fn); + break; + } + path ++; + count --; + bus = pdev->subordinate; + } + if (!pdev) { + printk(KERN_WARNING PREFIX + "Device scope device [%04x:%02x:%02x.%02x] not found\n", + segment, scope->bus, path->dev, path->fn); + *dev = NULL; + return 0; + } + if ((scope->entry_type == ACPI_DMAR_SCOPE_TYPE_ENDPOINT && \ + pdev->subordinate) || (scope->entry_type == \ + ACPI_DMAR_SCOPE_TYPE_BRIDGE && !pdev->subordinate)) { + pci_dev_put(pdev); + printk(KERN_WARNING PREFIX + "Device scope type does not match for %s\n", + pci_name(pdev)); + return -EINVAL; + } + *dev = pdev; + return 0; +} + +static int __init dmar_parse_dev_scope(void *start, void *end, int *cnt, + struct pci_dev ***devices, u16 segment) +{ + struct acpi_dmar_device_scope *scope; + void * tmp = start; + int index; + int ret; + + *cnt = 0; + while (start < end) { + scope = start; + if (scope->entry_type == ACPI_DMAR_SCOPE_TYPE_ENDPOINT || + scope->entry_type == ACPI_DMAR_SCOPE_TYPE_BRIDGE) + (*cnt)++; + else + printk(KERN_WARNING PREFIX + "Unsupported device scope\n"); + start += scope->length; + } + if (*cnt == 0) + return 0; + + *devices = kcalloc(*cnt, sizeof(struct pci_dev *), GFP_KERNEL); + if (!*devices) + return -ENOMEM; + + start = tmp; + index = 0; + while (start < end) { + scope = start; + if (scope->entry_type == ACPI_DMAR_SCOPE_TYPE_ENDPOINT || + scope->entry_type == ACPI_DMAR_SCOPE_TYPE_BRIDGE) { + ret = dmar_parse_one_dev_scope(scope, + &(*devices)[index], segment); + if (ret) { + kfree(*devices); + return ret; + } + index ++; + } + start += scope->length; + } + + return 0; +} + +/** + * dmar_parse_one_drhd - parses exactly one DMA remapping hardware definition + * structure which uniquely represent one DMA remapping hardware unit + * present in the platform + */ +static int __init +dmar_parse_one_drhd(struct acpi_dmar_header *header) +{ + struct acpi_dmar_hardware_unit *drhd; + struct dmar_drhd_unit *dmaru; + int ret = 0; + + dmaru = kzalloc(sizeof(*dmaru), GFP_KERNEL); + if (!dmaru) + return -ENOMEM; + + dmaru->hdr = header; + drhd = (struct acpi_dmar_hardware_unit *)header; + dmaru->reg_base_addr = drhd->address; + dmaru->include_all = drhd->flags & 0x1; /* BIT0: INCLUDE_ALL */ + + ret = alloc_iommu(dmaru); + if (ret) { + kfree(dmaru); + return ret; + } + dmar_register_drhd_unit(dmaru); + return 0; +} + +static int __init dmar_parse_dev(struct dmar_drhd_unit *dmaru) +{ + struct acpi_dmar_hardware_unit *drhd; + static int include_all; + int ret = 0; + + drhd = (struct acpi_dmar_hardware_unit *) dmaru->hdr; + + if (!dmaru->include_all) + ret = dmar_parse_dev_scope((void *)(drhd + 1), + ((void *)drhd) + drhd->header.length, + &dmaru->devices_cnt, &dmaru->devices, + drhd->segment); + else { + /* Only allow one INCLUDE_ALL */ + if (include_all) { + printk(KERN_WARNING PREFIX "Only one INCLUDE_ALL " + "device scope is allowed\n"); + ret = -EINVAL; + } + include_all = 1; + } + + if (ret) { + list_del(&dmaru->list); + kfree(dmaru); + } + return ret; +} + +#ifdef CONFIG_DMAR +LIST_HEAD(dmar_rmrr_units); + +static void __init dmar_register_rmrr_unit(struct dmar_rmrr_unit *rmrr) +{ + list_add(&rmrr->list, &dmar_rmrr_units); +} + + +static int __init +dmar_parse_one_rmrr(struct acpi_dmar_header *header) +{ + struct acpi_dmar_reserved_memory *rmrr; + struct dmar_rmrr_unit *rmrru; + + rmrru = kzalloc(sizeof(*rmrru), GFP_KERNEL); + if (!rmrru) + return -ENOMEM; + + rmrru->hdr = header; + rmrr = (struct acpi_dmar_reserved_memory *)header; + rmrru->base_address = rmrr->base_address; + rmrru->end_address = rmrr->end_address; + + dmar_register_rmrr_unit(rmrru); + return 0; +} + +static int __init +rmrr_parse_dev(struct dmar_rmrr_unit *rmrru) +{ + struct acpi_dmar_reserved_memory *rmrr; + int ret; + + rmrr = (struct acpi_dmar_reserved_memory *) rmrru->hdr; + ret = dmar_parse_dev_scope((void *)(rmrr + 1), + ((void *)rmrr) + rmrr->header.length, + &rmrru->devices_cnt, &rmrru->devices, rmrr->segment); + + if (ret || (rmrru->devices_cnt == 0)) { + list_del(&rmrru->list); + kfree(rmrru); + } + return ret; +} +#endif + +static void __init +dmar_table_print_dmar_entry(struct acpi_dmar_header *header) +{ + struct acpi_dmar_hardware_unit *drhd; + struct acpi_dmar_reserved_memory *rmrr; + + switch (header->type) { + case ACPI_DMAR_TYPE_HARDWARE_UNIT: + drhd = (struct acpi_dmar_hardware_unit *)header; + printk (KERN_INFO PREFIX + "DRHD (flags: 0x%08x)base: 0x%016Lx\n", + drhd->flags, (unsigned long long)drhd->address); + break; + case ACPI_DMAR_TYPE_RESERVED_MEMORY: + rmrr = (struct acpi_dmar_reserved_memory *)header; + + printk (KERN_INFO PREFIX + "RMRR base: 0x%016Lx end: 0x%016Lx\n", + (unsigned long long)rmrr->base_address, + (unsigned long long)rmrr->end_address); + break; + } +} + +/** + * dmar_table_detect - checks to see if the platform supports DMAR devices + */ +static int __init dmar_table_detect(void) +{ + acpi_status status = AE_OK; + + /* if we could find DMAR table, then there are DMAR devices */ + status = acpi_get_table(ACPI_SIG_DMAR, 0, + (struct acpi_table_header **)&dmar_tbl); + + if (ACPI_SUCCESS(status) && !dmar_tbl) { + printk (KERN_WARNING PREFIX "Unable to map DMAR\n"); + status = AE_NOT_FOUND; + } + + return (ACPI_SUCCESS(status) ? 1 : 0); +} + +/** + * parse_dmar_table - parses the DMA reporting table + */ +static int __init +parse_dmar_table(void) +{ + struct acpi_table_dmar *dmar; + struct acpi_dmar_header *entry_header; + int ret = 0; + + /* + * Do it again, earlier dmar_tbl mapping could be mapped with + * fixed map. + */ + dmar_table_detect(); + + dmar = (struct acpi_table_dmar *)dmar_tbl; + if (!dmar) + return -ENODEV; + + if (dmar->width < PAGE_SHIFT - 1) { + printk(KERN_WARNING PREFIX "Invalid DMAR haw\n"); + return -EINVAL; + } + + printk (KERN_INFO PREFIX "Host address width %d\n", + dmar->width + 1); + + entry_header = (struct acpi_dmar_header *)(dmar + 1); + while (((unsigned long)entry_header) < + (((unsigned long)dmar) + dmar_tbl->length)) { + dmar_table_print_dmar_entry(entry_header); + + switch (entry_header->type) { + case ACPI_DMAR_TYPE_HARDWARE_UNIT: + ret = dmar_parse_one_drhd(entry_header); + break; + case ACPI_DMAR_TYPE_RESERVED_MEMORY: +#ifdef CONFIG_DMAR + ret = dmar_parse_one_rmrr(entry_header); +#endif + break; + default: + printk(KERN_WARNING PREFIX + "Unknown DMAR structure type\n"); + ret = 0; /* for forward compatibility */ + break; + } + if (ret) + break; + + entry_header = ((void *)entry_header + entry_header->length); + } + return ret; +} + +int dmar_pci_device_match(struct pci_dev *devices[], int cnt, + struct pci_dev *dev) +{ + int index; + + while (dev) { + for (index = 0; index < cnt; index++) + if (dev == devices[index]) + return 1; + + /* Check our parent */ + dev = dev->bus->self; + } + + return 0; +} + +struct dmar_drhd_unit * +dmar_find_matched_drhd_unit(struct pci_dev *dev) +{ + struct dmar_drhd_unit *drhd = NULL; + + list_for_each_entry(drhd, &dmar_drhd_units, list) { + if (drhd->include_all || dmar_pci_device_match(drhd->devices, + drhd->devices_cnt, dev)) + return drhd; + } + + return NULL; +} + +int __init dmar_dev_scope_init(void) +{ + struct dmar_drhd_unit *drhd, *drhd_n; + int ret = -ENODEV; + + list_for_each_entry_safe(drhd, drhd_n, &dmar_drhd_units, list) { + ret = dmar_parse_dev(drhd); + if (ret) + return ret; + } + +#ifdef CONFIG_DMAR + { + struct dmar_rmrr_unit *rmrr, *rmrr_n; + list_for_each_entry_safe(rmrr, rmrr_n, &dmar_rmrr_units, list) { + ret = rmrr_parse_dev(rmrr); + if (ret) + return ret; + } + } +#endif + + return ret; +} + + +int __init dmar_table_init(void) +{ + static int dmar_table_initialized; + int ret; + + if (dmar_table_initialized) + return 0; + + dmar_table_initialized = 1; + + ret = parse_dmar_table(); + if (ret) { + if (ret != -ENODEV) + printk(KERN_INFO PREFIX "parse DMAR table failure.\n"); + return ret; + } + + if (list_empty(&dmar_drhd_units)) { + printk(KERN_INFO PREFIX "No DMAR devices found\n"); + return -ENODEV; + } + +#ifdef CONFIG_DMAR + if (list_empty(&dmar_rmrr_units)) + printk(KERN_INFO PREFIX "No RMRR found\n"); +#endif + +#ifdef CONFIG_INTR_REMAP + parse_ioapics_under_ir(); +#endif + return 0; +} + +void __init detect_intel_iommu(void) +{ + int ret; + + ret = dmar_table_detect(); + + { +#ifdef CONFIG_INTR_REMAP + struct acpi_table_dmar *dmar; + /* + * for now we will disable dma-remapping when interrupt + * remapping is enabled. + * When support for queued invalidation for IOTLB invalidation + * is added, we will not need this any more. + */ + dmar = (struct acpi_table_dmar *) dmar_tbl; + if (ret && cpu_has_x2apic && dmar->flags & 0x1) + printk(KERN_INFO + "Queued invalidation will be enabled to support " + "x2apic and Intr-remapping.\n"); +#endif +#ifdef CONFIG_DMAR + if (ret && !no_iommu && !iommu_detected && !swiotlb && + !dmar_disabled) + iommu_detected = 1; +#endif + } + dmar_tbl = NULL; +} + + +int alloc_iommu(struct dmar_drhd_unit *drhd) +{ + struct intel_iommu *iommu; + int map_size; + u32 ver; + static int iommu_allocated = 0; + + iommu = kzalloc(sizeof(*iommu), GFP_KERNEL); + if (!iommu) + return -ENOMEM; + + iommu->seq_id = iommu_allocated++; + + iommu->reg = ioremap(drhd->reg_base_addr, VTD_PAGE_SIZE); + if (!iommu->reg) { + printk(KERN_ERR "IOMMU: can't map the region\n"); + goto error; + } + iommu->cap = dmar_readq(iommu->reg + DMAR_CAP_REG); + iommu->ecap = dmar_readq(iommu->reg + DMAR_ECAP_REG); + + /* the registers might be more than one page */ + map_size = max_t(int, ecap_max_iotlb_offset(iommu->ecap), + cap_max_fault_reg_offset(iommu->cap)); + map_size = VTD_PAGE_ALIGN(map_size); + if (map_size > VTD_PAGE_SIZE) { + iounmap(iommu->reg); + iommu->reg = ioremap(drhd->reg_base_addr, map_size); + if (!iommu->reg) { + printk(KERN_ERR "IOMMU: can't map the region\n"); + goto error; + } + } + + ver = readl(iommu->reg + DMAR_VER_REG); + pr_debug("IOMMU %llx: ver %d:%d cap %llx ecap %llx\n", + (unsigned long long)drhd->reg_base_addr, + DMAR_VER_MAJOR(ver), DMAR_VER_MINOR(ver), + (unsigned long long)iommu->cap, + (unsigned long long)iommu->ecap); + + spin_lock_init(&iommu->register_lock); + + drhd->iommu = iommu; + return 0; +error: + kfree(iommu); + return -1; +} + +void free_iommu(struct intel_iommu *iommu) +{ + if (!iommu) + return; + +#ifdef CONFIG_DMAR + free_dmar_iommu(iommu); +#endif + + if (iommu->reg) + iounmap(iommu->reg); + kfree(iommu); +} + +/* + * Reclaim all the submitted descriptors which have completed its work. + */ +static inline void reclaim_free_desc(struct q_inval *qi) +{ + while (qi->desc_status[qi->free_tail] == QI_DONE) { + qi->desc_status[qi->free_tail] = QI_FREE; + qi->free_tail = (qi->free_tail + 1) % QI_LENGTH; + qi->free_cnt++; + } +} + +/* + * Submit the queued invalidation descriptor to the remapping + * hardware unit and wait for its completion. + */ +void qi_submit_sync(struct qi_desc *desc, struct intel_iommu *iommu) +{ + struct q_inval *qi = iommu->qi; + struct qi_desc *hw, wait_desc; + int wait_index, index; + unsigned long flags; + + if (!qi) + return; + + hw = qi->desc; + + spin_lock_irqsave(&qi->q_lock, flags); + while (qi->free_cnt < 3) { + spin_unlock_irqrestore(&qi->q_lock, flags); + cpu_relax(); + spin_lock_irqsave(&qi->q_lock, flags); + } + + index = qi->free_head; + wait_index = (index + 1) % QI_LENGTH; + + qi->desc_status[index] = qi->desc_status[wait_index] = QI_IN_USE; + + hw[index] = *desc; + + wait_desc.low = QI_IWD_STATUS_DATA(2) | QI_IWD_STATUS_WRITE | QI_IWD_TYPE; + wait_desc.high = virt_to_phys(&qi->desc_status[wait_index]); + + hw[wait_index] = wait_desc; + + __iommu_flush_cache(iommu, &hw[index], sizeof(struct qi_desc)); + __iommu_flush_cache(iommu, &hw[wait_index], sizeof(struct qi_desc)); + + qi->free_head = (qi->free_head + 2) % QI_LENGTH; + qi->free_cnt -= 2; + + spin_lock(&iommu->register_lock); + /* + * update the HW tail register indicating the presence of + * new descriptors. + */ + writel(qi->free_head << 4, iommu->reg + DMAR_IQT_REG); + spin_unlock(&iommu->register_lock); + + while (qi->desc_status[wait_index] != QI_DONE) { + /* + * We will leave the interrupts disabled, to prevent interrupt + * context to queue another cmd while a cmd is already submitted + * and waiting for completion on this cpu. This is to avoid + * a deadlock where the interrupt context can wait indefinitely + * for free slots in the queue. + */ + spin_unlock(&qi->q_lock); + cpu_relax(); + spin_lock(&qi->q_lock); + } + + qi->desc_status[index] = QI_DONE; + + reclaim_free_desc(qi); + spin_unlock_irqrestore(&qi->q_lock, flags); +} + +/* + * Flush the global interrupt entry cache. + */ +void qi_global_iec(struct intel_iommu *iommu) +{ + struct qi_desc desc; + + desc.low = QI_IEC_TYPE; + desc.high = 0; + + qi_submit_sync(&desc, iommu); +} + +int qi_flush_context(struct intel_iommu *iommu, u16 did, u16 sid, u8 fm, + u64 type, int non_present_entry_flush) +{ + + struct qi_desc desc; + + if (non_present_entry_flush) { + if (!cap_caching_mode(iommu->cap)) + return 1; + else + did = 0; + } + + desc.low = QI_CC_FM(fm) | QI_CC_SID(sid) | QI_CC_DID(did) + | QI_CC_GRAN(type) | QI_CC_TYPE; + desc.high = 0; + + qi_submit_sync(&desc, iommu); + + return 0; + +} + +int qi_flush_iotlb(struct intel_iommu *iommu, u16 did, u64 addr, + unsigned int size_order, u64 type, + int non_present_entry_flush) +{ + u8 dw = 0, dr = 0; + + struct qi_desc desc; + int ih = 0; + + if (non_present_entry_flush) { + if (!cap_caching_mode(iommu->cap)) + return 1; + else + did = 0; + } + + if (cap_write_drain(iommu->cap)) + dw = 1; + + if (cap_read_drain(iommu->cap)) + dr = 1; + + desc.low = QI_IOTLB_DID(did) | QI_IOTLB_DR(dr) | QI_IOTLB_DW(dw) + | QI_IOTLB_GRAN(type) | QI_IOTLB_TYPE; + desc.high = QI_IOTLB_ADDR(addr) | QI_IOTLB_IH(ih) + | QI_IOTLB_AM(size_order); + + qi_submit_sync(&desc, iommu); + + return 0; + +} + +/* + * Enable Queued Invalidation interface. This is a must to support + * interrupt-remapping. Also used by DMA-remapping, which replaces + * register based IOTLB invalidation. + */ +int dmar_enable_qi(struct intel_iommu *iommu) +{ + u32 cmd, sts; + unsigned long flags; + struct q_inval *qi; + + if (!ecap_qis(iommu->ecap)) + return -ENOENT; + + /* + * queued invalidation is already setup and enabled. + */ + if (iommu->qi) + return 0; + + iommu->qi = kmalloc(sizeof(*qi), GFP_KERNEL); + if (!iommu->qi) + return -ENOMEM; + + qi = iommu->qi; + + qi->desc = (void *)(get_zeroed_page(GFP_KERNEL)); + if (!qi->desc) { + kfree(qi); + iommu->qi = 0; + return -ENOMEM; + } + + qi->desc_status = kmalloc(QI_LENGTH * sizeof(int), GFP_KERNEL); + if (!qi->desc_status) { + free_page((unsigned long) qi->desc); + kfree(qi); + iommu->qi = 0; + return -ENOMEM; + } + + qi->free_head = qi->free_tail = 0; + qi->free_cnt = QI_LENGTH; + + spin_lock_init(&qi->q_lock); + + spin_lock_irqsave(&iommu->register_lock, flags); + /* write zero to the tail reg */ + writel(0, iommu->reg + DMAR_IQT_REG); + + dmar_writeq(iommu->reg + DMAR_IQA_REG, virt_to_phys(qi->desc)); + + cmd = iommu->gcmd | DMA_GCMD_QIE; + iommu->gcmd |= DMA_GCMD_QIE; + writel(cmd, iommu->reg + DMAR_GCMD_REG); + + /* Make sure hardware complete it */ + IOMMU_WAIT_OP(iommu, DMAR_GSTS_REG, readl, (sts & DMA_GSTS_QIES), sts); + spin_unlock_irqrestore(&iommu->register_lock, flags); + + return 0; +} diff --git a/drivers/pci/hotplug-pci.c b/drivers/pci/hotplug-pci.c new file mode 100644 index 0000000..4d4a644 --- /dev/null +++ b/drivers/pci/hotplug-pci.c @@ -0,0 +1,20 @@ +/* Core PCI functionality used only by PCI hotplug */ + +#include <linux/pci.h> +#include "pci.h" + + +unsigned int __devinit pci_do_scan_bus(struct pci_bus *bus) +{ + unsigned int max; + + max = pci_scan_child_bus(bus); + + /* + * Make the discovered devices available. + */ + pci_bus_add_devices(bus); + + return max; +} +EXPORT_SYMBOL(pci_do_scan_bus); diff --git a/drivers/pci/hotplug.c b/drivers/pci/hotplug.c new file mode 100644 index 0000000..2b5352a --- /dev/null +++ b/drivers/pci/hotplug.c @@ -0,0 +1,37 @@ +#include <linux/kernel.h> +#include <linux/pci.h> +#include <linux/module.h> +#include "pci.h" + +int pci_uevent(struct device *dev, struct kobj_uevent_env *env) +{ + struct pci_dev *pdev; + + if (!dev) + return -ENODEV; + + pdev = to_pci_dev(dev); + if (!pdev) + return -ENODEV; + + if (add_uevent_var(env, "PCI_CLASS=%04X", pdev->class)) + return -ENOMEM; + + if (add_uevent_var(env, "PCI_ID=%04X:%04X", pdev->vendor, pdev->device)) + return -ENOMEM; + + if (add_uevent_var(env, "PCI_SUBSYS_ID=%04X:%04X", pdev->subsystem_vendor, + pdev->subsystem_device)) + return -ENOMEM; + + if (add_uevent_var(env, "PCI_SLOT_NAME=%s", pci_name(pdev))) + return -ENOMEM; + + if (add_uevent_var(env, "MODALIAS=pci:v%08Xd%08Xsv%08Xsd%08Xbc%02Xsc%02Xi%02x", + pdev->vendor, pdev->device, + pdev->subsystem_vendor, pdev->subsystem_device, + (u8)(pdev->class >> 16), (u8)(pdev->class >> 8), + (u8)(pdev->class))) + return -ENOMEM; + return 0; +} diff --git a/drivers/pci/hotplug/Kconfig b/drivers/pci/hotplug/Kconfig new file mode 100644 index 0000000..eacfb13 --- /dev/null +++ b/drivers/pci/hotplug/Kconfig @@ -0,0 +1,176 @@ +# +# PCI Hotplug support +# + +menuconfig HOTPLUG_PCI + tristate "Support for PCI Hotplug" + depends on PCI && HOTPLUG + ---help--- + Say Y here if you have a motherboard with a PCI Hotplug controller. + This allows you to add and remove PCI cards while the machine is + powered up and running. + + To compile this driver as a module, choose M here: the + module will be called pci_hotplug. + + When in doubt, say N. + +if HOTPLUG_PCI + +config HOTPLUG_PCI_FAKE + tristate "Fake PCI Hotplug driver" + help + Say Y here if you want to use the fake PCI hotplug driver. It can + be used to simulate PCI hotplug events if even if your system is + not PCI hotplug capable. + + This driver will "emulate" removing PCI devices from the system. + If the "power" file is written to with "0" then the specified PCI + device will be completely removed from the kernel. + + WARNING, this does NOT turn off the power to the PCI device. + This is a "logical" removal, not a physical or electrical + removal. + + Use this module at your own risk. You have been warned! + + To compile this driver as a module, choose M here: the + module will be called fakephp. + + When in doubt, say N. + +config HOTPLUG_PCI_COMPAQ + tristate "Compaq PCI Hotplug driver" + depends on X86 && PCI_BIOS && PCI_LEGACY + help + Say Y here if you have a motherboard with a Compaq PCI Hotplug + controller. + + To compile this driver as a module, choose M here: the + module will be called cpqphp. + + When in doubt, say N. + +config HOTPLUG_PCI_COMPAQ_NVRAM + bool "Save configuration into NVRAM on Compaq servers" + depends on HOTPLUG_PCI_COMPAQ + help + Say Y here if you have a Compaq server that has a PCI Hotplug + controller. This will allow the PCI Hotplug driver to store the PCI + system configuration options in NVRAM. + + When in doubt, say N. + +config HOTPLUG_PCI_IBM + tristate "IBM PCI Hotplug driver" + depends on X86_IO_APIC && X86 && PCI_BIOS + help + Say Y here if you have a motherboard with a IBM PCI Hotplug + controller. + + To compile this driver as a module, choose M here: the + module will be called ibmphp. + + When in doubt, say N. + +config HOTPLUG_PCI_ACPI + tristate "ACPI PCI Hotplug driver" + depends on (!ACPI_DOCK && ACPI) || (ACPI_DOCK) + help + Say Y here if you have a system that supports PCI Hotplug using + ACPI. + + To compile this driver as a module, choose M here: the + module will be called acpiphp. + + When in doubt, say N. + +config HOTPLUG_PCI_ACPI_IBM + tristate "ACPI PCI Hotplug driver IBM extensions" + depends on HOTPLUG_PCI_ACPI + help + Say Y here if you have an IBM system that supports PCI Hotplug using + ACPI. + + To compile this driver as a module, choose M here: the + module will be called acpiphp_ibm. + + When in doubt, say N. + +config HOTPLUG_PCI_CPCI + bool "CompactPCI Hotplug driver" + help + Say Y here if you have a CompactPCI system card with CompactPCI + hotswap support per the PICMG 2.1 specification. + + When in doubt, say N. + +config HOTPLUG_PCI_CPCI_ZT5550 + tristate "Ziatech ZT5550 CompactPCI Hotplug driver" + depends on HOTPLUG_PCI_CPCI && X86 + help + Say Y here if you have an Performance Technologies (formerly Intel, + formerly just Ziatech) Ziatech ZT5550 CompactPCI system card. + + To compile this driver as a module, choose M here: the + module will be called cpcihp_zt5550. + + When in doubt, say N. + +config HOTPLUG_PCI_CPCI_GENERIC + tristate "Generic port I/O CompactPCI Hotplug driver" + depends on HOTPLUG_PCI_CPCI && X86 + help + Say Y here if you have a CompactPCI system card that exposes the #ENUM + hotswap signal as a bit in a system register that can be read through + standard port I/O. + + To compile this driver as a module, choose M here: the + module will be called cpcihp_generic. + + When in doubt, say N. + +config HOTPLUG_PCI_SHPC + tristate "SHPC PCI Hotplug driver" + help + Say Y here if you have a motherboard with a SHPC PCI Hotplug + controller. + + To compile this driver as a module, choose M here: the + module will be called shpchp. + + When in doubt, say N. + +config HOTPLUG_PCI_RPA + tristate "RPA PCI Hotplug driver" + depends on PPC_PSERIES && PPC64 && !HOTPLUG_PCI_FAKE + help + Say Y here if you have a RPA system that supports PCI Hotplug. + + To compile this driver as a module, choose M here: the + module will be called rpaphp. + + When in doubt, say N. + +config HOTPLUG_PCI_RPA_DLPAR + tristate "RPA Dynamic Logical Partitioning for I/O slots" + depends on HOTPLUG_PCI_RPA + help + Say Y here if your system supports Dynamic Logical Partitioning + for I/O slots. + + To compile this driver as a module, choose M here: the + module will be called rpadlpar_io. + + When in doubt, say N. + +config HOTPLUG_PCI_SGI + tristate "SGI PCI Hotplug Support" + depends on IA64_SGI_SN2 || IA64_GENERIC + help + Say Y here if you want to use the SGI Altix Hotplug + Driver for PCI devices. + + When in doubt, say N. + +endif # HOTPLUG_PCI diff --git a/drivers/pci/hotplug/Makefile b/drivers/pci/hotplug/Makefile new file mode 100644 index 0000000..9bdbe1a --- /dev/null +++ b/drivers/pci/hotplug/Makefile @@ -0,0 +1,63 @@ +# +# Makefile for the Linux kernel pci hotplug controller drivers. +# + +obj-$(CONFIG_HOTPLUG_PCI) += pci_hotplug.o +obj-$(CONFIG_HOTPLUG_PCI_COMPAQ) += cpqphp.o +obj-$(CONFIG_HOTPLUG_PCI_IBM) += ibmphp.o +obj-$(CONFIG_HOTPLUG_PCI_ACPI) += acpiphp.o +obj-$(CONFIG_HOTPLUG_PCI_ACPI_IBM) += acpiphp_ibm.o +obj-$(CONFIG_HOTPLUG_PCI_CPCI_ZT5550) += cpcihp_zt5550.o +obj-$(CONFIG_HOTPLUG_PCI_CPCI_GENERIC) += cpcihp_generic.o +obj-$(CONFIG_HOTPLUG_PCI_PCIE) += pciehp.o +obj-$(CONFIG_HOTPLUG_PCI_SHPC) += shpchp.o +obj-$(CONFIG_HOTPLUG_PCI_RPA) += rpaphp.o +obj-$(CONFIG_HOTPLUG_PCI_RPA_DLPAR) += rpadlpar_io.o +obj-$(CONFIG_HOTPLUG_PCI_SGI) += sgi_hotplug.o + +# Link this last so it doesn't claim devices that have a real hotplug driver +obj-$(CONFIG_HOTPLUG_PCI_FAKE) += fakephp.o + +pci_hotplug-objs := pci_hotplug_core.o + +ifdef CONFIG_HOTPLUG_PCI_CPCI +pci_hotplug-objs += cpci_hotplug_core.o \ + cpci_hotplug_pci.o +endif +ifdef CONFIG_ACPI +pci_hotplug-objs += acpi_pcihp.o +endif + +cpqphp-objs := cpqphp_core.o \ + cpqphp_ctrl.o \ + cpqphp_sysfs.o \ + cpqphp_pci.o +cpqphp-$(CONFIG_HOTPLUG_PCI_COMPAQ_NVRAM) += cpqphp_nvram.o +cpqphp-objs += $(cpqphp-y) + +ibmphp-objs := ibmphp_core.o \ + ibmphp_ebda.o \ + ibmphp_pci.o \ + ibmphp_res.o \ + ibmphp_hpc.o + +acpiphp-objs := acpiphp_core.o \ + acpiphp_glue.o + +rpaphp-objs := rpaphp_core.o \ + rpaphp_pci.o \ + rpaphp_slot.o + +rpadlpar_io-objs := rpadlpar_core.o \ + rpadlpar_sysfs.o + +pciehp-objs := pciehp_core.o \ + pciehp_ctrl.o \ + pciehp_pci.o \ + pciehp_hpc.o + +shpchp-objs := shpchp_core.o \ + shpchp_ctrl.o \ + shpchp_pci.o \ + shpchp_sysfs.o \ + shpchp_hpc.o diff --git a/drivers/pci/hotplug/acpi_pcihp.c b/drivers/pci/hotplug/acpi_pcihp.c new file mode 100644 index 0000000..e17ef54 --- /dev/null +++ b/drivers/pci/hotplug/acpi_pcihp.c @@ -0,0 +1,505 @@ +/* + * Common ACPI functions for hot plug platforms + * + * Copyright (C) 2006 Intel Corporation + * + * 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 <kristen.c.accardi@intel.com> + * + */ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/kernel.h> +#include <linux/types.h> +#include <linux/pci.h> +#include <linux/pci_hotplug.h> +#include <linux/pci-acpi.h> +#include <acpi/acpi.h> +#include <acpi/acpi_bus.h> +#include <acpi/actypes.h> + +#define MY_NAME "acpi_pcihp" + +#define dbg(fmt, arg...) do { if (debug_acpi) printk(KERN_DEBUG "%s: %s: " fmt , MY_NAME , __func__ , ## 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) + +#define METHOD_NAME__SUN "_SUN" +#define METHOD_NAME__HPP "_HPP" +#define METHOD_NAME_OSHP "OSHP" + +static int debug_acpi; + +static acpi_status +decode_type0_hpx_record(union acpi_object *record, struct hotplug_params *hpx) +{ + int i; + union acpi_object *fields = record->package.elements; + u32 revision = fields[1].integer.value; + + switch (revision) { + case 1: + if (record->package.count != 6) + return AE_ERROR; + for (i = 2; i < 6; i++) + if (fields[i].type != ACPI_TYPE_INTEGER) + return AE_ERROR; + hpx->t0 = &hpx->type0_data; + hpx->t0->revision = revision; + hpx->t0->cache_line_size = fields[2].integer.value; + hpx->t0->latency_timer = fields[3].integer.value; + hpx->t0->enable_serr = fields[4].integer.value; + hpx->t0->enable_perr = fields[5].integer.value; + break; + default: + printk(KERN_WARNING + "%s: Type 0 Revision %d record not supported\n", + __func__, revision); + return AE_ERROR; + } + return AE_OK; +} + +static acpi_status +decode_type1_hpx_record(union acpi_object *record, struct hotplug_params *hpx) +{ + int i; + union acpi_object *fields = record->package.elements; + u32 revision = fields[1].integer.value; + + switch (revision) { + case 1: + if (record->package.count != 5) + return AE_ERROR; + for (i = 2; i < 5; i++) + if (fields[i].type != ACPI_TYPE_INTEGER) + return AE_ERROR; + hpx->t1 = &hpx->type1_data; + hpx->t1->revision = revision; + hpx->t1->max_mem_read = fields[2].integer.value; + hpx->t1->avg_max_split = fields[3].integer.value; + hpx->t1->tot_max_split = fields[4].integer.value; + break; + default: + printk(KERN_WARNING + "%s: Type 1 Revision %d record not supported\n", + __func__, revision); + return AE_ERROR; + } + return AE_OK; +} + +static acpi_status +decode_type2_hpx_record(union acpi_object *record, struct hotplug_params *hpx) +{ + int i; + union acpi_object *fields = record->package.elements; + u32 revision = fields[1].integer.value; + + switch (revision) { + case 1: + if (record->package.count != 18) + return AE_ERROR; + for (i = 2; i < 18; i++) + if (fields[i].type != ACPI_TYPE_INTEGER) + return AE_ERROR; + hpx->t2 = &hpx->type2_data; + hpx->t2->revision = revision; + hpx->t2->unc_err_mask_and = fields[2].integer.value; + hpx->t2->unc_err_mask_or = fields[3].integer.value; + hpx->t2->unc_err_sever_and = fields[4].integer.value; + hpx->t2->unc_err_sever_or = fields[5].integer.value; + hpx->t2->cor_err_mask_and = fields[6].integer.value; + hpx->t2->cor_err_mask_or = fields[7].integer.value; + hpx->t2->adv_err_cap_and = fields[8].integer.value; + hpx->t2->adv_err_cap_or = fields[9].integer.value; + hpx->t2->pci_exp_devctl_and = fields[10].integer.value; + hpx->t2->pci_exp_devctl_or = fields[11].integer.value; + hpx->t2->pci_exp_lnkctl_and = fields[12].integer.value; + hpx->t2->pci_exp_lnkctl_or = fields[13].integer.value; + hpx->t2->sec_unc_err_sever_and = fields[14].integer.value; + hpx->t2->sec_unc_err_sever_or = fields[15].integer.value; + hpx->t2->sec_unc_err_mask_and = fields[16].integer.value; + hpx->t2->sec_unc_err_mask_or = fields[17].integer.value; + break; + default: + printk(KERN_WARNING + "%s: Type 2 Revision %d record not supported\n", + __func__, revision); + return AE_ERROR; + } + return AE_OK; +} + +static acpi_status +acpi_run_hpx(acpi_handle handle, struct hotplug_params *hpx) +{ + acpi_status status; + struct acpi_buffer buffer = {ACPI_ALLOCATE_BUFFER, NULL}; + union acpi_object *package, *record, *fields; + u32 type; + int i; + + /* Clear the return buffer with zeros */ + memset(hpx, 0, sizeof(struct hotplug_params)); + + status = acpi_evaluate_object(handle, "_HPX", NULL, &buffer); + if (ACPI_FAILURE(status)) + return status; + + package = (union acpi_object *)buffer.pointer; + if (package->type != ACPI_TYPE_PACKAGE) { + status = AE_ERROR; + goto exit; + } + + for (i = 0; i < package->package.count; i++) { + record = &package->package.elements[i]; + if (record->type != ACPI_TYPE_PACKAGE) { + status = AE_ERROR; + goto exit; + } + + fields = record->package.elements; + if (fields[0].type != ACPI_TYPE_INTEGER || + fields[1].type != ACPI_TYPE_INTEGER) { + status = AE_ERROR; + goto exit; + } + + type = fields[0].integer.value; + switch (type) { + case 0: + status = decode_type0_hpx_record(record, hpx); + if (ACPI_FAILURE(status)) + goto exit; + break; + case 1: + status = decode_type1_hpx_record(record, hpx); + if (ACPI_FAILURE(status)) + goto exit; + break; + case 2: + status = decode_type2_hpx_record(record, hpx); + if (ACPI_FAILURE(status)) + goto exit; + break; + default: + printk(KERN_ERR "%s: Type %d record not supported\n", + __func__, type); + status = AE_ERROR; + goto exit; + } + } + exit: + kfree(buffer.pointer); + return status; +} + +static acpi_status +acpi_run_hpp(acpi_handle handle, struct hotplug_params *hpp) +{ + acpi_status status; + u8 nui[4]; + struct acpi_buffer ret_buf = { 0, NULL}; + struct acpi_buffer string = { ACPI_ALLOCATE_BUFFER, NULL }; + union acpi_object *ext_obj, *package; + int i, len = 0; + + acpi_get_name(handle, ACPI_FULL_PATHNAME, &string); + + /* Clear the return buffer with zeros */ + memset(hpp, 0, sizeof(struct hotplug_params)); + + /* get _hpp */ + status = acpi_evaluate_object(handle, METHOD_NAME__HPP, NULL, &ret_buf); + switch (status) { + case AE_BUFFER_OVERFLOW: + ret_buf.pointer = kmalloc (ret_buf.length, GFP_KERNEL); + if (!ret_buf.pointer) { + printk(KERN_ERR "%s:%s alloc for _HPP fail\n", + __func__, (char *)string.pointer); + kfree(string.pointer); + return AE_NO_MEMORY; + } + status = acpi_evaluate_object(handle, METHOD_NAME__HPP, + NULL, &ret_buf); + if (ACPI_SUCCESS(status)) + break; + default: + if (ACPI_FAILURE(status)) { + pr_debug("%s:%s _HPP fail=0x%x\n", __func__, + (char *)string.pointer, status); + kfree(string.pointer); + return status; + } + } + + ext_obj = (union acpi_object *) ret_buf.pointer; + if (ext_obj->type != ACPI_TYPE_PACKAGE) { + printk(KERN_ERR "%s:%s _HPP obj not a package\n", __func__, + (char *)string.pointer); + status = AE_ERROR; + goto free_and_return; + } + + len = ext_obj->package.count; + package = (union acpi_object *) ret_buf.pointer; + for ( i = 0; (i < len) || (i < 4); i++) { + ext_obj = (union acpi_object *) &package->package.elements[i]; + switch (ext_obj->type) { + case ACPI_TYPE_INTEGER: + nui[i] = (u8)ext_obj->integer.value; + break; + default: + printk(KERN_ERR "%s:%s _HPP obj type incorrect\n", + __func__, (char *)string.pointer); + status = AE_ERROR; + goto free_and_return; + } + } + + hpp->t0 = &hpp->type0_data; + hpp->t0->cache_line_size = nui[0]; + hpp->t0->latency_timer = nui[1]; + hpp->t0->enable_serr = nui[2]; + hpp->t0->enable_perr = nui[3]; + + pr_debug(" _HPP: cache_line_size=0x%x\n", hpp->t0->cache_line_size); + pr_debug(" _HPP: latency timer =0x%x\n", hpp->t0->latency_timer); + pr_debug(" _HPP: enable SERR =0x%x\n", hpp->t0->enable_serr); + pr_debug(" _HPP: enable PERR =0x%x\n", hpp->t0->enable_perr); + +free_and_return: + kfree(string.pointer); + kfree(ret_buf.pointer); + return status; +} + + + +/* acpi_run_oshp - get control of hotplug from the firmware + * + * @handle - the handle of the hotplug controller. + */ +static acpi_status acpi_run_oshp(acpi_handle handle) +{ + acpi_status status; + struct acpi_buffer string = { ACPI_ALLOCATE_BUFFER, NULL }; + + acpi_get_name(handle, ACPI_FULL_PATHNAME, &string); + + /* run OSHP */ + status = acpi_evaluate_object(handle, METHOD_NAME_OSHP, NULL, NULL); + if (ACPI_FAILURE(status)) + if (status != AE_NOT_FOUND) + printk(KERN_ERR "%s:%s OSHP fails=0x%x\n", + __func__, (char *)string.pointer, status); + else + dbg("%s:%s OSHP not found\n", + __func__, (char *)string.pointer); + else + pr_debug("%s:%s OSHP passes\n", __func__, + (char *)string.pointer); + + kfree(string.pointer); + return status; +} + +/* acpi_get_hp_params_from_firmware + * + * @bus - the pci_bus of the bus on which the device is newly added + * @hpp - allocated by the caller + */ +acpi_status acpi_get_hp_params_from_firmware(struct pci_bus *bus, + struct hotplug_params *hpp) +{ + acpi_status status = AE_NOT_FOUND; + acpi_handle handle, phandle; + struct pci_bus *pbus = bus; + struct pci_dev *pdev; + + do { + pdev = pbus->self; + if (!pdev) { + handle = acpi_get_pci_rootbridge_handle( + pci_domain_nr(pbus), pbus->number); + break; + } + handle = DEVICE_ACPI_HANDLE(&(pdev->dev)); + pbus = pbus->parent; + } while (!handle); + + /* + * _HPP settings apply to all child buses, until another _HPP is + * encountered. If we don't find an _HPP for the input pci dev, + * look for it in the parent device scope since that would apply to + * this pci dev. If we don't find any _HPP, use hardcoded defaults + */ + while (handle) { + status = acpi_run_hpx(handle, hpp); + if (ACPI_SUCCESS(status)) + break; + status = acpi_run_hpp(handle, hpp); + if (ACPI_SUCCESS(status)) + break; + if (acpi_root_bridge(handle)) + break; + status = acpi_get_parent(handle, &phandle); + if (ACPI_FAILURE(status)) + break; + handle = phandle; + } + return status; +} +EXPORT_SYMBOL_GPL(acpi_get_hp_params_from_firmware); + +/** + * acpi_get_hp_hw_control_from_firmware + * @dev: the pci_dev of the bridge that has a hotplug controller + * @flags: requested control bits for _OSC + * + * Attempt to take hotplug control from firmware. + */ +int acpi_get_hp_hw_control_from_firmware(struct pci_dev *dev, u32 flags) +{ + acpi_status status; + acpi_handle chandle, handle; + struct pci_dev *pdev = dev; + struct pci_bus *parent; + struct acpi_buffer string = { ACPI_ALLOCATE_BUFFER, NULL }; + + flags &= (OSC_PCI_EXPRESS_NATIVE_HP_CONTROL | + OSC_SHPC_NATIVE_HP_CONTROL | + OSC_PCI_EXPRESS_CAP_STRUCTURE_CONTROL); + if (!flags) { + err("Invalid flags %u specified!\n", flags); + return -EINVAL; + } + + /* + * Per PCI firmware specification, we should run the ACPI _OSC + * method to get control of hotplug hardware before using it. If + * an _OSC is missing, we look for an OSHP to do the same thing. + * To handle different BIOS behavior, we look for _OSC on a root + * bridge preferentially (according to PCI fw spec). Later for + * OSHP within the scope of the hotplug controller and its parents, + * upto the host bridge under which this controller exists. + */ + handle = acpi_find_root_bridge_handle(pdev); + if (handle) { + acpi_get_name(handle, ACPI_FULL_PATHNAME, &string); + dbg("Trying to get hotplug control for %s\n", + (char *)string.pointer); + status = pci_osc_control_set(handle, flags); + if (ACPI_SUCCESS(status)) + goto got_one; + kfree(string.pointer); + string = (struct acpi_buffer){ ACPI_ALLOCATE_BUFFER, NULL }; + } + + pdev = dev; + handle = DEVICE_ACPI_HANDLE(&dev->dev); + while (!handle) { + /* + * This hotplug controller was not listed in the ACPI name + * space at all. Try to get acpi handle of parent pci bus. + */ + if (!pdev || !pdev->bus->parent) + break; + parent = pdev->bus->parent; + dbg("Could not find %s in acpi namespace, trying parent\n", + pci_name(pdev)); + if (!parent->self) + /* Parent must be a host bridge */ + handle = acpi_get_pci_rootbridge_handle( + pci_domain_nr(parent), + parent->number); + else + handle = DEVICE_ACPI_HANDLE(&(parent->self->dev)); + pdev = parent->self; + } + + while (handle) { + acpi_get_name(handle, ACPI_FULL_PATHNAME, &string); + dbg("Trying to get hotplug control for %s \n", + (char *)string.pointer); + status = acpi_run_oshp(handle); + if (ACPI_SUCCESS(status)) + goto got_one; + if (acpi_root_bridge(handle)) + break; + chandle = handle; + status = acpi_get_parent(chandle, &handle); + if (ACPI_FAILURE(status)) + break; + } + + dbg("Cannot get control of hotplug hardware for pci %s\n", + pci_name(dev)); + + kfree(string.pointer); + return -ENODEV; +got_one: + dbg("Gained control for hotplug HW for pci %s (%s)\n", pci_name(dev), + (char *)string.pointer); + kfree(string.pointer); + return 0; +} +EXPORT_SYMBOL(acpi_get_hp_hw_control_from_firmware); + +/* acpi_root_bridge - check to see if this acpi object is a root bridge + * + * @handle - the acpi object in question. + */ +int acpi_root_bridge(acpi_handle handle) +{ + acpi_status status; + struct acpi_device_info *info; + struct acpi_buffer buffer = {ACPI_ALLOCATE_BUFFER, NULL}; + int i; + + status = acpi_get_object_info(handle, &buffer); + if (ACPI_SUCCESS(status)) { + info = buffer.pointer; + if ((info->valid & ACPI_VALID_HID) && + !strcmp(PCI_ROOT_HID_STRING, + info->hardware_id.value)) { + kfree(buffer.pointer); + return 1; + } + if (info->valid & ACPI_VALID_CID) { + for (i=0; i < info->compatibility_id.count; i++) { + if (!strcmp(PCI_ROOT_HID_STRING, + info->compatibility_id.id[i].value)) { + kfree(buffer.pointer); + return 1; + } + } + } + kfree(buffer.pointer); + } + return 0; +} +EXPORT_SYMBOL_GPL(acpi_root_bridge); + +module_param(debug_acpi, bool, 0644); +MODULE_PARM_DESC(debug_acpi, "Debugging mode for ACPI enabled or not"); diff --git a/drivers/pci/hotplug/acpiphp.h b/drivers/pci/hotplug/acpiphp.h new file mode 100644 index 0000000..9bcb6cb --- /dev/null +++ b/drivers/pci/hotplug/acpiphp.h @@ -0,0 +1,223 @@ +/* + * ACPI PCI Hot Plug Controller Driver + * + * Copyright (C) 1995,2001 Compaq Computer Corporation + * Copyright (C) 2001 Greg Kroah-Hartman (greg@kroah.com) + * Copyright (C) 2001 IBM Corp. + * Copyright (C) 2002 Hiroshi Aono (h-aono@ap.jp.nec.com) + * Copyright (C) 2002,2003 Takayoshi Kochi (t-kochi@bq.jp.nec.com) + * Copyright (C) 2002,2003 NEC Corporation + * Copyright (C) 2003-2005 Matthew Wilcox (matthew.wilcox@hp.com) + * Copyright (C) 2003-2005 Hewlett Packard + * + * 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 <gregkh@us.ibm.com>, + * <t-kochi@bq.jp.nec.com> + * + */ + +#ifndef _ACPIPHP_H +#define _ACPIPHP_H + +#include <linux/acpi.h> +#include <linux/kobject.h> +#include <linux/mutex.h> +#include <linux/pci_hotplug.h> + +#define dbg(format, arg...) \ + do { \ + if (acpiphp_debug) \ + printk(KERN_DEBUG "%s: " format, \ + 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) + +struct acpiphp_bridge; +struct acpiphp_slot; + +/* + * struct slot - slot information for each *physical* slot + */ +struct slot { + struct hotplug_slot *hotplug_slot; + struct acpiphp_slot *acpi_slot; + struct hotplug_slot_info info; +}; + +static inline const char *slot_name(struct slot *slot) +{ + return hotplug_slot_name(slot->hotplug_slot); +} + +/* + * struct acpiphp_bridge - PCI bridge information + * + * for each bridge device in ACPI namespace + */ +struct acpiphp_bridge { + struct list_head list; + acpi_handle handle; + struct acpiphp_slot *slots; + + /* Ejectable PCI-to-PCI bridge (PCI bridge and PCI function) */ + struct acpiphp_func *func; + + int type; + int nr_slots; + + u32 flags; + + /* This bus (host bridge) or Secondary bus (PCI-to-PCI bridge) */ + struct pci_bus *pci_bus; + + /* PCI-to-PCI bridge device */ + struct pci_dev *pci_dev; + + /* ACPI 2.0 _HPP parameters */ + struct hotplug_params hpp; + + spinlock_t res_lock; +}; + + +/* + * struct acpiphp_slot - PCI slot information + * + * PCI slot information for each *physical* PCI slot + */ +struct acpiphp_slot { + struct acpiphp_slot *next; + struct acpiphp_bridge *bridge; /* parent */ + struct list_head funcs; /* one slot may have different + objects (i.e. for each function) */ + struct slot *slot; + struct mutex crit_sect; + + u8 device; /* pci device# */ + + unsigned long long sun; /* ACPI _SUN (slot unique number) */ + u32 flags; /* see below */ +}; + + +/* + * struct acpiphp_func - PCI function information + * + * PCI function information for each object in ACPI namespace + * typically 8 objects per slot (i.e. for each PCI function) + */ +struct acpiphp_func { + struct acpiphp_slot *slot; /* parent */ + struct acpiphp_bridge *bridge; /* Ejectable PCI-to-PCI bridge */ + + struct list_head sibling; + struct pci_dev *pci_dev; + struct notifier_block nb; + acpi_handle handle; + + u8 function; /* pci function# */ + u32 flags; /* see below */ +}; + +/* + * struct acpiphp_attention_info - device specific attention registration + * + * ACPI has no generic method of setting/getting attention status + * this allows for device specific driver registration + */ +struct acpiphp_attention_info +{ + int (*set_attn)(struct hotplug_slot *slot, u8 status); + int (*get_attn)(struct hotplug_slot *slot, u8 *status); + struct module *owner; +}; + +struct acpiphp_ioapic { + struct pci_dev *dev; + u32 gsi_base; + struct list_head list; +}; + +/* PCI bus bridge HID */ +#define ACPI_PCI_HOST_HID "PNP0A03" + +/* PCI BRIDGE type */ +#define BRIDGE_TYPE_HOST 0 +#define BRIDGE_TYPE_P2P 1 + +/* ACPI _STA method value (ignore bit 4; battery present) */ +#define ACPI_STA_PRESENT (0x00000001) +#define ACPI_STA_ENABLED (0x00000002) +#define ACPI_STA_SHOW_IN_UI (0x00000004) +#define ACPI_STA_FUNCTIONING (0x00000008) +#define ACPI_STA_ALL (0x0000000f) + +/* bridge flags */ +#define BRIDGE_HAS_STA (0x00000001) +#define BRIDGE_HAS_EJ0 (0x00000002) +#define BRIDGE_HAS_HPP (0x00000004) +#define BRIDGE_HAS_PS0 (0x00000010) +#define BRIDGE_HAS_PS1 (0x00000020) +#define BRIDGE_HAS_PS2 (0x00000040) +#define BRIDGE_HAS_PS3 (0x00000080) + +/* slot flags */ + +#define SLOT_POWEREDON (0x00000001) +#define SLOT_ENABLED (0x00000002) +#define SLOT_MULTIFUNCTION (0x00000004) + +/* function flags */ + +#define FUNC_HAS_STA (0x00000001) +#define FUNC_HAS_EJ0 (0x00000002) +#define FUNC_HAS_PS0 (0x00000010) +#define FUNC_HAS_PS1 (0x00000020) +#define FUNC_HAS_PS2 (0x00000040) +#define FUNC_HAS_PS3 (0x00000080) +#define FUNC_HAS_DCK (0x00000100) + +/* function prototypes */ + +/* acpiphp_core.c */ +extern int acpiphp_register_attention(struct acpiphp_attention_info*info); +extern int acpiphp_unregister_attention(struct acpiphp_attention_info *info); +extern int acpiphp_register_hotplug_slot(struct acpiphp_slot *slot); +extern void acpiphp_unregister_hotplug_slot(struct acpiphp_slot *slot); + +/* acpiphp_glue.c */ +extern int acpiphp_glue_init (void); +extern void acpiphp_glue_exit (void); +extern int acpiphp_get_num_slots (void); +typedef int (*acpiphp_callback)(struct acpiphp_slot *slot, void *data); + +extern int acpiphp_enable_slot (struct acpiphp_slot *slot); +extern int acpiphp_disable_slot (struct acpiphp_slot *slot); +extern int acpiphp_eject_slot (struct acpiphp_slot *slot); +extern u8 acpiphp_get_power_status (struct acpiphp_slot *slot); +extern u8 acpiphp_get_attention_status (struct acpiphp_slot *slot); +extern u8 acpiphp_get_latch_status (struct acpiphp_slot *slot); +extern u8 acpiphp_get_adapter_status (struct acpiphp_slot *slot); + +/* variables */ +extern int acpiphp_debug; + +#endif /* _ACPIPHP_H */ diff --git a/drivers/pci/hotplug/acpiphp_core.c b/drivers/pci/hotplug/acpiphp_core.c new file mode 100644 index 0000000..43c10bd --- /dev/null +++ b/drivers/pci/hotplug/acpiphp_core.c @@ -0,0 +1,402 @@ +/* + * ACPI PCI Hot Plug Controller Driver + * + * Copyright (C) 1995,2001 Compaq Computer Corporation + * Copyright (C) 2001 Greg Kroah-Hartman (greg@kroah.com) + * Copyright (C) 2001 IBM Corp. + * Copyright (C) 2002 Hiroshi Aono (h-aono@ap.jp.nec.com) + * Copyright (C) 2002,2003 Takayoshi Kochi (t-kochi@bq.jp.nec.com) + * Copyright (C) 2002,2003 NEC Corporation + * Copyright (C) 2003-2005 Matthew Wilcox (matthew.wilcox@hp.com) + * Copyright (C) 2003-2005 Hewlett Packard + * + * 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 <kristen.c.accardi@intel.com> + * + */ + +#include <linux/init.h> +#include <linux/module.h> +#include <linux/moduleparam.h> + +#include <linux/kernel.h> +#include <linux/pci.h> +#include <linux/pci_hotplug.h> +#include <linux/slab.h> +#include <linux/smp.h> +#include "acpiphp.h" + +#define MY_NAME "acpiphp" + +/* name size which is used for entries in pcihpfs */ +#define SLOT_NAME_SIZE 21 /* {_SUN} */ + +static int debug; +int acpiphp_debug; + +/* local variables */ +static int num_slots; +static struct acpiphp_attention_info *attention_info; + +#define DRIVER_VERSION "0.5" +#define DRIVER_AUTHOR "Greg Kroah-Hartman <gregkh@us.ibm.com>, Takayoshi Kochi <t-kochi@bq.jp.nec.com>, Matthew Wilcox <willy@hp.com>" +#define DRIVER_DESC "ACPI Hot Plug PCI Controller Driver" + +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); +MODULE_PARM_DESC(debug, "Debugging mode enabled or not"); +module_param(debug, bool, 0644); + +/* export the attention callback registration methods */ +EXPORT_SYMBOL_GPL(acpiphp_register_attention); +EXPORT_SYMBOL_GPL(acpiphp_unregister_attention); + +static int enable_slot (struct hotplug_slot *slot); +static int disable_slot (struct hotplug_slot *slot); +static int set_attention_status (struct hotplug_slot *slot, u8 value); +static int get_power_status (struct hotplug_slot *slot, u8 *value); +static int get_attention_status (struct hotplug_slot *slot, u8 *value); +static int get_latch_status (struct hotplug_slot *slot, u8 *value); +static int get_adapter_status (struct hotplug_slot *slot, u8 *value); + +static struct hotplug_slot_ops acpi_hotplug_slot_ops = { + .owner = THIS_MODULE, + .enable_slot = enable_slot, + .disable_slot = disable_slot, + .set_attention_status = set_attention_status, + .get_power_status = get_power_status, + .get_attention_status = get_attention_status, + .get_latch_status = get_latch_status, + .get_adapter_status = get_adapter_status, +}; + +/** + * acpiphp_register_attention - set attention LED callback + * @info: must be completely filled with LED callbacks + * + * Description: This is used to register a hardware specific ACPI + * driver that manipulates the attention LED. All the fields in + * info must be set. + */ +int acpiphp_register_attention(struct acpiphp_attention_info *info) +{ + int retval = -EINVAL; + + if (info && info->owner && info->set_attn && + info->get_attn && !attention_info) { + retval = 0; + attention_info = info; + } + return retval; +} + + +/** + * acpiphp_unregister_attention - unset attention LED callback + * @info: must match the pointer used to register + * + * Description: This is used to un-register a hardware specific acpi + * driver that manipulates the attention LED. The pointer to the + * info struct must be the same as the one used to set it. + */ +int acpiphp_unregister_attention(struct acpiphp_attention_info *info) +{ + int retval = -EINVAL; + + if (info && attention_info == info) { + attention_info = NULL; + retval = 0; + } + return retval; +} + + +/** + * enable_slot - power on and enable a slot + * @hotplug_slot: slot to enable + * + * Actual tasks are done in acpiphp_enable_slot() + */ +static int enable_slot(struct hotplug_slot *hotplug_slot) +{ + struct slot *slot = hotplug_slot->private; + + dbg("%s - physical_slot = %s\n", __func__, slot_name(slot)); + + /* enable the specified slot */ + return acpiphp_enable_slot(slot->acpi_slot); +} + + +/** + * disable_slot - disable and power off a slot + * @hotplug_slot: slot to disable + * + * Actual tasks are done in acpiphp_disable_slot() + */ +static int disable_slot(struct hotplug_slot *hotplug_slot) +{ + struct slot *slot = hotplug_slot->private; + int retval; + + dbg("%s - physical_slot = %s\n", __func__, slot_name(slot)); + + /* disable the specified slot */ + retval = acpiphp_disable_slot(slot->acpi_slot); + if (!retval) + retval = acpiphp_eject_slot(slot->acpi_slot); + return retval; +} + + +/** + * set_attention_status - set attention LED + * @hotplug_slot: slot to set attention LED on + * @status: value to set attention LED to (0 or 1) + * + * attention status LED, so we use a callback that + * was registered with us. This allows hardware specific + * ACPI implementations to blink the light for us. + */ + static int set_attention_status(struct hotplug_slot *hotplug_slot, u8 status) + { + int retval = -ENODEV; + + dbg("%s - physical_slot = %s\n", __func__, hotplug_slot_name(hotplug_slot)); + + if (attention_info && try_module_get(attention_info->owner)) { + retval = attention_info->set_attn(hotplug_slot, status); + module_put(attention_info->owner); + } else + attention_info = NULL; + return retval; + } + + +/** + * get_power_status - get power status of a slot + * @hotplug_slot: slot to get status + * @value: pointer to store status + * + * Some platforms may not implement _STA method properly. + * In that case, the value returned may not be reliable. + */ +static int get_power_status(struct hotplug_slot *hotplug_slot, u8 *value) +{ + struct slot *slot = hotplug_slot->private; + + dbg("%s - physical_slot = %s\n", __func__, slot_name(slot)); + + *value = acpiphp_get_power_status(slot->acpi_slot); + + return 0; +} + + +/** + * get_attention_status - get attention LED status + * @hotplug_slot: slot to get status from + * @value: returns with value of attention LED + * + * ACPI doesn't have known method to determine the state + * of the attention status LED, so we use a callback that + * was registered with us. This allows hardware specific + * ACPI implementations to determine its state. + */ +static int get_attention_status(struct hotplug_slot *hotplug_slot, u8 *value) +{ + int retval = -EINVAL; + + dbg("%s - physical_slot = %s\n", __func__, hotplug_slot_name(hotplug_slot)); + + if (attention_info && try_module_get(attention_info->owner)) { + retval = attention_info->get_attn(hotplug_slot, value); + module_put(attention_info->owner); + } else + attention_info = NULL; + return retval; +} + + +/** + * get_latch_status - get latch status of a slot + * @hotplug_slot: slot to get status + * @value: pointer to store status + * + * ACPI doesn't provide any formal means to access latch status. + * Instead, we fake latch status from _STA. + */ +static int get_latch_status(struct hotplug_slot *hotplug_slot, u8 *value) +{ + struct slot *slot = hotplug_slot->private; + + dbg("%s - physical_slot = %s\n", __func__, slot_name(slot)); + + *value = acpiphp_get_latch_status(slot->acpi_slot); + + return 0; +} + + +/** + * get_adapter_status - get adapter status of a slot + * @hotplug_slot: slot to get status + * @value: pointer to store status + * + * ACPI doesn't provide any formal means to access adapter status. + * Instead, we fake adapter status from _STA. + */ +static int get_adapter_status(struct hotplug_slot *hotplug_slot, u8 *value) +{ + struct slot *slot = hotplug_slot->private; + + dbg("%s - physical_slot = %s\n", __func__, slot_name(slot)); + + *value = acpiphp_get_adapter_status(slot->acpi_slot); + + return 0; +} + +static int __init init_acpi(void) +{ + int retval; + + /* initialize internal data structure etc. */ + retval = acpiphp_glue_init(); + + /* read initial number of slots */ + if (!retval) { + num_slots = acpiphp_get_num_slots(); + if (num_slots == 0) { + acpiphp_glue_exit(); + retval = -ENODEV; + } + } + + return retval; +} + +/** + * release_slot - free up the memory used by a slot + * @hotplug_slot: slot to free + */ +static void release_slot(struct hotplug_slot *hotplug_slot) +{ + struct slot *slot = hotplug_slot->private; + + dbg("%s - physical_slot = %s\n", __func__, slot_name(slot)); + + kfree(slot->hotplug_slot); + kfree(slot); +} + +/* callback routine to initialize 'struct slot' for each slot */ +int acpiphp_register_hotplug_slot(struct acpiphp_slot *acpiphp_slot) +{ + struct slot *slot; + int retval = -ENOMEM; + char name[SLOT_NAME_SIZE]; + + slot = kzalloc(sizeof(*slot), GFP_KERNEL); + if (!slot) + goto error; + + slot->hotplug_slot = kzalloc(sizeof(*slot->hotplug_slot), GFP_KERNEL); + if (!slot->hotplug_slot) + goto error_slot; + + slot->hotplug_slot->info = &slot->info; + + slot->hotplug_slot->private = slot; + slot->hotplug_slot->release = &release_slot; + slot->hotplug_slot->ops = &acpi_hotplug_slot_ops; + + slot->acpi_slot = acpiphp_slot; + slot->hotplug_slot->info->power_status = acpiphp_get_power_status(slot->acpi_slot); + slot->hotplug_slot->info->attention_status = 0; + slot->hotplug_slot->info->latch_status = acpiphp_get_latch_status(slot->acpi_slot); + slot->hotplug_slot->info->adapter_status = acpiphp_get_adapter_status(slot->acpi_slot); + slot->hotplug_slot->info->max_bus_speed = PCI_SPEED_UNKNOWN; + slot->hotplug_slot->info->cur_bus_speed = PCI_SPEED_UNKNOWN; + + acpiphp_slot->slot = slot; + snprintf(name, SLOT_NAME_SIZE, "%llu", slot->acpi_slot->sun); + + retval = pci_hp_register(slot->hotplug_slot, + acpiphp_slot->bridge->pci_bus, + acpiphp_slot->device, + name); + if (retval == -EBUSY) + goto error_hpslot; + if (retval) { + err("pci_hp_register failed with error %d\n", retval); + goto error_hpslot; + } + + info("Slot [%s] registered\n", slot_name(slot)); + + return 0; +error_hpslot: + kfree(slot->hotplug_slot); +error_slot: + kfree(slot); +error: + return retval; +} + + +void acpiphp_unregister_hotplug_slot(struct acpiphp_slot *acpiphp_slot) +{ + struct slot *slot = acpiphp_slot->slot; + int retval = 0; + + info("Slot [%s] unregistered\n", slot_name(slot)); + + retval = pci_hp_deregister(slot->hotplug_slot); + if (retval) + err("pci_hp_deregister failed with error %d\n", retval); +} + + +static int __init acpiphp_init(void) +{ + info(DRIVER_DESC " version: " DRIVER_VERSION "\n"); + + if (acpi_pci_disabled) + return 0; + + acpiphp_debug = debug; + + /* read all the ACPI info from the system */ + return init_acpi(); +} + + +static void __exit acpiphp_exit(void) +{ + if (acpi_pci_disabled) + return; + + /* deallocate internal data structures etc. */ + acpiphp_glue_exit(); +} + +module_init(acpiphp_init); +module_exit(acpiphp_exit); diff --git a/drivers/pci/hotplug/acpiphp_glue.c b/drivers/pci/hotplug/acpiphp_glue.c new file mode 100644 index 0000000..3affc64 --- /dev/null +++ b/drivers/pci/hotplug/acpiphp_glue.c @@ -0,0 +1,1887 @@ +/* + * ACPI PCI HotPlug glue functions to ACPI CA subsystem + * + * Copyright (C) 2002,2003 Takayoshi Kochi (t-kochi@bq.jp.nec.com) + * Copyright (C) 2002 Hiroshi Aono (h-aono@ap.jp.nec.com) + * Copyright (C) 2002,2003 NEC Corporation + * Copyright (C) 2003-2005 Matthew Wilcox (matthew.wilcox@hp.com) + * Copyright (C) 2003-2005 Hewlett Packard + * Copyright (C) 2005 Rajesh Shah (rajesh.shah@intel.com) + * Copyright (C) 2005 Intel Corporation + * + * 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 <kristen.c.accardi@intel.com> + * + */ + +/* + * Lifetime rules for pci_dev: + * - The one in acpiphp_func has its refcount elevated by pci_get_slot() + * when the driver is loaded or when an insertion event occurs. It loses + * a refcount when its ejected or the driver unloads. + * - The one in acpiphp_bridge has its refcount elevated by pci_get_slot() + * when the bridge is scanned and it loses a refcount when the bridge + * is removed. + */ + +#include <linux/init.h> +#include <linux/module.h> + +#include <linux/kernel.h> +#include <linux/pci.h> +#include <linux/pci_hotplug.h> +#include <linux/mutex.h> + +#include "../pci.h" +#include "acpiphp.h" + +static LIST_HEAD(bridge_list); +static LIST_HEAD(ioapic_list); +static DEFINE_SPINLOCK(ioapic_list_lock); + +#define MY_NAME "acpiphp_glue" + +static void handle_hotplug_event_bridge (acpi_handle, u32, void *); +static void acpiphp_sanitize_bus(struct pci_bus *bus); +static void acpiphp_set_hpp_values(acpi_handle handle, struct pci_bus *bus); +static void handle_hotplug_event_func(acpi_handle handle, u32 type, void *context); + + +/* + * initialization & terminatation routines + */ + +/** + * is_ejectable - determine if a slot is ejectable + * @handle: handle to acpi namespace + * + * Ejectable slot should satisfy at least these conditions: + * + * 1. has _ADR method + * 2. has _EJ0 method + * + * optionally + * + * 1. has _STA method + * 2. has _PS0 method + * 3. has _PS3 method + * 4. .. + */ +static int is_ejectable(acpi_handle handle) +{ + acpi_status status; + acpi_handle tmp; + + status = acpi_get_handle(handle, "_ADR", &tmp); + if (ACPI_FAILURE(status)) { + return 0; + } + + status = acpi_get_handle(handle, "_EJ0", &tmp); + if (ACPI_FAILURE(status)) { + return 0; + } + + return 1; +} + + +/* callback routine to check for the existence of ejectable slots */ +static acpi_status +is_ejectable_slot(acpi_handle handle, u32 lvl, void *context, void **rv) +{ + int *count = (int *)context; + + if (is_ejectable(handle)) { + (*count)++; + /* only one ejectable slot is enough */ + return AE_CTRL_TERMINATE; + } else { + return AE_OK; + } +} + +/* callback routine to check for the existence of a pci dock device */ +static acpi_status +is_pci_dock_device(acpi_handle handle, u32 lvl, void *context, void **rv) +{ + int *count = (int *)context; + + if (is_dock_device(handle)) { + (*count)++; + return AE_CTRL_TERMINATE; + } else { + return AE_OK; + } +} + + + + +/* + * the _DCK method can do funny things... and sometimes not + * hah-hah funny. + * + * TBD - figure out a way to only call fixups for + * systems that require them. + */ +static int post_dock_fixups(struct notifier_block *nb, unsigned long val, + void *v) +{ + struct acpiphp_func *func = container_of(nb, struct acpiphp_func, nb); + struct pci_bus *bus = func->slot->bridge->pci_bus; + u32 buses; + + if (!bus->self) + return NOTIFY_OK; + + /* fixup bad _DCK function that rewrites + * secondary bridge on slot + */ + pci_read_config_dword(bus->self, + PCI_PRIMARY_BUS, + &buses); + + if (((buses >> 8) & 0xff) != bus->secondary) { + buses = (buses & 0xff000000) + | ((unsigned int)(bus->primary) << 0) + | ((unsigned int)(bus->secondary) << 8) + | ((unsigned int)(bus->subordinate) << 16); + pci_write_config_dword(bus->self, PCI_PRIMARY_BUS, buses); + } + return NOTIFY_OK; +} + + +static struct acpi_dock_ops acpiphp_dock_ops = { + .handler = handle_hotplug_event_func, +}; + +/* callback routine to register each ACPI PCI slot object */ +static acpi_status +register_slot(acpi_handle handle, u32 lvl, void *context, void **rv) +{ + struct acpiphp_bridge *bridge = (struct acpiphp_bridge *)context; + struct acpiphp_slot *slot; + struct acpiphp_func *newfunc; + acpi_handle tmp; + acpi_status status = AE_OK; + unsigned long long adr, sun; + int device, function, retval; + + status = acpi_evaluate_integer(handle, "_ADR", NULL, &adr); + + if (ACPI_FAILURE(status)) + return AE_OK; + + status = acpi_get_handle(handle, "_EJ0", &tmp); + + if (ACPI_FAILURE(status) && !(is_dock_device(handle))) + return AE_OK; + + device = (adr >> 16) & 0xffff; + function = adr & 0xffff; + + newfunc = kzalloc(sizeof(struct acpiphp_func), GFP_KERNEL); + if (!newfunc) + return AE_NO_MEMORY; + + INIT_LIST_HEAD(&newfunc->sibling); + newfunc->handle = handle; + newfunc->function = function; + if (ACPI_SUCCESS(status)) + newfunc->flags = FUNC_HAS_EJ0; + + if (ACPI_SUCCESS(acpi_get_handle(handle, "_STA", &tmp))) + newfunc->flags |= FUNC_HAS_STA; + + if (ACPI_SUCCESS(acpi_get_handle(handle, "_PS0", &tmp))) + newfunc->flags |= FUNC_HAS_PS0; + + if (ACPI_SUCCESS(acpi_get_handle(handle, "_PS3", &tmp))) + newfunc->flags |= FUNC_HAS_PS3; + + if (ACPI_SUCCESS(acpi_get_handle(handle, "_DCK", &tmp))) + newfunc->flags |= FUNC_HAS_DCK; + + status = acpi_evaluate_integer(handle, "_SUN", NULL, &sun); + if (ACPI_FAILURE(status)) { + /* + * use the count of the number of slots we've found + * for the number of the slot + */ + sun = bridge->nr_slots+1; + } + + /* search for objects that share the same slot */ + for (slot = bridge->slots; slot; slot = slot->next) + if (slot->device == device) { + if (slot->sun != sun) + warn("sibling found, but _SUN doesn't match!\n"); + break; + } + + if (!slot) { + slot = kzalloc(sizeof(struct acpiphp_slot), GFP_KERNEL); + if (!slot) { + kfree(newfunc); + return AE_NO_MEMORY; + } + + slot->bridge = bridge; + slot->device = device; + slot->sun = sun; + INIT_LIST_HEAD(&slot->funcs); + mutex_init(&slot->crit_sect); + + slot->next = bridge->slots; + bridge->slots = slot; + + bridge->nr_slots++; + + dbg("found ACPI PCI Hotplug slot %llu at PCI %04x:%02x:%02x\n", + slot->sun, pci_domain_nr(bridge->pci_bus), + bridge->pci_bus->number, slot->device); + retval = acpiphp_register_hotplug_slot(slot); + if (retval) { + if (retval == -EBUSY) + warn("Slot %llu already registered by another " + "hotplug driver\n", slot->sun); + else + warn("acpiphp_register_hotplug_slot failed " + "(err code = 0x%x)\n", retval); + goto err_exit; + } + } + + newfunc->slot = slot; + list_add_tail(&newfunc->sibling, &slot->funcs); + + /* associate corresponding pci_dev */ + newfunc->pci_dev = pci_get_slot(bridge->pci_bus, + PCI_DEVFN(device, function)); + if (newfunc->pci_dev) { + slot->flags |= (SLOT_ENABLED | SLOT_POWEREDON); + } + + if (is_dock_device(handle)) { + /* we don't want to call this device's _EJ0 + * because we want the dock notify handler + * to call it after it calls _DCK + */ + newfunc->flags &= ~FUNC_HAS_EJ0; + if (register_hotplug_dock_device(handle, + &acpiphp_dock_ops, newfunc)) + dbg("failed to register dock device\n"); + + /* we need to be notified when dock events happen + * outside of the hotplug operation, since we may + * need to do fixups before we can hotplug. + */ + newfunc->nb.notifier_call = post_dock_fixups; + if (register_dock_notifier(&newfunc->nb)) + dbg("failed to register a dock notifier"); + } + + /* install notify handler */ + if (!(newfunc->flags & FUNC_HAS_DCK)) { + status = acpi_install_notify_handler(handle, + ACPI_SYSTEM_NOTIFY, + handle_hotplug_event_func, + newfunc); + + if (ACPI_FAILURE(status)) + err("failed to register interrupt notify handler\n"); + } else + status = AE_OK; + + return status; + + err_exit: + bridge->nr_slots--; + bridge->slots = slot->next; + kfree(slot); + kfree(newfunc); + + return AE_OK; +} + + +/* see if it's worth looking at this bridge */ +static int detect_ejectable_slots(acpi_handle *bridge_handle) +{ + acpi_status status; + int count; + + count = 0; + + /* only check slots defined directly below bridge object */ + status = acpi_walk_namespace(ACPI_TYPE_DEVICE, bridge_handle, (u32)1, + is_ejectable_slot, (void *)&count, NULL); + + /* + * we also need to add this bridge if there is a dock bridge or + * other pci device on a dock station (removable) + */ + if (!count) + status = acpi_walk_namespace(ACPI_TYPE_DEVICE, bridge_handle, + (u32)1, is_pci_dock_device, (void *)&count, + NULL); + + return count; +} + + +/* decode ACPI 2.0 _HPP hot plug parameters */ +static void decode_hpp(struct acpiphp_bridge *bridge) +{ + acpi_status status; + + status = acpi_get_hp_params_from_firmware(bridge->pci_bus, &bridge->hpp); + if (ACPI_FAILURE(status) || + !bridge->hpp.t0 || (bridge->hpp.t0->revision > 1)) { + /* use default numbers */ + printk(KERN_WARNING + "%s: Could not get hotplug parameters. Use defaults\n", + __func__); + bridge->hpp.t0 = &bridge->hpp.type0_data; + bridge->hpp.t0->revision = 0; + bridge->hpp.t0->cache_line_size = 0x10; + bridge->hpp.t0->latency_timer = 0x40; + bridge->hpp.t0->enable_serr = 0; + bridge->hpp.t0->enable_perr = 0; + } +} + + + +/* initialize miscellaneous stuff for both root and PCI-to-PCI bridge */ +static void init_bridge_misc(struct acpiphp_bridge *bridge) +{ + acpi_status status; + + /* decode ACPI 2.0 _HPP (hot plug parameters) */ + decode_hpp(bridge); + + /* must be added to the list prior to calling register_slot */ + list_add(&bridge->list, &bridge_list); + + /* register all slot objects under this bridge */ + status = acpi_walk_namespace(ACPI_TYPE_DEVICE, bridge->handle, (u32)1, + register_slot, bridge, NULL); + if (ACPI_FAILURE(status)) { + list_del(&bridge->list); + return; + } + + /* install notify handler */ + if (bridge->type != BRIDGE_TYPE_HOST) { + if ((bridge->flags & BRIDGE_HAS_EJ0) && bridge->func) { + status = acpi_remove_notify_handler(bridge->func->handle, + ACPI_SYSTEM_NOTIFY, + handle_hotplug_event_func); + if (ACPI_FAILURE(status)) + err("failed to remove notify handler\n"); + } + status = acpi_install_notify_handler(bridge->handle, + ACPI_SYSTEM_NOTIFY, + handle_hotplug_event_bridge, + bridge); + + if (ACPI_FAILURE(status)) { + err("failed to register interrupt notify handler\n"); + } + } +} + + +/* find acpiphp_func from acpiphp_bridge */ +static struct acpiphp_func *acpiphp_bridge_handle_to_function(acpi_handle handle) +{ + struct list_head *node, *l; + struct acpiphp_bridge *bridge; + struct acpiphp_slot *slot; + struct acpiphp_func *func; + + list_for_each(node, &bridge_list) { + bridge = list_entry(node, struct acpiphp_bridge, list); + for (slot = bridge->slots; slot; slot = slot->next) { + list_for_each(l, &slot->funcs) { + func = list_entry(l, struct acpiphp_func, + sibling); + if (func->handle == handle) + return func; + } + } + } + + return NULL; +} + + +static inline void config_p2p_bridge_flags(struct acpiphp_bridge *bridge) +{ + acpi_handle dummy_handle; + + if (ACPI_SUCCESS(acpi_get_handle(bridge->handle, + "_STA", &dummy_handle))) + bridge->flags |= BRIDGE_HAS_STA; + + if (ACPI_SUCCESS(acpi_get_handle(bridge->handle, + "_EJ0", &dummy_handle))) + bridge->flags |= BRIDGE_HAS_EJ0; + + if (ACPI_SUCCESS(acpi_get_handle(bridge->handle, + "_PS0", &dummy_handle))) + bridge->flags |= BRIDGE_HAS_PS0; + + if (ACPI_SUCCESS(acpi_get_handle(bridge->handle, + "_PS3", &dummy_handle))) + bridge->flags |= BRIDGE_HAS_PS3; + + /* is this ejectable p2p bridge? */ + if (bridge->flags & BRIDGE_HAS_EJ0) { + struct acpiphp_func *func; + + dbg("found ejectable p2p bridge\n"); + + /* make link between PCI bridge and PCI function */ + func = acpiphp_bridge_handle_to_function(bridge->handle); + if (!func) + return; + bridge->func = func; + func->bridge = bridge; + } +} + + +/* allocate and initialize host bridge data structure */ +static void add_host_bridge(acpi_handle *handle, struct pci_bus *pci_bus) +{ + struct acpiphp_bridge *bridge; + + bridge = kzalloc(sizeof(struct acpiphp_bridge), GFP_KERNEL); + if (bridge == NULL) + return; + + bridge->type = BRIDGE_TYPE_HOST; + bridge->handle = handle; + + bridge->pci_bus = pci_bus; + + spin_lock_init(&bridge->res_lock); + + init_bridge_misc(bridge); +} + + +/* allocate and initialize PCI-to-PCI bridge data structure */ +static void add_p2p_bridge(acpi_handle *handle, struct pci_dev *pci_dev) +{ + struct acpiphp_bridge *bridge; + + bridge = kzalloc(sizeof(struct acpiphp_bridge), GFP_KERNEL); + if (bridge == NULL) { + err("out of memory\n"); + return; + } + + bridge->type = BRIDGE_TYPE_P2P; + bridge->handle = handle; + config_p2p_bridge_flags(bridge); + + bridge->pci_dev = pci_dev_get(pci_dev); + bridge->pci_bus = pci_dev->subordinate; + if (!bridge->pci_bus) { + err("This is not a PCI-to-PCI bridge!\n"); + goto err; + } + + spin_lock_init(&bridge->res_lock); + + init_bridge_misc(bridge); + return; + err: + pci_dev_put(pci_dev); + kfree(bridge); + return; +} + + +/* callback routine to find P2P bridges */ +static acpi_status +find_p2p_bridge(acpi_handle handle, u32 lvl, void *context, void **rv) +{ + acpi_status status; + acpi_handle dummy_handle; + unsigned long long tmp; + int device, function; + struct pci_dev *dev; + struct pci_bus *pci_bus = context; + + status = acpi_get_handle(handle, "_ADR", &dummy_handle); + if (ACPI_FAILURE(status)) + return AE_OK; /* continue */ + + status = acpi_evaluate_integer(handle, "_ADR", NULL, &tmp); + if (ACPI_FAILURE(status)) { + dbg("%s: _ADR evaluation failure\n", __func__); + return AE_OK; + } + + device = (tmp >> 16) & 0xffff; + function = tmp & 0xffff; + + dev = pci_get_slot(pci_bus, PCI_DEVFN(device, function)); + + if (!dev || !dev->subordinate) + goto out; + + /* check if this bridge has ejectable slots */ + if ((detect_ejectable_slots(handle) > 0)) { + dbg("found PCI-to-PCI bridge at PCI %s\n", pci_name(dev)); + add_p2p_bridge(handle, dev); + } + + /* search P2P bridges under this p2p bridge */ + status = acpi_walk_namespace(ACPI_TYPE_DEVICE, handle, (u32)1, + find_p2p_bridge, dev->subordinate, NULL); + if (ACPI_FAILURE(status)) + warn("find_p2p_bridge failed (error code = 0x%x)\n", status); + + out: + pci_dev_put(dev); + return AE_OK; +} + + +/* find hot-pluggable slots, and then find P2P bridge */ +static int add_bridge(acpi_handle handle) +{ + acpi_status status; + unsigned long long tmp; + int seg, bus; + acpi_handle dummy_handle; + struct pci_bus *pci_bus; + + /* if the bridge doesn't have _STA, we assume it is always there */ + status = acpi_get_handle(handle, "_STA", &dummy_handle); + if (ACPI_SUCCESS(status)) { + status = acpi_evaluate_integer(handle, "_STA", NULL, &tmp); + if (ACPI_FAILURE(status)) { + dbg("%s: _STA evaluation failure\n", __func__); + return 0; + } + if ((tmp & ACPI_STA_FUNCTIONING) == 0) + /* don't register this object */ + return 0; + } + + /* get PCI segment number */ + status = acpi_evaluate_integer(handle, "_SEG", NULL, &tmp); + + seg = ACPI_SUCCESS(status) ? tmp : 0; + + /* get PCI bus number */ + status = acpi_evaluate_integer(handle, "_BBN", NULL, &tmp); + + if (ACPI_SUCCESS(status)) { + bus = tmp; + } else { + warn("can't get bus number, assuming 0\n"); + bus = 0; + } + + pci_bus = pci_find_bus(seg, bus); + if (!pci_bus) { + err("Can't find bus %04x:%02x\n", seg, bus); + return 0; + } + + /* check if this bridge has ejectable slots */ + if (detect_ejectable_slots(handle) > 0) { + dbg("found PCI host-bus bridge with hot-pluggable slots\n"); + add_host_bridge(handle, pci_bus); + } + + /* search P2P bridges under this host bridge */ + status = acpi_walk_namespace(ACPI_TYPE_DEVICE, handle, (u32)1, + find_p2p_bridge, pci_bus, NULL); + + if (ACPI_FAILURE(status)) + warn("find_p2p_bridge failed (error code = 0x%x)\n", status); + + return 0; +} + +static struct acpiphp_bridge *acpiphp_handle_to_bridge(acpi_handle handle) +{ + struct list_head *head; + list_for_each(head, &bridge_list) { + struct acpiphp_bridge *bridge = list_entry(head, + struct acpiphp_bridge, list); + if (bridge->handle == handle) + return bridge; + } + + return NULL; +} + +static void cleanup_bridge(struct acpiphp_bridge *bridge) +{ + struct list_head *list, *tmp; + struct acpiphp_slot *slot; + acpi_status status; + acpi_handle handle = bridge->handle; + + status = acpi_remove_notify_handler(handle, ACPI_SYSTEM_NOTIFY, + handle_hotplug_event_bridge); + if (ACPI_FAILURE(status)) + err("failed to remove notify handler\n"); + + if ((bridge->type != BRIDGE_TYPE_HOST) && + ((bridge->flags & BRIDGE_HAS_EJ0) && bridge->func)) { + status = acpi_install_notify_handler(bridge->func->handle, + ACPI_SYSTEM_NOTIFY, + handle_hotplug_event_func, + bridge->func); + if (ACPI_FAILURE(status)) + err("failed to install interrupt notify handler\n"); + } + + slot = bridge->slots; + while (slot) { + struct acpiphp_slot *next = slot->next; + list_for_each_safe (list, tmp, &slot->funcs) { + struct acpiphp_func *func; + func = list_entry(list, struct acpiphp_func, sibling); + if (is_dock_device(func->handle)) { + unregister_hotplug_dock_device(func->handle); + unregister_dock_notifier(&func->nb); + } + if (!(func->flags & FUNC_HAS_DCK)) { + status = acpi_remove_notify_handler(func->handle, + ACPI_SYSTEM_NOTIFY, + handle_hotplug_event_func); + if (ACPI_FAILURE(status)) + err("failed to remove notify handler\n"); + } + pci_dev_put(func->pci_dev); + list_del(list); + kfree(func); + } + acpiphp_unregister_hotplug_slot(slot); + list_del(&slot->funcs); + kfree(slot); + slot = next; + } + + pci_dev_put(bridge->pci_dev); + list_del(&bridge->list); + kfree(bridge); +} + +static acpi_status +cleanup_p2p_bridge(acpi_handle handle, u32 lvl, void *context, void **rv) +{ + struct acpiphp_bridge *bridge; + + /* cleanup p2p bridges under this P2P bridge + in a depth-first manner */ + acpi_walk_namespace(ACPI_TYPE_DEVICE, handle, (u32)1, + cleanup_p2p_bridge, NULL, NULL); + + bridge = acpiphp_handle_to_bridge(handle); + if (bridge) + cleanup_bridge(bridge); + + return AE_OK; +} + +static void remove_bridge(acpi_handle handle) +{ + struct acpiphp_bridge *bridge; + + /* cleanup p2p bridges under this host bridge + in a depth-first manner */ + acpi_walk_namespace(ACPI_TYPE_DEVICE, handle, + (u32)1, cleanup_p2p_bridge, NULL, NULL); + + /* + * On root bridges with hotplug slots directly underneath (ie, + * no p2p bridge inbetween), we call cleanup_bridge(). + * + * The else clause cleans up root bridges that either had no + * hotplug slots at all, or had a p2p bridge underneath. + */ + bridge = acpiphp_handle_to_bridge(handle); + if (bridge) + cleanup_bridge(bridge); + else + acpi_remove_notify_handler(handle, ACPI_SYSTEM_NOTIFY, + handle_hotplug_event_bridge); +} + +static struct pci_dev * get_apic_pci_info(acpi_handle handle) +{ + struct acpi_pci_id id; + struct pci_bus *bus; + struct pci_dev *dev; + + if (ACPI_FAILURE(acpi_get_pci_id(handle, &id))) + return NULL; + + bus = pci_find_bus(id.segment, id.bus); + if (!bus) + return NULL; + + dev = pci_get_slot(bus, PCI_DEVFN(id.device, id.function)); + if (!dev) + return NULL; + + if ((dev->class != PCI_CLASS_SYSTEM_PIC_IOAPIC) && + (dev->class != PCI_CLASS_SYSTEM_PIC_IOXAPIC)) + { + pci_dev_put(dev); + return NULL; + } + + return dev; +} + +static int get_gsi_base(acpi_handle handle, u32 *gsi_base) +{ + acpi_status status; + int result = -1; + unsigned long long gsb; + struct acpi_buffer buffer = {ACPI_ALLOCATE_BUFFER, NULL}; + union acpi_object *obj; + void *table; + + status = acpi_evaluate_integer(handle, "_GSB", NULL, &gsb); + if (ACPI_SUCCESS(status)) { + *gsi_base = (u32)gsb; + return 0; + } + + status = acpi_evaluate_object(handle, "_MAT", NULL, &buffer); + if (ACPI_FAILURE(status) || !buffer.length || !buffer.pointer) + return -1; + + obj = buffer.pointer; + if (obj->type != ACPI_TYPE_BUFFER) + goto out; + + table = obj->buffer.pointer; + switch (((struct acpi_subtable_header *)table)->type) { + case ACPI_MADT_TYPE_IO_SAPIC: + *gsi_base = ((struct acpi_madt_io_sapic *)table)->global_irq_base; + result = 0; + break; + case ACPI_MADT_TYPE_IO_APIC: + *gsi_base = ((struct acpi_madt_io_apic *)table)->global_irq_base; + result = 0; + break; + default: + break; + } + out: + kfree(buffer.pointer); + return result; +} + +static acpi_status +ioapic_add(acpi_handle handle, u32 lvl, void *context, void **rv) +{ + acpi_status status; + unsigned long long sta; + acpi_handle tmp; + struct pci_dev *pdev; + u32 gsi_base; + u64 phys_addr; + struct acpiphp_ioapic *ioapic; + + /* Evaluate _STA if present */ + status = acpi_evaluate_integer(handle, "_STA", NULL, &sta); + if (ACPI_SUCCESS(status) && sta != ACPI_STA_ALL) + return AE_CTRL_DEPTH; + + /* Scan only PCI bus scope */ + status = acpi_get_handle(handle, "_HID", &tmp); + if (ACPI_SUCCESS(status)) + return AE_CTRL_DEPTH; + + if (get_gsi_base(handle, &gsi_base)) + return AE_OK; + + ioapic = kmalloc(sizeof(*ioapic), GFP_KERNEL); + if (!ioapic) + return AE_NO_MEMORY; + + pdev = get_apic_pci_info(handle); + if (!pdev) + goto exit_kfree; + + if (pci_enable_device(pdev)) + goto exit_pci_dev_put; + + pci_set_master(pdev); + + if (pci_request_region(pdev, 0, "I/O APIC(acpiphp)")) + goto exit_pci_disable_device; + + phys_addr = pci_resource_start(pdev, 0); + if (acpi_register_ioapic(handle, phys_addr, gsi_base)) + goto exit_pci_release_region; + + ioapic->gsi_base = gsi_base; + ioapic->dev = pdev; + spin_lock(&ioapic_list_lock); + list_add_tail(&ioapic->list, &ioapic_list); + spin_unlock(&ioapic_list_lock); + + return AE_OK; + + exit_pci_release_region: + pci_release_region(pdev, 0); + exit_pci_disable_device: + pci_disable_device(pdev); + exit_pci_dev_put: + pci_dev_put(pdev); + exit_kfree: + kfree(ioapic); + + return AE_OK; +} + +static acpi_status +ioapic_remove(acpi_handle handle, u32 lvl, void *context, void **rv) +{ + acpi_status status; + unsigned long long sta; + acpi_handle tmp; + u32 gsi_base; + struct acpiphp_ioapic *pos, *n, *ioapic = NULL; + + /* Evaluate _STA if present */ + status = acpi_evaluate_integer(handle, "_STA", NULL, &sta); + if (ACPI_SUCCESS(status) && sta != ACPI_STA_ALL) + return AE_CTRL_DEPTH; + + /* Scan only PCI bus scope */ + status = acpi_get_handle(handle, "_HID", &tmp); + if (ACPI_SUCCESS(status)) + return AE_CTRL_DEPTH; + + if (get_gsi_base(handle, &gsi_base)) + return AE_OK; + + acpi_unregister_ioapic(handle, gsi_base); + + spin_lock(&ioapic_list_lock); + list_for_each_entry_safe(pos, n, &ioapic_list, list) { + if (pos->gsi_base != gsi_base) + continue; + ioapic = pos; + list_del(&ioapic->list); + break; + } + spin_unlock(&ioapic_list_lock); + + if (!ioapic) + return AE_OK; + + pci_release_region(ioapic->dev, 0); + pci_disable_device(ioapic->dev); + pci_dev_put(ioapic->dev); + kfree(ioapic); + + return AE_OK; +} + +static int acpiphp_configure_ioapics(acpi_handle handle) +{ + ioapic_add(handle, 0, NULL, NULL); + acpi_walk_namespace(ACPI_TYPE_DEVICE, handle, + ACPI_UINT32_MAX, ioapic_add, NULL, NULL); + return 0; +} + +static int acpiphp_unconfigure_ioapics(acpi_handle handle) +{ + ioapic_remove(handle, 0, NULL, NULL); + acpi_walk_namespace(ACPI_TYPE_DEVICE, handle, + ACPI_UINT32_MAX, ioapic_remove, NULL, NULL); + return 0; +} + +static int power_on_slot(struct acpiphp_slot *slot) +{ + acpi_status status; + struct acpiphp_func *func; + struct list_head *l; + int retval = 0; + + /* if already enabled, just skip */ + if (slot->flags & SLOT_POWEREDON) + goto err_exit; + + list_for_each (l, &slot->funcs) { + func = list_entry(l, struct acpiphp_func, sibling); + + if (func->flags & FUNC_HAS_PS0) { + dbg("%s: executing _PS0\n", __func__); + status = acpi_evaluate_object(func->handle, "_PS0", NULL, NULL); + if (ACPI_FAILURE(status)) { + warn("%s: _PS0 failed\n", __func__); + retval = -1; + goto err_exit; + } else + break; + } + } + + /* TBD: evaluate _STA to check if the slot is enabled */ + + slot->flags |= SLOT_POWEREDON; + + err_exit: + return retval; +} + + +static int power_off_slot(struct acpiphp_slot *slot) +{ + acpi_status status; + struct acpiphp_func *func; + struct list_head *l; + + int retval = 0; + + /* if already disabled, just skip */ + if ((slot->flags & SLOT_POWEREDON) == 0) + goto err_exit; + + list_for_each (l, &slot->funcs) { + func = list_entry(l, struct acpiphp_func, sibling); + + if (func->flags & FUNC_HAS_PS3) { + status = acpi_evaluate_object(func->handle, "_PS3", NULL, NULL); + if (ACPI_FAILURE(status)) { + warn("%s: _PS3 failed\n", __func__); + retval = -1; + goto err_exit; + } else + break; + } + } + + /* TBD: evaluate _STA to check if the slot is disabled */ + + slot->flags &= (~SLOT_POWEREDON); + + err_exit: + return retval; +} + + + +/** + * acpiphp_max_busnr - return the highest reserved bus number under the given bus. + * @bus: bus to start search with + */ +static unsigned char acpiphp_max_busnr(struct pci_bus *bus) +{ + struct list_head *tmp; + unsigned char max, n; + + /* + * pci_bus_max_busnr will return the highest + * reserved busnr for all these children. + * that is equivalent to the bus->subordinate + * value. We don't want to use the parent's + * bus->subordinate value because it could have + * padding in it. + */ + max = bus->secondary; + + list_for_each(tmp, &bus->children) { + n = pci_bus_max_busnr(pci_bus_b(tmp)); + if (n > max) + max = n; + } + return max; +} + + +/** + * acpiphp_bus_add - add a new bus to acpi subsystem + * @func: acpiphp_func of the bridge + */ +static int acpiphp_bus_add(struct acpiphp_func *func) +{ + acpi_handle phandle; + struct acpi_device *device, *pdevice; + int ret_val; + + acpi_get_parent(func->handle, &phandle); + if (acpi_bus_get_device(phandle, &pdevice)) { + dbg("no parent device, assuming NULL\n"); + pdevice = NULL; + } + if (!acpi_bus_get_device(func->handle, &device)) { + dbg("bus exists... trim\n"); + /* this shouldn't be in here, so remove + * the bus then re-add it... + */ + ret_val = acpi_bus_trim(device, 1); + dbg("acpi_bus_trim return %x\n", ret_val); + } + + ret_val = acpi_bus_add(&device, pdevice, func->handle, + ACPI_BUS_TYPE_DEVICE); + if (ret_val) { + dbg("error adding bus, %x\n", + -ret_val); + goto acpiphp_bus_add_out; + } + /* + * try to start anyway. We could have failed to add + * simply because this bus had previously been added + * on another add. Don't bother with the return value + * we just keep going. + */ + ret_val = acpi_bus_start(device); + +acpiphp_bus_add_out: + return ret_val; +} + + +/** + * acpiphp_bus_trim - trim a bus from acpi subsystem + * @handle: handle to acpi namespace + */ +static int acpiphp_bus_trim(acpi_handle handle) +{ + struct acpi_device *device; + int retval; + + retval = acpi_bus_get_device(handle, &device); + if (retval) { + dbg("acpi_device not found\n"); + return retval; + } + + retval = acpi_bus_trim(device, 1); + if (retval) + err("cannot remove from acpi list\n"); + + return retval; +} + +/** + * enable_device - enable, configure a slot + * @slot: slot to be enabled + * + * This function should be called per *physical slot*, + * not per each slot object in ACPI namespace. + */ +static int __ref enable_device(struct acpiphp_slot *slot) +{ + struct pci_dev *dev; + struct pci_bus *bus = slot->bridge->pci_bus; + struct list_head *l; + struct acpiphp_func *func; + int retval = 0; + int num, max, pass; + acpi_status status; + + if (slot->flags & SLOT_ENABLED) + goto err_exit; + + /* sanity check: dev should be NULL when hot-plugged in */ + dev = pci_get_slot(bus, PCI_DEVFN(slot->device, 0)); + if (dev) { + /* This case shouldn't happen */ + err("pci_dev structure already exists.\n"); + pci_dev_put(dev); + retval = -1; + goto err_exit; + } + + num = pci_scan_slot(bus, PCI_DEVFN(slot->device, 0)); + if (num == 0) { + err("No new device found\n"); + retval = -1; + goto err_exit; + } + + max = acpiphp_max_busnr(bus); + for (pass = 0; pass < 2; pass++) { + list_for_each_entry(dev, &bus->devices, bus_list) { + if (PCI_SLOT(dev->devfn) != slot->device) + continue; + if (dev->hdr_type == PCI_HEADER_TYPE_BRIDGE || + dev->hdr_type == PCI_HEADER_TYPE_CARDBUS) { + max = pci_scan_bridge(bus, dev, max, pass); + if (pass && dev->subordinate) + pci_bus_size_bridges(dev->subordinate); + } + } + } + + list_for_each (l, &slot->funcs) { + func = list_entry(l, struct acpiphp_func, sibling); + acpiphp_bus_add(func); + } + + pci_bus_assign_resources(bus); + acpiphp_sanitize_bus(bus); + acpiphp_set_hpp_values(slot->bridge->handle, bus); + list_for_each_entry(func, &slot->funcs, sibling) + acpiphp_configure_ioapics(func->handle); + pci_enable_bridges(bus); + pci_bus_add_devices(bus); + + /* associate pci_dev to our representation */ + list_for_each (l, &slot->funcs) { + func = list_entry(l, struct acpiphp_func, sibling); + func->pci_dev = pci_get_slot(bus, PCI_DEVFN(slot->device, + func->function)); + if (!func->pci_dev) + continue; + + if (func->pci_dev->hdr_type != PCI_HEADER_TYPE_BRIDGE && + func->pci_dev->hdr_type != PCI_HEADER_TYPE_CARDBUS) + continue; + + status = find_p2p_bridge(func->handle, (u32)1, bus, NULL); + if (ACPI_FAILURE(status)) + warn("find_p2p_bridge failed (error code = 0x%x)\n", + status); + } + + slot->flags |= SLOT_ENABLED; + + err_exit: + return retval; +} + +static void disable_bridges(struct pci_bus *bus) +{ + struct pci_dev *dev; + list_for_each_entry(dev, &bus->devices, bus_list) { + if (dev->subordinate) { + disable_bridges(dev->subordinate); + pci_disable_device(dev); + } + } +} + +/** + * disable_device - disable a slot + * @slot: ACPI PHP slot + */ +static int disable_device(struct acpiphp_slot *slot) +{ + int retval = 0; + struct acpiphp_func *func; + struct list_head *l; + + /* is this slot already disabled? */ + if (!(slot->flags & SLOT_ENABLED)) + goto err_exit; + + list_for_each (l, &slot->funcs) { + func = list_entry(l, struct acpiphp_func, sibling); + + if (func->bridge) { + /* cleanup p2p bridges under this P2P bridge */ + cleanup_p2p_bridge(func->bridge->handle, + (u32)1, NULL, NULL); + func->bridge = NULL; + } + + if (func->pci_dev) { + pci_stop_bus_device(func->pci_dev); + if (func->pci_dev->subordinate) { + disable_bridges(func->pci_dev->subordinate); + pci_disable_device(func->pci_dev); + } + } + } + + list_for_each (l, &slot->funcs) { + func = list_entry(l, struct acpiphp_func, sibling); + + acpiphp_unconfigure_ioapics(func->handle); + acpiphp_bus_trim(func->handle); + /* try to remove anyway. + * acpiphp_bus_add might have been failed */ + + if (!func->pci_dev) + continue; + + pci_remove_bus_device(func->pci_dev); + pci_dev_put(func->pci_dev); + func->pci_dev = NULL; + } + + slot->flags &= (~SLOT_ENABLED); + + err_exit: + return retval; +} + + +/** + * get_slot_status - get ACPI slot status + * @slot: ACPI PHP slot + * + * If a slot has _STA for each function and if any one of them + * returned non-zero status, return it. + * + * If a slot doesn't have _STA and if any one of its functions' + * configuration space is configured, return 0x0f as a _STA. + * + * Otherwise return 0. + */ +static unsigned int get_slot_status(struct acpiphp_slot *slot) +{ + acpi_status status; + unsigned long long sta = 0; + u32 dvid; + struct list_head *l; + struct acpiphp_func *func; + + list_for_each (l, &slot->funcs) { + func = list_entry(l, struct acpiphp_func, sibling); + + if (func->flags & FUNC_HAS_STA) { + status = acpi_evaluate_integer(func->handle, "_STA", NULL, &sta); + if (ACPI_SUCCESS(status) && sta) + break; + } else { + pci_bus_read_config_dword(slot->bridge->pci_bus, + PCI_DEVFN(slot->device, + func->function), + PCI_VENDOR_ID, &dvid); + if (dvid != 0xffffffff) { + sta = ACPI_STA_ALL; + break; + } + } + } + + return (unsigned int)sta; +} + +/** + * acpiphp_eject_slot - physically eject the slot + * @slot: ACPI PHP slot + */ +int acpiphp_eject_slot(struct acpiphp_slot *slot) +{ + acpi_status status; + struct acpiphp_func *func; + struct list_head *l; + struct acpi_object_list arg_list; + union acpi_object arg; + + list_for_each (l, &slot->funcs) { + func = list_entry(l, struct acpiphp_func, sibling); + + /* We don't want to call _EJ0 on non-existing functions. */ + if ((func->flags & FUNC_HAS_EJ0)) { + /* _EJ0 method take one argument */ + arg_list.count = 1; + arg_list.pointer = &arg; + arg.type = ACPI_TYPE_INTEGER; + arg.integer.value = 1; + + status = acpi_evaluate_object(func->handle, "_EJ0", &arg_list, NULL); + if (ACPI_FAILURE(status)) { + warn("%s: _EJ0 failed\n", __func__); + return -1; + } else + break; + } + } + return 0; +} + +/** + * acpiphp_check_bridge - re-enumerate devices + * @bridge: where to begin re-enumeration + * + * Iterate over all slots under this bridge and make sure that if a + * card is present they are enabled, and if not they are disabled. + */ +static int acpiphp_check_bridge(struct acpiphp_bridge *bridge) +{ + struct acpiphp_slot *slot; + int retval = 0; + int enabled, disabled; + + enabled = disabled = 0; + + for (slot = bridge->slots; slot; slot = slot->next) { + unsigned int status = get_slot_status(slot); + if (slot->flags & SLOT_ENABLED) { + if (status == ACPI_STA_ALL) + continue; + retval = acpiphp_disable_slot(slot); + if (retval) { + err("Error occurred in disabling\n"); + goto err_exit; + } else { + acpiphp_eject_slot(slot); + } + disabled++; + } else { + if (status != ACPI_STA_ALL) + continue; + retval = acpiphp_enable_slot(slot); + if (retval) { + err("Error occurred in enabling\n"); + goto err_exit; + } + enabled++; + } + } + + dbg("%s: %d enabled, %d disabled\n", __func__, enabled, disabled); + + err_exit: + return retval; +} + +static void program_hpp(struct pci_dev *dev, struct acpiphp_bridge *bridge) +{ + u16 pci_cmd, pci_bctl; + struct pci_dev *cdev; + + /* Program hpp values for this device */ + if (!(dev->hdr_type == PCI_HEADER_TYPE_NORMAL || + (dev->hdr_type == PCI_HEADER_TYPE_BRIDGE && + (dev->class >> 8) == PCI_CLASS_BRIDGE_PCI))) + return; + + if ((dev->class >> 8) == PCI_CLASS_BRIDGE_HOST) + return; + + pci_write_config_byte(dev, PCI_CACHE_LINE_SIZE, + bridge->hpp.t0->cache_line_size); + pci_write_config_byte(dev, PCI_LATENCY_TIMER, + bridge->hpp.t0->latency_timer); + pci_read_config_word(dev, PCI_COMMAND, &pci_cmd); + if (bridge->hpp.t0->enable_serr) + pci_cmd |= PCI_COMMAND_SERR; + else + pci_cmd &= ~PCI_COMMAND_SERR; + if (bridge->hpp.t0->enable_perr) + pci_cmd |= PCI_COMMAND_PARITY; + else + pci_cmd &= ~PCI_COMMAND_PARITY; + pci_write_config_word(dev, PCI_COMMAND, pci_cmd); + + /* Program bridge control value and child devices */ + if ((dev->class >> 8) == PCI_CLASS_BRIDGE_PCI) { + pci_write_config_byte(dev, PCI_SEC_LATENCY_TIMER, + bridge->hpp.t0->latency_timer); + pci_read_config_word(dev, PCI_BRIDGE_CONTROL, &pci_bctl); + if (bridge->hpp.t0->enable_serr) + pci_bctl |= PCI_BRIDGE_CTL_SERR; + else + pci_bctl &= ~PCI_BRIDGE_CTL_SERR; + if (bridge->hpp.t0->enable_perr) + pci_bctl |= PCI_BRIDGE_CTL_PARITY; + else + pci_bctl &= ~PCI_BRIDGE_CTL_PARITY; + pci_write_config_word(dev, PCI_BRIDGE_CONTROL, pci_bctl); + if (dev->subordinate) { + list_for_each_entry(cdev, &dev->subordinate->devices, + bus_list) + program_hpp(cdev, bridge); + } + } +} + +static void acpiphp_set_hpp_values(acpi_handle handle, struct pci_bus *bus) +{ + struct acpiphp_bridge bridge; + struct pci_dev *dev; + + memset(&bridge, 0, sizeof(bridge)); + bridge.handle = handle; + bridge.pci_bus = bus; + bridge.pci_dev = bus->self; + decode_hpp(&bridge); + list_for_each_entry(dev, &bus->devices, bus_list) + program_hpp(dev, &bridge); + +} + +/* + * Remove devices for which we could not assign resources, call + * arch specific code to fix-up the bus + */ +static void acpiphp_sanitize_bus(struct pci_bus *bus) +{ + struct pci_dev *dev; + int i; + unsigned long type_mask = IORESOURCE_IO | IORESOURCE_MEM; + + list_for_each_entry(dev, &bus->devices, bus_list) { + for (i=0; i<PCI_BRIDGE_RESOURCES; i++) { + struct resource *res = &dev->resource[i]; + if ((res->flags & type_mask) && !res->start && + res->end) { + /* Could not assign a required resources + * for this device, remove it */ + pci_remove_bus_device(dev); + break; + } + } + } +} + +/* Program resources in newly inserted bridge */ +static int acpiphp_configure_bridge (acpi_handle handle) +{ + struct acpi_pci_id pci_id; + struct pci_bus *bus; + + if (ACPI_FAILURE(acpi_get_pci_id(handle, &pci_id))) { + err("cannot get PCI domain and bus number for bridge\n"); + return -EINVAL; + } + bus = pci_find_bus(pci_id.segment, pci_id.bus); + if (!bus) { + err("cannot find bus %d:%d\n", + pci_id.segment, pci_id.bus); + return -EINVAL; + } + + pci_bus_size_bridges(bus); + pci_bus_assign_resources(bus); + acpiphp_sanitize_bus(bus); + acpiphp_set_hpp_values(handle, bus); + pci_enable_bridges(bus); + acpiphp_configure_ioapics(handle); + return 0; +} + +static void handle_bridge_insertion(acpi_handle handle, u32 type) +{ + struct acpi_device *device, *pdevice; + acpi_handle phandle; + + if ((type != ACPI_NOTIFY_BUS_CHECK) && + (type != ACPI_NOTIFY_DEVICE_CHECK)) { + err("unexpected notification type %d\n", type); + return; + } + + acpi_get_parent(handle, &phandle); + if (acpi_bus_get_device(phandle, &pdevice)) { + dbg("no parent device, assuming NULL\n"); + pdevice = NULL; + } + if (acpi_bus_add(&device, pdevice, handle, ACPI_BUS_TYPE_DEVICE)) { + err("cannot add bridge to acpi list\n"); + return; + } + if (!acpiphp_configure_bridge(handle) && + !acpi_bus_start(device)) + add_bridge(handle); + else + err("cannot configure and start bridge\n"); + +} + +/* + * ACPI event handlers + */ + +static acpi_status +count_sub_bridges(acpi_handle handle, u32 lvl, void *context, void **rv) +{ + int *count = (int *)context; + struct acpiphp_bridge *bridge; + + bridge = acpiphp_handle_to_bridge(handle); + if (bridge) + (*count)++; + return AE_OK ; +} + +static acpi_status +check_sub_bridges(acpi_handle handle, u32 lvl, void *context, void **rv) +{ + struct acpiphp_bridge *bridge; + char objname[64]; + struct acpi_buffer buffer = { .length = sizeof(objname), + .pointer = objname }; + + bridge = acpiphp_handle_to_bridge(handle); + if (bridge) { + acpi_get_name(handle, ACPI_FULL_PATHNAME, &buffer); + dbg("%s: re-enumerating slots under %s\n", + __func__, objname); + acpiphp_check_bridge(bridge); + } + return AE_OK ; +} + +/** + * handle_hotplug_event_bridge - handle ACPI event on bridges + * @handle: Notify()'ed acpi_handle + * @type: Notify code + * @context: pointer to acpiphp_bridge structure + * + * Handles ACPI event notification on {host,p2p} bridges. + */ +static void handle_hotplug_event_bridge(acpi_handle handle, u32 type, void *context) +{ + struct acpiphp_bridge *bridge; + char objname[64]; + struct acpi_buffer buffer = { .length = sizeof(objname), + .pointer = objname }; + struct acpi_device *device; + int num_sub_bridges = 0; + + if (acpi_bus_get_device(handle, &device)) { + /* This bridge must have just been physically inserted */ + handle_bridge_insertion(handle, type); + return; + } + + bridge = acpiphp_handle_to_bridge(handle); + if (type == ACPI_NOTIFY_BUS_CHECK) { + acpi_walk_namespace(ACPI_TYPE_DEVICE, handle, ACPI_UINT32_MAX, + count_sub_bridges, &num_sub_bridges, NULL); + } + + if (!bridge && !num_sub_bridges) { + err("cannot get bridge info\n"); + return; + } + + acpi_get_name(handle, ACPI_FULL_PATHNAME, &buffer); + + switch (type) { + case ACPI_NOTIFY_BUS_CHECK: + /* bus re-enumerate */ + dbg("%s: Bus check notify on %s\n", __func__, objname); + if (bridge) { + dbg("%s: re-enumerating slots under %s\n", + __func__, objname); + acpiphp_check_bridge(bridge); + } + if (num_sub_bridges) + acpi_walk_namespace(ACPI_TYPE_DEVICE, handle, + ACPI_UINT32_MAX, check_sub_bridges, NULL, NULL); + break; + + case ACPI_NOTIFY_DEVICE_CHECK: + /* device check */ + dbg("%s: Device check notify on %s\n", __func__, objname); + acpiphp_check_bridge(bridge); + break; + + case ACPI_NOTIFY_DEVICE_WAKE: + /* wake event */ + dbg("%s: Device wake notify on %s\n", __func__, objname); + break; + + case ACPI_NOTIFY_EJECT_REQUEST: + /* request device eject */ + dbg("%s: Device eject notify on %s\n", __func__, objname); + if ((bridge->type != BRIDGE_TYPE_HOST) && + (bridge->flags & BRIDGE_HAS_EJ0)) { + struct acpiphp_slot *slot; + slot = bridge->func->slot; + if (!acpiphp_disable_slot(slot)) + acpiphp_eject_slot(slot); + } + break; + + case ACPI_NOTIFY_FREQUENCY_MISMATCH: + printk(KERN_ERR "Device %s cannot be configured due" + " to a frequency mismatch\n", objname); + break; + + case ACPI_NOTIFY_BUS_MODE_MISMATCH: + printk(KERN_ERR "Device %s cannot be configured due" + " to a bus mode mismatch\n", objname); + break; + + case ACPI_NOTIFY_POWER_FAULT: + printk(KERN_ERR "Device %s has suffered a power fault\n", + objname); + break; + + default: + warn("notify_handler: unknown event type 0x%x for %s\n", type, objname); + break; + } +} + +/** + * handle_hotplug_event_func - handle ACPI event on functions (i.e. slots) + * @handle: Notify()'ed acpi_handle + * @type: Notify code + * @context: pointer to acpiphp_func structure + * + * Handles ACPI event notification on slots. + */ +static void handle_hotplug_event_func(acpi_handle handle, u32 type, void *context) +{ + struct acpiphp_func *func; + char objname[64]; + struct acpi_buffer buffer = { .length = sizeof(objname), + .pointer = objname }; + + acpi_get_name(handle, ACPI_FULL_PATHNAME, &buffer); + + func = (struct acpiphp_func *)context; + + switch (type) { + case ACPI_NOTIFY_BUS_CHECK: + /* bus re-enumerate */ + dbg("%s: Bus check notify on %s\n", __func__, objname); + acpiphp_enable_slot(func->slot); + break; + + case ACPI_NOTIFY_DEVICE_CHECK: + /* device check : re-enumerate from parent bus */ + dbg("%s: Device check notify on %s\n", __func__, objname); + acpiphp_check_bridge(func->slot->bridge); + break; + + case ACPI_NOTIFY_DEVICE_WAKE: + /* wake event */ + dbg("%s: Device wake notify on %s\n", __func__, objname); + break; + + case ACPI_NOTIFY_EJECT_REQUEST: + /* request device eject */ + dbg("%s: Device eject notify on %s\n", __func__, objname); + if (!(acpiphp_disable_slot(func->slot))) + acpiphp_eject_slot(func->slot); + break; + + default: + warn("notify_handler: unknown event type 0x%x for %s\n", type, objname); + break; + } +} + + +static acpi_status +find_root_bridges(acpi_handle handle, u32 lvl, void *context, void **rv) +{ + int *count = (int *)context; + + if (acpi_root_bridge(handle)) { + acpi_install_notify_handler(handle, ACPI_SYSTEM_NOTIFY, + handle_hotplug_event_bridge, NULL); + (*count)++; + } + return AE_OK ; +} + +static struct acpi_pci_driver acpi_pci_hp_driver = { + .add = add_bridge, + .remove = remove_bridge, +}; + +/** + * acpiphp_glue_init - initializes all PCI hotplug - ACPI glue data structures + */ +int __init acpiphp_glue_init(void) +{ + int num = 0; + + acpi_walk_namespace(ACPI_TYPE_DEVICE, ACPI_ROOT_OBJECT, + ACPI_UINT32_MAX, find_root_bridges, &num, NULL); + + if (num <= 0) + return -1; + else + acpi_pci_register_driver(&acpi_pci_hp_driver); + + return 0; +} + + +/** + * acpiphp_glue_exit - terminates all PCI hotplug - ACPI glue data structures + * + * This function frees all data allocated in acpiphp_glue_init(). + */ +void acpiphp_glue_exit(void) +{ + acpi_pci_unregister_driver(&acpi_pci_hp_driver); +} + + +/** + * acpiphp_get_num_slots - count number of slots in a system + */ +int __init acpiphp_get_num_slots(void) +{ + struct acpiphp_bridge *bridge; + int num_slots = 0; + + list_for_each_entry (bridge, &bridge_list, list) { + dbg("Bus %04x:%02x has %d slot%s\n", + pci_domain_nr(bridge->pci_bus), + bridge->pci_bus->number, bridge->nr_slots, + bridge->nr_slots == 1 ? "" : "s"); + num_slots += bridge->nr_slots; + } + + dbg("Total %d slots\n", num_slots); + return num_slots; +} + + +#if 0 +/** + * acpiphp_for_each_slot - call function for each slot + * @fn: callback function + * @data: context to be passed to callback function + */ +static int acpiphp_for_each_slot(acpiphp_callback fn, void *data) +{ + struct list_head *node; + struct acpiphp_bridge *bridge; + struct acpiphp_slot *slot; + int retval = 0; + + list_for_each (node, &bridge_list) { + bridge = (struct acpiphp_bridge *)node; + for (slot = bridge->slots; slot; slot = slot->next) { + retval = fn(slot, data); + if (!retval) + goto err_exit; + } + } + + err_exit: + return retval; +} +#endif + + +/** + * acpiphp_enable_slot - power on slot + * @slot: ACPI PHP slot + */ +int acpiphp_enable_slot(struct acpiphp_slot *slot) +{ + int retval; + + mutex_lock(&slot->crit_sect); + + /* wake up all functions */ + retval = power_on_slot(slot); + if (retval) + goto err_exit; + + if (get_slot_status(slot) == ACPI_STA_ALL) { + /* configure all functions */ + retval = enable_device(slot); + if (retval) + power_off_slot(slot); + } else { + dbg("%s: Slot status is not ACPI_STA_ALL\n", __func__); + power_off_slot(slot); + } + + err_exit: + mutex_unlock(&slot->crit_sect); + return retval; +} + +/** + * acpiphp_disable_slot - power off slot + * @slot: ACPI PHP slot + */ +int acpiphp_disable_slot(struct acpiphp_slot *slot) +{ + int retval = 0; + + mutex_lock(&slot->crit_sect); + + /* unconfigure all functions */ + retval = disable_device(slot); + if (retval) + goto err_exit; + + /* power off all functions */ + retval = power_off_slot(slot); + if (retval) + goto err_exit; + + err_exit: + mutex_unlock(&slot->crit_sect); + return retval; +} + + +/* + * slot enabled: 1 + * slot disabled: 0 + */ +u8 acpiphp_get_power_status(struct acpiphp_slot *slot) +{ + return (slot->flags & SLOT_POWEREDON); +} + + +/* + * latch open: 1 + * latch closed: 0 + */ +u8 acpiphp_get_latch_status(struct acpiphp_slot *slot) +{ + unsigned int sta; + + sta = get_slot_status(slot); + + return (sta & ACPI_STA_SHOW_IN_UI) ? 0 : 1; +} + + +/* + * adapter presence : 1 + * absence : 0 + */ +u8 acpiphp_get_adapter_status(struct acpiphp_slot *slot) +{ + unsigned int sta; + + sta = get_slot_status(slot); + + return (sta == 0) ? 0 : 1; +} diff --git a/drivers/pci/hotplug/acpiphp_ibm.c b/drivers/pci/hotplug/acpiphp_ibm.c new file mode 100644 index 0000000..881fdd2 --- /dev/null +++ b/drivers/pci/hotplug/acpiphp_ibm.c @@ -0,0 +1,500 @@ +/* + * ACPI PCI Hot Plug IBM Extension + * + * Copyright (C) 2004 Vernon Mauery <vernux@us.ibm.com> + * Copyright (C) 2004 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 <vernux@us.ibm.com> + * + */ + +#include <linux/init.h> +#include <linux/module.h> +#include <linux/kernel.h> +#include <acpi/acpi_bus.h> +#include <linux/sysfs.h> +#include <linux/kobject.h> +#include <asm/uaccess.h> +#include <linux/moduleparam.h> +#include <linux/pci.h> + +#include "acpiphp.h" +#include "../pci.h" + +#define DRIVER_VERSION "1.0.1" +#define DRIVER_AUTHOR "Irene Zubarev <zubarev@us.ibm.com>, Vernon Mauery <vernux@us.ibm.com>" +#define DRIVER_DESC "ACPI Hot Plug PCI Controller Driver IBM extension" + +static int debug; + +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); +MODULE_VERSION(DRIVER_VERSION); +module_param(debug, bool, 0644); +MODULE_PARM_DESC(debug, " Debugging mode enabled or not"); +#define MY_NAME "acpiphp_ibm" + +#undef dbg +#define dbg(format, arg...) \ +do { \ + if (debug) \ + printk(KERN_DEBUG "%s: " format, \ + MY_NAME , ## arg); \ +} while (0) + +#define FOUND_APCI 0x61504349 +/* these are the names for the IBM ACPI pseudo-device */ +#define IBM_HARDWARE_ID1 "IBM37D0" +#define IBM_HARDWARE_ID2 "IBM37D4" + +#define hpslot_to_sun(A) (((struct slot *)((A)->private))->acpi_slot->sun) + +/* union apci_descriptor - allows access to the + * various device descriptors that are embedded in the + * aPCI table + */ +union apci_descriptor { + struct { + char sig[4]; + u8 len; + } header; + struct { + u8 type; + u8 len; + u16 slot_id; + u8 bus_id; + u8 dev_num; + u8 slot_num; + u8 slot_attr[2]; + u8 attn; + u8 status[2]; + u8 sun; + u8 res[3]; + } slot; + struct { + u8 type; + u8 len; + } generic; +}; + +/* struct notification - keeps info about the device + * that cause the ACPI notification event + */ +struct notification { + struct acpi_device *device; + u8 event; +}; + +static int ibm_set_attention_status(struct hotplug_slot *slot, u8 status); +static int ibm_get_attention_status(struct hotplug_slot *slot, u8 *status); +static void ibm_handle_events(acpi_handle handle, u32 event, void *context); +static int ibm_get_table_from_acpi(char **bufp); +static ssize_t ibm_read_apci_table(struct kobject *kobj, + struct bin_attribute *bin_attr, + char *buffer, loff_t pos, size_t size); +static acpi_status __init ibm_find_acpi_device(acpi_handle handle, + u32 lvl, void *context, void **rv); +static int __init ibm_acpiphp_init(void); +static void __exit ibm_acpiphp_exit(void); + +static acpi_handle ibm_acpi_handle; +static struct notification ibm_note; +static struct bin_attribute ibm_apci_table_attr = { + .attr = { + .name = "apci_table", + .mode = S_IRUGO, + }, + .read = ibm_read_apci_table, + .write = NULL, +}; +static struct acpiphp_attention_info ibm_attention_info = +{ + .set_attn = ibm_set_attention_status, + .get_attn = ibm_get_attention_status, + .owner = THIS_MODULE, +}; + +/** + * ibm_slot_from_id - workaround for bad ibm hardware + * @id: the slot number that linux refers to the slot by + * + * Description: This method returns the aCPI slot descriptor + * corresponding to the Linux slot number. This descriptor + * has info about the aPCI slot id and attention status. + * This descriptor must be freed using kfree when done. + */ +static union apci_descriptor *ibm_slot_from_id(int id) +{ + int ind = 0, size; + union apci_descriptor *ret = NULL, *des; + char *table; + + size = ibm_get_table_from_acpi(&table); + des = (union apci_descriptor *)table; + if (memcmp(des->header.sig, "aPCI", 4) != 0) + goto ibm_slot_done; + + des = (union apci_descriptor *)&table[ind += des->header.len]; + while (ind < size && (des->generic.type != 0x82 || + des->slot.slot_num != id)) { + des = (union apci_descriptor *)&table[ind += des->generic.len]; + } + + if (ind < size && des->slot.slot_num == id) + ret = des; + +ibm_slot_done: + if (ret) { + ret = kmalloc(sizeof(union apci_descriptor), GFP_KERNEL); + memcpy(ret, des, sizeof(union apci_descriptor)); + } + kfree(table); + return ret; +} + +/** + * ibm_set_attention_status - callback method to set the attention LED + * @slot: the hotplug_slot to work with + * @status: what to set the LED to (0 or 1) + * + * Description: This method is registered with the acpiphp module as a + * callback to do the device specific task of setting the LED status. + */ +static int ibm_set_attention_status(struct hotplug_slot *slot, u8 status) +{ + union acpi_object args[2]; + struct acpi_object_list params = { .pointer = args, .count = 2 }; + acpi_status stat; + unsigned long long rc; + union apci_descriptor *ibm_slot; + + ibm_slot = ibm_slot_from_id(hpslot_to_sun(slot)); + + dbg("%s: set slot %d (%d) attention status to %d\n", __func__, + ibm_slot->slot.slot_num, ibm_slot->slot.slot_id, + (status ? 1 : 0)); + + args[0].type = ACPI_TYPE_INTEGER; + args[0].integer.value = ibm_slot->slot.slot_id; + args[1].type = ACPI_TYPE_INTEGER; + args[1].integer.value = (status) ? 1 : 0; + + kfree(ibm_slot); + + stat = acpi_evaluate_integer(ibm_acpi_handle, "APLS", ¶ms, &rc); + if (ACPI_FAILURE(stat)) { + err("APLS evaluation failed: 0x%08x\n", stat); + return -ENODEV; + } else if (!rc) { + err("APLS method failed: 0x%08llx\n", rc); + return -ERANGE; + } + return 0; +} + +/** + * ibm_get_attention_status - callback method to get attention LED status + * @slot: the hotplug_slot to work with + * @status: returns what the LED is set to (0 or 1) + * + * Description: This method is registered with the acpiphp module as a + * callback to do the device specific task of getting the LED status. + * + * Because there is no direct method of getting the LED status directly + * from an ACPI call, we read the aPCI table and parse out our + * slot descriptor to read the status from that. + */ +static int ibm_get_attention_status(struct hotplug_slot *slot, u8 *status) +{ + union apci_descriptor *ibm_slot; + + ibm_slot = ibm_slot_from_id(hpslot_to_sun(slot)); + + if (ibm_slot->slot.attn & 0xa0 || ibm_slot->slot.status[1] & 0x08) + *status = 1; + else + *status = 0; + + dbg("%s: get slot %d (%d) attention status is %d\n", __func__, + ibm_slot->slot.slot_num, ibm_slot->slot.slot_id, + *status); + + kfree(ibm_slot); + return 0; +} + +/** + * ibm_handle_events - listens for ACPI events for the IBM37D0 device + * @handle: an ACPI handle to the device that caused the event + * @event: the event info (device specific) + * @context: passed context (our notification struct) + * + * Description: This method is registered as a callback with the ACPI + * subsystem it is called when this device has an event to notify the OS of. + * + * The events actually come from the device as two events that get + * synthesized into one event with data by this function. The event + * ID comes first and then the slot number that caused it. We report + * this as one event to the OS. + * + * From section 5.6.2.2 of the ACPI 2.0 spec, I understand that the OSPM will + * only re-enable the interrupt that causes this event AFTER this method + * has returned, thereby enforcing serial access for the notification struct. + */ +static void ibm_handle_events(acpi_handle handle, u32 event, void *context) +{ + u8 detail = event & 0x0f; + u8 subevent = event & 0xf0; + struct notification *note = context; + + dbg("%s: Received notification %02x\n", __func__, event); + + if (subevent == 0x80) { + dbg("%s: generationg bus event\n", __func__); + acpi_bus_generate_proc_event(note->device, note->event, detail); + acpi_bus_generate_netlink_event(note->device->pnp.device_class, + note->device->dev.bus_id, + note->event, detail); + } else + note->event = event; +} + +/** + * ibm_get_table_from_acpi - reads the APLS buffer from ACPI + * @bufp: address to pointer to allocate for the table + * + * Description: This method reads the APLS buffer in from ACPI and + * stores the "stripped" table into a single buffer + * it allocates and passes the address back in bufp. + * + * If NULL is passed in as buffer, this method only calculates + * the size of the table and returns that without filling + * in the buffer. + * + * Returns < 0 on error or the size of the table on success. + */ +static int ibm_get_table_from_acpi(char **bufp) +{ + union acpi_object *package; + struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL }; + acpi_status status; + char *lbuf = NULL; + int i, size = -EIO; + + status = acpi_evaluate_object(ibm_acpi_handle, "APCI", NULL, &buffer); + if (ACPI_FAILURE(status)) { + err("%s: APCI evaluation failed\n", __func__); + return -ENODEV; + } + + package = (union acpi_object *) buffer.pointer; + if (!(package) || + (package->type != ACPI_TYPE_PACKAGE) || + !(package->package.elements)) { + err("%s: Invalid APCI object\n", __func__); + goto read_table_done; + } + + for(size = 0, i = 0; i < package->package.count; i++) { + if (package->package.elements[i].type != ACPI_TYPE_BUFFER) { + err("%s: Invalid APCI element %d\n", __func__, i); + goto read_table_done; + } + size += package->package.elements[i].buffer.length; + } + + if (bufp == NULL) + goto read_table_done; + + lbuf = kzalloc(size, GFP_KERNEL); + dbg("%s: element count: %i, ASL table size: %i, &table = 0x%p\n", + __func__, package->package.count, size, lbuf); + + if (lbuf) { + *bufp = lbuf; + } else { + size = -ENOMEM; + goto read_table_done; + } + + size = 0; + for (i=0; i<package->package.count; i++) { + memcpy(&lbuf[size], + package->package.elements[i].buffer.pointer, + package->package.elements[i].buffer.length); + size += package->package.elements[i].buffer.length; + } + +read_table_done: + kfree(buffer.pointer); + return size; +} + +/** + * ibm_read_apci_table - callback for the sysfs apci_table file + * @kobj: the kobject this binary attribute is a part of + * @bin_attr: struct bin_attribute for this file + * @buffer: the kernel space buffer to fill + * @pos: the offset into the file + * @size: the number of bytes requested + * + * Description: Gets registered with sysfs as the reader callback + * to be executed when /sys/bus/pci/slots/apci_table gets read. + * + * Since we don't get notified on open and close for this file, + * things get really tricky here... + * our solution is to only allow reading the table in all at once. + */ +static ssize_t ibm_read_apci_table(struct kobject *kobj, + struct bin_attribute *bin_attr, + char *buffer, loff_t pos, size_t size) +{ + int bytes_read = -EINVAL; + char *table = NULL; + + dbg("%s: pos = %d, size = %zd\n", __func__, (int)pos, size); + + if (pos == 0) { + bytes_read = ibm_get_table_from_acpi(&table); + if (bytes_read > 0 && bytes_read <= size) + memcpy(buffer, table, bytes_read); + kfree(table); + } + return bytes_read; +} + +/** + * ibm_find_acpi_device - callback to find our ACPI device + * @handle: the ACPI handle of the device we are inspecting + * @lvl: depth into the namespace tree + * @context: a pointer to our handle to fill when we find the device + * @rv: a return value to fill if desired + * + * Description: Used as a callback when calling acpi_walk_namespace + * to find our device. When this method returns non-zero + * acpi_walk_namespace quits its search and returns our value. + */ +static acpi_status __init ibm_find_acpi_device(acpi_handle handle, + u32 lvl, void *context, void **rv) +{ + acpi_handle *phandle = (acpi_handle *)context; + acpi_status status; + struct acpi_device_info *info; + struct acpi_buffer info_buffer = { ACPI_ALLOCATE_BUFFER, NULL }; + int retval = 0; + + status = acpi_get_object_info(handle, &info_buffer); + if (ACPI_FAILURE(status)) { + err("%s: Failed to get device information status=0x%x\n", + __func__, status); + return retval; + } + info = info_buffer.pointer; + info->hardware_id.value[sizeof(info->hardware_id.value) - 1] = '\0'; + + if (info->current_status && (info->valid & ACPI_VALID_HID) && + (!strcmp(info->hardware_id.value, IBM_HARDWARE_ID1) || + !strcmp(info->hardware_id.value, IBM_HARDWARE_ID2))) { + dbg("found hardware: %s, handle: %p\n", + info->hardware_id.value, handle); + *phandle = handle; + /* returning non-zero causes the search to stop + * and returns this value to the caller of + * acpi_walk_namespace, but it also causes some warnings + * in the acpi debug code to print... + */ + retval = FOUND_APCI; + } + kfree(info); + return retval; +} + +static int __init ibm_acpiphp_init(void) +{ + int retval = 0; + acpi_status status; + struct acpi_device *device; + struct kobject *sysdir = &pci_slots_kset->kobj; + + dbg("%s\n", __func__); + + if (acpi_walk_namespace(ACPI_TYPE_DEVICE, ACPI_ROOT_OBJECT, + ACPI_UINT32_MAX, ibm_find_acpi_device, + &ibm_acpi_handle, NULL) != FOUND_APCI) { + err("%s: acpi_walk_namespace failed\n", __func__); + retval = -ENODEV; + goto init_return; + } + dbg("%s: found IBM aPCI device\n", __func__); + if (acpi_bus_get_device(ibm_acpi_handle, &device)) { + err("%s: acpi_bus_get_device failed\n", __func__); + retval = -ENODEV; + goto init_return; + } + if (acpiphp_register_attention(&ibm_attention_info)) { + retval = -ENODEV; + goto init_return; + } + + ibm_note.device = device; + status = acpi_install_notify_handler(ibm_acpi_handle, + ACPI_DEVICE_NOTIFY, ibm_handle_events, + &ibm_note); + if (ACPI_FAILURE(status)) { + err("%s: Failed to register notification handler\n", + __func__); + retval = -EBUSY; + goto init_cleanup; + } + + ibm_apci_table_attr.size = ibm_get_table_from_acpi(NULL); + retval = sysfs_create_bin_file(sysdir, &ibm_apci_table_attr); + + return retval; + +init_cleanup: + acpiphp_unregister_attention(&ibm_attention_info); +init_return: + return retval; +} + +static void __exit ibm_acpiphp_exit(void) +{ + acpi_status status; + struct kobject *sysdir = &pci_slots_kset->kobj; + + dbg("%s\n", __func__); + + if (acpiphp_unregister_attention(&ibm_attention_info)) + err("%s: attention info deregistration failed", __func__); + + status = acpi_remove_notify_handler( + ibm_acpi_handle, + ACPI_DEVICE_NOTIFY, + ibm_handle_events); + if (ACPI_FAILURE(status)) + err("%s: Notification handler removal failed\n", __func__); + /* remove the /sys entries */ + sysfs_remove_bin_file(sysdir, &ibm_apci_table_attr); +} + +module_init(ibm_acpiphp_init); +module_exit(ibm_acpiphp_exit); diff --git a/drivers/pci/hotplug/cpci_hotplug.h b/drivers/pci/hotplug/cpci_hotplug.h new file mode 100644 index 0000000..9fff878 --- /dev/null +++ b/drivers/pci/hotplug/cpci_hotplug.h @@ -0,0 +1,102 @@ +/* + * CompactPCI Hot Plug Core Functions + * + * Copyright (C) 2002 SOMA Networks, Inc. + * 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 <scottm@somanetworks.com> + */ + +#ifndef _CPCI_HOTPLUG_H +#define _CPCI_HOTPLUG_H + +#include <linux/types.h> +#include <linux/pci.h> +#include <linux/pci_hotplug.h> + +/* PICMG 2.1 R2.0 HS CSR bits: */ +#define HS_CSR_INS 0x0080 +#define HS_CSR_EXT 0x0040 +#define HS_CSR_PI 0x0030 +#define HS_CSR_LOO 0x0008 +#define HS_CSR_PIE 0x0004 +#define HS_CSR_EIM 0x0002 +#define HS_CSR_DHA 0x0001 + +struct slot { + u8 number; + unsigned int devfn; + struct pci_bus *bus; + struct pci_dev *dev; + unsigned int extracting; + struct hotplug_slot *hotplug_slot; + struct list_head slot_list; +}; + +struct cpci_hp_controller_ops { + int (*query_enum) (void); + int (*enable_irq) (void); + int (*disable_irq) (void); + int (*check_irq) (void *dev_id); + int (*hardware_test) (struct slot* slot, u32 value); + u8 (*get_power) (struct slot* slot); + int (*set_power) (struct slot* slot, int value); +}; + +struct cpci_hp_controller { + unsigned int irq; + unsigned long irq_flags; + char *devname; + void *dev_id; + char *name; + struct cpci_hp_controller_ops *ops; +}; + +static inline const char *slot_name(struct slot *slot) +{ + return hotplug_slot_name(slot->hotplug_slot); +} + +extern int cpci_hp_register_controller(struct cpci_hp_controller *controller); +extern int cpci_hp_unregister_controller(struct cpci_hp_controller *controller); +extern int cpci_hp_register_bus(struct pci_bus *bus, u8 first, u8 last); +extern int cpci_hp_unregister_bus(struct pci_bus *bus); +extern int cpci_hp_start(void); +extern int cpci_hp_stop(void); + +/* + * Internal function prototypes, these functions should not be used by + * board/chassis drivers. + */ +extern u8 cpci_get_attention_status(struct slot *slot); +extern u8 cpci_get_latch_status(struct slot *slot); +extern u8 cpci_get_adapter_status(struct slot *slot); +extern u16 cpci_get_hs_csr(struct slot * slot); +extern int cpci_set_attention_status(struct slot *slot, int status); +extern int cpci_check_and_clear_ins(struct slot * slot); +extern int cpci_check_ext(struct slot * slot); +extern int cpci_clear_ext(struct slot * slot); +extern int cpci_led_on(struct slot * slot); +extern int cpci_led_off(struct slot * slot); +extern int cpci_configure_slot(struct slot *slot); +extern int cpci_unconfigure_slot(struct slot *slot); + +#endif /* _CPCI_HOTPLUG_H */ diff --git a/drivers/pci/hotplug/cpci_hotplug_core.c b/drivers/pci/hotplug/cpci_hotplug_core.c new file mode 100644 index 0000000..de94f4f --- /dev/null +++ b/drivers/pci/hotplug/cpci_hotplug_core.c @@ -0,0 +1,726 @@ +/* + * CompactPCI Hot Plug Driver + * + * Copyright (C) 2002,2005 SOMA Networks, Inc. + * 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 <scottm@somanetworks.com> + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/pci.h> +#include <linux/pci_hotplug.h> +#include <linux/init.h> +#include <linux/interrupt.h> +#include <linux/smp_lock.h> +#include <asm/atomic.h> +#include <linux/delay.h> +#include <linux/kthread.h> +#include "cpci_hotplug.h" + +#define DRIVER_AUTHOR "Scott Murray <scottm@somanetworks.com>" +#define DRIVER_DESC "CompactPCI Hot Plug Core" + +#define MY_NAME "cpci_hotplug" + +#define dbg(format, arg...) \ + do { \ + if (cpci_debug) \ + printk (KERN_DEBUG "%s: " format "\n", \ + MY_NAME , ## arg); \ + } while (0) +#define err(format, arg...) printk(KERN_ERR "%s: " format "\n", MY_NAME , ## arg) +#define info(format, arg...) printk(KERN_INFO "%s: " format "\n", MY_NAME , ## arg) +#define warn(format, arg...) printk(KERN_WARNING "%s: " format "\n", MY_NAME , ## arg) + +/* local variables */ +static DECLARE_RWSEM(list_rwsem); +static LIST_HEAD(slot_list); +static int slots; +static atomic_t extracting; +int cpci_debug; +static struct cpci_hp_controller *controller; +static struct task_struct *cpci_thread; +static int thread_finished; + +static int enable_slot(struct hotplug_slot *slot); +static int disable_slot(struct hotplug_slot *slot); +static int set_attention_status(struct hotplug_slot *slot, u8 value); +static int get_power_status(struct hotplug_slot *slot, u8 * value); +static int get_attention_status(struct hotplug_slot *slot, u8 * value); +static int get_adapter_status(struct hotplug_slot *slot, u8 * value); +static int get_latch_status(struct hotplug_slot *slot, u8 * value); + +static struct hotplug_slot_ops cpci_hotplug_slot_ops = { + .owner = THIS_MODULE, + .enable_slot = enable_slot, + .disable_slot = disable_slot, + .set_attention_status = set_attention_status, + .get_power_status = get_power_status, + .get_attention_status = get_attention_status, + .get_adapter_status = get_adapter_status, + .get_latch_status = get_latch_status, +}; + +static int +update_latch_status(struct hotplug_slot *hotplug_slot, u8 value) +{ + struct hotplug_slot_info info; + + memcpy(&info, hotplug_slot->info, sizeof(struct hotplug_slot_info)); + info.latch_status = value; + return pci_hp_change_slot_info(hotplug_slot, &info); +} + +static int +update_adapter_status(struct hotplug_slot *hotplug_slot, u8 value) +{ + struct hotplug_slot_info info; + + memcpy(&info, hotplug_slot->info, sizeof(struct hotplug_slot_info)); + info.adapter_status = value; + return pci_hp_change_slot_info(hotplug_slot, &info); +} + +static int +enable_slot(struct hotplug_slot *hotplug_slot) +{ + struct slot *slot = hotplug_slot->private; + int retval = 0; + + dbg("%s - physical_slot = %s", __func__, slot_name(slot)); + + if (controller->ops->set_power) + retval = controller->ops->set_power(slot, 1); + return retval; +} + +static int +disable_slot(struct hotplug_slot *hotplug_slot) +{ + struct slot *slot = hotplug_slot->private; + int retval = 0; + + dbg("%s - physical_slot = %s", __func__, slot_name(slot)); + + down_write(&list_rwsem); + + /* Unconfigure device */ + dbg("%s - unconfiguring slot %s", __func__, slot_name(slot)); + if ((retval = cpci_unconfigure_slot(slot))) { + err("%s - could not unconfigure slot %s", + __func__, slot_name(slot)); + goto disable_error; + } + dbg("%s - finished unconfiguring slot %s", __func__, slot_name(slot)); + + /* Clear EXT (by setting it) */ + if (cpci_clear_ext(slot)) { + err("%s - could not clear EXT for slot %s", + __func__, slot_name(slot)); + retval = -ENODEV; + goto disable_error; + } + cpci_led_on(slot); + + if (controller->ops->set_power) + if ((retval = controller->ops->set_power(slot, 0))) + goto disable_error; + + if (update_adapter_status(slot->hotplug_slot, 0)) + warn("failure to update adapter file"); + + if (slot->extracting) { + slot->extracting = 0; + atomic_dec(&extracting); + } +disable_error: + up_write(&list_rwsem); + return retval; +} + +static u8 +cpci_get_power_status(struct slot *slot) +{ + u8 power = 1; + + if (controller->ops->get_power) + power = controller->ops->get_power(slot); + return power; +} + +static int +get_power_status(struct hotplug_slot *hotplug_slot, u8 * value) +{ + struct slot *slot = hotplug_slot->private; + + *value = cpci_get_power_status(slot); + return 0; +} + +static int +get_attention_status(struct hotplug_slot *hotplug_slot, u8 * value) +{ + struct slot *slot = hotplug_slot->private; + + *value = cpci_get_attention_status(slot); + return 0; +} + +static int +set_attention_status(struct hotplug_slot *hotplug_slot, u8 status) +{ + return cpci_set_attention_status(hotplug_slot->private, status); +} + +static int +get_adapter_status(struct hotplug_slot *hotplug_slot, u8 * value) +{ + *value = hotplug_slot->info->adapter_status; + return 0; +} + +static int +get_latch_status(struct hotplug_slot *hotplug_slot, u8 * value) +{ + *value = hotplug_slot->info->latch_status; + return 0; +} + +static void release_slot(struct hotplug_slot *hotplug_slot) +{ + struct slot *slot = hotplug_slot->private; + + kfree(slot->hotplug_slot->info); + kfree(slot->hotplug_slot); + if (slot->dev) + pci_dev_put(slot->dev); + kfree(slot); +} + +#define SLOT_NAME_SIZE 6 + +int +cpci_hp_register_bus(struct pci_bus *bus, u8 first, u8 last) +{ + struct slot *slot; + struct hotplug_slot *hotplug_slot; + struct hotplug_slot_info *info; + char name[SLOT_NAME_SIZE]; + int status = -ENOMEM; + int i; + + if (!(controller && bus)) + return -ENODEV; + + /* + * Create a structure for each slot, and register that slot + * with the pci_hotplug subsystem. + */ + for (i = first; i <= last; ++i) { + slot = kzalloc(sizeof (struct slot), GFP_KERNEL); + if (!slot) + goto error; + + hotplug_slot = + kzalloc(sizeof (struct hotplug_slot), GFP_KERNEL); + if (!hotplug_slot) + goto error_slot; + slot->hotplug_slot = hotplug_slot; + + info = kzalloc(sizeof (struct hotplug_slot_info), GFP_KERNEL); + if (!info) + goto error_hpslot; + hotplug_slot->info = info; + + slot->bus = bus; + slot->number = i; + slot->devfn = PCI_DEVFN(i, 0); + + snprintf(name, SLOT_NAME_SIZE, "%02x:%02x", bus->number, i); + + hotplug_slot->private = slot; + hotplug_slot->release = &release_slot; + hotplug_slot->ops = &cpci_hotplug_slot_ops; + + /* + * Initialize the slot info structure with some known + * good values. + */ + dbg("initializing slot %s", name); + info->power_status = cpci_get_power_status(slot); + info->attention_status = cpci_get_attention_status(slot); + + dbg("registering slot %s", name); + status = pci_hp_register(slot->hotplug_slot, bus, i, name); + if (status) { + err("pci_hp_register failed with error %d", status); + goto error_info; + } + dbg("slot registered with name: %s", slot_name(slot)); + + /* Add slot to our internal list */ + down_write(&list_rwsem); + list_add(&slot->slot_list, &slot_list); + slots++; + up_write(&list_rwsem); + } + return 0; +error_info: + kfree(info); +error_hpslot: + kfree(hotplug_slot); +error_slot: + kfree(slot); +error: + return status; +} + +int +cpci_hp_unregister_bus(struct pci_bus *bus) +{ + struct slot *slot; + struct slot *tmp; + int status = 0; + + down_write(&list_rwsem); + if (!slots) { + up_write(&list_rwsem); + return -1; + } + list_for_each_entry_safe(slot, tmp, &slot_list, slot_list) { + if (slot->bus == bus) { + list_del(&slot->slot_list); + slots--; + + dbg("deregistering slot %s", slot_name(slot)); + status = pci_hp_deregister(slot->hotplug_slot); + if (status) { + err("pci_hp_deregister failed with error %d", + status); + break; + } + } + } + up_write(&list_rwsem); + return status; +} + +/* This is the interrupt mode interrupt handler */ +static irqreturn_t +cpci_hp_intr(int irq, void *data) +{ + dbg("entered cpci_hp_intr"); + + /* Check to see if it was our interrupt */ + if ((controller->irq_flags & IRQF_SHARED) && + !controller->ops->check_irq(controller->dev_id)) { + dbg("exited cpci_hp_intr, not our interrupt"); + return IRQ_NONE; + } + + /* Disable ENUM interrupt */ + controller->ops->disable_irq(); + + /* Trigger processing by the event thread */ + wake_up_process(cpci_thread); + return IRQ_HANDLED; +} + +/* + * According to PICMG 2.1 R2.0, section 6.3.2, upon + * initialization, the system driver shall clear the + * INS bits of the cold-inserted devices. + */ +static int +init_slots(int clear_ins) +{ + struct slot *slot; + struct pci_dev* dev; + + dbg("%s - enter", __func__); + down_read(&list_rwsem); + if (!slots) { + up_read(&list_rwsem); + return -1; + } + list_for_each_entry(slot, &slot_list, slot_list) { + dbg("%s - looking at slot %s", __func__, slot_name(slot)); + if (clear_ins && cpci_check_and_clear_ins(slot)) + dbg("%s - cleared INS for slot %s", + __func__, slot_name(slot)); + dev = pci_get_slot(slot->bus, PCI_DEVFN(slot->number, 0)); + if (dev) { + if (update_adapter_status(slot->hotplug_slot, 1)) + warn("failure to update adapter file"); + if (update_latch_status(slot->hotplug_slot, 1)) + warn("failure to update latch file"); + slot->dev = dev; + } + } + up_read(&list_rwsem); + dbg("%s - exit", __func__); + return 0; +} + +static int +check_slots(void) +{ + struct slot *slot; + int extracted; + int inserted; + u16 hs_csr; + + down_read(&list_rwsem); + if (!slots) { + up_read(&list_rwsem); + err("no slots registered, shutting down"); + return -1; + } + extracted = inserted = 0; + list_for_each_entry(slot, &slot_list, slot_list) { + dbg("%s - looking at slot %s", __func__, slot_name(slot)); + if (cpci_check_and_clear_ins(slot)) { + /* + * Some broken hardware (e.g. PLX 9054AB) asserts + * ENUM# twice... + */ + if (slot->dev) { + warn("slot %s already inserted", + slot_name(slot)); + inserted++; + continue; + } + + /* Process insertion */ + dbg("%s - slot %s inserted", __func__, slot_name(slot)); + + /* GSM, debug */ + hs_csr = cpci_get_hs_csr(slot); + dbg("%s - slot %s HS_CSR (1) = %04x", + __func__, slot_name(slot), hs_csr); + + /* Configure device */ + dbg("%s - configuring slot %s", + __func__, slot_name(slot)); + if (cpci_configure_slot(slot)) { + err("%s - could not configure slot %s", + __func__, slot_name(slot)); + continue; + } + dbg("%s - finished configuring slot %s", + __func__, slot_name(slot)); + + /* GSM, debug */ + hs_csr = cpci_get_hs_csr(slot); + dbg("%s - slot %s HS_CSR (2) = %04x", + __func__, slot_name(slot), hs_csr); + + if (update_latch_status(slot->hotplug_slot, 1)) + warn("failure to update latch file"); + + if (update_adapter_status(slot->hotplug_slot, 1)) + warn("failure to update adapter file"); + + cpci_led_off(slot); + + /* GSM, debug */ + hs_csr = cpci_get_hs_csr(slot); + dbg("%s - slot %s HS_CSR (3) = %04x", + __func__, slot_name(slot), hs_csr); + + inserted++; + } else if (cpci_check_ext(slot)) { + /* Process extraction request */ + dbg("%s - slot %s extracted", + __func__, slot_name(slot)); + + /* GSM, debug */ + hs_csr = cpci_get_hs_csr(slot); + dbg("%s - slot %s HS_CSR = %04x", + __func__, slot_name(slot), hs_csr); + + if (!slot->extracting) { + if (update_latch_status(slot->hotplug_slot, 0)) { + warn("failure to update latch file"); + } + slot->extracting = 1; + atomic_inc(&extracting); + } + extracted++; + } else if (slot->extracting) { + hs_csr = cpci_get_hs_csr(slot); + if (hs_csr == 0xffff) { + /* + * Hmmm, we're likely hosed at this point, should we + * bother trying to tell the driver or not? + */ + err("card in slot %s was improperly removed", + slot_name(slot)); + if (update_adapter_status(slot->hotplug_slot, 0)) + warn("failure to update adapter file"); + slot->extracting = 0; + atomic_dec(&extracting); + } + } + } + up_read(&list_rwsem); + dbg("inserted=%d, extracted=%d, extracting=%d", + inserted, extracted, atomic_read(&extracting)); + if (inserted || extracted) + return extracted; + else if (!atomic_read(&extracting)) { + err("cannot find ENUM# source, shutting down"); + return -1; + } + return 0; +} + +/* This is the interrupt mode worker thread body */ +static int +event_thread(void *data) +{ + int rc; + + dbg("%s - event thread started", __func__); + while (1) { + dbg("event thread sleeping"); + set_current_state(TASK_INTERRUPTIBLE); + schedule(); + if (kthread_should_stop()) + break; + do { + rc = check_slots(); + if (rc > 0) { + /* Give userspace a chance to handle extraction */ + msleep(500); + } else if (rc < 0) { + dbg("%s - error checking slots", __func__); + thread_finished = 1; + goto out; + } + } while (atomic_read(&extracting) && !kthread_should_stop()); + if (kthread_should_stop()) + break; + + /* Re-enable ENUM# interrupt */ + dbg("%s - re-enabling irq", __func__); + controller->ops->enable_irq(); + } + out: + return 0; +} + +/* This is the polling mode worker thread body */ +static int +poll_thread(void *data) +{ + int rc; + + while (1) { + if (kthread_should_stop() || signal_pending(current)) + break; + if (controller->ops->query_enum()) { + do { + rc = check_slots(); + if (rc > 0) { + /* Give userspace a chance to handle extraction */ + msleep(500); + } else if (rc < 0) { + dbg("%s - error checking slots", __func__); + thread_finished = 1; + goto out; + } + } while (atomic_read(&extracting) && !kthread_should_stop()); + } + msleep(100); + } + out: + return 0; +} + +static int +cpci_start_thread(void) +{ + if (controller->irq) + cpci_thread = kthread_run(event_thread, NULL, "cpci_hp_eventd"); + else + cpci_thread = kthread_run(poll_thread, NULL, "cpci_hp_polld"); + if (IS_ERR(cpci_thread)) { + err("Can't start up our thread"); + return PTR_ERR(cpci_thread); + } + thread_finished = 0; + return 0; +} + +static void +cpci_stop_thread(void) +{ + kthread_stop(cpci_thread); + thread_finished = 1; +} + +int +cpci_hp_register_controller(struct cpci_hp_controller *new_controller) +{ + int status = 0; + + if (controller) + return -1; + if (!(new_controller && new_controller->ops)) + return -EINVAL; + if (new_controller->irq) { + if (!(new_controller->ops->enable_irq && + new_controller->ops->disable_irq)) + status = -EINVAL; + if (request_irq(new_controller->irq, + cpci_hp_intr, + new_controller->irq_flags, + MY_NAME, + new_controller->dev_id)) { + err("Can't get irq %d for the hotplug cPCI controller", + new_controller->irq); + status = -ENODEV; + } + dbg("%s - acquired controller irq %d", + __func__, new_controller->irq); + } + if (!status) + controller = new_controller; + return status; +} + +static void +cleanup_slots(void) +{ + struct slot *slot; + struct slot *tmp; + + /* + * Unregister all of our slots with the pci_hotplug subsystem, + * and free up all memory that we had allocated. + */ + down_write(&list_rwsem); + if (!slots) + goto cleanup_null; + list_for_each_entry_safe(slot, tmp, &slot_list, slot_list) { + list_del(&slot->slot_list); + pci_hp_deregister(slot->hotplug_slot); + } +cleanup_null: + up_write(&list_rwsem); + return; +} + +int +cpci_hp_unregister_controller(struct cpci_hp_controller *old_controller) +{ + int status = 0; + + if (controller) { + if (!thread_finished) + cpci_stop_thread(); + if (controller->irq) + free_irq(controller->irq, controller->dev_id); + controller = NULL; + cleanup_slots(); + } else + status = -ENODEV; + return status; +} + +int +cpci_hp_start(void) +{ + static int first = 1; + int status; + + dbg("%s - enter", __func__); + if (!controller) + return -ENODEV; + + down_read(&list_rwsem); + if (list_empty(&slot_list)) { + up_read(&list_rwsem); + return -ENODEV; + } + up_read(&list_rwsem); + + status = init_slots(first); + if (first) + first = 0; + if (status) + return status; + + status = cpci_start_thread(); + if (status) + return status; + dbg("%s - thread started", __func__); + + if (controller->irq) { + /* Start enum interrupt processing */ + dbg("%s - enabling irq", __func__); + controller->ops->enable_irq(); + } + dbg("%s - exit", __func__); + return 0; +} + +int +cpci_hp_stop(void) +{ + if (!controller) + return -ENODEV; + if (controller->irq) { + /* Stop enum interrupt processing */ + dbg("%s - disabling irq", __func__); + controller->ops->disable_irq(); + } + cpci_stop_thread(); + return 0; +} + +int __init +cpci_hotplug_init(int debug) +{ + cpci_debug = debug; + return 0; +} + +void __exit +cpci_hotplug_exit(void) +{ + /* + * Clean everything up. + */ + cpci_hp_stop(); + cpci_hp_unregister_controller(controller); +} + +EXPORT_SYMBOL_GPL(cpci_hp_register_controller); +EXPORT_SYMBOL_GPL(cpci_hp_unregister_controller); +EXPORT_SYMBOL_GPL(cpci_hp_register_bus); +EXPORT_SYMBOL_GPL(cpci_hp_unregister_bus); +EXPORT_SYMBOL_GPL(cpci_hp_start); +EXPORT_SYMBOL_GPL(cpci_hp_stop); diff --git a/drivers/pci/hotplug/cpci_hotplug_pci.c b/drivers/pci/hotplug/cpci_hotplug_pci.c new file mode 100644 index 0000000..829c327 --- /dev/null +++ b/drivers/pci/hotplug/cpci_hotplug_pci.c @@ -0,0 +1,353 @@ +/* + * CompactPCI Hot Plug Driver PCI functions + * + * Copyright (C) 2002,2005 by SOMA Networks, Inc. + * + * 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 <scottm@somanetworks.com> + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/pci.h> +#include <linux/pci_hotplug.h> +#include <linux/proc_fs.h> +#include "../pci.h" +#include "cpci_hotplug.h" + +#define MY_NAME "cpci_hotplug" + +extern int cpci_debug; + +#define dbg(format, arg...) \ + do { \ + if (cpci_debug) \ + printk (KERN_DEBUG "%s: " format "\n", \ + MY_NAME , ## arg); \ + } while (0) +#define err(format, arg...) printk(KERN_ERR "%s: " format "\n", MY_NAME , ## arg) +#define info(format, arg...) printk(KERN_INFO "%s: " format "\n", MY_NAME , ## arg) +#define warn(format, arg...) printk(KERN_WARNING "%s: " format "\n", MY_NAME , ## arg) + + +u8 cpci_get_attention_status(struct slot* slot) +{ + int hs_cap; + u16 hs_csr; + + hs_cap = pci_bus_find_capability(slot->bus, + slot->devfn, + PCI_CAP_ID_CHSWP); + if (!hs_cap) + return 0; + + if (pci_bus_read_config_word(slot->bus, + slot->devfn, + hs_cap + 2, + &hs_csr)) + return 0; + + return hs_csr & 0x0008 ? 1 : 0; +} + +int cpci_set_attention_status(struct slot* slot, int status) +{ + int hs_cap; + u16 hs_csr; + + hs_cap = pci_bus_find_capability(slot->bus, + slot->devfn, + PCI_CAP_ID_CHSWP); + if (!hs_cap) + return 0; + if (pci_bus_read_config_word(slot->bus, + slot->devfn, + hs_cap + 2, + &hs_csr)) + return 0; + if (status) + hs_csr |= HS_CSR_LOO; + else + hs_csr &= ~HS_CSR_LOO; + if (pci_bus_write_config_word(slot->bus, + slot->devfn, + hs_cap + 2, + hs_csr)) + return 0; + return 1; +} + +u16 cpci_get_hs_csr(struct slot* slot) +{ + int hs_cap; + u16 hs_csr; + + hs_cap = pci_bus_find_capability(slot->bus, + slot->devfn, + PCI_CAP_ID_CHSWP); + if (!hs_cap) + return 0xFFFF; + if (pci_bus_read_config_word(slot->bus, + slot->devfn, + hs_cap + 2, + &hs_csr)) + return 0xFFFF; + return hs_csr; +} + +int cpci_check_and_clear_ins(struct slot* slot) +{ + int hs_cap; + u16 hs_csr; + int ins = 0; + + hs_cap = pci_bus_find_capability(slot->bus, + slot->devfn, + PCI_CAP_ID_CHSWP); + if (!hs_cap) + return 0; + if (pci_bus_read_config_word(slot->bus, + slot->devfn, + hs_cap + 2, + &hs_csr)) + return 0; + if (hs_csr & HS_CSR_INS) { + /* Clear INS (by setting it) */ + if (pci_bus_write_config_word(slot->bus, + slot->devfn, + hs_cap + 2, + hs_csr)) + ins = 0; + else + ins = 1; + } + return ins; +} + +int cpci_check_ext(struct slot* slot) +{ + int hs_cap; + u16 hs_csr; + int ext = 0; + + hs_cap = pci_bus_find_capability(slot->bus, + slot->devfn, + PCI_CAP_ID_CHSWP); + if (!hs_cap) + return 0; + if (pci_bus_read_config_word(slot->bus, + slot->devfn, + hs_cap + 2, + &hs_csr)) + return 0; + if (hs_csr & HS_CSR_EXT) + ext = 1; + return ext; +} + +int cpci_clear_ext(struct slot* slot) +{ + int hs_cap; + u16 hs_csr; + + hs_cap = pci_bus_find_capability(slot->bus, + slot->devfn, + PCI_CAP_ID_CHSWP); + if (!hs_cap) + return -ENODEV; + if (pci_bus_read_config_word(slot->bus, + slot->devfn, + hs_cap + 2, + &hs_csr)) + return -ENODEV; + if (hs_csr & HS_CSR_EXT) { + /* Clear EXT (by setting it) */ + if (pci_bus_write_config_word(slot->bus, + slot->devfn, + hs_cap + 2, + hs_csr)) + return -ENODEV; + } + return 0; +} + +int cpci_led_on(struct slot* slot) +{ + int hs_cap; + u16 hs_csr; + + hs_cap = pci_bus_find_capability(slot->bus, + slot->devfn, + PCI_CAP_ID_CHSWP); + if (!hs_cap) + return -ENODEV; + if (pci_bus_read_config_word(slot->bus, + slot->devfn, + hs_cap + 2, + &hs_csr)) + return -ENODEV; + if ((hs_csr & HS_CSR_LOO) != HS_CSR_LOO) { + hs_csr |= HS_CSR_LOO; + if (pci_bus_write_config_word(slot->bus, + slot->devfn, + hs_cap + 2, + hs_csr)) { + err("Could not set LOO for slot %s", + hotplug_slot_name(slot->hotplug_slot)); + return -ENODEV; + } + } + return 0; +} + +int cpci_led_off(struct slot* slot) +{ + int hs_cap; + u16 hs_csr; + + hs_cap = pci_bus_find_capability(slot->bus, + slot->devfn, + PCI_CAP_ID_CHSWP); + if (!hs_cap) + return -ENODEV; + if (pci_bus_read_config_word(slot->bus, + slot->devfn, + hs_cap + 2, + &hs_csr)) + return -ENODEV; + if (hs_csr & HS_CSR_LOO) { + hs_csr &= ~HS_CSR_LOO; + if (pci_bus_write_config_word(slot->bus, + slot->devfn, + hs_cap + 2, + hs_csr)) { + err("Could not clear LOO for slot %s", + hotplug_slot_name(slot->hotplug_slot)); + return -ENODEV; + } + } + return 0; +} + + +/* + * Device configuration functions + */ + +int __ref cpci_configure_slot(struct slot *slot) +{ + struct pci_bus *parent; + int fn; + + dbg("%s - enter", __func__); + + if (slot->dev == NULL) { + dbg("pci_dev null, finding %02x:%02x:%x", + slot->bus->number, PCI_SLOT(slot->devfn), PCI_FUNC(slot->devfn)); + slot->dev = pci_get_slot(slot->bus, slot->devfn); + } + + /* Still NULL? Well then scan for it! */ + if (slot->dev == NULL) { + int n; + dbg("pci_dev still null"); + + /* + * This will generate pci_dev structures for all functions, but + * we will only call this case when lookup fails. + */ + n = pci_scan_slot(slot->bus, slot->devfn); + dbg("%s: pci_scan_slot returned %d", __func__, n); + slot->dev = pci_get_slot(slot->bus, slot->devfn); + if (slot->dev == NULL) { + err("Could not find PCI device for slot %02x", slot->number); + return -ENODEV; + } + } + parent = slot->dev->bus; + + for (fn = 0; fn < 8; fn++) { + struct pci_dev *dev; + + dev = pci_get_slot(parent, PCI_DEVFN(PCI_SLOT(slot->devfn), fn)); + if (!dev) + continue; + if ((dev->hdr_type == PCI_HEADER_TYPE_BRIDGE) || + (dev->hdr_type == PCI_HEADER_TYPE_CARDBUS)) { + /* Find an unused bus number for the new bridge */ + struct pci_bus *child; + unsigned char busnr, start = parent->secondary; + unsigned char end = parent->subordinate; + + for (busnr = start; busnr <= end; busnr++) { + if (!pci_find_bus(pci_domain_nr(parent), + busnr)) + break; + } + if (busnr >= end) { + err("No free bus for hot-added bridge\n"); + pci_dev_put(dev); + continue; + } + child = pci_add_new_bus(parent, dev, busnr); + if (!child) { + err("Cannot add new bus for %s\n", + pci_name(dev)); + pci_dev_put(dev); + continue; + } + child->subordinate = pci_do_scan_bus(child); + pci_bus_size_bridges(child); + } + pci_dev_put(dev); + } + + pci_bus_assign_resources(parent); + pci_bus_add_devices(parent); + pci_enable_bridges(parent); + + dbg("%s - exit", __func__); + return 0; +} + +int cpci_unconfigure_slot(struct slot* slot) +{ + int i; + struct pci_dev *dev; + + dbg("%s - enter", __func__); + if (!slot->dev) { + err("No device for slot %02x\n", slot->number); + return -ENODEV; + } + + for (i = 0; i < 8; i++) { + dev = pci_get_slot(slot->bus, + PCI_DEVFN(PCI_SLOT(slot->devfn), i)); + if (dev) { + pci_remove_bus_device(dev); + pci_dev_put(dev); + } + } + pci_dev_put(slot->dev); + slot->dev = NULL; + + dbg("%s - exit", __func__); + return 0; +} diff --git a/drivers/pci/hotplug/cpcihp_generic.c b/drivers/pci/hotplug/cpcihp_generic.c new file mode 100644 index 0000000..148fb46 --- /dev/null +++ b/drivers/pci/hotplug/cpcihp_generic.c @@ -0,0 +1,229 @@ +/* + * cpcihp_generic.c + * + * Generic port I/O CompactPCI driver + * + * Copyright 2002 SOMA Networks, Inc. + * Copyright 2001 Intel San Luis Obispo + * Copyright 2000,2001 MontaVista Software Inc. + * + * 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 SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL + * THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * 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. + * + * This generic CompactPCI hotplug driver should allow using the PCI hotplug + * mechanism on any CompactPCI board that exposes the #ENUM signal as a bit + * in a system register that can be read through standard port I/O. + * + * Send feedback to <scottm@somanetworks.com> + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/errno.h> +#include <linux/pci.h> +#include <linux/string.h> +#include "cpci_hotplug.h" + +#define DRIVER_VERSION "0.1" +#define DRIVER_AUTHOR "Scott Murray <scottm@somanetworks.com>" +#define DRIVER_DESC "Generic port I/O CompactPCI Hot Plug Driver" + +#if !defined(MODULE) +#define MY_NAME "cpcihp_generic" +#else +#define MY_NAME THIS_MODULE->name +#endif + +#define dbg(format, arg...) \ + do { \ + if(debug) \ + printk (KERN_DEBUG "%s: " format "\n", \ + MY_NAME , ## arg); \ + } while(0) +#define err(format, arg...) printk(KERN_ERR "%s: " format "\n", MY_NAME , ## arg) +#define info(format, arg...) printk(KERN_INFO "%s: " format "\n", MY_NAME , ## arg) +#define warn(format, arg...) printk(KERN_WARNING "%s: " format "\n", MY_NAME , ## arg) + +/* local variables */ +static int debug; +static char *bridge; +static u8 bridge_busnr; +static u8 bridge_slot; +static struct pci_bus *bus; +static u8 first_slot; +static u8 last_slot; +static u16 port; +static unsigned int enum_bit; +static u8 enum_mask; + +static struct cpci_hp_controller_ops generic_hpc_ops; +static struct cpci_hp_controller generic_hpc; + +static int __init validate_parameters(void) +{ + char* str; + char* p; + unsigned long tmp; + + if(!bridge) { + info("not configured, disabling."); + return -EINVAL; + } + str = bridge; + if(!*str) + return -EINVAL; + + tmp = simple_strtoul(str, &p, 16); + if(p == str || tmp > 0xff) { + err("Invalid hotplug bus bridge device bus number"); + return -EINVAL; + } + bridge_busnr = (u8) tmp; + dbg("bridge_busnr = 0x%02x", bridge_busnr); + if(*p != ':') { + err("Invalid hotplug bus bridge device"); + return -EINVAL; + } + str = p + 1; + tmp = simple_strtoul(str, &p, 16); + if(p == str || tmp > 0x1f) { + err("Invalid hotplug bus bridge device slot number"); + return -EINVAL; + } + bridge_slot = (u8) tmp; + dbg("bridge_slot = 0x%02x", bridge_slot); + + dbg("first_slot = 0x%02x", first_slot); + dbg("last_slot = 0x%02x", last_slot); + if(!(first_slot && last_slot)) { + err("Need to specify first_slot and last_slot"); + return -EINVAL; + } + if(last_slot < first_slot) { + err("first_slot must be less than last_slot"); + return -EINVAL; + } + + dbg("port = 0x%04x", port); + dbg("enum_bit = 0x%02x", enum_bit); + if(enum_bit > 7) { + err("Invalid #ENUM bit"); + return -EINVAL; + } + enum_mask = 1 << enum_bit; + return 0; +} + +static int query_enum(void) +{ + u8 value; + + value = inb_p(port); + return ((value & enum_mask) == enum_mask); +} + +static int __init cpcihp_generic_init(void) +{ + int status; + struct resource* r; + struct pci_dev* dev; + + info(DRIVER_DESC " version: " DRIVER_VERSION); + status = validate_parameters(); + if (status) + return status; + + r = request_region(port, 1, "#ENUM hotswap signal register"); + if(!r) + return -EBUSY; + + bus = pci_find_bus(0, bridge_busnr); + if (!bus) { + err("Invalid bus number %d", bridge_busnr); + return -EINVAL; + } + dev = pci_get_slot(bus, PCI_DEVFN(bridge_slot, 0)); + if(!dev || dev->hdr_type != PCI_HEADER_TYPE_BRIDGE) { + err("Invalid bridge device %s", bridge); + return -EINVAL; + } + bus = dev->subordinate; + pci_dev_put(dev); + + memset(&generic_hpc, 0, sizeof (struct cpci_hp_controller)); + generic_hpc_ops.query_enum = query_enum; + generic_hpc.ops = &generic_hpc_ops; + + status = cpci_hp_register_controller(&generic_hpc); + if(status != 0) { + err("Could not register cPCI hotplug controller"); + return -ENODEV; + } + dbg("registered controller"); + + status = cpci_hp_register_bus(bus, first_slot, last_slot); + if(status != 0) { + err("Could not register cPCI hotplug bus"); + goto init_bus_register_error; + } + dbg("registered bus"); + + status = cpci_hp_start(); + if(status != 0) { + err("Could not started cPCI hotplug system"); + goto init_start_error; + } + dbg("started cpci hp system"); + return 0; +init_start_error: + cpci_hp_unregister_bus(bus); +init_bus_register_error: + cpci_hp_unregister_controller(&generic_hpc); + err("status = %d", status); + return status; + +} + +static void __exit cpcihp_generic_exit(void) +{ + cpci_hp_stop(); + cpci_hp_unregister_bus(bus); + cpci_hp_unregister_controller(&generic_hpc); + release_region(port, 1); +} + +module_init(cpcihp_generic_init); +module_exit(cpcihp_generic_exit); + +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); +module_param(debug, bool, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(debug, "Debugging mode enabled or not"); +module_param(bridge, charp, 0); +MODULE_PARM_DESC(bridge, "Hotswap bus bridge device, <bus>:<slot> (bus and slot are in hexadecimal)"); +module_param(first_slot, byte, 0); +MODULE_PARM_DESC(first_slot, "Hotswap bus first slot number"); +module_param(last_slot, byte, 0); +MODULE_PARM_DESC(last_slot, "Hotswap bus last slot number"); +module_param(port, ushort, 0); +MODULE_PARM_DESC(port, "#ENUM signal I/O port"); +module_param(enum_bit, uint, 0); +MODULE_PARM_DESC(enum_bit, "#ENUM signal bit (0-7)"); diff --git a/drivers/pci/hotplug/cpcihp_zt5550.c b/drivers/pci/hotplug/cpcihp_zt5550.c new file mode 100644 index 0000000..41f6a8d --- /dev/null +++ b/drivers/pci/hotplug/cpcihp_zt5550.c @@ -0,0 +1,328 @@ +/* + * cpcihp_zt5550.c + * + * Intel/Ziatech ZT5550 CompactPCI Host Controller driver + * + * Copyright 2002 SOMA Networks, Inc. + * Copyright 2001 Intel San Luis Obispo + * Copyright 2000,2001 MontaVista Software Inc. + * + * 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 SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL + * THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * 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 <scottm@somanetworks.com> + */ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/init.h> +#include <linux/errno.h> +#include <linux/pci.h> +#include <linux/interrupt.h> +#include <linux/signal.h> /* IRQF_SHARED */ +#include "cpci_hotplug.h" +#include "cpcihp_zt5550.h" + +#define DRIVER_VERSION "0.2" +#define DRIVER_AUTHOR "Scott Murray <scottm@somanetworks.com>" +#define DRIVER_DESC "ZT5550 CompactPCI Hot Plug Driver" + +#define MY_NAME "cpcihp_zt5550" + +#define dbg(format, arg...) \ + do { \ + if(debug) \ + printk (KERN_DEBUG "%s: " format "\n", \ + MY_NAME , ## arg); \ + } while(0) +#define err(format, arg...) printk(KERN_ERR "%s: " format "\n", MY_NAME , ## arg) +#define info(format, arg...) printk(KERN_INFO "%s: " format "\n", MY_NAME , ## arg) +#define warn(format, arg...) printk(KERN_WARNING "%s: " format "\n", MY_NAME , ## arg) + +/* local variables */ +static int debug; +static int poll; +static struct cpci_hp_controller_ops zt5550_hpc_ops; +static struct cpci_hp_controller zt5550_hpc; + +/* Primary cPCI bus bridge device */ +static struct pci_dev *bus0_dev; +static struct pci_bus *bus0; + +/* Host controller device */ +static struct pci_dev *hc_dev; + +/* Host controller register addresses */ +static void __iomem *hc_registers; +static void __iomem *csr_hc_index; +static void __iomem *csr_hc_data; +static void __iomem *csr_int_status; +static void __iomem *csr_int_mask; + + +static int zt5550_hc_config(struct pci_dev *pdev) +{ + int ret; + + /* Since we know that no boards exist with two HC chips, treat it as an error */ + if(hc_dev) { + err("too many host controller devices?"); + return -EBUSY; + } + + ret = pci_enable_device(pdev); + if(ret) { + err("cannot enable %s\n", pci_name(pdev)); + return ret; + } + + hc_dev = pdev; + dbg("hc_dev = %p", hc_dev); + dbg("pci resource start %llx", (unsigned long long)pci_resource_start(hc_dev, 1)); + dbg("pci resource len %llx", (unsigned long long)pci_resource_len(hc_dev, 1)); + + if(!request_mem_region(pci_resource_start(hc_dev, 1), + pci_resource_len(hc_dev, 1), MY_NAME)) { + err("cannot reserve MMIO region"); + ret = -ENOMEM; + goto exit_disable_device; + } + + hc_registers = + ioremap(pci_resource_start(hc_dev, 1), pci_resource_len(hc_dev, 1)); + if(!hc_registers) { + err("cannot remap MMIO region %llx @ %llx", + (unsigned long long)pci_resource_len(hc_dev, 1), + (unsigned long long)pci_resource_start(hc_dev, 1)); + ret = -ENODEV; + goto exit_release_region; + } + + csr_hc_index = hc_registers + CSR_HCINDEX; + csr_hc_data = hc_registers + CSR_HCDATA; + csr_int_status = hc_registers + CSR_INTSTAT; + csr_int_mask = hc_registers + CSR_INTMASK; + + /* + * Disable host control, fault and serial interrupts + */ + dbg("disabling host control, fault and serial interrupts"); + writeb((u8) HC_INT_MASK_REG, csr_hc_index); + writeb((u8) ALL_INDEXED_INTS_MASK, csr_hc_data); + dbg("disabled host control, fault and serial interrupts"); + + /* + * Disable timer0, timer1 and ENUM interrupts + */ + dbg("disabling timer0, timer1 and ENUM interrupts"); + writeb((u8) ALL_DIRECT_INTS_MASK, csr_int_mask); + dbg("disabled timer0, timer1 and ENUM interrupts"); + return 0; + +exit_release_region: + release_mem_region(pci_resource_start(hc_dev, 1), + pci_resource_len(hc_dev, 1)); +exit_disable_device: + pci_disable_device(hc_dev); + return ret; +} + +static int zt5550_hc_cleanup(void) +{ + if(!hc_dev) + return -ENODEV; + + iounmap(hc_registers); + release_mem_region(pci_resource_start(hc_dev, 1), + pci_resource_len(hc_dev, 1)); + pci_disable_device(hc_dev); + return 0; +} + +static int zt5550_hc_query_enum(void) +{ + u8 value; + + value = inb_p(ENUM_PORT); + return ((value & ENUM_MASK) == ENUM_MASK); +} + +static int zt5550_hc_check_irq(void *dev_id) +{ + int ret; + u8 reg; + + ret = 0; + if(dev_id == zt5550_hpc.dev_id) { + reg = readb(csr_int_status); + if(reg) + ret = 1; + } + return ret; +} + +static int zt5550_hc_enable_irq(void) +{ + u8 reg; + + if(hc_dev == NULL) { + return -ENODEV; + } + reg = readb(csr_int_mask); + reg = reg & ~ENUM_INT_MASK; + writeb(reg, csr_int_mask); + return 0; +} + +static int zt5550_hc_disable_irq(void) +{ + u8 reg; + + if(hc_dev == NULL) { + return -ENODEV; + } + + reg = readb(csr_int_mask); + reg = reg | ENUM_INT_MASK; + writeb(reg, csr_int_mask); + return 0; +} + +static int zt5550_hc_init_one (struct pci_dev *pdev, const struct pci_device_id *ent) +{ + int status; + + status = zt5550_hc_config(pdev); + if(status != 0) { + return status; + } + dbg("returned from zt5550_hc_config"); + + memset(&zt5550_hpc, 0, sizeof (struct cpci_hp_controller)); + zt5550_hpc_ops.query_enum = zt5550_hc_query_enum; + zt5550_hpc.ops = &zt5550_hpc_ops; + if(!poll) { + zt5550_hpc.irq = hc_dev->irq; + zt5550_hpc.irq_flags = IRQF_SHARED; + zt5550_hpc.dev_id = hc_dev; + + zt5550_hpc_ops.enable_irq = zt5550_hc_enable_irq; + zt5550_hpc_ops.disable_irq = zt5550_hc_disable_irq; + zt5550_hpc_ops.check_irq = zt5550_hc_check_irq; + } else { + info("using ENUM# polling mode"); + } + + status = cpci_hp_register_controller(&zt5550_hpc); + if(status != 0) { + err("could not register cPCI hotplug controller"); + goto init_hc_error; + } + dbg("registered controller"); + + /* Look for first device matching cPCI bus's bridge vendor and device IDs */ + if(!(bus0_dev = pci_get_device(PCI_VENDOR_ID_DEC, + PCI_DEVICE_ID_DEC_21154, NULL))) { + status = -ENODEV; + goto init_register_error; + } + bus0 = bus0_dev->subordinate; + pci_dev_put(bus0_dev); + + status = cpci_hp_register_bus(bus0, 0x0a, 0x0f); + if(status != 0) { + err("could not register cPCI hotplug bus"); + goto init_register_error; + } + dbg("registered bus"); + + status = cpci_hp_start(); + if(status != 0) { + err("could not started cPCI hotplug system"); + cpci_hp_unregister_bus(bus0); + goto init_register_error; + } + dbg("started cpci hp system"); + + return 0; +init_register_error: + cpci_hp_unregister_controller(&zt5550_hpc); +init_hc_error: + err("status = %d", status); + zt5550_hc_cleanup(); + return status; + +} + +static void __devexit zt5550_hc_remove_one(struct pci_dev *pdev) +{ + cpci_hp_stop(); + cpci_hp_unregister_bus(bus0); + cpci_hp_unregister_controller(&zt5550_hpc); + zt5550_hc_cleanup(); +} + + +static struct pci_device_id zt5550_hc_pci_tbl[] = { + { PCI_VENDOR_ID_ZIATECH, PCI_DEVICE_ID_ZIATECH_5550_HC, PCI_ANY_ID, PCI_ANY_ID, }, + { 0, } +}; +MODULE_DEVICE_TABLE(pci, zt5550_hc_pci_tbl); + +static struct pci_driver zt5550_hc_driver = { + .name = "zt5550_hc", + .id_table = zt5550_hc_pci_tbl, + .probe = zt5550_hc_init_one, + .remove = __devexit_p(zt5550_hc_remove_one), +}; + +static int __init zt5550_init(void) +{ + struct resource* r; + int rc; + + info(DRIVER_DESC " version: " DRIVER_VERSION); + r = request_region(ENUM_PORT, 1, "#ENUM hotswap signal register"); + if(!r) + return -EBUSY; + + rc = pci_register_driver(&zt5550_hc_driver); + if(rc < 0) + release_region(ENUM_PORT, 1); + return rc; +} + +static void __exit +zt5550_exit(void) +{ + pci_unregister_driver(&zt5550_hc_driver); + release_region(ENUM_PORT, 1); +} + +module_init(zt5550_init); +module_exit(zt5550_exit); + +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); +module_param(debug, bool, 0644); +MODULE_PARM_DESC(debug, "Debugging mode enabled or not"); +module_param(poll, bool, 0644); +MODULE_PARM_DESC(poll, "#ENUM polling mode enabled or not"); diff --git a/drivers/pci/hotplug/cpcihp_zt5550.h b/drivers/pci/hotplug/cpcihp_zt5550.h new file mode 100644 index 0000000..bebc606 --- /dev/null +++ b/drivers/pci/hotplug/cpcihp_zt5550.h @@ -0,0 +1,79 @@ +/* + * cpcihp_zt5550.h + * + * Intel/Ziatech ZT5550 CompactPCI Host Controller driver definitions + * + * Copyright 2002 SOMA Networks, Inc. + * Copyright 2001 Intel San Luis Obispo + * Copyright 2000,2001 MontaVista Software Inc. + * + * 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 SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL + * THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * 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 <scottm@somanetworks.com> + */ + +#ifndef _CPCIHP_ZT5550_H +#define _CPCIHP_ZT5550_H + +/* Direct registers */ +#define CSR_HCINDEX 0x00 +#define CSR_HCDATA 0x04 +#define CSR_INTSTAT 0x08 +#define CSR_INTMASK 0x09 +#define CSR_CNT0CMD 0x0C +#define CSR_CNT1CMD 0x0E +#define CSR_CNT0 0x10 +#define CSR_CNT1 0x14 + +/* Masks for interrupt bits in CSR_INTMASK direct register */ +#define CNT0_INT_MASK 0x01 +#define CNT1_INT_MASK 0x02 +#define ENUM_INT_MASK 0x04 +#define ALL_DIRECT_INTS_MASK 0x07 + +/* Indexed registers (through CSR_INDEX, CSR_DATA) */ +#define HC_INT_MASK_REG 0x04 +#define HC_STATUS_REG 0x08 +#define HC_CMD_REG 0x0C +#define ARB_CONFIG_GNT_REG 0x10 +#define ARB_CONFIG_CFG_REG 0x12 +#define ARB_CONFIG_REG 0x10 +#define ISOL_CONFIG_REG 0x18 +#define FAULT_STATUS_REG 0x20 +#define FAULT_CONFIG_REG 0x24 +#define WD_CONFIG_REG 0x2C +#define HC_DIAG_REG 0x30 +#define SERIAL_COMM_REG 0x34 +#define SERIAL_OUT_REG 0x38 +#define SERIAL_IN_REG 0x3C + +/* Masks for interrupt bits in HC_INT_MASK_REG indexed register */ +#define SERIAL_INT_MASK 0x01 +#define FAULT_INT_MASK 0x02 +#define HCF_INT_MASK 0x04 +#define ALL_INDEXED_INTS_MASK 0x07 + +/* Digital I/O port storing ENUM# */ +#define ENUM_PORT 0xE1 +/* Mask to get to the ENUM# bit on the bus */ +#define ENUM_MASK 0x40 + +#endif /* _CPCIHP_ZT5550_H */ diff --git a/drivers/pci/hotplug/cpqphp.h b/drivers/pci/hotplug/cpqphp.h new file mode 100644 index 0000000..afaf8f6 --- /dev/null +++ b/drivers/pci/hotplug/cpqphp.h @@ -0,0 +1,722 @@ +/* + * Compaq Hot Plug Controller Driver + * + * Copyright (C) 1995,2001 Compaq Computer Corporation + * Copyright (C) 2001 Greg Kroah-Hartman (greg@kroah.com) + * Copyright (C) 2001 IBM + * + * 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> + * + */ +#ifndef _CPQPHP_H +#define _CPQPHP_H + +#include <linux/interrupt.h> +#include <asm/io.h> /* for read? and write? functions */ +#include <linux/delay.h> /* for delays */ +#include <linux/mutex.h> + +#define MY_NAME "cpqphp" + +#define dbg(fmt, arg...) do { if (cpqhp_debug) printk(KERN_DEBUG "%s: " 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) + + + +struct smbios_system_slot { + u8 type; + u8 length; + u16 handle; + u8 name_string_num; + u8 slot_type; + u8 slot_width; + u8 slot_current_usage; + u8 slot_length; + u16 slot_number; + u8 properties1; + u8 properties2; +} __attribute__ ((packed)); + +/* offsets to the smbios generic type based on the above structure layout */ +enum smbios_system_slot_offsets { + SMBIOS_SLOT_GENERIC_TYPE = offsetof(struct smbios_system_slot, type), + SMBIOS_SLOT_GENERIC_LENGTH = offsetof(struct smbios_system_slot, length), + SMBIOS_SLOT_GENERIC_HANDLE = offsetof(struct smbios_system_slot, handle), + SMBIOS_SLOT_NAME_STRING_NUM = offsetof(struct smbios_system_slot, name_string_num), + SMBIOS_SLOT_TYPE = offsetof(struct smbios_system_slot, slot_type), + SMBIOS_SLOT_WIDTH = offsetof(struct smbios_system_slot, slot_width), + SMBIOS_SLOT_CURRENT_USAGE = offsetof(struct smbios_system_slot, slot_current_usage), + SMBIOS_SLOT_LENGTH = offsetof(struct smbios_system_slot, slot_length), + SMBIOS_SLOT_NUMBER = offsetof(struct smbios_system_slot, slot_number), + SMBIOS_SLOT_PROPERTIES1 = offsetof(struct smbios_system_slot, properties1), + SMBIOS_SLOT_PROPERTIES2 = offsetof(struct smbios_system_slot, properties2), +}; + +struct smbios_generic { + u8 type; + u8 length; + u16 handle; +} __attribute__ ((packed)); + +/* offsets to the smbios generic type based on the above structure layout */ +enum smbios_generic_offsets { + SMBIOS_GENERIC_TYPE = offsetof(struct smbios_generic, type), + SMBIOS_GENERIC_LENGTH = offsetof(struct smbios_generic, length), + SMBIOS_GENERIC_HANDLE = offsetof(struct smbios_generic, handle), +}; + +struct smbios_entry_point { + char anchor[4]; + u8 ep_checksum; + u8 ep_length; + u8 major_version; + u8 minor_version; + u16 max_size_entry; + u8 ep_rev; + u8 reserved[5]; + char int_anchor[5]; + u8 int_checksum; + u16 st_length; + u32 st_address; + u16 number_of_entrys; + u8 bcd_rev; +} __attribute__ ((packed)); + +/* offsets to the smbios entry point based on the above structure layout */ +enum smbios_entry_point_offsets { + ANCHOR = offsetof(struct smbios_entry_point, anchor[0]), + EP_CHECKSUM = offsetof(struct smbios_entry_point, ep_checksum), + EP_LENGTH = offsetof(struct smbios_entry_point, ep_length), + MAJOR_VERSION = offsetof(struct smbios_entry_point, major_version), + MINOR_VERSION = offsetof(struct smbios_entry_point, minor_version), + MAX_SIZE_ENTRY = offsetof(struct smbios_entry_point, max_size_entry), + EP_REV = offsetof(struct smbios_entry_point, ep_rev), + INT_ANCHOR = offsetof(struct smbios_entry_point, int_anchor[0]), + INT_CHECKSUM = offsetof(struct smbios_entry_point, int_checksum), + ST_LENGTH = offsetof(struct smbios_entry_point, st_length), + ST_ADDRESS = offsetof(struct smbios_entry_point, st_address), + NUMBER_OF_ENTRYS = offsetof(struct smbios_entry_point, number_of_entrys), + BCD_REV = offsetof(struct smbios_entry_point, bcd_rev), +}; + +struct ctrl_reg { /* offset */ + u8 slot_RST; /* 0x00 */ + u8 slot_enable; /* 0x01 */ + u16 misc; /* 0x02 */ + u32 led_control; /* 0x04 */ + u32 int_input_clear; /* 0x08 */ + u32 int_mask; /* 0x0a */ + u8 reserved0; /* 0x10 */ + u8 reserved1; /* 0x11 */ + u8 reserved2; /* 0x12 */ + u8 gen_output_AB; /* 0x13 */ + u32 non_int_input; /* 0x14 */ + u32 reserved3; /* 0x18 */ + u32 reserved4; /* 0x1a */ + u32 reserved5; /* 0x20 */ + u8 reserved6; /* 0x24 */ + u8 reserved7; /* 0x25 */ + u16 reserved8; /* 0x26 */ + u8 slot_mask; /* 0x28 */ + u8 reserved9; /* 0x29 */ + u8 reserved10; /* 0x2a */ + u8 reserved11; /* 0x2b */ + u8 slot_SERR; /* 0x2c */ + u8 slot_power; /* 0x2d */ + u8 reserved12; /* 0x2e */ + u8 reserved13; /* 0x2f */ + u8 next_curr_freq; /* 0x30 */ + u8 reset_freq_mode; /* 0x31 */ +} __attribute__ ((packed)); + +/* offsets to the controller registers based on the above structure layout */ +enum ctrl_offsets { + SLOT_RST = offsetof(struct ctrl_reg, slot_RST), + SLOT_ENABLE = offsetof(struct ctrl_reg, slot_enable), + MISC = offsetof(struct ctrl_reg, misc), + LED_CONTROL = offsetof(struct ctrl_reg, led_control), + INT_INPUT_CLEAR = offsetof(struct ctrl_reg, int_input_clear), + INT_MASK = offsetof(struct ctrl_reg, int_mask), + CTRL_RESERVED0 = offsetof(struct ctrl_reg, reserved0), + CTRL_RESERVED1 = offsetof(struct ctrl_reg, reserved1), + CTRL_RESERVED2 = offsetof(struct ctrl_reg, reserved1), + GEN_OUTPUT_AB = offsetof(struct ctrl_reg, gen_output_AB), + NON_INT_INPUT = offsetof(struct ctrl_reg, non_int_input), + CTRL_RESERVED3 = offsetof(struct ctrl_reg, reserved3), + CTRL_RESERVED4 = offsetof(struct ctrl_reg, reserved4), + CTRL_RESERVED5 = offsetof(struct ctrl_reg, reserved5), + CTRL_RESERVED6 = offsetof(struct ctrl_reg, reserved6), + CTRL_RESERVED7 = offsetof(struct ctrl_reg, reserved7), + CTRL_RESERVED8 = offsetof(struct ctrl_reg, reserved8), + SLOT_MASK = offsetof(struct ctrl_reg, slot_mask), + CTRL_RESERVED9 = offsetof(struct ctrl_reg, reserved9), + CTRL_RESERVED10 = offsetof(struct ctrl_reg, reserved10), + CTRL_RESERVED11 = offsetof(struct ctrl_reg, reserved11), + SLOT_SERR = offsetof(struct ctrl_reg, slot_SERR), + SLOT_POWER = offsetof(struct ctrl_reg, slot_power), + NEXT_CURR_FREQ = offsetof(struct ctrl_reg, next_curr_freq), + RESET_FREQ_MODE = offsetof(struct ctrl_reg, reset_freq_mode), +}; + +struct hrt { + char sig0; + char sig1; + char sig2; + char sig3; + u16 unused_IRQ; + u16 PCIIRQ; + u8 number_of_entries; + u8 revision; + u16 reserved1; + u32 reserved2; +} __attribute__ ((packed)); + +/* offsets to the hotplug resource table registers based on the above structure layout */ +enum hrt_offsets { + SIG0 = offsetof(struct hrt, sig0), + SIG1 = offsetof(struct hrt, sig1), + SIG2 = offsetof(struct hrt, sig2), + SIG3 = offsetof(struct hrt, sig3), + UNUSED_IRQ = offsetof(struct hrt, unused_IRQ), + PCIIRQ = offsetof(struct hrt, PCIIRQ), + NUMBER_OF_ENTRIES = offsetof(struct hrt, number_of_entries), + REVISION = offsetof(struct hrt, revision), + HRT_RESERVED1 = offsetof(struct hrt, reserved1), + HRT_RESERVED2 = offsetof(struct hrt, reserved2), +}; + +struct slot_rt { + u8 dev_func; + u8 primary_bus; + u8 secondary_bus; + u8 max_bus; + u16 io_base; + u16 io_length; + u16 mem_base; + u16 mem_length; + u16 pre_mem_base; + u16 pre_mem_length; +} __attribute__ ((packed)); + +/* offsets to the hotplug slot resource table registers based on the above structure layout */ +enum slot_rt_offsets { + DEV_FUNC = offsetof(struct slot_rt, dev_func), + PRIMARY_BUS = offsetof(struct slot_rt, primary_bus), + SECONDARY_BUS = offsetof(struct slot_rt, secondary_bus), + MAX_BUS = offsetof(struct slot_rt, max_bus), + IO_BASE = offsetof(struct slot_rt, io_base), + IO_LENGTH = offsetof(struct slot_rt, io_length), + MEM_BASE = offsetof(struct slot_rt, mem_base), + MEM_LENGTH = offsetof(struct slot_rt, mem_length), + PRE_MEM_BASE = offsetof(struct slot_rt, pre_mem_base), + PRE_MEM_LENGTH = offsetof(struct slot_rt, pre_mem_length), +}; + +struct pci_func { + struct pci_func *next; + u8 bus; + u8 device; + u8 function; + u8 is_a_board; + u16 status; + u8 configured; + u8 switch_save; + u8 presence_save; + u32 base_length[0x06]; + u8 base_type[0x06]; + u16 reserved2; + u32 config_space[0x20]; + struct pci_resource *mem_head; + struct pci_resource *p_mem_head; + struct pci_resource *io_head; + struct pci_resource *bus_head; + struct timer_list *p_task_event; + struct pci_dev* pci_dev; +}; + +struct slot { + struct slot *next; + u8 bus; + u8 device; + u8 number; + u8 is_a_board; + u8 configured; + u8 state; + u8 switch_save; + u8 presence_save; + u32 capabilities; + u16 reserved2; + struct timer_list task_event; + u8 hp_slot; + struct controller *ctrl; + void __iomem *p_sm_slot; + struct hotplug_slot *hotplug_slot; +}; + +struct pci_resource { + struct pci_resource * next; + u32 base; + u32 length; +}; + +struct event_info { + u32 event_type; + u8 hp_slot; +}; + +struct controller { + struct controller *next; + u32 ctrl_int_comp; + struct mutex crit_sect; /* critical section mutex */ + void __iomem *hpc_reg; /* cookie for our pci controller location */ + struct pci_resource *mem_head; + struct pci_resource *p_mem_head; + struct pci_resource *io_head; + struct pci_resource *bus_head; + struct pci_dev *pci_dev; + struct pci_bus *pci_bus; + struct event_info event_queue[10]; + struct slot *slot; + u8 next_event; + u8 interrupt; + u8 cfgspc_irq; + u8 bus; /* bus number for the pci hotplug controller */ + u8 rev; + u8 slot_device_offset; + u8 first_slot; + u8 add_support; + u8 push_flag; + enum pci_bus_speed speed; + enum pci_bus_speed speed_capability; + u8 push_button; /* 0 = no pushbutton, 1 = pushbutton present */ + u8 slot_switch_type; /* 0 = no switch, 1 = switch present */ + u8 defeature_PHP; /* 0 = PHP not supported, 1 = PHP supported */ + u8 alternate_base_address; /* 0 = not supported, 1 = supported */ + u8 pci_config_space; /* Index/data access to working registers 0 = not supported, 1 = supported */ + u8 pcix_speed_capability; /* PCI-X */ + u8 pcix_support; /* PCI-X */ + u16 vendor_id; + struct work_struct int_task_event; + wait_queue_head_t queue; /* sleep & wake process */ + struct dentry *dentry; /* debugfs dentry */ +}; + +struct irq_mapping { + u8 barber_pole; + u8 valid_INT; + u8 interrupt[4]; +}; + +struct resource_lists { + struct pci_resource *mem_head; + struct pci_resource *p_mem_head; + struct pci_resource *io_head; + struct pci_resource *bus_head; + struct irq_mapping *irqs; +}; + +#define ROM_PHY_ADDR 0x0F0000 +#define ROM_PHY_LEN 0x00ffff + +#define PCI_HPC_ID 0xA0F7 +#define PCI_SUB_HPC_ID 0xA2F7 +#define PCI_SUB_HPC_ID2 0xA2F8 +#define PCI_SUB_HPC_ID3 0xA2F9 +#define PCI_SUB_HPC_ID_INTC 0xA2FA +#define PCI_SUB_HPC_ID4 0xA2FD + +#define INT_BUTTON_IGNORE 0 +#define INT_PRESENCE_ON 1 +#define INT_PRESENCE_OFF 2 +#define INT_SWITCH_CLOSE 3 +#define INT_SWITCH_OPEN 4 +#define INT_POWER_FAULT 5 +#define INT_POWER_FAULT_CLEAR 6 +#define INT_BUTTON_PRESS 7 +#define INT_BUTTON_RELEASE 8 +#define INT_BUTTON_CANCEL 9 + +#define STATIC_STATE 0 +#define BLINKINGON_STATE 1 +#define BLINKINGOFF_STATE 2 +#define POWERON_STATE 3 +#define POWEROFF_STATE 4 + +#define PCISLOT_INTERLOCK_CLOSED 0x00000001 +#define PCISLOT_ADAPTER_PRESENT 0x00000002 +#define PCISLOT_POWERED 0x00000004 +#define PCISLOT_66_MHZ_OPERATION 0x00000008 +#define PCISLOT_64_BIT_OPERATION 0x00000010 +#define PCISLOT_REPLACE_SUPPORTED 0x00000020 +#define PCISLOT_ADD_SUPPORTED 0x00000040 +#define PCISLOT_INTERLOCK_SUPPORTED 0x00000080 +#define PCISLOT_66_MHZ_SUPPORTED 0x00000100 +#define PCISLOT_64_BIT_SUPPORTED 0x00000200 + +#define PCI_TO_PCI_BRIDGE_CLASS 0x00060400 + +#define INTERLOCK_OPEN 0x00000002 +#define ADD_NOT_SUPPORTED 0x00000003 +#define CARD_FUNCTIONING 0x00000005 +#define ADAPTER_NOT_SAME 0x00000006 +#define NO_ADAPTER_PRESENT 0x00000009 +#define NOT_ENOUGH_RESOURCES 0x0000000B +#define DEVICE_TYPE_NOT_SUPPORTED 0x0000000C +#define POWER_FAILURE 0x0000000E + +#define REMOVE_NOT_SUPPORTED 0x00000003 + + +/* + * error Messages + */ +#define msg_initialization_err "Initialization failure, error=%d\n" +#define msg_HPC_rev_error "Unsupported revision of the PCI hot plug controller found.\n" +#define msg_HPC_non_compaq_or_intel "The PCI hot plug controller is not supported by this driver.\n" +#define msg_HPC_not_supported "this system is not supported by this version of cpqphpd. Upgrade to a newer version of cpqphpd\n" +#define msg_unable_to_save "unable to store PCI hot plug add resource information. This system must be rebooted before adding any PCI devices.\n" +#define msg_button_on "PCI slot #%d - powering on due to button press.\n" +#define msg_button_off "PCI slot #%d - powering off due to button press.\n" +#define msg_button_cancel "PCI slot #%d - action canceled due to button press.\n" +#define msg_button_ignore "PCI slot #%d - button press ignored. (action in progress...)\n" + + +/* debugfs functions for the hotplug controller info */ +extern void cpqhp_initialize_debugfs (void); +extern void cpqhp_shutdown_debugfs (void); +extern void cpqhp_create_debugfs_files (struct controller *ctrl); +extern void cpqhp_remove_debugfs_files (struct controller *ctrl); + +/* controller functions */ +extern void cpqhp_pushbutton_thread (unsigned long event_pointer); +extern irqreturn_t cpqhp_ctrl_intr (int IRQ, void *data); +extern int cpqhp_find_available_resources (struct controller *ctrl, void __iomem *rom_start); +extern int cpqhp_event_start_thread (void); +extern void cpqhp_event_stop_thread (void); +extern struct pci_func *cpqhp_slot_create (unsigned char busnumber); +extern struct pci_func *cpqhp_slot_find (unsigned char bus, unsigned char device, unsigned char index); +extern int cpqhp_process_SI (struct controller *ctrl, struct pci_func *func); +extern int cpqhp_process_SS (struct controller *ctrl, struct pci_func *func); +extern int cpqhp_hardware_test (struct controller *ctrl, int test_num); + +/* resource functions */ +extern int cpqhp_resource_sort_and_combine (struct pci_resource **head); + +/* pci functions */ +extern int cpqhp_set_irq (u8 bus_num, u8 dev_num, u8 int_pin, u8 irq_num); +extern int cpqhp_get_bus_dev (struct controller *ctrl, u8 *bus_num, u8 *dev_num, u8 slot); +extern int cpqhp_save_config (struct controller *ctrl, int busnumber, int is_hot_plug); +extern int cpqhp_save_base_addr_length (struct controller *ctrl, struct pci_func * func); +extern int cpqhp_save_used_resources (struct controller *ctrl, struct pci_func * func); +extern int cpqhp_configure_board (struct controller *ctrl, struct pci_func * func); +extern int cpqhp_save_slot_config (struct controller *ctrl, struct pci_func * new_slot); +extern int cpqhp_valid_replace (struct controller *ctrl, struct pci_func * func); +extern void cpqhp_destroy_board_resources (struct pci_func * func); +extern int cpqhp_return_board_resources (struct pci_func * func, struct resource_lists * resources); +extern void cpqhp_destroy_resource_list (struct resource_lists * resources); +extern int cpqhp_configure_device (struct controller* ctrl, struct pci_func* func); +extern int cpqhp_unconfigure_device (struct pci_func* func); + +/* Global variables */ +extern int cpqhp_debug; +extern int cpqhp_legacy_mode; +extern struct controller *cpqhp_ctrl_list; +extern struct pci_func *cpqhp_slot_list[256]; + +/* these can be gotten rid of, but for debugging they are purty */ +extern u8 cpqhp_nic_irq; +extern u8 cpqhp_disk_irq; + + +/* inline functions */ + +static inline char *slot_name(struct slot *slot) +{ + return hotplug_slot_name(slot->hotplug_slot); +} + +/* + * return_resource + * + * Puts node back in the resource list pointed to by head + * + */ +static inline void return_resource(struct pci_resource **head, struct pci_resource *node) +{ + if (!node || !head) + return; + node->next = *head; + *head = node; +} + +static inline void set_SOGO(struct controller *ctrl) +{ + u16 misc; + + misc = readw(ctrl->hpc_reg + MISC); + misc = (misc | 0x0001) & 0xFFFB; + writew(misc, ctrl->hpc_reg + MISC); +} + + +static inline void amber_LED_on(struct controller *ctrl, u8 slot) +{ + u32 led_control; + + led_control = readl(ctrl->hpc_reg + LED_CONTROL); + led_control |= (0x01010000L << slot); + writel(led_control, ctrl->hpc_reg + LED_CONTROL); +} + + +static inline void amber_LED_off(struct controller *ctrl, u8 slot) +{ + u32 led_control; + + led_control = readl(ctrl->hpc_reg + LED_CONTROL); + led_control &= ~(0x01010000L << slot); + writel(led_control, ctrl->hpc_reg + LED_CONTROL); +} + + +static inline int read_amber_LED(struct controller *ctrl, u8 slot) +{ + u32 led_control; + + led_control = readl(ctrl->hpc_reg + LED_CONTROL); + led_control &= (0x01010000L << slot); + + return led_control ? 1 : 0; +} + + +static inline void green_LED_on(struct controller *ctrl, u8 slot) +{ + u32 led_control; + + led_control = readl(ctrl->hpc_reg + LED_CONTROL); + led_control |= 0x0101L << slot; + writel(led_control, ctrl->hpc_reg + LED_CONTROL); +} + +static inline void green_LED_off(struct controller *ctrl, u8 slot) +{ + u32 led_control; + + led_control = readl(ctrl->hpc_reg + LED_CONTROL); + led_control &= ~(0x0101L << slot); + writel(led_control, ctrl->hpc_reg + LED_CONTROL); +} + + +static inline void green_LED_blink(struct controller *ctrl, u8 slot) +{ + u32 led_control; + + led_control = readl(ctrl->hpc_reg + LED_CONTROL); + led_control &= ~(0x0101L << slot); + led_control |= (0x0001L << slot); + writel(led_control, ctrl->hpc_reg + LED_CONTROL); +} + + +static inline void slot_disable(struct controller *ctrl, u8 slot) +{ + u8 slot_enable; + + slot_enable = readb(ctrl->hpc_reg + SLOT_ENABLE); + slot_enable &= ~(0x01 << slot); + writeb(slot_enable, ctrl->hpc_reg + SLOT_ENABLE); +} + + +static inline void slot_enable(struct controller *ctrl, u8 slot) +{ + u8 slot_enable; + + slot_enable = readb(ctrl->hpc_reg + SLOT_ENABLE); + slot_enable |= (0x01 << slot); + writeb(slot_enable, ctrl->hpc_reg + SLOT_ENABLE); +} + + +static inline u8 is_slot_enabled(struct controller *ctrl, u8 slot) +{ + u8 slot_enable; + + slot_enable = readb(ctrl->hpc_reg + SLOT_ENABLE); + slot_enable &= (0x01 << slot); + return slot_enable ? 1 : 0; +} + + +static inline u8 read_slot_enable(struct controller *ctrl) +{ + return readb(ctrl->hpc_reg + SLOT_ENABLE); +} + + +/* + * get_controller_speed - find the current frequency/mode of controller. + * + * @ctrl: controller to get frequency/mode for. + * + * Returns controller speed. + * + */ +static inline u8 get_controller_speed(struct controller *ctrl) +{ + u8 curr_freq; + u16 misc; + + if (ctrl->pcix_support) { + curr_freq = readb(ctrl->hpc_reg + NEXT_CURR_FREQ); + if ((curr_freq & 0xB0) == 0xB0) + return PCI_SPEED_133MHz_PCIX; + if ((curr_freq & 0xA0) == 0xA0) + return PCI_SPEED_100MHz_PCIX; + if ((curr_freq & 0x90) == 0x90) + return PCI_SPEED_66MHz_PCIX; + if (curr_freq & 0x10) + return PCI_SPEED_66MHz; + + return PCI_SPEED_33MHz; + } + + misc = readw(ctrl->hpc_reg + MISC); + return (misc & 0x0800) ? PCI_SPEED_66MHz : PCI_SPEED_33MHz; +} + + +/* + * get_adapter_speed - find the max supported frequency/mode of adapter. + * + * @ctrl: hotplug controller. + * @hp_slot: hotplug slot where adapter is installed. + * + * Returns adapter speed. + * + */ +static inline u8 get_adapter_speed(struct controller *ctrl, u8 hp_slot) +{ + u32 temp_dword = readl(ctrl->hpc_reg + NON_INT_INPUT); + dbg("slot: %d, PCIXCAP: %8x\n", hp_slot, temp_dword); + if (ctrl->pcix_support) { + if (temp_dword & (0x10000 << hp_slot)) + return PCI_SPEED_133MHz_PCIX; + if (temp_dword & (0x100 << hp_slot)) + return PCI_SPEED_66MHz_PCIX; + } + + if (temp_dword & (0x01 << hp_slot)) + return PCI_SPEED_66MHz; + + return PCI_SPEED_33MHz; +} + +static inline void enable_slot_power(struct controller *ctrl, u8 slot) +{ + u8 slot_power; + + slot_power = readb(ctrl->hpc_reg + SLOT_POWER); + slot_power |= (0x01 << slot); + writeb(slot_power, ctrl->hpc_reg + SLOT_POWER); +} + +static inline void disable_slot_power(struct controller *ctrl, u8 slot) +{ + u8 slot_power; + + slot_power = readb(ctrl->hpc_reg + SLOT_POWER); + slot_power &= ~(0x01 << slot); + writeb(slot_power, ctrl->hpc_reg + SLOT_POWER); +} + + +static inline int cpq_get_attention_status(struct controller *ctrl, struct slot *slot) +{ + u8 hp_slot; + + hp_slot = slot->device - ctrl->slot_device_offset; + + return read_amber_LED(ctrl, hp_slot); +} + + +static inline int get_slot_enabled(struct controller *ctrl, struct slot *slot) +{ + u8 hp_slot; + + hp_slot = slot->device - ctrl->slot_device_offset; + + return is_slot_enabled(ctrl, hp_slot); +} + + +static inline int cpq_get_latch_status(struct controller *ctrl, struct slot *slot) +{ + u32 status; + u8 hp_slot; + + hp_slot = slot->device - ctrl->slot_device_offset; + dbg("%s: slot->device = %d, ctrl->slot_device_offset = %d \n", + __func__, slot->device, ctrl->slot_device_offset); + + status = (readl(ctrl->hpc_reg + INT_INPUT_CLEAR) & (0x01L << hp_slot)); + + return(status == 0) ? 1 : 0; +} + + +static inline int get_presence_status(struct controller *ctrl, struct slot *slot) +{ + int presence_save = 0; + u8 hp_slot; + u32 tempdword; + + hp_slot = slot->device - ctrl->slot_device_offset; + + tempdword = readl(ctrl->hpc_reg + INT_INPUT_CLEAR); + presence_save = (int) ((((~tempdword) >> 23) | ((~tempdword) >> 15)) >> hp_slot) & 0x02; + + return presence_save; +} + +static inline int wait_for_ctrl_irq(struct controller *ctrl) +{ + DECLARE_WAITQUEUE(wait, current); + int retval = 0; + + dbg("%s - start\n", __func__); + add_wait_queue(&ctrl->queue, &wait); + /* Sleep for up to 1 second to wait for the LED to change. */ + msleep_interruptible(1000); + remove_wait_queue(&ctrl->queue, &wait); + if (signal_pending(current)) + retval = -EINTR; + + dbg("%s - end\n", __func__); + return retval; +} + +#endif + diff --git a/drivers/pci/hotplug/cpqphp_core.c b/drivers/pci/hotplug/cpqphp_core.c new file mode 100644 index 0000000..8514c3a --- /dev/null +++ b/drivers/pci/hotplug/cpqphp_core.c @@ -0,0 +1,1536 @@ +/* + * Compaq Hot Plug Controller Driver + * + * Copyright (C) 1995,2001 Compaq Computer Corporation + * 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> + * + * Jan 12, 2003 - Added 66/100/133MHz PCI-X support, + * Torben Mathiasen <torben.mathiasen@hp.com> + * + */ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/kernel.h> +#include <linux/types.h> +#include <linux/proc_fs.h> +#include <linux/slab.h> +#include <linux/workqueue.h> +#include <linux/pci.h> +#include <linux/pci_hotplug.h> +#include <linux/init.h> +#include <linux/interrupt.h> + +#include <asm/uaccess.h> + +#include "cpqphp.h" +#include "cpqphp_nvram.h" +#include "../../../arch/x86/pci/pci.h" /* horrible hack showing how processor dependent we are... */ + + +/* Global variables */ +int cpqhp_debug; +int cpqhp_legacy_mode; +struct controller *cpqhp_ctrl_list; /* = NULL */ +struct pci_func *cpqhp_slot_list[256]; + +/* local variables */ +static void __iomem *smbios_table; +static void __iomem *smbios_start; +static void __iomem *cpqhp_rom_start; +static int power_mode; +static int debug; +static int initialized; + +#define DRIVER_VERSION "0.9.8" +#define DRIVER_AUTHOR "Dan Zink <dan.zink@compaq.com>, Greg Kroah-Hartman <greg@kroah.com>" +#define DRIVER_DESC "Compaq Hot Plug PCI Controller Driver" + +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); + +module_param(power_mode, bool, 0644); +MODULE_PARM_DESC(power_mode, "Power mode enabled or not"); + +module_param(debug, bool, 0644); +MODULE_PARM_DESC(debug, "Debugging mode enabled or not"); + +#define CPQHPC_MODULE_MINOR 208 + +static int one_time_init (void); +static int set_attention_status (struct hotplug_slot *slot, u8 value); +static int process_SI (struct hotplug_slot *slot); +static int process_SS (struct hotplug_slot *slot); +static int hardware_test (struct hotplug_slot *slot, u32 value); +static int get_power_status (struct hotplug_slot *slot, u8 *value); +static int get_attention_status (struct hotplug_slot *slot, u8 *value); +static int get_latch_status (struct hotplug_slot *slot, u8 *value); +static int get_adapter_status (struct hotplug_slot *slot, u8 *value); +static int get_max_bus_speed (struct hotplug_slot *slot, enum pci_bus_speed *value); +static int get_cur_bus_speed (struct hotplug_slot *slot, enum pci_bus_speed *value); + +static struct hotplug_slot_ops cpqphp_hotplug_slot_ops = { + .owner = THIS_MODULE, + .set_attention_status = set_attention_status, + .enable_slot = process_SI, + .disable_slot = process_SS, + .hardware_test = hardware_test, + .get_power_status = get_power_status, + .get_attention_status = get_attention_status, + .get_latch_status = get_latch_status, + .get_adapter_status = get_adapter_status, + .get_max_bus_speed = get_max_bus_speed, + .get_cur_bus_speed = get_cur_bus_speed, +}; + + +static inline int is_slot64bit(struct slot *slot) +{ + return (readb(slot->p_sm_slot + SMBIOS_SLOT_WIDTH) == 0x06) ? 1 : 0; +} + +static inline int is_slot66mhz(struct slot *slot) +{ + return (readb(slot->p_sm_slot + SMBIOS_SLOT_TYPE) == 0x0E) ? 1 : 0; +} + +/** + * detect_SMBIOS_pointer - find the System Management BIOS Table in mem region. + * @begin: begin pointer for region to be scanned. + * @end: end pointer for region to be scanned. + * + * Returns pointer to the head of the SMBIOS tables (or %NULL). + */ +static void __iomem * detect_SMBIOS_pointer(void __iomem *begin, void __iomem *end) +{ + void __iomem *fp; + void __iomem *endp; + u8 temp1, temp2, temp3, temp4; + int status = 0; + + endp = (end - sizeof(u32) + 1); + + for (fp = begin; fp <= endp; fp += 16) { + temp1 = readb(fp); + temp2 = readb(fp+1); + temp3 = readb(fp+2); + temp4 = readb(fp+3); + if (temp1 == '_' && + temp2 == 'S' && + temp3 == 'M' && + temp4 == '_') { + status = 1; + break; + } + } + + if (!status) + fp = NULL; + + dbg("Discovered SMBIOS Entry point at %p\n", fp); + + return fp; +} + +/** + * init_SERR - Initializes the per slot SERR generation. + * @ctrl: controller to use + * + * For unexpected switch opens + */ +static int init_SERR(struct controller * ctrl) +{ + u32 tempdword; + u32 number_of_slots; + u8 physical_slot; + + if (!ctrl) + return 1; + + tempdword = ctrl->first_slot; + + number_of_slots = readb(ctrl->hpc_reg + SLOT_MASK) & 0x0F; + // Loop through slots + while (number_of_slots) { + physical_slot = tempdword; + writeb(0, ctrl->hpc_reg + SLOT_SERR); + tempdword++; + number_of_slots--; + } + + return 0; +} + + +/* nice debugging output */ +static int pci_print_IRQ_route (void) +{ + struct irq_routing_table *routing_table; + int len; + int loop; + + u8 tbus, tdevice, tslot; + + routing_table = pcibios_get_irq_routing_table(); + if (routing_table == NULL) { + err("No BIOS Routing Table??? Not good\n"); + return -ENOMEM; + } + + len = (routing_table->size - sizeof(struct irq_routing_table)) / + sizeof(struct irq_info); + // Make sure I got at least one entry + if (len == 0) { + kfree(routing_table); + return -1; + } + + dbg("bus dev func slot\n"); + + for (loop = 0; loop < len; ++loop) { + tbus = routing_table->slots[loop].bus; + tdevice = routing_table->slots[loop].devfn; + tslot = routing_table->slots[loop].slot; + dbg("%d %d %d %d\n", tbus, tdevice >> 3, tdevice & 0x7, tslot); + + } + kfree(routing_table); + return 0; +} + + +/** + * get_subsequent_smbios_entry: get the next entry from bios table. + * @smbios_start: where to start in the SMBIOS table + * @smbios_table: location of the SMBIOS table + * @curr: %NULL or pointer to previously returned structure + * + * Gets the first entry if previous == NULL; + * otherwise, returns the next entry. + * Uses global SMBIOS Table pointer. + * + * Returns a pointer to an SMBIOS structure or NULL if none found. + */ +static void __iomem *get_subsequent_smbios_entry(void __iomem *smbios_start, + void __iomem *smbios_table, + void __iomem *curr) +{ + u8 bail = 0; + u8 previous_byte = 1; + void __iomem *p_temp; + void __iomem *p_max; + + if (!smbios_table || !curr) + return(NULL); + + // set p_max to the end of the table + p_max = smbios_start + readw(smbios_table + ST_LENGTH); + + p_temp = curr; + p_temp += readb(curr + SMBIOS_GENERIC_LENGTH); + + while ((p_temp < p_max) && !bail) { + /* Look for the double NULL terminator + * The first condition is the previous byte + * and the second is the curr */ + if (!previous_byte && !(readb(p_temp))) { + bail = 1; + } + + previous_byte = readb(p_temp); + p_temp++; + } + + if (p_temp < p_max) { + return p_temp; + } else { + return NULL; + } +} + + +/** + * get_SMBIOS_entry - return the requested SMBIOS entry or %NULL + * @smbios_start: where to start in the SMBIOS table + * @smbios_table: location of the SMBIOS table + * @type: SMBIOS structure type to be returned + * @previous: %NULL or pointer to previously returned structure + * + * Gets the first entry of the specified type if previous == %NULL; + * Otherwise, returns the next entry of the given type. + * Uses global SMBIOS Table pointer. + * Uses get_subsequent_smbios_entry. + * + * Returns a pointer to an SMBIOS structure or %NULL if none found. + */ +static void __iomem *get_SMBIOS_entry(void __iomem *smbios_start, + void __iomem *smbios_table, + u8 type, + void __iomem *previous) +{ + if (!smbios_table) + return NULL; + + if (!previous) { + previous = smbios_start; + } else { + previous = get_subsequent_smbios_entry(smbios_start, + smbios_table, previous); + } + + while (previous) { + if (readb(previous + SMBIOS_GENERIC_TYPE) != type) { + previous = get_subsequent_smbios_entry(smbios_start, + smbios_table, previous); + } else { + break; + } + } + + return previous; +} + +static void release_slot(struct hotplug_slot *hotplug_slot) +{ + struct slot *slot = hotplug_slot->private; + + dbg("%s - physical_slot = %s\n", __func__, slot_name(slot)); + + kfree(slot->hotplug_slot->info); + kfree(slot->hotplug_slot); + kfree(slot); +} + +#define SLOT_NAME_SIZE 10 + +static int ctrl_slot_setup(struct controller *ctrl, + void __iomem *smbios_start, + void __iomem *smbios_table) +{ + struct slot *slot; + struct hotplug_slot *hotplug_slot; + struct hotplug_slot_info *hotplug_slot_info; + u8 number_of_slots; + u8 slot_device; + u8 slot_number; + u8 ctrl_slot; + u32 tempdword; + char name[SLOT_NAME_SIZE]; + void __iomem *slot_entry= NULL; + int result = -ENOMEM; + + dbg("%s\n", __func__); + + tempdword = readl(ctrl->hpc_reg + INT_INPUT_CLEAR); + + number_of_slots = readb(ctrl->hpc_reg + SLOT_MASK) & 0x0F; + slot_device = readb(ctrl->hpc_reg + SLOT_MASK) >> 4; + slot_number = ctrl->first_slot; + + while (number_of_slots) { + slot = kzalloc(sizeof(*slot), GFP_KERNEL); + if (!slot) + goto error; + + slot->hotplug_slot = kzalloc(sizeof(*(slot->hotplug_slot)), + GFP_KERNEL); + if (!slot->hotplug_slot) + goto error_slot; + hotplug_slot = slot->hotplug_slot; + + hotplug_slot->info = + kzalloc(sizeof(*(hotplug_slot->info)), + GFP_KERNEL); + if (!hotplug_slot->info) + goto error_hpslot; + hotplug_slot_info = hotplug_slot->info; + + slot->ctrl = ctrl; + slot->bus = ctrl->bus; + slot->device = slot_device; + slot->number = slot_number; + dbg("slot->number = %u\n", slot->number); + + slot_entry = get_SMBIOS_entry(smbios_start, smbios_table, 9, + slot_entry); + + while (slot_entry && (readw(slot_entry + SMBIOS_SLOT_NUMBER) != + slot->number)) { + slot_entry = get_SMBIOS_entry(smbios_start, + smbios_table, 9, slot_entry); + } + + slot->p_sm_slot = slot_entry; + + init_timer(&slot->task_event); + slot->task_event.expires = jiffies + 5 * HZ; + slot->task_event.function = cpqhp_pushbutton_thread; + + //FIXME: these capabilities aren't used but if they are + // they need to be correctly implemented + slot->capabilities |= PCISLOT_REPLACE_SUPPORTED; + slot->capabilities |= PCISLOT_INTERLOCK_SUPPORTED; + + if (is_slot64bit(slot)) + slot->capabilities |= PCISLOT_64_BIT_SUPPORTED; + if (is_slot66mhz(slot)) + slot->capabilities |= PCISLOT_66_MHZ_SUPPORTED; + if (ctrl->speed == PCI_SPEED_66MHz) + slot->capabilities |= PCISLOT_66_MHZ_OPERATION; + + ctrl_slot = + slot_device - (readb(ctrl->hpc_reg + SLOT_MASK) >> 4); + + // Check presence + slot->capabilities |= + ((((~tempdword) >> 23) | + ((~tempdword) >> 15)) >> ctrl_slot) & 0x02; + // Check the switch state + slot->capabilities |= + ((~tempdword & 0xFF) >> ctrl_slot) & 0x01; + // Check the slot enable + slot->capabilities |= + ((read_slot_enable(ctrl) << 2) >> ctrl_slot) & 0x04; + + /* register this slot with the hotplug pci core */ + hotplug_slot->release = &release_slot; + hotplug_slot->private = slot; + snprintf(name, SLOT_NAME_SIZE, "%u", slot->number); + hotplug_slot->ops = &cpqphp_hotplug_slot_ops; + + hotplug_slot_info->power_status = get_slot_enabled(ctrl, slot); + hotplug_slot_info->attention_status = + cpq_get_attention_status(ctrl, slot); + hotplug_slot_info->latch_status = + cpq_get_latch_status(ctrl, slot); + hotplug_slot_info->adapter_status = + get_presence_status(ctrl, slot); + + dbg("registering bus %d, dev %d, number %d, " + "ctrl->slot_device_offset %d, slot %d\n", + slot->bus, slot->device, + slot->number, ctrl->slot_device_offset, + slot_number); + result = pci_hp_register(hotplug_slot, + ctrl->pci_dev->bus, + slot->device, + name); + if (result) { + err("pci_hp_register failed with error %d\n", result); + goto error_info; + } + + slot->next = ctrl->slot; + ctrl->slot = slot; + + number_of_slots--; + slot_device++; + slot_number++; + } + + return 0; +error_info: + kfree(hotplug_slot_info); +error_hpslot: + kfree(hotplug_slot); +error_slot: + kfree(slot); +error: + return result; +} + +static int ctrl_slot_cleanup (struct controller * ctrl) +{ + struct slot *old_slot, *next_slot; + + old_slot = ctrl->slot; + ctrl->slot = NULL; + + while (old_slot) { + /* memory will be freed by the release_slot callback */ + next_slot = old_slot->next; + pci_hp_deregister (old_slot->hotplug_slot); + old_slot = next_slot; + } + + cpqhp_remove_debugfs_files(ctrl); + + //Free IRQ associated with hot plug device + free_irq(ctrl->interrupt, ctrl); + //Unmap the memory + iounmap(ctrl->hpc_reg); + //Finally reclaim PCI mem + release_mem_region(pci_resource_start(ctrl->pci_dev, 0), + pci_resource_len(ctrl->pci_dev, 0)); + + return(0); +} + + +//============================================================================ +// function: get_slot_mapping +// +// Description: Attempts to determine a logical slot mapping for a PCI +// device. Won't work for more than one PCI-PCI bridge +// in a slot. +// +// Input: u8 bus_num - bus number of PCI device +// u8 dev_num - device number of PCI device +// u8 *slot - Pointer to u8 where slot number will +// be returned +// +// Output: SUCCESS or FAILURE +//============================================================================= +static int +get_slot_mapping(struct pci_bus *bus, u8 bus_num, u8 dev_num, u8 *slot) +{ + struct irq_routing_table *PCIIRQRoutingInfoLength; + u32 work; + long len; + long loop; + + u8 tbus, tdevice, tslot, bridgeSlot; + + dbg("%s: %p, %d, %d, %p\n", __func__, bus, bus_num, dev_num, slot); + + bridgeSlot = 0xFF; + + PCIIRQRoutingInfoLength = pcibios_get_irq_routing_table(); + if (!PCIIRQRoutingInfoLength) + return -1; + + len = (PCIIRQRoutingInfoLength->size - + sizeof(struct irq_routing_table)) / sizeof(struct irq_info); + // Make sure I got at least one entry + if (len == 0) { + kfree(PCIIRQRoutingInfoLength); + return -1; + } + + for (loop = 0; loop < len; ++loop) { + tbus = PCIIRQRoutingInfoLength->slots[loop].bus; + tdevice = PCIIRQRoutingInfoLength->slots[loop].devfn >> 3; + tslot = PCIIRQRoutingInfoLength->slots[loop].slot; + + if ((tbus == bus_num) && (tdevice == dev_num)) { + *slot = tslot; + kfree(PCIIRQRoutingInfoLength); + return 0; + } else { + /* Did not get a match on the target PCI device. Check + * if the current IRQ table entry is a PCI-to-PCI bridge + * device. If so, and it's secondary bus matches the + * bus number for the target device, I need to save the + * bridge's slot number. If I can not find an entry for + * the target device, I will have to assume it's on the + * other side of the bridge, and assign it the bridge's + * slot. */ + bus->number = tbus; + pci_bus_read_config_dword(bus, PCI_DEVFN(tdevice, 0), + PCI_CLASS_REVISION, &work); + + if ((work >> 8) == PCI_TO_PCI_BRIDGE_CLASS) { + pci_bus_read_config_dword(bus, + PCI_DEVFN(tdevice, 0), + PCI_PRIMARY_BUS, &work); + // See if bridge's secondary bus matches target bus. + if (((work >> 8) & 0x000000FF) == (long) bus_num) { + bridgeSlot = tslot; + } + } + } + + } + + // If we got here, we didn't find an entry in the IRQ mapping table + // for the target PCI device. If we did determine that the target + // device is on the other side of a PCI-to-PCI bridge, return the + // slot number for the bridge. + if (bridgeSlot != 0xFF) { + *slot = bridgeSlot; + kfree(PCIIRQRoutingInfoLength); + return 0; + } + kfree(PCIIRQRoutingInfoLength); + // Couldn't find an entry in the routing table for this PCI device + return -1; +} + + +/** + * cpqhp_set_attention_status - Turns the Amber LED for a slot on or off + * @ctrl: struct controller to use + * @func: PCI device/function info + * @status: LED control flag: 1 = LED on, 0 = LED off + */ +static int +cpqhp_set_attention_status(struct controller *ctrl, struct pci_func *func, + u32 status) +{ + u8 hp_slot; + + if (func == NULL) + return(1); + + hp_slot = func->device - ctrl->slot_device_offset; + + // Wait for exclusive access to hardware + mutex_lock(&ctrl->crit_sect); + + if (status == 1) { + amber_LED_on (ctrl, hp_slot); + } else if (status == 0) { + amber_LED_off (ctrl, hp_slot); + } else { + // Done with exclusive hardware access + mutex_unlock(&ctrl->crit_sect); + return(1); + } + + set_SOGO(ctrl); + + // Wait for SOBS to be unset + wait_for_ctrl_irq (ctrl); + + // Done with exclusive hardware access + mutex_unlock(&ctrl->crit_sect); + + return(0); +} + + +/** + * set_attention_status - Turns the Amber LED for a slot on or off + * @hotplug_slot: slot to change LED on + * @status: LED control flag + */ +static int set_attention_status (struct hotplug_slot *hotplug_slot, u8 status) +{ + struct pci_func *slot_func; + struct slot *slot = hotplug_slot->private; + struct controller *ctrl = slot->ctrl; + u8 bus; + u8 devfn; + u8 device; + u8 function; + + dbg("%s - physical_slot = %s\n", __func__, slot_name(slot)); + + if (cpqhp_get_bus_dev(ctrl, &bus, &devfn, slot->number) == -1) + return -ENODEV; + + device = devfn >> 3; + function = devfn & 0x7; + dbg("bus, dev, fn = %d, %d, %d\n", bus, device, function); + + slot_func = cpqhp_slot_find(bus, device, function); + if (!slot_func) + return -ENODEV; + + return cpqhp_set_attention_status(ctrl, slot_func, status); +} + + +static int process_SI(struct hotplug_slot *hotplug_slot) +{ + struct pci_func *slot_func; + struct slot *slot = hotplug_slot->private; + struct controller *ctrl = slot->ctrl; + u8 bus; + u8 devfn; + u8 device; + u8 function; + + dbg("%s - physical_slot = %s\n", __func__, slot_name(slot)); + + if (cpqhp_get_bus_dev(ctrl, &bus, &devfn, slot->number) == -1) + return -ENODEV; + + device = devfn >> 3; + function = devfn & 0x7; + dbg("bus, dev, fn = %d, %d, %d\n", bus, device, function); + + slot_func = cpqhp_slot_find(bus, device, function); + if (!slot_func) + return -ENODEV; + + slot_func->bus = bus; + slot_func->device = device; + slot_func->function = function; + slot_func->configured = 0; + dbg("board_added(%p, %p)\n", slot_func, ctrl); + return cpqhp_process_SI(ctrl, slot_func); +} + + +static int process_SS(struct hotplug_slot *hotplug_slot) +{ + struct pci_func *slot_func; + struct slot *slot = hotplug_slot->private; + struct controller *ctrl = slot->ctrl; + u8 bus; + u8 devfn; + u8 device; + u8 function; + + dbg("%s - physical_slot = %s\n", __func__, slot_name(slot)); + + if (cpqhp_get_bus_dev(ctrl, &bus, &devfn, slot->number) == -1) + return -ENODEV; + + device = devfn >> 3; + function = devfn & 0x7; + dbg("bus, dev, fn = %d, %d, %d\n", bus, device, function); + + slot_func = cpqhp_slot_find(bus, device, function); + if (!slot_func) + return -ENODEV; + + dbg("In %s, slot_func = %p, ctrl = %p\n", __func__, slot_func, ctrl); + return cpqhp_process_SS(ctrl, slot_func); +} + + +static int hardware_test(struct hotplug_slot *hotplug_slot, u32 value) +{ + struct slot *slot = hotplug_slot->private; + struct controller *ctrl = slot->ctrl; + + dbg("%s - physical_slot = %s\n", __func__, slot_name(slot)); + + return cpqhp_hardware_test(ctrl, value); +} + + +static int get_power_status(struct hotplug_slot *hotplug_slot, u8 *value) +{ + struct slot *slot = hotplug_slot->private; + struct controller *ctrl = slot->ctrl; + + dbg("%s - physical_slot = %s\n", __func__, slot_name(slot)); + + *value = get_slot_enabled(ctrl, slot); + return 0; +} + +static int get_attention_status(struct hotplug_slot *hotplug_slot, u8 *value) +{ + struct slot *slot = hotplug_slot->private; + struct controller *ctrl = slot->ctrl; + + dbg("%s - physical_slot = %s\n", __func__, slot_name(slot)); + + *value = cpq_get_attention_status(ctrl, slot); + return 0; +} + +static int get_latch_status(struct hotplug_slot *hotplug_slot, u8 *value) +{ + struct slot *slot = hotplug_slot->private; + struct controller *ctrl = slot->ctrl; + + dbg("%s - physical_slot = %s\n", __func__, slot_name(slot)); + + *value = cpq_get_latch_status(ctrl, slot); + + return 0; +} + +static int get_adapter_status(struct hotplug_slot *hotplug_slot, u8 *value) +{ + struct slot *slot = hotplug_slot->private; + struct controller *ctrl = slot->ctrl; + + dbg("%s - physical_slot = %s\n", __func__, slot_name(slot)); + + *value = get_presence_status(ctrl, slot); + + return 0; +} + +static int get_max_bus_speed (struct hotplug_slot *hotplug_slot, enum pci_bus_speed *value) +{ + struct slot *slot = hotplug_slot->private; + struct controller *ctrl = slot->ctrl; + + dbg("%s - physical_slot = %s\n", __func__, slot_name(slot)); + + *value = ctrl->speed_capability; + + return 0; +} + +static int get_cur_bus_speed (struct hotplug_slot *hotplug_slot, enum pci_bus_speed *value) +{ + struct slot *slot = hotplug_slot->private; + struct controller *ctrl = slot->ctrl; + + dbg("%s - physical_slot = %s\n", __func__, slot_name(slot)); + + *value = ctrl->speed; + + return 0; +} + +static int cpqhpc_probe(struct pci_dev *pdev, const struct pci_device_id *ent) +{ + u8 num_of_slots = 0; + u8 hp_slot = 0; + u8 device; + u8 bus_cap; + u16 temp_word; + u16 vendor_id; + u16 subsystem_vid; + u16 subsystem_deviceid; + u32 rc; + struct controller *ctrl; + struct pci_func *func; + int err; + + err = pci_enable_device(pdev); + if (err) { + printk(KERN_ERR MY_NAME ": cannot enable PCI device %s (%d)\n", + pci_name(pdev), err); + return err; + } + + // Need to read VID early b/c it's used to differentiate CPQ and INTC discovery + rc = pci_read_config_word(pdev, PCI_VENDOR_ID, &vendor_id); + if (rc || ((vendor_id != PCI_VENDOR_ID_COMPAQ) && (vendor_id != PCI_VENDOR_ID_INTEL))) { + err(msg_HPC_non_compaq_or_intel); + rc = -ENODEV; + goto err_disable_device; + } + dbg("Vendor ID: %x\n", vendor_id); + + dbg("revision: %d\n", pdev->revision); + if ((vendor_id == PCI_VENDOR_ID_COMPAQ) && (!pdev->revision)) { + err(msg_HPC_rev_error); + rc = -ENODEV; + goto err_disable_device; + } + + /* Check for the proper subsytem ID's + * Intel uses a different SSID programming model than Compaq. + * For Intel, each SSID bit identifies a PHP capability. + * Also Intel HPC's may have RID=0. + */ + if ((pdev->revision > 2) || (vendor_id == PCI_VENDOR_ID_INTEL)) { + // TODO: This code can be made to support non-Compaq or Intel subsystem IDs + rc = pci_read_config_word(pdev, PCI_SUBSYSTEM_VENDOR_ID, &subsystem_vid); + if (rc) { + err("%s : pci_read_config_word failed\n", __func__); + goto err_disable_device; + } + dbg("Subsystem Vendor ID: %x\n", subsystem_vid); + if ((subsystem_vid != PCI_VENDOR_ID_COMPAQ) && (subsystem_vid != PCI_VENDOR_ID_INTEL)) { + err(msg_HPC_non_compaq_or_intel); + rc = -ENODEV; + goto err_disable_device; + } + + ctrl = kzalloc(sizeof(struct controller), GFP_KERNEL); + if (!ctrl) { + err("%s : out of memory\n", __func__); + rc = -ENOMEM; + goto err_disable_device; + } + + rc = pci_read_config_word(pdev, PCI_SUBSYSTEM_ID, &subsystem_deviceid); + if (rc) { + err("%s : pci_read_config_word failed\n", __func__); + goto err_free_ctrl; + } + + info("Hot Plug Subsystem Device ID: %x\n", subsystem_deviceid); + + /* Set Vendor ID, so it can be accessed later from other functions */ + ctrl->vendor_id = vendor_id; + + switch (subsystem_vid) { + case PCI_VENDOR_ID_COMPAQ: + if (pdev->revision >= 0x13) { /* CIOBX */ + ctrl->push_flag = 1; + ctrl->slot_switch_type = 1; + ctrl->push_button = 1; + ctrl->pci_config_space = 1; + ctrl->defeature_PHP = 1; + ctrl->pcix_support = 1; + ctrl->pcix_speed_capability = 1; + pci_read_config_byte(pdev, 0x41, &bus_cap); + if (bus_cap & 0x80) { + dbg("bus max supports 133MHz PCI-X\n"); + ctrl->speed_capability = PCI_SPEED_133MHz_PCIX; + break; + } + if (bus_cap & 0x40) { + dbg("bus max supports 100MHz PCI-X\n"); + ctrl->speed_capability = PCI_SPEED_100MHz_PCIX; + break; + } + if (bus_cap & 20) { + dbg("bus max supports 66MHz PCI-X\n"); + ctrl->speed_capability = PCI_SPEED_66MHz_PCIX; + break; + } + if (bus_cap & 10) { + dbg("bus max supports 66MHz PCI\n"); + ctrl->speed_capability = PCI_SPEED_66MHz; + break; + } + + break; + } + + switch (subsystem_deviceid) { + case PCI_SUB_HPC_ID: + /* Original 6500/7000 implementation */ + ctrl->slot_switch_type = 1; + ctrl->speed_capability = PCI_SPEED_33MHz; + ctrl->push_button = 0; + ctrl->pci_config_space = 1; + ctrl->defeature_PHP = 1; + ctrl->pcix_support = 0; + ctrl->pcix_speed_capability = 0; + break; + case PCI_SUB_HPC_ID2: + /* First Pushbutton implementation */ + ctrl->push_flag = 1; + ctrl->slot_switch_type = 1; + ctrl->speed_capability = PCI_SPEED_33MHz; + ctrl->push_button = 1; + ctrl->pci_config_space = 1; + ctrl->defeature_PHP = 1; + ctrl->pcix_support = 0; + ctrl->pcix_speed_capability = 0; + break; + case PCI_SUB_HPC_ID_INTC: + /* Third party (6500/7000) */ + ctrl->slot_switch_type = 1; + ctrl->speed_capability = PCI_SPEED_33MHz; + ctrl->push_button = 0; + ctrl->pci_config_space = 1; + ctrl->defeature_PHP = 1; + ctrl->pcix_support = 0; + ctrl->pcix_speed_capability = 0; + break; + case PCI_SUB_HPC_ID3: + /* First 66 Mhz implementation */ + ctrl->push_flag = 1; + ctrl->slot_switch_type = 1; + ctrl->speed_capability = PCI_SPEED_66MHz; + ctrl->push_button = 1; + ctrl->pci_config_space = 1; + ctrl->defeature_PHP = 1; + ctrl->pcix_support = 0; + ctrl->pcix_speed_capability = 0; + break; + case PCI_SUB_HPC_ID4: + /* First PCI-X implementation, 100MHz */ + ctrl->push_flag = 1; + ctrl->slot_switch_type = 1; + ctrl->speed_capability = PCI_SPEED_100MHz_PCIX; + ctrl->push_button = 1; + ctrl->pci_config_space = 1; + ctrl->defeature_PHP = 1; + ctrl->pcix_support = 1; + ctrl->pcix_speed_capability = 0; + break; + default: + err(msg_HPC_not_supported); + rc = -ENODEV; + goto err_free_ctrl; + } + break; + + case PCI_VENDOR_ID_INTEL: + /* Check for speed capability (0=33, 1=66) */ + if (subsystem_deviceid & 0x0001) { + ctrl->speed_capability = PCI_SPEED_66MHz; + } else { + ctrl->speed_capability = PCI_SPEED_33MHz; + } + + /* Check for push button */ + if (subsystem_deviceid & 0x0002) { + /* no push button */ + ctrl->push_button = 0; + } else { + /* push button supported */ + ctrl->push_button = 1; + } + + /* Check for slot switch type (0=mechanical, 1=not mechanical) */ + if (subsystem_deviceid & 0x0004) { + /* no switch */ + ctrl->slot_switch_type = 0; + } else { + /* switch */ + ctrl->slot_switch_type = 1; + } + + /* PHP Status (0=De-feature PHP, 1=Normal operation) */ + if (subsystem_deviceid & 0x0008) { + ctrl->defeature_PHP = 1; // PHP supported + } else { + ctrl->defeature_PHP = 0; // PHP not supported + } + + /* Alternate Base Address Register Interface (0=not supported, 1=supported) */ + if (subsystem_deviceid & 0x0010) { + ctrl->alternate_base_address = 1; // supported + } else { + ctrl->alternate_base_address = 0; // not supported + } + + /* PCI Config Space Index (0=not supported, 1=supported) */ + if (subsystem_deviceid & 0x0020) { + ctrl->pci_config_space = 1; // supported + } else { + ctrl->pci_config_space = 0; // not supported + } + + /* PCI-X support */ + if (subsystem_deviceid & 0x0080) { + /* PCI-X capable */ + ctrl->pcix_support = 1; + /* Frequency of operation in PCI-X mode */ + if (subsystem_deviceid & 0x0040) { + /* 133MHz PCI-X if bit 7 is 1 */ + ctrl->pcix_speed_capability = 1; + } else { + /* 100MHz PCI-X if bit 7 is 1 and bit 0 is 0, */ + /* 66MHz PCI-X if bit 7 is 1 and bit 0 is 1 */ + ctrl->pcix_speed_capability = 0; + } + } else { + /* Conventional PCI */ + ctrl->pcix_support = 0; + ctrl->pcix_speed_capability = 0; + } + break; + + default: + err(msg_HPC_not_supported); + rc = -ENODEV; + goto err_free_ctrl; + } + + } else { + err(msg_HPC_not_supported); + return -ENODEV; + } + + // Tell the user that we found one. + info("Initializing the PCI hot plug controller residing on PCI bus %d\n", + pdev->bus->number); + + dbg("Hotplug controller capabilities:\n"); + dbg(" speed_capability %d\n", ctrl->speed_capability); + dbg(" slot_switch_type %s\n", ctrl->slot_switch_type ? + "switch present" : "no switch"); + dbg(" defeature_PHP %s\n", ctrl->defeature_PHP ? + "PHP supported" : "PHP not supported"); + dbg(" alternate_base_address %s\n", ctrl->alternate_base_address ? + "supported" : "not supported"); + dbg(" pci_config_space %s\n", ctrl->pci_config_space ? + "supported" : "not supported"); + dbg(" pcix_speed_capability %s\n", ctrl->pcix_speed_capability ? + "supported" : "not supported"); + dbg(" pcix_support %s\n", ctrl->pcix_support ? + "supported" : "not supported"); + + ctrl->pci_dev = pdev; + pci_set_drvdata(pdev, ctrl); + + /* make our own copy of the pci bus structure, + * as we like tweaking it a lot */ + ctrl->pci_bus = kmalloc(sizeof(*ctrl->pci_bus), GFP_KERNEL); + if (!ctrl->pci_bus) { + err("out of memory\n"); + rc = -ENOMEM; + goto err_free_ctrl; + } + memcpy(ctrl->pci_bus, pdev->bus, sizeof(*ctrl->pci_bus)); + + ctrl->bus = pdev->bus->number; + ctrl->rev = pdev->revision; + dbg("bus device function rev: %d %d %d %d\n", ctrl->bus, + PCI_SLOT(pdev->devfn), PCI_FUNC(pdev->devfn), ctrl->rev); + + mutex_init(&ctrl->crit_sect); + init_waitqueue_head(&ctrl->queue); + + /* initialize our threads if they haven't already been started up */ + rc = one_time_init(); + if (rc) { + goto err_free_bus; + } + + dbg("pdev = %p\n", pdev); + dbg("pci resource start %llx\n", (unsigned long long)pci_resource_start(pdev, 0)); + dbg("pci resource len %llx\n", (unsigned long long)pci_resource_len(pdev, 0)); + + if (!request_mem_region(pci_resource_start(pdev, 0), + pci_resource_len(pdev, 0), MY_NAME)) { + err("cannot reserve MMIO region\n"); + rc = -ENOMEM; + goto err_free_bus; + } + + ctrl->hpc_reg = ioremap(pci_resource_start(pdev, 0), + pci_resource_len(pdev, 0)); + if (!ctrl->hpc_reg) { + err("cannot remap MMIO region %llx @ %llx\n", + (unsigned long long)pci_resource_len(pdev, 0), + (unsigned long long)pci_resource_start(pdev, 0)); + rc = -ENODEV; + goto err_free_mem_region; + } + + // Check for 66Mhz operation + ctrl->speed = get_controller_speed(ctrl); + + + /******************************************************** + * + * Save configuration headers for this and + * subordinate PCI buses + * + ********************************************************/ + + // find the physical slot number of the first hot plug slot + + /* Get slot won't work for devices behind bridges, but + * in this case it will always be called for the "base" + * bus/dev/func of a slot. + * CS: this is leveraging the PCIIRQ routing code from the kernel + * (pci-pc.c: get_irq_routing_table) */ + rc = get_slot_mapping(ctrl->pci_bus, pdev->bus->number, + (readb(ctrl->hpc_reg + SLOT_MASK) >> 4), + &(ctrl->first_slot)); + dbg("get_slot_mapping: first_slot = %d, returned = %d\n", + ctrl->first_slot, rc); + if (rc) { + err(msg_initialization_err, rc); + goto err_iounmap; + } + + // Store PCI Config Space for all devices on this bus + rc = cpqhp_save_config(ctrl, ctrl->bus, readb(ctrl->hpc_reg + SLOT_MASK)); + if (rc) { + err("%s: unable to save PCI configuration data, error %d\n", + __func__, rc); + goto err_iounmap; + } + + /* + * Get IO, memory, and IRQ resources for new devices + */ + // The next line is required for cpqhp_find_available_resources + ctrl->interrupt = pdev->irq; + if (ctrl->interrupt < 0x10) { + cpqhp_legacy_mode = 1; + dbg("System seems to be configured for Full Table Mapped MPS mode\n"); + } + + ctrl->cfgspc_irq = 0; + pci_read_config_byte(pdev, PCI_INTERRUPT_LINE, &ctrl->cfgspc_irq); + + rc = cpqhp_find_available_resources(ctrl, cpqhp_rom_start); + ctrl->add_support = !rc; + if (rc) { + dbg("cpqhp_find_available_resources = 0x%x\n", rc); + err("unable to locate PCI configuration resources for hot plug add.\n"); + goto err_iounmap; + } + + /* + * Finish setting up the hot plug ctrl device + */ + ctrl->slot_device_offset = readb(ctrl->hpc_reg + SLOT_MASK) >> 4; + dbg("NumSlots %d \n", ctrl->slot_device_offset); + + ctrl->next_event = 0; + + /* Setup the slot information structures */ + rc = ctrl_slot_setup(ctrl, smbios_start, smbios_table); + if (rc) { + err(msg_initialization_err, 6); + err("%s: unable to save PCI configuration data, error %d\n", + __func__, rc); + goto err_iounmap; + } + + /* Mask all general input interrupts */ + writel(0xFFFFFFFFL, ctrl->hpc_reg + INT_MASK); + + /* set up the interrupt */ + dbg("HPC interrupt = %d \n", ctrl->interrupt); + if (request_irq(ctrl->interrupt, cpqhp_ctrl_intr, + IRQF_SHARED, MY_NAME, ctrl)) { + err("Can't get irq %d for the hotplug pci controller\n", + ctrl->interrupt); + rc = -ENODEV; + goto err_iounmap; + } + + /* Enable Shift Out interrupt and clear it, also enable SERR on power fault */ + temp_word = readw(ctrl->hpc_reg + MISC); + temp_word |= 0x4006; + writew(temp_word, ctrl->hpc_reg + MISC); + + // Changed 05/05/97 to clear all interrupts at start + writel(0xFFFFFFFFL, ctrl->hpc_reg + INT_INPUT_CLEAR); + + ctrl->ctrl_int_comp = readl(ctrl->hpc_reg + INT_INPUT_CLEAR); + + writel(0x0L, ctrl->hpc_reg + INT_MASK); + + if (!cpqhp_ctrl_list) { + cpqhp_ctrl_list = ctrl; + ctrl->next = NULL; + } else { + ctrl->next = cpqhp_ctrl_list; + cpqhp_ctrl_list = ctrl; + } + + // turn off empty slots here unless command line option "ON" set + // Wait for exclusive access to hardware + mutex_lock(&ctrl->crit_sect); + + num_of_slots = readb(ctrl->hpc_reg + SLOT_MASK) & 0x0F; + + // find first device number for the ctrl + device = readb(ctrl->hpc_reg + SLOT_MASK) >> 4; + + while (num_of_slots) { + dbg("num_of_slots: %d\n", num_of_slots); + func = cpqhp_slot_find(ctrl->bus, device, 0); + if (!func) + break; + + hp_slot = func->device - ctrl->slot_device_offset; + dbg("hp_slot: %d\n", hp_slot); + + // We have to save the presence info for these slots + temp_word = ctrl->ctrl_int_comp >> 16; + func->presence_save = (temp_word >> hp_slot) & 0x01; + func->presence_save |= (temp_word >> (hp_slot + 7)) & 0x02; + + if (ctrl->ctrl_int_comp & (0x1L << hp_slot)) { + func->switch_save = 0; + } else { + func->switch_save = 0x10; + } + + if (!power_mode) { + if (!func->is_a_board) { + green_LED_off(ctrl, hp_slot); + slot_disable(ctrl, hp_slot); + } + } + + device++; + num_of_slots--; + } + + if (!power_mode) { + set_SOGO(ctrl); + // Wait for SOBS to be unset + wait_for_ctrl_irq(ctrl); + } + + rc = init_SERR(ctrl); + if (rc) { + err("init_SERR failed\n"); + mutex_unlock(&ctrl->crit_sect); + goto err_free_irq; + } + + // Done with exclusive hardware access + mutex_unlock(&ctrl->crit_sect); + + cpqhp_create_debugfs_files(ctrl); + + return 0; + +err_free_irq: + free_irq(ctrl->interrupt, ctrl); +err_iounmap: + iounmap(ctrl->hpc_reg); +err_free_mem_region: + release_mem_region(pci_resource_start(pdev, 0), pci_resource_len(pdev, 0)); +err_free_bus: + kfree(ctrl->pci_bus); +err_free_ctrl: + kfree(ctrl); +err_disable_device: + pci_disable_device(pdev); + return rc; +} + + +static int one_time_init(void) +{ + int loop; + int retval = 0; + + if (initialized) + return 0; + + power_mode = 0; + + retval = pci_print_IRQ_route(); + if (retval) + goto error; + + dbg("Initialize + Start the notification mechanism \n"); + + retval = cpqhp_event_start_thread(); + if (retval) + goto error; + + dbg("Initialize slot lists\n"); + for (loop = 0; loop < 256; loop++) { + cpqhp_slot_list[loop] = NULL; + } + + // FIXME: We also need to hook the NMI handler eventually. + // this also needs to be worked with Christoph + // register_NMI_handler(); + + // Map rom address + cpqhp_rom_start = ioremap(ROM_PHY_ADDR, ROM_PHY_LEN); + if (!cpqhp_rom_start) { + err ("Could not ioremap memory region for ROM\n"); + retval = -EIO; + goto error; + } + + /* Now, map the int15 entry point if we are on compaq specific hardware */ + compaq_nvram_init(cpqhp_rom_start); + + /* Map smbios table entry point structure */ + smbios_table = detect_SMBIOS_pointer(cpqhp_rom_start, + cpqhp_rom_start + ROM_PHY_LEN); + if (!smbios_table) { + err ("Could not find the SMBIOS pointer in memory\n"); + retval = -EIO; + goto error_rom_start; + } + + smbios_start = ioremap(readl(smbios_table + ST_ADDRESS), + readw(smbios_table + ST_LENGTH)); + if (!smbios_start) { + err ("Could not ioremap memory region taken from SMBIOS values\n"); + retval = -EIO; + goto error_smbios_start; + } + + initialized = 1; + + return retval; + +error_smbios_start: + iounmap(smbios_start); +error_rom_start: + iounmap(cpqhp_rom_start); +error: + return retval; +} + + +static void __exit unload_cpqphpd(void) +{ + struct pci_func *next; + struct pci_func *TempSlot; + int loop; + u32 rc; + struct controller *ctrl; + struct controller *tctrl; + struct pci_resource *res; + struct pci_resource *tres; + + rc = compaq_nvram_store(cpqhp_rom_start); + + ctrl = cpqhp_ctrl_list; + + while (ctrl) { + if (ctrl->hpc_reg) { + u16 misc; + rc = read_slot_enable (ctrl); + + writeb(0, ctrl->hpc_reg + SLOT_SERR); + writel(0xFFFFFFC0L | ~rc, ctrl->hpc_reg + INT_MASK); + + misc = readw(ctrl->hpc_reg + MISC); + misc &= 0xFFFD; + writew(misc, ctrl->hpc_reg + MISC); + } + + ctrl_slot_cleanup(ctrl); + + res = ctrl->io_head; + while (res) { + tres = res; + res = res->next; + kfree(tres); + } + + res = ctrl->mem_head; + while (res) { + tres = res; + res = res->next; + kfree(tres); + } + + res = ctrl->p_mem_head; + while (res) { + tres = res; + res = res->next; + kfree(tres); + } + + res = ctrl->bus_head; + while (res) { + tres = res; + res = res->next; + kfree(tres); + } + + kfree (ctrl->pci_bus); + + tctrl = ctrl; + ctrl = ctrl->next; + kfree(tctrl); + } + + for (loop = 0; loop < 256; loop++) { + next = cpqhp_slot_list[loop]; + while (next != NULL) { + res = next->io_head; + while (res) { + tres = res; + res = res->next; + kfree(tres); + } + + res = next->mem_head; + while (res) { + tres = res; + res = res->next; + kfree(tres); + } + + res = next->p_mem_head; + while (res) { + tres = res; + res = res->next; + kfree(tres); + } + + res = next->bus_head; + while (res) { + tres = res; + res = res->next; + kfree(tres); + } + + TempSlot = next; + next = next->next; + kfree(TempSlot); + } + } + + // Stop the notification mechanism + if (initialized) + cpqhp_event_stop_thread(); + + //unmap the rom address + if (cpqhp_rom_start) + iounmap(cpqhp_rom_start); + if (smbios_start) + iounmap(smbios_start); +} + + + +static struct pci_device_id hpcd_pci_tbl[] = { + { + /* handle any PCI Hotplug controller */ + .class = ((PCI_CLASS_SYSTEM_PCI_HOTPLUG << 8) | 0x00), + .class_mask = ~0, + + /* no matter who makes it */ + .vendor = PCI_ANY_ID, + .device = PCI_ANY_ID, + .subvendor = PCI_ANY_ID, + .subdevice = PCI_ANY_ID, + + }, { /* end: all zeroes */ } +}; + +MODULE_DEVICE_TABLE(pci, hpcd_pci_tbl); + + + +static struct pci_driver cpqhpc_driver = { + .name = "compaq_pci_hotplug", + .id_table = hpcd_pci_tbl, + .probe = cpqhpc_probe, + /* remove: cpqhpc_remove_one, */ +}; + + + +static int __init cpqhpc_init(void) +{ + int result; + + cpqhp_debug = debug; + + info (DRIVER_DESC " version: " DRIVER_VERSION "\n"); + cpqhp_initialize_debugfs(); + result = pci_register_driver(&cpqhpc_driver); + dbg("pci_register_driver = %d\n", result); + return result; +} + + +static void __exit cpqhpc_cleanup(void) +{ + dbg("unload_cpqphpd()\n"); + unload_cpqphpd(); + + dbg("pci_unregister_driver\n"); + pci_unregister_driver(&cpqhpc_driver); + cpqhp_shutdown_debugfs(); +} + + +module_init(cpqhpc_init); +module_exit(cpqhpc_cleanup); + + diff --git a/drivers/pci/hotplug/cpqphp_ctrl.c b/drivers/pci/hotplug/cpqphp_ctrl.c new file mode 100644 index 0000000..a60a252 --- /dev/null +++ b/drivers/pci/hotplug/cpqphp_ctrl.c @@ -0,0 +1,2998 @@ +/* + * Compaq Hot Plug Controller Driver + * + * Copyright (C) 1995,2001 Compaq Computer Corporation + * 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/module.h> +#include <linux/kernel.h> +#include <linux/types.h> +#include <linux/slab.h> +#include <linux/workqueue.h> +#include <linux/interrupt.h> +#include <linux/delay.h> +#include <linux/wait.h> +#include <linux/smp_lock.h> +#include <linux/pci.h> +#include <linux/pci_hotplug.h> +#include <linux/kthread.h> +#include "cpqphp.h" + +static u32 configure_new_device(struct controller* ctrl, struct pci_func *func, + u8 behind_bridge, struct resource_lists *resources); +static int configure_new_function(struct controller* ctrl, struct pci_func *func, + u8 behind_bridge, struct resource_lists *resources); +static void interrupt_event_handler(struct controller *ctrl); + + +static struct task_struct *cpqhp_event_thread; +static unsigned long pushbutton_pending; /* = 0 */ + +/* delay is in jiffies to wait for */ +static void long_delay(int delay) +{ + /* + * XXX(hch): if someone is bored please convert all callers + * to call msleep_interruptible directly. They really want + * to specify timeouts in natural units and spend a lot of + * effort converting them to jiffies.. + */ + msleep_interruptible(jiffies_to_msecs(delay)); +} + + +/* FIXME: The following line needs to be somewhere else... */ +#define WRONG_BUS_FREQUENCY 0x07 +static u8 handle_switch_change(u8 change, struct controller * ctrl) +{ + int hp_slot; + u8 rc = 0; + u16 temp_word; + struct pci_func *func; + struct event_info *taskInfo; + + if (!change) + return 0; + + /* Switch Change */ + dbg("cpqsbd: Switch interrupt received.\n"); + + for (hp_slot = 0; hp_slot < 6; hp_slot++) { + if (change & (0x1L << hp_slot)) { + /********************************** + * this one changed. + **********************************/ + func = cpqhp_slot_find(ctrl->bus, + (hp_slot + ctrl->slot_device_offset), 0); + + /* this is the structure that tells the worker thread + *what to do */ + taskInfo = &(ctrl->event_queue[ctrl->next_event]); + ctrl->next_event = (ctrl->next_event + 1) % 10; + taskInfo->hp_slot = hp_slot; + + rc++; + + temp_word = ctrl->ctrl_int_comp >> 16; + func->presence_save = (temp_word >> hp_slot) & 0x01; + func->presence_save |= (temp_word >> (hp_slot + 7)) & 0x02; + + if (ctrl->ctrl_int_comp & (0x1L << hp_slot)) { + /********************************** + * Switch opened + **********************************/ + + func->switch_save = 0; + + taskInfo->event_type = INT_SWITCH_OPEN; + } else { + /********************************** + * Switch closed + **********************************/ + + func->switch_save = 0x10; + + taskInfo->event_type = INT_SWITCH_CLOSE; + } + } + } + + return rc; +} + +/** + * cpqhp_find_slot - find the struct slot of given device + * @ctrl: scan lots of this controller + * @device: the device id to find + */ +static struct slot *cpqhp_find_slot(struct controller *ctrl, u8 device) +{ + struct slot *slot = ctrl->slot; + + while (slot && (slot->device != device)) { + slot = slot->next; + } + + return slot; +} + + +static u8 handle_presence_change(u16 change, struct controller * ctrl) +{ + int hp_slot; + u8 rc = 0; + u8 temp_byte; + u16 temp_word; + struct pci_func *func; + struct event_info *taskInfo; + struct slot *p_slot; + + if (!change) + return 0; + + /********************************** + * Presence Change + **********************************/ + dbg("cpqsbd: Presence/Notify input change.\n"); + dbg(" Changed bits are 0x%4.4x\n", change ); + + for (hp_slot = 0; hp_slot < 6; hp_slot++) { + if (change & (0x0101 << hp_slot)) { + /********************************** + * this one changed. + **********************************/ + func = cpqhp_slot_find(ctrl->bus, + (hp_slot + ctrl->slot_device_offset), 0); + + taskInfo = &(ctrl->event_queue[ctrl->next_event]); + ctrl->next_event = (ctrl->next_event + 1) % 10; + taskInfo->hp_slot = hp_slot; + + rc++; + + p_slot = cpqhp_find_slot(ctrl, hp_slot + (readb(ctrl->hpc_reg + SLOT_MASK) >> 4)); + if (!p_slot) + return 0; + + /* If the switch closed, must be a button + * If not in button mode, nevermind */ + if (func->switch_save && (ctrl->push_button == 1)) { + temp_word = ctrl->ctrl_int_comp >> 16; + temp_byte = (temp_word >> hp_slot) & 0x01; + temp_byte |= (temp_word >> (hp_slot + 7)) & 0x02; + + if (temp_byte != func->presence_save) { + /************************************** + * button Pressed (doesn't do anything) + **************************************/ + dbg("hp_slot %d button pressed\n", hp_slot); + taskInfo->event_type = INT_BUTTON_PRESS; + } else { + /********************************** + * button Released - TAKE ACTION!!!! + **********************************/ + dbg("hp_slot %d button released\n", hp_slot); + taskInfo->event_type = INT_BUTTON_RELEASE; + + /* Cancel if we are still blinking */ + if ((p_slot->state == BLINKINGON_STATE) + || (p_slot->state == BLINKINGOFF_STATE)) { + taskInfo->event_type = INT_BUTTON_CANCEL; + dbg("hp_slot %d button cancel\n", hp_slot); + } else if ((p_slot->state == POWERON_STATE) + || (p_slot->state == POWEROFF_STATE)) { + /* info(msg_button_ignore, p_slot->number); */ + taskInfo->event_type = INT_BUTTON_IGNORE; + dbg("hp_slot %d button ignore\n", hp_slot); + } + } + } else { + /* Switch is open, assume a presence change + * Save the presence state */ + temp_word = ctrl->ctrl_int_comp >> 16; + func->presence_save = (temp_word >> hp_slot) & 0x01; + func->presence_save |= (temp_word >> (hp_slot + 7)) & 0x02; + + if ((!(ctrl->ctrl_int_comp & (0x010000 << hp_slot))) || + (!(ctrl->ctrl_int_comp & (0x01000000 << hp_slot)))) { + /* Present */ + taskInfo->event_type = INT_PRESENCE_ON; + } else { + /* Not Present */ + taskInfo->event_type = INT_PRESENCE_OFF; + } + } + } + } + + return rc; +} + + +static u8 handle_power_fault(u8 change, struct controller * ctrl) +{ + int hp_slot; + u8 rc = 0; + struct pci_func *func; + struct event_info *taskInfo; + + if (!change) + return 0; + + /********************************** + * power fault + **********************************/ + + info("power fault interrupt\n"); + + for (hp_slot = 0; hp_slot < 6; hp_slot++) { + if (change & (0x01 << hp_slot)) { + /********************************** + * this one changed. + **********************************/ + func = cpqhp_slot_find(ctrl->bus, + (hp_slot + ctrl->slot_device_offset), 0); + + taskInfo = &(ctrl->event_queue[ctrl->next_event]); + ctrl->next_event = (ctrl->next_event + 1) % 10; + taskInfo->hp_slot = hp_slot; + + rc++; + + if (ctrl->ctrl_int_comp & (0x00000100 << hp_slot)) { + /********************************** + * power fault Cleared + **********************************/ + func->status = 0x00; + + taskInfo->event_type = INT_POWER_FAULT_CLEAR; + } else { + /********************************** + * power fault + **********************************/ + taskInfo->event_type = INT_POWER_FAULT; + + if (ctrl->rev < 4) { + amber_LED_on (ctrl, hp_slot); + green_LED_off (ctrl, hp_slot); + set_SOGO (ctrl); + + /* this is a fatal condition, we want + * to crash the machine to protect from + * data corruption. simulated_NMI + * shouldn't ever return */ + /* FIXME + simulated_NMI(hp_slot, ctrl); */ + + /* The following code causes a software + * crash just in case simulated_NMI did + * return */ + /*FIXME + panic(msg_power_fault); */ + } else { + /* set power fault status for this board */ + func->status = 0xFF; + info("power fault bit %x set\n", hp_slot); + } + } + } + } + + return rc; +} + + +/** + * sort_by_size - sort nodes on the list by their length, smallest first. + * @head: list to sort + */ +static int sort_by_size(struct pci_resource **head) +{ + struct pci_resource *current_res; + struct pci_resource *next_res; + int out_of_order = 1; + + if (!(*head)) + return 1; + + if (!((*head)->next)) + return 0; + + while (out_of_order) { + out_of_order = 0; + + /* Special case for swapping list head */ + if (((*head)->next) && + ((*head)->length > (*head)->next->length)) { + out_of_order++; + current_res = *head; + *head = (*head)->next; + current_res->next = (*head)->next; + (*head)->next = current_res; + } + + current_res = *head; + + while (current_res->next && current_res->next->next) { + if (current_res->next->length > current_res->next->next->length) { + out_of_order++; + next_res = current_res->next; + current_res->next = current_res->next->next; + current_res = current_res->next; + next_res->next = current_res->next; + current_res->next = next_res; + } else + current_res = current_res->next; + } + } /* End of out_of_order loop */ + + return 0; +} + + +/** + * sort_by_max_size - sort nodes on the list by their length, largest first. + * @head: list to sort + */ +static int sort_by_max_size(struct pci_resource **head) +{ + struct pci_resource *current_res; + struct pci_resource *next_res; + int out_of_order = 1; + + if (!(*head)) + return 1; + + if (!((*head)->next)) + return 0; + + while (out_of_order) { + out_of_order = 0; + + /* Special case for swapping list head */ + if (((*head)->next) && + ((*head)->length < (*head)->next->length)) { + out_of_order++; + current_res = *head; + *head = (*head)->next; + current_res->next = (*head)->next; + (*head)->next = current_res; + } + + current_res = *head; + + while (current_res->next && current_res->next->next) { + if (current_res->next->length < current_res->next->next->length) { + out_of_order++; + next_res = current_res->next; + current_res->next = current_res->next->next; + current_res = current_res->next; + next_res->next = current_res->next; + current_res->next = next_res; + } else + current_res = current_res->next; + } + } /* End of out_of_order loop */ + + return 0; +} + + +/** + * do_pre_bridge_resource_split - find node of resources that are unused + * @head: new list head + * @orig_head: original list head + * @alignment: max node size (?) + */ +static struct pci_resource *do_pre_bridge_resource_split(struct pci_resource **head, + struct pci_resource **orig_head, u32 alignment) +{ + struct pci_resource *prevnode = NULL; + struct pci_resource *node; + struct pci_resource *split_node; + u32 rc; + u32 temp_dword; + dbg("do_pre_bridge_resource_split\n"); + + if (!(*head) || !(*orig_head)) + return NULL; + + rc = cpqhp_resource_sort_and_combine(head); + + if (rc) + return NULL; + + if ((*head)->base != (*orig_head)->base) + return NULL; + + if ((*head)->length == (*orig_head)->length) + return NULL; + + + /* If we got here, there the bridge requires some of the resource, but + * we may be able to split some off of the front */ + + node = *head; + + if (node->length & (alignment -1)) { + /* this one isn't an aligned length, so we'll make a new entry + * and split it up. */ + split_node = kmalloc(sizeof(*split_node), GFP_KERNEL); + + if (!split_node) + return NULL; + + temp_dword = (node->length | (alignment-1)) + 1 - alignment; + + split_node->base = node->base; + split_node->length = temp_dword; + + node->length -= temp_dword; + node->base += split_node->length; + + /* Put it in the list */ + *head = split_node; + split_node->next = node; + } + + if (node->length < alignment) + return NULL; + + /* Now unlink it */ + if (*head == node) { + *head = node->next; + } else { + prevnode = *head; + while (prevnode->next != node) + prevnode = prevnode->next; + + prevnode->next = node->next; + } + node->next = NULL; + + return node; +} + + +/** + * do_bridge_resource_split - find one node of resources that aren't in use + * @head: list head + * @alignment: max node size (?) + */ +static struct pci_resource *do_bridge_resource_split(struct pci_resource **head, u32 alignment) +{ + struct pci_resource *prevnode = NULL; + struct pci_resource *node; + u32 rc; + u32 temp_dword; + + rc = cpqhp_resource_sort_and_combine(head); + + if (rc) + return NULL; + + node = *head; + + while (node->next) { + prevnode = node; + node = node->next; + kfree(prevnode); + } + + if (node->length < alignment) + goto error; + + if (node->base & (alignment - 1)) { + /* Short circuit if adjusted size is too small */ + temp_dword = (node->base | (alignment-1)) + 1; + if ((node->length - (temp_dword - node->base)) < alignment) + goto error; + + node->length -= (temp_dword - node->base); + node->base = temp_dword; + } + + if (node->length & (alignment - 1)) + /* There's stuff in use after this node */ + goto error; + + return node; +error: + kfree(node); + return NULL; +} + + +/** + * get_io_resource - find first node of given size not in ISA aliasing window. + * @head: list to search + * @size: size of node to find, must be a power of two. + * + * Description: This function sorts the resource list by size and then returns + * returns the first node of "size" length that is not in the ISA aliasing + * window. If it finds a node larger than "size" it will split it up. + */ +static struct pci_resource *get_io_resource(struct pci_resource **head, u32 size) +{ + struct pci_resource *prevnode; + struct pci_resource *node; + struct pci_resource *split_node; + u32 temp_dword; + + if (!(*head)) + return NULL; + + if ( cpqhp_resource_sort_and_combine(head) ) + return NULL; + + if ( sort_by_size(head) ) + return NULL; + + for (node = *head; node; node = node->next) { + if (node->length < size) + continue; + + if (node->base & (size - 1)) { + /* this one isn't base aligned properly + * so we'll make a new entry and split it up */ + temp_dword = (node->base | (size-1)) + 1; + + /* Short circuit if adjusted size is too small */ + if ((node->length - (temp_dword - node->base)) < size) + continue; + + split_node = kmalloc(sizeof(*split_node), GFP_KERNEL); + + if (!split_node) + return NULL; + + split_node->base = node->base; + split_node->length = temp_dword - node->base; + node->base = temp_dword; + node->length -= split_node->length; + + /* Put it in the list */ + split_node->next = node->next; + node->next = split_node; + } /* End of non-aligned base */ + + /* Don't need to check if too small since we already did */ + if (node->length > size) { + /* this one is longer than we need + * so we'll make a new entry and split it up */ + split_node = kmalloc(sizeof(*split_node), GFP_KERNEL); + + if (!split_node) + return NULL; + + split_node->base = node->base + size; + split_node->length = node->length - size; + node->length = size; + + /* Put it in the list */ + split_node->next = node->next; + node->next = split_node; + } /* End of too big on top end */ + + /* For IO make sure it's not in the ISA aliasing space */ + if (node->base & 0x300L) + continue; + + /* If we got here, then it is the right size + * Now take it out of the list and break */ + if (*head == node) { + *head = node->next; + } else { + prevnode = *head; + while (prevnode->next != node) + prevnode = prevnode->next; + + prevnode->next = node->next; + } + node->next = NULL; + break; + } + + return node; +} + + +/** + * get_max_resource - get largest node which has at least the given size. + * @head: the list to search the node in + * @size: the minimum size of the node to find + * + * Description: Gets the largest node that is at least "size" big from the + * list pointed to by head. It aligns the node on top and bottom + * to "size" alignment before returning it. + */ +static struct pci_resource *get_max_resource(struct pci_resource **head, u32 size) +{ + struct pci_resource *max; + struct pci_resource *temp; + struct pci_resource *split_node; + u32 temp_dword; + + if (cpqhp_resource_sort_and_combine(head)) + return NULL; + + if (sort_by_max_size(head)) + return NULL; + + for (max = *head; max; max = max->next) { + /* If not big enough we could probably just bail, + * instead we'll continue to the next. */ + if (max->length < size) + continue; + + if (max->base & (size - 1)) { + /* this one isn't base aligned properly + * so we'll make a new entry and split it up */ + temp_dword = (max->base | (size-1)) + 1; + + /* Short circuit if adjusted size is too small */ + if ((max->length - (temp_dword - max->base)) < size) + continue; + + split_node = kmalloc(sizeof(*split_node), GFP_KERNEL); + + if (!split_node) + return NULL; + + split_node->base = max->base; + split_node->length = temp_dword - max->base; + max->base = temp_dword; + max->length -= split_node->length; + + split_node->next = max->next; + max->next = split_node; + } + + if ((max->base + max->length) & (size - 1)) { + /* this one isn't end aligned properly at the top + * so we'll make a new entry and split it up */ + split_node = kmalloc(sizeof(*split_node), GFP_KERNEL); + + if (!split_node) + return NULL; + temp_dword = ((max->base + max->length) & ~(size - 1)); + split_node->base = temp_dword; + split_node->length = max->length + max->base + - split_node->base; + max->length -= split_node->length; + + split_node->next = max->next; + max->next = split_node; + } + + /* Make sure it didn't shrink too much when we aligned it */ + if (max->length < size) + continue; + + /* Now take it out of the list */ + temp = *head; + if (temp == max) { + *head = max->next; + } else { + while (temp && temp->next != max) { + temp = temp->next; + } + + temp->next = max->next; + } + + max->next = NULL; + break; + } + + return max; +} + + +/** + * get_resource - find resource of given size and split up larger ones. + * @head: the list to search for resources + * @size: the size limit to use + * + * Description: This function sorts the resource list by size and then + * returns the first node of "size" length. If it finds a node + * larger than "size" it will split it up. + * + * size must be a power of two. + */ +static struct pci_resource *get_resource(struct pci_resource **head, u32 size) +{ + struct pci_resource *prevnode; + struct pci_resource *node; + struct pci_resource *split_node; + u32 temp_dword; + + if (cpqhp_resource_sort_and_combine(head)) + return NULL; + + if (sort_by_size(head)) + return NULL; + + for (node = *head; node; node = node->next) { + dbg("%s: req_size =%x node=%p, base=%x, length=%x\n", + __func__, size, node, node->base, node->length); + if (node->length < size) + continue; + + if (node->base & (size - 1)) { + dbg("%s: not aligned\n", __func__); + /* this one isn't base aligned properly + * so we'll make a new entry and split it up */ + temp_dword = (node->base | (size-1)) + 1; + + /* Short circuit if adjusted size is too small */ + if ((node->length - (temp_dword - node->base)) < size) + continue; + + split_node = kmalloc(sizeof(*split_node), GFP_KERNEL); + + if (!split_node) + return NULL; + + split_node->base = node->base; + split_node->length = temp_dword - node->base; + node->base = temp_dword; + node->length -= split_node->length; + + split_node->next = node->next; + node->next = split_node; + } /* End of non-aligned base */ + + /* Don't need to check if too small since we already did */ + if (node->length > size) { + dbg("%s: too big\n", __func__); + /* this one is longer than we need + * so we'll make a new entry and split it up */ + split_node = kmalloc(sizeof(*split_node), GFP_KERNEL); + + if (!split_node) + return NULL; + + split_node->base = node->base + size; + split_node->length = node->length - size; + node->length = size; + + /* Put it in the list */ + split_node->next = node->next; + node->next = split_node; + } /* End of too big on top end */ + + dbg("%s: got one!!!\n", __func__); + /* If we got here, then it is the right size + * Now take it out of the list */ + if (*head == node) { + *head = node->next; + } else { + prevnode = *head; + while (prevnode->next != node) + prevnode = prevnode->next; + + prevnode->next = node->next; + } + node->next = NULL; + break; + } + return node; +} + + +/** + * cpqhp_resource_sort_and_combine - sort nodes by base addresses and clean up + * @head: the list to sort and clean up + * + * Description: Sorts all of the nodes in the list in ascending order by + * their base addresses. Also does garbage collection by + * combining adjacent nodes. + * + * Returns %0 if success. + */ +int cpqhp_resource_sort_and_combine(struct pci_resource **head) +{ + struct pci_resource *node1; + struct pci_resource *node2; + int out_of_order = 1; + + dbg("%s: head = %p, *head = %p\n", __func__, head, *head); + + if (!(*head)) + return 1; + + dbg("*head->next = %p\n",(*head)->next); + + if (!(*head)->next) + return 0; /* only one item on the list, already sorted! */ + + dbg("*head->base = 0x%x\n",(*head)->base); + dbg("*head->next->base = 0x%x\n",(*head)->next->base); + while (out_of_order) { + out_of_order = 0; + + /* Special case for swapping list head */ + if (((*head)->next) && + ((*head)->base > (*head)->next->base)) { + node1 = *head; + (*head) = (*head)->next; + node1->next = (*head)->next; + (*head)->next = node1; + out_of_order++; + } + + node1 = (*head); + + while (node1->next && node1->next->next) { + if (node1->next->base > node1->next->next->base) { + out_of_order++; + node2 = node1->next; + node1->next = node1->next->next; + node1 = node1->next; + node2->next = node1->next; + node1->next = node2; + } else + node1 = node1->next; + } + } /* End of out_of_order loop */ + + node1 = *head; + + while (node1 && node1->next) { + if ((node1->base + node1->length) == node1->next->base) { + /* Combine */ + dbg("8..\n"); + node1->length += node1->next->length; + node2 = node1->next; + node1->next = node1->next->next; + kfree(node2); + } else + node1 = node1->next; + } + + return 0; +} + + +irqreturn_t cpqhp_ctrl_intr(int IRQ, void *data) +{ + struct controller *ctrl = data; + u8 schedule_flag = 0; + u8 reset; + u16 misc; + u32 Diff; + u32 temp_dword; + + + misc = readw(ctrl->hpc_reg + MISC); + /*************************************** + * Check to see if it was our interrupt + ***************************************/ + if (!(misc & 0x000C)) { + return IRQ_NONE; + } + + if (misc & 0x0004) { + /********************************** + * Serial Output interrupt Pending + **********************************/ + + /* Clear the interrupt */ + misc |= 0x0004; + writew(misc, ctrl->hpc_reg + MISC); + + /* Read to clear posted writes */ + misc = readw(ctrl->hpc_reg + MISC); + + dbg ("%s - waking up\n", __func__); + wake_up_interruptible(&ctrl->queue); + } + + if (misc & 0x0008) { + /* General-interrupt-input interrupt Pending */ + Diff = readl(ctrl->hpc_reg + INT_INPUT_CLEAR) ^ ctrl->ctrl_int_comp; + + ctrl->ctrl_int_comp = readl(ctrl->hpc_reg + INT_INPUT_CLEAR); + + /* Clear the interrupt */ + writel(Diff, ctrl->hpc_reg + INT_INPUT_CLEAR); + + /* Read it back to clear any posted writes */ + temp_dword = readl(ctrl->hpc_reg + INT_INPUT_CLEAR); + + if (!Diff) + /* Clear all interrupts */ + writel(0xFFFFFFFF, ctrl->hpc_reg + INT_INPUT_CLEAR); + + schedule_flag += handle_switch_change((u8)(Diff & 0xFFL), ctrl); + schedule_flag += handle_presence_change((u16)((Diff & 0xFFFF0000L) >> 16), ctrl); + schedule_flag += handle_power_fault((u8)((Diff & 0xFF00L) >> 8), ctrl); + } + + reset = readb(ctrl->hpc_reg + RESET_FREQ_MODE); + if (reset & 0x40) { + /* Bus reset has completed */ + reset &= 0xCF; + writeb(reset, ctrl->hpc_reg + RESET_FREQ_MODE); + reset = readb(ctrl->hpc_reg + RESET_FREQ_MODE); + wake_up_interruptible(&ctrl->queue); + } + + if (schedule_flag) { + wake_up_process(cpqhp_event_thread); + dbg("Waking even thread"); + } + return IRQ_HANDLED; +} + + +/** + * cpqhp_slot_create - Creates a node and adds it to the proper bus. + * @busnumber: bus where new node is to be located + * + * Returns pointer to the new node or %NULL if unsuccessful. + */ +struct pci_func *cpqhp_slot_create(u8 busnumber) +{ + struct pci_func *new_slot; + struct pci_func *next; + + new_slot = kzalloc(sizeof(*new_slot), GFP_KERNEL); + if (new_slot == NULL) { + /* I'm not dead yet! + * You will be. */ + return new_slot; + } + + new_slot->next = NULL; + new_slot->configured = 1; + + if (cpqhp_slot_list[busnumber] == NULL) { + cpqhp_slot_list[busnumber] = new_slot; + } else { + next = cpqhp_slot_list[busnumber]; + while (next->next != NULL) + next = next->next; + next->next = new_slot; + } + return new_slot; +} + + +/** + * slot_remove - Removes a node from the linked list of slots. + * @old_slot: slot to remove + * + * Returns %0 if successful, !0 otherwise. + */ +static int slot_remove(struct pci_func * old_slot) +{ + struct pci_func *next; + + if (old_slot == NULL) + return 1; + + next = cpqhp_slot_list[old_slot->bus]; + + if (next == NULL) { + return 1; + } + + if (next == old_slot) { + cpqhp_slot_list[old_slot->bus] = old_slot->next; + cpqhp_destroy_board_resources(old_slot); + kfree(old_slot); + return 0; + } + + while ((next->next != old_slot) && (next->next != NULL)) { + next = next->next; + } + + if (next->next == old_slot) { + next->next = old_slot->next; + cpqhp_destroy_board_resources(old_slot); + kfree(old_slot); + return 0; + } else + return 2; +} + + +/** + * bridge_slot_remove - Removes a node from the linked list of slots. + * @bridge: bridge to remove + * + * Returns %0 if successful, !0 otherwise. + */ +static int bridge_slot_remove(struct pci_func *bridge) +{ + u8 subordinateBus, secondaryBus; + u8 tempBus; + struct pci_func *next; + + secondaryBus = (bridge->config_space[0x06] >> 8) & 0xFF; + subordinateBus = (bridge->config_space[0x06] >> 16) & 0xFF; + + for (tempBus = secondaryBus; tempBus <= subordinateBus; tempBus++) { + next = cpqhp_slot_list[tempBus]; + + while (!slot_remove(next)) { + next = cpqhp_slot_list[tempBus]; + } + } + + next = cpqhp_slot_list[bridge->bus]; + + if (next == NULL) + return 1; + + if (next == bridge) { + cpqhp_slot_list[bridge->bus] = bridge->next; + goto out; + } + + while ((next->next != bridge) && (next->next != NULL)) + next = next->next; + + if (next->next != bridge) + return 2; + next->next = bridge->next; +out: + kfree(bridge); + return 0; +} + + +/** + * cpqhp_slot_find - Looks for a node by bus, and device, multiple functions accessed + * @bus: bus to find + * @device: device to find + * @index: is %0 for first function found, %1 for the second... + * + * Returns pointer to the node if successful, %NULL otherwise. + */ +struct pci_func *cpqhp_slot_find(u8 bus, u8 device, u8 index) +{ + int found = -1; + struct pci_func *func; + + func = cpqhp_slot_list[bus]; + + if ((func == NULL) || ((func->device == device) && (index == 0))) + return func; + + if (func->device == device) + found++; + + while (func->next != NULL) { + func = func->next; + + if (func->device == device) + found++; + + if (found == index) + return func; + } + + return NULL; +} + + +/* DJZ: I don't think is_bridge will work as is. + * FIXME */ +static int is_bridge(struct pci_func * func) +{ + /* Check the header type */ + if (((func->config_space[0x03] >> 16) & 0xFF) == 0x01) + return 1; + else + return 0; +} + + +/** + * set_controller_speed - set the frequency and/or mode of a specific controller segment. + * @ctrl: controller to change frequency/mode for. + * @adapter_speed: the speed of the adapter we want to match. + * @hp_slot: the slot number where the adapter is installed. + * + * Returns %0 if we successfully change frequency and/or mode to match the + * adapter speed. + */ +static u8 set_controller_speed(struct controller *ctrl, u8 adapter_speed, u8 hp_slot) +{ + struct slot *slot; + u8 reg; + u8 slot_power = readb(ctrl->hpc_reg + SLOT_POWER); + u16 reg16; + u32 leds = readl(ctrl->hpc_reg + LED_CONTROL); + + if (ctrl->speed == adapter_speed) + return 0; + + /* We don't allow freq/mode changes if we find another adapter running + * in another slot on this controller */ + for(slot = ctrl->slot; slot; slot = slot->next) { + if (slot->device == (hp_slot + ctrl->slot_device_offset)) + continue; + if (!slot->hotplug_slot || !slot->hotplug_slot->info) + continue; + if (slot->hotplug_slot->info->adapter_status == 0) + continue; + /* If another adapter is running on the same segment but at a + * lower speed/mode, we allow the new adapter to function at + * this rate if supported */ + if (ctrl->speed < adapter_speed) + return 0; + + return 1; + } + + /* If the controller doesn't support freq/mode changes and the + * controller is running at a higher mode, we bail */ + if ((ctrl->speed > adapter_speed) && (!ctrl->pcix_speed_capability)) + return 1; + + /* But we allow the adapter to run at a lower rate if possible */ + if ((ctrl->speed < adapter_speed) && (!ctrl->pcix_speed_capability)) + return 0; + + /* We try to set the max speed supported by both the adapter and + * controller */ + if (ctrl->speed_capability < adapter_speed) { + if (ctrl->speed == ctrl->speed_capability) + return 0; + adapter_speed = ctrl->speed_capability; + } + + writel(0x0L, ctrl->hpc_reg + LED_CONTROL); + writeb(0x00, ctrl->hpc_reg + SLOT_ENABLE); + + set_SOGO(ctrl); + wait_for_ctrl_irq(ctrl); + + if (adapter_speed != PCI_SPEED_133MHz_PCIX) + reg = 0xF5; + else + reg = 0xF4; + pci_write_config_byte(ctrl->pci_dev, 0x41, reg); + + reg16 = readw(ctrl->hpc_reg + NEXT_CURR_FREQ); + reg16 &= ~0x000F; + switch(adapter_speed) { + case(PCI_SPEED_133MHz_PCIX): + reg = 0x75; + reg16 |= 0xB; + break; + case(PCI_SPEED_100MHz_PCIX): + reg = 0x74; + reg16 |= 0xA; + break; + case(PCI_SPEED_66MHz_PCIX): + reg = 0x73; + reg16 |= 0x9; + break; + case(PCI_SPEED_66MHz): + reg = 0x73; + reg16 |= 0x1; + break; + default: /* 33MHz PCI 2.2 */ + reg = 0x71; + break; + + } + reg16 |= 0xB << 12; + writew(reg16, ctrl->hpc_reg + NEXT_CURR_FREQ); + + mdelay(5); + + /* Reenable interrupts */ + writel(0, ctrl->hpc_reg + INT_MASK); + + pci_write_config_byte(ctrl->pci_dev, 0x41, reg); + + /* Restart state machine */ + reg = ~0xF; + pci_read_config_byte(ctrl->pci_dev, 0x43, ®); + pci_write_config_byte(ctrl->pci_dev, 0x43, reg); + + /* Only if mode change...*/ + if (((ctrl->speed == PCI_SPEED_66MHz) && (adapter_speed == PCI_SPEED_66MHz_PCIX)) || + ((ctrl->speed == PCI_SPEED_66MHz_PCIX) && (adapter_speed == PCI_SPEED_66MHz))) + set_SOGO(ctrl); + + wait_for_ctrl_irq(ctrl); + mdelay(1100); + + /* Restore LED/Slot state */ + writel(leds, ctrl->hpc_reg + LED_CONTROL); + writeb(slot_power, ctrl->hpc_reg + SLOT_ENABLE); + + set_SOGO(ctrl); + wait_for_ctrl_irq(ctrl); + + ctrl->speed = adapter_speed; + slot = cpqhp_find_slot(ctrl, hp_slot + ctrl->slot_device_offset); + + info("Successfully changed frequency/mode for adapter in slot %d\n", + slot->number); + return 0; +} + +/* the following routines constitute the bulk of the + hotplug controller logic + */ + + +/** + * board_replaced - Called after a board has been replaced in the system. + * @func: PCI device/function information + * @ctrl: hotplug controller + * + * This is only used if we don't have resources for hot add. + * Turns power on for the board. + * Checks to see if board is the same. + * If board is same, reconfigures it. + * If board isn't same, turns it back off. + */ +static u32 board_replaced(struct pci_func *func, struct controller *ctrl) +{ + u8 hp_slot; + u8 temp_byte; + u8 adapter_speed; + u32 rc = 0; + + hp_slot = func->device - ctrl->slot_device_offset; + + if (readl(ctrl->hpc_reg + INT_INPUT_CLEAR) & (0x01L << hp_slot)) { + /********************************** + * The switch is open. + **********************************/ + rc = INTERLOCK_OPEN; + } else if (is_slot_enabled (ctrl, hp_slot)) { + /********************************** + * The board is already on + **********************************/ + rc = CARD_FUNCTIONING; + } else { + mutex_lock(&ctrl->crit_sect); + + /* turn on board without attaching to the bus */ + enable_slot_power (ctrl, hp_slot); + + set_SOGO(ctrl); + + /* Wait for SOBS to be unset */ + wait_for_ctrl_irq (ctrl); + + /* Change bits in slot power register to force another shift out + * NOTE: this is to work around the timer bug */ + temp_byte = readb(ctrl->hpc_reg + SLOT_POWER); + writeb(0x00, ctrl->hpc_reg + SLOT_POWER); + writeb(temp_byte, ctrl->hpc_reg + SLOT_POWER); + + set_SOGO(ctrl); + + /* Wait for SOBS to be unset */ + wait_for_ctrl_irq (ctrl); + + adapter_speed = get_adapter_speed(ctrl, hp_slot); + if (ctrl->speed != adapter_speed) + if (set_controller_speed(ctrl, adapter_speed, hp_slot)) + rc = WRONG_BUS_FREQUENCY; + + /* turn off board without attaching to the bus */ + disable_slot_power (ctrl, hp_slot); + + set_SOGO(ctrl); + + /* Wait for SOBS to be unset */ + wait_for_ctrl_irq (ctrl); + + mutex_unlock(&ctrl->crit_sect); + + if (rc) + return rc; + + mutex_lock(&ctrl->crit_sect); + + slot_enable (ctrl, hp_slot); + green_LED_blink (ctrl, hp_slot); + + amber_LED_off (ctrl, hp_slot); + + set_SOGO(ctrl); + + /* Wait for SOBS to be unset */ + wait_for_ctrl_irq (ctrl); + + mutex_unlock(&ctrl->crit_sect); + + /* Wait for ~1 second because of hot plug spec */ + long_delay(1*HZ); + + /* Check for a power fault */ + if (func->status == 0xFF) { + /* power fault occurred, but it was benign */ + rc = POWER_FAILURE; + func->status = 0; + } else + rc = cpqhp_valid_replace(ctrl, func); + + if (!rc) { + /* It must be the same board */ + + rc = cpqhp_configure_board(ctrl, func); + + /* If configuration fails, turn it off + * Get slot won't work for devices behind + * bridges, but in this case it will always be + * called for the "base" bus/dev/func of an + * adapter. */ + + mutex_lock(&ctrl->crit_sect); + + amber_LED_on (ctrl, hp_slot); + green_LED_off (ctrl, hp_slot); + slot_disable (ctrl, hp_slot); + + set_SOGO(ctrl); + + /* Wait for SOBS to be unset */ + wait_for_ctrl_irq (ctrl); + + mutex_unlock(&ctrl->crit_sect); + + if (rc) + return rc; + else + return 1; + + } else { + /* Something is wrong + + * Get slot won't work for devices behind bridges, but + * in this case it will always be called for the "base" + * bus/dev/func of an adapter. */ + + mutex_lock(&ctrl->crit_sect); + + amber_LED_on (ctrl, hp_slot); + green_LED_off (ctrl, hp_slot); + slot_disable (ctrl, hp_slot); + + set_SOGO(ctrl); + + /* Wait for SOBS to be unset */ + wait_for_ctrl_irq (ctrl); + + mutex_unlock(&ctrl->crit_sect); + } + + } + return rc; + +} + + +/** + * board_added - Called after a board has been added to the system. + * @func: PCI device/function info + * @ctrl: hotplug controller + * + * Turns power on for the board. + * Configures board. + */ +static u32 board_added(struct pci_func *func, struct controller *ctrl) +{ + u8 hp_slot; + u8 temp_byte; + u8 adapter_speed; + int index; + u32 temp_register = 0xFFFFFFFF; + u32 rc = 0; + struct pci_func *new_slot = NULL; + struct slot *p_slot; + struct resource_lists res_lists; + + hp_slot = func->device - ctrl->slot_device_offset; + dbg("%s: func->device, slot_offset, hp_slot = %d, %d ,%d\n", + __func__, func->device, ctrl->slot_device_offset, hp_slot); + + mutex_lock(&ctrl->crit_sect); + + /* turn on board without attaching to the bus */ + enable_slot_power(ctrl, hp_slot); + + set_SOGO(ctrl); + + /* Wait for SOBS to be unset */ + wait_for_ctrl_irq (ctrl); + + /* Change bits in slot power register to force another shift out + * NOTE: this is to work around the timer bug */ + temp_byte = readb(ctrl->hpc_reg + SLOT_POWER); + writeb(0x00, ctrl->hpc_reg + SLOT_POWER); + writeb(temp_byte, ctrl->hpc_reg + SLOT_POWER); + + set_SOGO(ctrl); + + /* Wait for SOBS to be unset */ + wait_for_ctrl_irq (ctrl); + + adapter_speed = get_adapter_speed(ctrl, hp_slot); + if (ctrl->speed != adapter_speed) + if (set_controller_speed(ctrl, adapter_speed, hp_slot)) + rc = WRONG_BUS_FREQUENCY; + + /* turn off board without attaching to the bus */ + disable_slot_power (ctrl, hp_slot); + + set_SOGO(ctrl); + + /* Wait for SOBS to be unset */ + wait_for_ctrl_irq(ctrl); + + mutex_unlock(&ctrl->crit_sect); + + if (rc) + return rc; + + p_slot = cpqhp_find_slot(ctrl, hp_slot + ctrl->slot_device_offset); + + /* turn on board and blink green LED */ + + dbg("%s: before down\n", __func__); + mutex_lock(&ctrl->crit_sect); + dbg("%s: after down\n", __func__); + + dbg("%s: before slot_enable\n", __func__); + slot_enable (ctrl, hp_slot); + + dbg("%s: before green_LED_blink\n", __func__); + green_LED_blink (ctrl, hp_slot); + + dbg("%s: before amber_LED_blink\n", __func__); + amber_LED_off (ctrl, hp_slot); + + dbg("%s: before set_SOGO\n", __func__); + set_SOGO(ctrl); + + /* Wait for SOBS to be unset */ + dbg("%s: before wait_for_ctrl_irq\n", __func__); + wait_for_ctrl_irq (ctrl); + dbg("%s: after wait_for_ctrl_irq\n", __func__); + + dbg("%s: before up\n", __func__); + mutex_unlock(&ctrl->crit_sect); + dbg("%s: after up\n", __func__); + + /* Wait for ~1 second because of hot plug spec */ + dbg("%s: before long_delay\n", __func__); + long_delay(1*HZ); + dbg("%s: after long_delay\n", __func__); + + dbg("%s: func status = %x\n", __func__, func->status); + /* Check for a power fault */ + if (func->status == 0xFF) { + /* power fault occurred, but it was benign */ + temp_register = 0xFFFFFFFF; + dbg("%s: temp register set to %x by power fault\n", __func__, temp_register); + rc = POWER_FAILURE; + func->status = 0; + } else { + /* Get vendor/device ID u32 */ + ctrl->pci_bus->number = func->bus; + rc = pci_bus_read_config_dword (ctrl->pci_bus, PCI_DEVFN(func->device, func->function), PCI_VENDOR_ID, &temp_register); + dbg("%s: pci_read_config_dword returns %d\n", __func__, rc); + dbg("%s: temp_register is %x\n", __func__, temp_register); + + if (rc != 0) { + /* Something's wrong here */ + temp_register = 0xFFFFFFFF; + dbg("%s: temp register set to %x by error\n", __func__, temp_register); + } + /* Preset return code. It will be changed later if things go okay. */ + rc = NO_ADAPTER_PRESENT; + } + + /* All F's is an empty slot or an invalid board */ + if (temp_register != 0xFFFFFFFF) { /* Check for a board in the slot */ + res_lists.io_head = ctrl->io_head; + res_lists.mem_head = ctrl->mem_head; + res_lists.p_mem_head = ctrl->p_mem_head; + res_lists.bus_head = ctrl->bus_head; + res_lists.irqs = NULL; + + rc = configure_new_device(ctrl, func, 0, &res_lists); + + dbg("%s: back from configure_new_device\n", __func__); + ctrl->io_head = res_lists.io_head; + ctrl->mem_head = res_lists.mem_head; + ctrl->p_mem_head = res_lists.p_mem_head; + ctrl->bus_head = res_lists.bus_head; + + cpqhp_resource_sort_and_combine(&(ctrl->mem_head)); + cpqhp_resource_sort_and_combine(&(ctrl->p_mem_head)); + cpqhp_resource_sort_and_combine(&(ctrl->io_head)); + cpqhp_resource_sort_and_combine(&(ctrl->bus_head)); + + if (rc) { + mutex_lock(&ctrl->crit_sect); + + amber_LED_on (ctrl, hp_slot); + green_LED_off (ctrl, hp_slot); + slot_disable (ctrl, hp_slot); + + set_SOGO(ctrl); + + /* Wait for SOBS to be unset */ + wait_for_ctrl_irq (ctrl); + + mutex_unlock(&ctrl->crit_sect); + return rc; + } else { + cpqhp_save_slot_config(ctrl, func); + } + + + func->status = 0; + func->switch_save = 0x10; + func->is_a_board = 0x01; + + /* next, we will instantiate the linux pci_dev structures (with + * appropriate driver notification, if already present) */ + dbg("%s: configure linux pci_dev structure\n", __func__); + index = 0; + do { + new_slot = cpqhp_slot_find(ctrl->bus, func->device, index++); + if (new_slot && !new_slot->pci_dev) { + cpqhp_configure_device(ctrl, new_slot); + } + } while (new_slot); + + mutex_lock(&ctrl->crit_sect); + + green_LED_on (ctrl, hp_slot); + + set_SOGO(ctrl); + + /* Wait for SOBS to be unset */ + wait_for_ctrl_irq (ctrl); + + mutex_unlock(&ctrl->crit_sect); + } else { + mutex_lock(&ctrl->crit_sect); + + amber_LED_on (ctrl, hp_slot); + green_LED_off (ctrl, hp_slot); + slot_disable (ctrl, hp_slot); + + set_SOGO(ctrl); + + /* Wait for SOBS to be unset */ + wait_for_ctrl_irq (ctrl); + + mutex_unlock(&ctrl->crit_sect); + + return rc; + } + return 0; +} + + +/** + * remove_board - Turns off slot and LEDs + * @func: PCI device/function info + * @replace_flag: whether replacing or adding a new device + * @ctrl: target controller + */ +static u32 remove_board(struct pci_func * func, u32 replace_flag, struct controller * ctrl) +{ + int index; + u8 skip = 0; + u8 device; + u8 hp_slot; + u8 temp_byte; + u32 rc; + struct resource_lists res_lists; + struct pci_func *temp_func; + + if (cpqhp_unconfigure_device(func)) + return 1; + + device = func->device; + + hp_slot = func->device - ctrl->slot_device_offset; + dbg("In %s, hp_slot = %d\n", __func__, hp_slot); + + /* When we get here, it is safe to change base address registers. + * We will attempt to save the base address register lengths */ + if (replace_flag || !ctrl->add_support) + rc = cpqhp_save_base_addr_length(ctrl, func); + else if (!func->bus_head && !func->mem_head && + !func->p_mem_head && !func->io_head) { + /* Here we check to see if we've saved any of the board's + * resources already. If so, we'll skip the attempt to + * determine what's being used. */ + index = 0; + temp_func = cpqhp_slot_find(func->bus, func->device, index++); + while (temp_func) { + if (temp_func->bus_head || temp_func->mem_head + || temp_func->p_mem_head || temp_func->io_head) { + skip = 1; + break; + } + temp_func = cpqhp_slot_find(temp_func->bus, temp_func->device, index++); + } + + if (!skip) + rc = cpqhp_save_used_resources(ctrl, func); + } + /* Change status to shutdown */ + if (func->is_a_board) + func->status = 0x01; + func->configured = 0; + + mutex_lock(&ctrl->crit_sect); + + green_LED_off (ctrl, hp_slot); + slot_disable (ctrl, hp_slot); + + set_SOGO(ctrl); + + /* turn off SERR for slot */ + temp_byte = readb(ctrl->hpc_reg + SLOT_SERR); + temp_byte &= ~(0x01 << hp_slot); + writeb(temp_byte, ctrl->hpc_reg + SLOT_SERR); + + /* Wait for SOBS to be unset */ + wait_for_ctrl_irq (ctrl); + + mutex_unlock(&ctrl->crit_sect); + + if (!replace_flag && ctrl->add_support) { + while (func) { + res_lists.io_head = ctrl->io_head; + res_lists.mem_head = ctrl->mem_head; + res_lists.p_mem_head = ctrl->p_mem_head; + res_lists.bus_head = ctrl->bus_head; + + cpqhp_return_board_resources(func, &res_lists); + + ctrl->io_head = res_lists.io_head; + ctrl->mem_head = res_lists.mem_head; + ctrl->p_mem_head = res_lists.p_mem_head; + ctrl->bus_head = res_lists.bus_head; + + cpqhp_resource_sort_and_combine(&(ctrl->mem_head)); + cpqhp_resource_sort_and_combine(&(ctrl->p_mem_head)); + cpqhp_resource_sort_and_combine(&(ctrl->io_head)); + cpqhp_resource_sort_and_combine(&(ctrl->bus_head)); + + if (is_bridge(func)) { + bridge_slot_remove(func); + } else + slot_remove(func); + + func = cpqhp_slot_find(ctrl->bus, device, 0); + } + + /* Setup slot structure with entry for empty slot */ + func = cpqhp_slot_create(ctrl->bus); + + if (func == NULL) + return 1; + + func->bus = ctrl->bus; + func->device = device; + func->function = 0; + func->configured = 0; + func->switch_save = 0x10; + func->is_a_board = 0; + func->p_task_event = NULL; + } + + return 0; +} + +static void pushbutton_helper_thread(unsigned long data) +{ + pushbutton_pending = data; + wake_up_process(cpqhp_event_thread); +} + + +/* this is the main worker thread */ +static int event_thread(void* data) +{ + struct controller *ctrl; + + while (1) { + dbg("!!!!event_thread sleeping\n"); + set_current_state(TASK_INTERRUPTIBLE); + schedule(); + + if (kthread_should_stop()) + break; + /* Do stuff here */ + if (pushbutton_pending) + cpqhp_pushbutton_thread(pushbutton_pending); + else + for (ctrl = cpqhp_ctrl_list; ctrl; ctrl=ctrl->next) + interrupt_event_handler(ctrl); + } + dbg("event_thread signals exit\n"); + return 0; +} + +int cpqhp_event_start_thread(void) +{ + cpqhp_event_thread = kthread_run(event_thread, NULL, "phpd_event"); + if (IS_ERR(cpqhp_event_thread)) { + err ("Can't start up our event thread\n"); + return PTR_ERR(cpqhp_event_thread); + } + + return 0; +} + + +void cpqhp_event_stop_thread(void) +{ + kthread_stop(cpqhp_event_thread); +} + + +static int update_slot_info(struct controller *ctrl, struct slot *slot) +{ + struct hotplug_slot_info *info; + int result; + + info = kmalloc(sizeof(*info), GFP_KERNEL); + if (!info) + return -ENOMEM; + + info->power_status = get_slot_enabled(ctrl, slot); + info->attention_status = cpq_get_attention_status(ctrl, slot); + info->latch_status = cpq_get_latch_status(ctrl, slot); + info->adapter_status = get_presence_status(ctrl, slot); + result = pci_hp_change_slot_info(slot->hotplug_slot, info); + kfree (info); + return result; +} + +static void interrupt_event_handler(struct controller *ctrl) +{ + int loop = 0; + int change = 1; + struct pci_func *func; + u8 hp_slot; + struct slot *p_slot; + + while (change) { + change = 0; + + for (loop = 0; loop < 10; loop++) { + /* dbg("loop %d\n", loop); */ + if (ctrl->event_queue[loop].event_type != 0) { + hp_slot = ctrl->event_queue[loop].hp_slot; + + func = cpqhp_slot_find(ctrl->bus, (hp_slot + ctrl->slot_device_offset), 0); + if (!func) + return; + + p_slot = cpqhp_find_slot(ctrl, hp_slot + ctrl->slot_device_offset); + if (!p_slot) + return; + + dbg("hp_slot %d, func %p, p_slot %p\n", + hp_slot, func, p_slot); + + if (ctrl->event_queue[loop].event_type == INT_BUTTON_PRESS) { + dbg("button pressed\n"); + } else if (ctrl->event_queue[loop].event_type == + INT_BUTTON_CANCEL) { + dbg("button cancel\n"); + del_timer(&p_slot->task_event); + + mutex_lock(&ctrl->crit_sect); + + if (p_slot->state == BLINKINGOFF_STATE) { + /* slot is on */ + dbg("turn on green LED\n"); + green_LED_on (ctrl, hp_slot); + } else if (p_slot->state == BLINKINGON_STATE) { + /* slot is off */ + dbg("turn off green LED\n"); + green_LED_off (ctrl, hp_slot); + } + + info(msg_button_cancel, p_slot->number); + + p_slot->state = STATIC_STATE; + + amber_LED_off (ctrl, hp_slot); + + set_SOGO(ctrl); + + /* Wait for SOBS to be unset */ + wait_for_ctrl_irq (ctrl); + + mutex_unlock(&ctrl->crit_sect); + } + /*** button Released (No action on press...) */ + else if (ctrl->event_queue[loop].event_type == INT_BUTTON_RELEASE) { + dbg("button release\n"); + + if (is_slot_enabled (ctrl, hp_slot)) { + dbg("slot is on\n"); + p_slot->state = BLINKINGOFF_STATE; + info(msg_button_off, p_slot->number); + } else { + dbg("slot is off\n"); + p_slot->state = BLINKINGON_STATE; + info(msg_button_on, p_slot->number); + } + mutex_lock(&ctrl->crit_sect); + + dbg("blink green LED and turn off amber\n"); + + amber_LED_off (ctrl, hp_slot); + green_LED_blink (ctrl, hp_slot); + + set_SOGO(ctrl); + + /* Wait for SOBS to be unset */ + wait_for_ctrl_irq (ctrl); + + mutex_unlock(&ctrl->crit_sect); + init_timer(&p_slot->task_event); + p_slot->hp_slot = hp_slot; + p_slot->ctrl = ctrl; +/* p_slot->physical_slot = physical_slot; */ + p_slot->task_event.expires = jiffies + 5 * HZ; /* 5 second delay */ + p_slot->task_event.function = pushbutton_helper_thread; + p_slot->task_event.data = (u32) p_slot; + + dbg("add_timer p_slot = %p\n", p_slot); + add_timer(&p_slot->task_event); + } + /***********POWER FAULT */ + else if (ctrl->event_queue[loop].event_type == INT_POWER_FAULT) { + dbg("power fault\n"); + } else { + /* refresh notification */ + if (p_slot) + update_slot_info(ctrl, p_slot); + } + + ctrl->event_queue[loop].event_type = 0; + + change = 1; + } + } /* End of FOR loop */ + } + + return; +} + + +/** + * cpqhp_pushbutton_thread - handle pushbutton events + * @slot: target slot (struct) + * + * Scheduled procedure to handle blocking stuff for the pushbuttons. + * Handles all pending events and exits. + */ +void cpqhp_pushbutton_thread(unsigned long slot) +{ + u8 hp_slot; + u8 device; + struct pci_func *func; + struct slot *p_slot = (struct slot *) slot; + struct controller *ctrl = (struct controller *) p_slot->ctrl; + + pushbutton_pending = 0; + hp_slot = p_slot->hp_slot; + + device = p_slot->device; + + if (is_slot_enabled(ctrl, hp_slot)) { + p_slot->state = POWEROFF_STATE; + /* power Down board */ + func = cpqhp_slot_find(p_slot->bus, p_slot->device, 0); + dbg("In power_down_board, func = %p, ctrl = %p\n", func, ctrl); + if (!func) { + dbg("Error! func NULL in %s\n", __func__); + return ; + } + + if (cpqhp_process_SS(ctrl, func) != 0) { + amber_LED_on(ctrl, hp_slot); + green_LED_on(ctrl, hp_slot); + + set_SOGO(ctrl); + + /* Wait for SOBS to be unset */ + wait_for_ctrl_irq(ctrl); + } + + p_slot->state = STATIC_STATE; + } else { + p_slot->state = POWERON_STATE; + /* slot is off */ + + func = cpqhp_slot_find(p_slot->bus, p_slot->device, 0); + dbg("In add_board, func = %p, ctrl = %p\n", func, ctrl); + if (!func) { + dbg("Error! func NULL in %s\n", __func__); + return ; + } + + if (func != NULL && ctrl != NULL) { + if (cpqhp_process_SI(ctrl, func) != 0) { + amber_LED_on(ctrl, hp_slot); + green_LED_off(ctrl, hp_slot); + + set_SOGO(ctrl); + + /* Wait for SOBS to be unset */ + wait_for_ctrl_irq (ctrl); + } + } + + p_slot->state = STATIC_STATE; + } + + return; +} + + +int cpqhp_process_SI(struct controller *ctrl, struct pci_func *func) +{ + u8 device, hp_slot; + u16 temp_word; + u32 tempdword; + int rc; + struct slot* p_slot; + int physical_slot = 0; + + tempdword = 0; + + device = func->device; + hp_slot = device - ctrl->slot_device_offset; + p_slot = cpqhp_find_slot(ctrl, device); + if (p_slot) + physical_slot = p_slot->number; + + /* Check to see if the interlock is closed */ + tempdword = readl(ctrl->hpc_reg + INT_INPUT_CLEAR); + + if (tempdword & (0x01 << hp_slot)) { + return 1; + } + + if (func->is_a_board) { + rc = board_replaced(func, ctrl); + } else { + /* add board */ + slot_remove(func); + + func = cpqhp_slot_create(ctrl->bus); + if (func == NULL) + return 1; + + func->bus = ctrl->bus; + func->device = device; + func->function = 0; + func->configured = 0; + func->is_a_board = 1; + + /* We have to save the presence info for these slots */ + temp_word = ctrl->ctrl_int_comp >> 16; + func->presence_save = (temp_word >> hp_slot) & 0x01; + func->presence_save |= (temp_word >> (hp_slot + 7)) & 0x02; + + if (ctrl->ctrl_int_comp & (0x1L << hp_slot)) { + func->switch_save = 0; + } else { + func->switch_save = 0x10; + } + + rc = board_added(func, ctrl); + if (rc) { + if (is_bridge(func)) { + bridge_slot_remove(func); + } else + slot_remove(func); + + /* Setup slot structure with entry for empty slot */ + func = cpqhp_slot_create(ctrl->bus); + + if (func == NULL) + return 1; + + func->bus = ctrl->bus; + func->device = device; + func->function = 0; + func->configured = 0; + func->is_a_board = 0; + + /* We have to save the presence info for these slots */ + temp_word = ctrl->ctrl_int_comp >> 16; + func->presence_save = (temp_word >> hp_slot) & 0x01; + func->presence_save |= + (temp_word >> (hp_slot + 7)) & 0x02; + + if (ctrl->ctrl_int_comp & (0x1L << hp_slot)) { + func->switch_save = 0; + } else { + func->switch_save = 0x10; + } + } + } + + if (rc) { + dbg("%s: rc = %d\n", __func__, rc); + } + + if (p_slot) + update_slot_info(ctrl, p_slot); + + return rc; +} + + +int cpqhp_process_SS(struct controller *ctrl, struct pci_func *func) +{ + u8 device, class_code, header_type, BCR; + u8 index = 0; + u8 replace_flag; + u32 rc = 0; + unsigned int devfn; + struct slot* p_slot; + struct pci_bus *pci_bus = ctrl->pci_bus; + int physical_slot=0; + + device = func->device; + func = cpqhp_slot_find(ctrl->bus, device, index++); + p_slot = cpqhp_find_slot(ctrl, device); + if (p_slot) { + physical_slot = p_slot->number; + } + + /* Make sure there are no video controllers here */ + while (func && !rc) { + pci_bus->number = func->bus; + devfn = PCI_DEVFN(func->device, func->function); + + /* Check the Class Code */ + rc = pci_bus_read_config_byte (pci_bus, devfn, 0x0B, &class_code); + if (rc) + return rc; + + if (class_code == PCI_BASE_CLASS_DISPLAY) { + /* Display/Video adapter (not supported) */ + rc = REMOVE_NOT_SUPPORTED; + } else { + /* See if it's a bridge */ + rc = pci_bus_read_config_byte (pci_bus, devfn, PCI_HEADER_TYPE, &header_type); + if (rc) + return rc; + + /* If it's a bridge, check the VGA Enable bit */ + if ((header_type & 0x7F) == PCI_HEADER_TYPE_BRIDGE) { + rc = pci_bus_read_config_byte (pci_bus, devfn, PCI_BRIDGE_CONTROL, &BCR); + if (rc) + return rc; + + /* If the VGA Enable bit is set, remove isn't + * supported */ + if (BCR & PCI_BRIDGE_CTL_VGA) { + rc = REMOVE_NOT_SUPPORTED; + } + } + } + + func = cpqhp_slot_find(ctrl->bus, device, index++); + } + + func = cpqhp_slot_find(ctrl->bus, device, 0); + if ((func != NULL) && !rc) { + /* FIXME: Replace flag should be passed into process_SS */ + replace_flag = !(ctrl->add_support); + rc = remove_board(func, replace_flag, ctrl); + } else if (!rc) { + rc = 1; + } + + if (p_slot) + update_slot_info(ctrl, p_slot); + + return rc; +} + +/** + * switch_leds - switch the leds, go from one site to the other. + * @ctrl: controller to use + * @num_of_slots: number of slots to use + * @work_LED: LED control value + * @direction: 1 to start from the left side, 0 to start right. + */ +static void switch_leds(struct controller *ctrl, const int num_of_slots, + u32 *work_LED, const int direction) +{ + int loop; + + for (loop = 0; loop < num_of_slots; loop++) { + if (direction) + *work_LED = *work_LED >> 1; + else + *work_LED = *work_LED << 1; + writel(*work_LED, ctrl->hpc_reg + LED_CONTROL); + + set_SOGO(ctrl); + + /* Wait for SOGO interrupt */ + wait_for_ctrl_irq(ctrl); + + /* Get ready for next iteration */ + long_delay((2*HZ)/10); + } +} + +/** + * cpqhp_hardware_test - runs hardware tests + * @ctrl: target controller + * @test_num: the number written to the "test" file in sysfs. + * + * For hot plug ctrl folks to play with. + */ +int cpqhp_hardware_test(struct controller *ctrl, int test_num) +{ + u32 save_LED; + u32 work_LED; + int loop; + int num_of_slots; + + num_of_slots = readb(ctrl->hpc_reg + SLOT_MASK) & 0x0f; + + switch (test_num) { + case 1: + /* Do stuff here! */ + + /* Do that funky LED thing */ + /* so we can restore them later */ + save_LED = readl(ctrl->hpc_reg + LED_CONTROL); + work_LED = 0x01010101; + switch_leds(ctrl, num_of_slots, &work_LED, 0); + switch_leds(ctrl, num_of_slots, &work_LED, 1); + switch_leds(ctrl, num_of_slots, &work_LED, 0); + switch_leds(ctrl, num_of_slots, &work_LED, 1); + + work_LED = 0x01010000; + writel(work_LED, ctrl->hpc_reg + LED_CONTROL); + switch_leds(ctrl, num_of_slots, &work_LED, 0); + switch_leds(ctrl, num_of_slots, &work_LED, 1); + work_LED = 0x00000101; + writel(work_LED, ctrl->hpc_reg + LED_CONTROL); + switch_leds(ctrl, num_of_slots, &work_LED, 0); + switch_leds(ctrl, num_of_slots, &work_LED, 1); + + work_LED = 0x01010000; + writel(work_LED, ctrl->hpc_reg + LED_CONTROL); + for (loop = 0; loop < num_of_slots; loop++) { + set_SOGO(ctrl); + + /* Wait for SOGO interrupt */ + wait_for_ctrl_irq (ctrl); + + /* Get ready for next iteration */ + long_delay((3*HZ)/10); + work_LED = work_LED >> 16; + writel(work_LED, ctrl->hpc_reg + LED_CONTROL); + + set_SOGO(ctrl); + + /* Wait for SOGO interrupt */ + wait_for_ctrl_irq (ctrl); + + /* Get ready for next iteration */ + long_delay((3*HZ)/10); + work_LED = work_LED << 16; + writel(work_LED, ctrl->hpc_reg + LED_CONTROL); + work_LED = work_LED << 1; + writel(work_LED, ctrl->hpc_reg + LED_CONTROL); + } + + /* put it back the way it was */ + writel(save_LED, ctrl->hpc_reg + LED_CONTROL); + + set_SOGO(ctrl); + + /* Wait for SOBS to be unset */ + wait_for_ctrl_irq (ctrl); + break; + case 2: + /* Do other stuff here! */ + break; + case 3: + /* and more... */ + break; + } + return 0; +} + + +/** + * configure_new_device - Configures the PCI header information of one board. + * @ctrl: pointer to controller structure + * @func: pointer to function structure + * @behind_bridge: 1 if this is a recursive call, 0 if not + * @resources: pointer to set of resource lists + * + * Returns 0 if success. + */ +static u32 configure_new_device(struct controller * ctrl, struct pci_func * func, + u8 behind_bridge, struct resource_lists * resources) +{ + u8 temp_byte, function, max_functions, stop_it; + int rc; + u32 ID; + struct pci_func *new_slot; + int index; + + new_slot = func; + + dbg("%s\n", __func__); + /* Check for Multi-function device */ + ctrl->pci_bus->number = func->bus; + rc = pci_bus_read_config_byte (ctrl->pci_bus, PCI_DEVFN(func->device, func->function), 0x0E, &temp_byte); + if (rc) { + dbg("%s: rc = %d\n", __func__, rc); + return rc; + } + + if (temp_byte & 0x80) /* Multi-function device */ + max_functions = 8; + else + max_functions = 1; + + function = 0; + + do { + rc = configure_new_function(ctrl, new_slot, behind_bridge, resources); + + if (rc) { + dbg("configure_new_function failed %d\n",rc); + index = 0; + + while (new_slot) { + new_slot = cpqhp_slot_find(new_slot->bus, new_slot->device, index++); + + if (new_slot) + cpqhp_return_board_resources(new_slot, resources); + } + + return rc; + } + + function++; + + stop_it = 0; + + /* The following loop skips to the next present function + * and creates a board structure */ + + while ((function < max_functions) && (!stop_it)) { + pci_bus_read_config_dword (ctrl->pci_bus, PCI_DEVFN(func->device, function), 0x00, &ID); + + if (ID == 0xFFFFFFFF) { /* There's nothing there. */ + function++; + } else { /* There's something there */ + /* Setup slot structure. */ + new_slot = cpqhp_slot_create(func->bus); + + if (new_slot == NULL) + return 1; + + new_slot->bus = func->bus; + new_slot->device = func->device; + new_slot->function = function; + new_slot->is_a_board = 1; + new_slot->status = 0; + + stop_it++; + } + } + + } while (function < max_functions); + dbg("returning from configure_new_device\n"); + + return 0; +} + + +/* + Configuration logic that involves the hotplug data structures and + their bookkeeping + */ + + +/** + * configure_new_function - Configures the PCI header information of one device + * @ctrl: pointer to controller structure + * @func: pointer to function structure + * @behind_bridge: 1 if this is a recursive call, 0 if not + * @resources: pointer to set of resource lists + * + * Calls itself recursively for bridged devices. + * Returns 0 if success. + */ +static int configure_new_function(struct controller *ctrl, struct pci_func *func, + u8 behind_bridge, + struct resource_lists *resources) +{ + int cloop; + u8 IRQ = 0; + u8 temp_byte; + u8 device; + u8 class_code; + u16 command; + u16 temp_word; + u32 temp_dword; + u32 rc; + u32 temp_register; + u32 base; + u32 ID; + unsigned int devfn; + struct pci_resource *mem_node; + struct pci_resource *p_mem_node; + struct pci_resource *io_node; + struct pci_resource *bus_node; + struct pci_resource *hold_mem_node; + struct pci_resource *hold_p_mem_node; + struct pci_resource *hold_IO_node; + struct pci_resource *hold_bus_node; + struct irq_mapping irqs; + struct pci_func *new_slot; + struct pci_bus *pci_bus; + struct resource_lists temp_resources; + + pci_bus = ctrl->pci_bus; + pci_bus->number = func->bus; + devfn = PCI_DEVFN(func->device, func->function); + + /* Check for Bridge */ + rc = pci_bus_read_config_byte(pci_bus, devfn, PCI_HEADER_TYPE, &temp_byte); + if (rc) + return rc; + + if ((temp_byte & 0x7F) == PCI_HEADER_TYPE_BRIDGE) { /* PCI-PCI Bridge */ + /* set Primary bus */ + dbg("set Primary bus = %d\n", func->bus); + rc = pci_bus_write_config_byte(pci_bus, devfn, PCI_PRIMARY_BUS, func->bus); + if (rc) + return rc; + + /* find range of busses to use */ + dbg("find ranges of buses to use\n"); + bus_node = get_max_resource(&(resources->bus_head), 1); + + /* If we don't have any busses to allocate, we can't continue */ + if (!bus_node) + return -ENOMEM; + + /* set Secondary bus */ + temp_byte = bus_node->base; + dbg("set Secondary bus = %d\n", bus_node->base); + rc = pci_bus_write_config_byte(pci_bus, devfn, PCI_SECONDARY_BUS, temp_byte); + if (rc) + return rc; + + /* set subordinate bus */ + temp_byte = bus_node->base + bus_node->length - 1; + dbg("set subordinate bus = %d\n", bus_node->base + bus_node->length - 1); + rc = pci_bus_write_config_byte(pci_bus, devfn, PCI_SUBORDINATE_BUS, temp_byte); + if (rc) + return rc; + + /* set subordinate Latency Timer and base Latency Timer */ + temp_byte = 0x40; + rc = pci_bus_write_config_byte(pci_bus, devfn, PCI_SEC_LATENCY_TIMER, temp_byte); + if (rc) + return rc; + rc = pci_bus_write_config_byte(pci_bus, devfn, PCI_LATENCY_TIMER, temp_byte); + if (rc) + return rc; + + /* set Cache Line size */ + temp_byte = 0x08; + rc = pci_bus_write_config_byte(pci_bus, devfn, PCI_CACHE_LINE_SIZE, temp_byte); + if (rc) + return rc; + + /* Setup the IO, memory, and prefetchable windows */ + io_node = get_max_resource(&(resources->io_head), 0x1000); + if (!io_node) + return -ENOMEM; + mem_node = get_max_resource(&(resources->mem_head), 0x100000); + if (!mem_node) + return -ENOMEM; + p_mem_node = get_max_resource(&(resources->p_mem_head), 0x100000); + if (!p_mem_node) + return -ENOMEM; + dbg("Setup the IO, memory, and prefetchable windows\n"); + dbg("io_node\n"); + dbg("(base, len, next) (%x, %x, %p)\n", io_node->base, + io_node->length, io_node->next); + dbg("mem_node\n"); + dbg("(base, len, next) (%x, %x, %p)\n", mem_node->base, + mem_node->length, mem_node->next); + dbg("p_mem_node\n"); + dbg("(base, len, next) (%x, %x, %p)\n", p_mem_node->base, + p_mem_node->length, p_mem_node->next); + + /* set up the IRQ info */ + if (!resources->irqs) { + irqs.barber_pole = 0; + irqs.interrupt[0] = 0; + irqs.interrupt[1] = 0; + irqs.interrupt[2] = 0; + irqs.interrupt[3] = 0; + irqs.valid_INT = 0; + } else { + irqs.barber_pole = resources->irqs->barber_pole; + irqs.interrupt[0] = resources->irqs->interrupt[0]; + irqs.interrupt[1] = resources->irqs->interrupt[1]; + irqs.interrupt[2] = resources->irqs->interrupt[2]; + irqs.interrupt[3] = resources->irqs->interrupt[3]; + irqs.valid_INT = resources->irqs->valid_INT; + } + + /* set up resource lists that are now aligned on top and bottom + * for anything behind the bridge. */ + temp_resources.bus_head = bus_node; + temp_resources.io_head = io_node; + temp_resources.mem_head = mem_node; + temp_resources.p_mem_head = p_mem_node; + temp_resources.irqs = &irqs; + + /* Make copies of the nodes we are going to pass down so that + * if there is a problem,we can just use these to free resources */ + hold_bus_node = kmalloc(sizeof(*hold_bus_node), GFP_KERNEL); + hold_IO_node = kmalloc(sizeof(*hold_IO_node), GFP_KERNEL); + hold_mem_node = kmalloc(sizeof(*hold_mem_node), GFP_KERNEL); + hold_p_mem_node = kmalloc(sizeof(*hold_p_mem_node), GFP_KERNEL); + + if (!hold_bus_node || !hold_IO_node || !hold_mem_node || !hold_p_mem_node) { + kfree(hold_bus_node); + kfree(hold_IO_node); + kfree(hold_mem_node); + kfree(hold_p_mem_node); + + return 1; + } + + memcpy(hold_bus_node, bus_node, sizeof(struct pci_resource)); + + bus_node->base += 1; + bus_node->length -= 1; + bus_node->next = NULL; + + /* If we have IO resources copy them and fill in the bridge's + * IO range registers */ + if (io_node) { + memcpy(hold_IO_node, io_node, sizeof(struct pci_resource)); + io_node->next = NULL; + + /* set IO base and Limit registers */ + temp_byte = io_node->base >> 8; + rc = pci_bus_write_config_byte(pci_bus, devfn, PCI_IO_BASE, temp_byte); + + temp_byte = (io_node->base + io_node->length - 1) >> 8; + rc = pci_bus_write_config_byte(pci_bus, devfn, PCI_IO_LIMIT, temp_byte); + } else { + kfree(hold_IO_node); + hold_IO_node = NULL; + } + + /* If we have memory resources copy them and fill in the + * bridge's memory range registers. Otherwise, fill in the + * range registers with values that disable them. */ + if (mem_node) { + memcpy(hold_mem_node, mem_node, sizeof(struct pci_resource)); + mem_node->next = NULL; + + /* set Mem base and Limit registers */ + temp_word = mem_node->base >> 16; + rc = pci_bus_write_config_word(pci_bus, devfn, PCI_MEMORY_BASE, temp_word); + + temp_word = (mem_node->base + mem_node->length - 1) >> 16; + rc = pci_bus_write_config_word(pci_bus, devfn, PCI_MEMORY_LIMIT, temp_word); + } else { + temp_word = 0xFFFF; + rc = pci_bus_write_config_word(pci_bus, devfn, PCI_MEMORY_BASE, temp_word); + + temp_word = 0x0000; + rc = pci_bus_write_config_word(pci_bus, devfn, PCI_MEMORY_LIMIT, temp_word); + + kfree(hold_mem_node); + hold_mem_node = NULL; + } + + memcpy(hold_p_mem_node, p_mem_node, sizeof(struct pci_resource)); + p_mem_node->next = NULL; + + /* set Pre Mem base and Limit registers */ + temp_word = p_mem_node->base >> 16; + rc = pci_bus_write_config_word (pci_bus, devfn, PCI_PREF_MEMORY_BASE, temp_word); + + temp_word = (p_mem_node->base + p_mem_node->length - 1) >> 16; + rc = pci_bus_write_config_word (pci_bus, devfn, PCI_PREF_MEMORY_LIMIT, temp_word); + + /* Adjust this to compensate for extra adjustment in first loop */ + irqs.barber_pole--; + + rc = 0; + + /* Here we actually find the devices and configure them */ + for (device = 0; (device <= 0x1F) && !rc; device++) { + irqs.barber_pole = (irqs.barber_pole + 1) & 0x03; + + ID = 0xFFFFFFFF; + pci_bus->number = hold_bus_node->base; + pci_bus_read_config_dword (pci_bus, PCI_DEVFN(device, 0), 0x00, &ID); + pci_bus->number = func->bus; + + if (ID != 0xFFFFFFFF) { /* device present */ + /* Setup slot structure. */ + new_slot = cpqhp_slot_create(hold_bus_node->base); + + if (new_slot == NULL) { + rc = -ENOMEM; + continue; + } + + new_slot->bus = hold_bus_node->base; + new_slot->device = device; + new_slot->function = 0; + new_slot->is_a_board = 1; + new_slot->status = 0; + + rc = configure_new_device(ctrl, new_slot, 1, &temp_resources); + dbg("configure_new_device rc=0x%x\n",rc); + } /* End of IF (device in slot?) */ + } /* End of FOR loop */ + + if (rc) + goto free_and_out; + /* save the interrupt routing information */ + if (resources->irqs) { + resources->irqs->interrupt[0] = irqs.interrupt[0]; + resources->irqs->interrupt[1] = irqs.interrupt[1]; + resources->irqs->interrupt[2] = irqs.interrupt[2]; + resources->irqs->interrupt[3] = irqs.interrupt[3]; + resources->irqs->valid_INT = irqs.valid_INT; + } else if (!behind_bridge) { + /* We need to hook up the interrupts here */ + for (cloop = 0; cloop < 4; cloop++) { + if (irqs.valid_INT & (0x01 << cloop)) { + rc = cpqhp_set_irq(func->bus, func->device, + 0x0A + cloop, irqs.interrupt[cloop]); + if (rc) + goto free_and_out; + } + } /* end of for loop */ + } + /* Return unused bus resources + * First use the temporary node to store information for + * the board */ + if (hold_bus_node && bus_node && temp_resources.bus_head) { + hold_bus_node->length = bus_node->base - hold_bus_node->base; + + hold_bus_node->next = func->bus_head; + func->bus_head = hold_bus_node; + + temp_byte = temp_resources.bus_head->base - 1; + + /* set subordinate bus */ + rc = pci_bus_write_config_byte (pci_bus, devfn, PCI_SUBORDINATE_BUS, temp_byte); + + if (temp_resources.bus_head->length == 0) { + kfree(temp_resources.bus_head); + temp_resources.bus_head = NULL; + } else { + return_resource(&(resources->bus_head), temp_resources.bus_head); + } + } + + /* If we have IO space available and there is some left, + * return the unused portion */ + if (hold_IO_node && temp_resources.io_head) { + io_node = do_pre_bridge_resource_split(&(temp_resources.io_head), + &hold_IO_node, 0x1000); + + /* Check if we were able to split something off */ + if (io_node) { + hold_IO_node->base = io_node->base + io_node->length; + + temp_byte = (hold_IO_node->base) >> 8; + rc = pci_bus_write_config_word (pci_bus, devfn, PCI_IO_BASE, temp_byte); + + return_resource(&(resources->io_head), io_node); + } + + io_node = do_bridge_resource_split(&(temp_resources.io_head), 0x1000); + + /* Check if we were able to split something off */ + if (io_node) { + /* First use the temporary node to store + * information for the board */ + hold_IO_node->length = io_node->base - hold_IO_node->base; + + /* If we used any, add it to the board's list */ + if (hold_IO_node->length) { + hold_IO_node->next = func->io_head; + func->io_head = hold_IO_node; + + temp_byte = (io_node->base - 1) >> 8; + rc = pci_bus_write_config_byte (pci_bus, devfn, PCI_IO_LIMIT, temp_byte); + + return_resource(&(resources->io_head), io_node); + } else { + /* it doesn't need any IO */ + temp_word = 0x0000; + rc = pci_bus_write_config_word (pci_bus, devfn, PCI_IO_LIMIT, temp_word); + + return_resource(&(resources->io_head), io_node); + kfree(hold_IO_node); + } + } else { + /* it used most of the range */ + hold_IO_node->next = func->io_head; + func->io_head = hold_IO_node; + } + } else if (hold_IO_node) { + /* it used the whole range */ + hold_IO_node->next = func->io_head; + func->io_head = hold_IO_node; + } + /* If we have memory space available and there is some left, + * return the unused portion */ + if (hold_mem_node && temp_resources.mem_head) { + mem_node = do_pre_bridge_resource_split(&(temp_resources. mem_head), + &hold_mem_node, 0x100000); + + /* Check if we were able to split something off */ + if (mem_node) { + hold_mem_node->base = mem_node->base + mem_node->length; + + temp_word = (hold_mem_node->base) >> 16; + rc = pci_bus_write_config_word (pci_bus, devfn, PCI_MEMORY_BASE, temp_word); + + return_resource(&(resources->mem_head), mem_node); + } + + mem_node = do_bridge_resource_split(&(temp_resources.mem_head), 0x100000); + + /* Check if we were able to split something off */ + if (mem_node) { + /* First use the temporary node to store + * information for the board */ + hold_mem_node->length = mem_node->base - hold_mem_node->base; + + if (hold_mem_node->length) { + hold_mem_node->next = func->mem_head; + func->mem_head = hold_mem_node; + + /* configure end address */ + temp_word = (mem_node->base - 1) >> 16; + rc = pci_bus_write_config_word (pci_bus, devfn, PCI_MEMORY_LIMIT, temp_word); + + /* Return unused resources to the pool */ + return_resource(&(resources->mem_head), mem_node); + } else { + /* it doesn't need any Mem */ + temp_word = 0x0000; + rc = pci_bus_write_config_word (pci_bus, devfn, PCI_MEMORY_LIMIT, temp_word); + + return_resource(&(resources->mem_head), mem_node); + kfree(hold_mem_node); + } + } else { + /* it used most of the range */ + hold_mem_node->next = func->mem_head; + func->mem_head = hold_mem_node; + } + } else if (hold_mem_node) { + /* it used the whole range */ + hold_mem_node->next = func->mem_head; + func->mem_head = hold_mem_node; + } + /* If we have prefetchable memory space available and there + * is some left at the end, return the unused portion */ + if (hold_p_mem_node && temp_resources.p_mem_head) { + p_mem_node = do_pre_bridge_resource_split(&(temp_resources.p_mem_head), + &hold_p_mem_node, 0x100000); + + /* Check if we were able to split something off */ + if (p_mem_node) { + hold_p_mem_node->base = p_mem_node->base + p_mem_node->length; + + temp_word = (hold_p_mem_node->base) >> 16; + rc = pci_bus_write_config_word (pci_bus, devfn, PCI_PREF_MEMORY_BASE, temp_word); + + return_resource(&(resources->p_mem_head), p_mem_node); + } + + p_mem_node = do_bridge_resource_split(&(temp_resources.p_mem_head), 0x100000); + + /* Check if we were able to split something off */ + if (p_mem_node) { + /* First use the temporary node to store + * information for the board */ + hold_p_mem_node->length = p_mem_node->base - hold_p_mem_node->base; + + /* If we used any, add it to the board's list */ + if (hold_p_mem_node->length) { + hold_p_mem_node->next = func->p_mem_head; + func->p_mem_head = hold_p_mem_node; + + temp_word = (p_mem_node->base - 1) >> 16; + rc = pci_bus_write_config_word (pci_bus, devfn, PCI_PREF_MEMORY_LIMIT, temp_word); + + return_resource(&(resources->p_mem_head), p_mem_node); + } else { + /* it doesn't need any PMem */ + temp_word = 0x0000; + rc = pci_bus_write_config_word (pci_bus, devfn, PCI_PREF_MEMORY_LIMIT, temp_word); + + return_resource(&(resources->p_mem_head), p_mem_node); + kfree(hold_p_mem_node); + } + } else { + /* it used the most of the range */ + hold_p_mem_node->next = func->p_mem_head; + func->p_mem_head = hold_p_mem_node; + } + } else if (hold_p_mem_node) { + /* it used the whole range */ + hold_p_mem_node->next = func->p_mem_head; + func->p_mem_head = hold_p_mem_node; + } + /* We should be configuring an IRQ and the bridge's base address + * registers if it needs them. Although we have never seen such + * a device */ + + /* enable card */ + command = 0x0157; /* = PCI_COMMAND_IO | + * PCI_COMMAND_MEMORY | + * PCI_COMMAND_MASTER | + * PCI_COMMAND_INVALIDATE | + * PCI_COMMAND_PARITY | + * PCI_COMMAND_SERR */ + rc = pci_bus_write_config_word (pci_bus, devfn, PCI_COMMAND, command); + + /* set Bridge Control Register */ + command = 0x07; /* = PCI_BRIDGE_CTL_PARITY | + * PCI_BRIDGE_CTL_SERR | + * PCI_BRIDGE_CTL_NO_ISA */ + rc = pci_bus_write_config_word (pci_bus, devfn, PCI_BRIDGE_CONTROL, command); + } else if ((temp_byte & 0x7F) == PCI_HEADER_TYPE_NORMAL) { + /* Standard device */ + rc = pci_bus_read_config_byte (pci_bus, devfn, 0x0B, &class_code); + + if (class_code == PCI_BASE_CLASS_DISPLAY) { + /* Display (video) adapter (not supported) */ + return DEVICE_TYPE_NOT_SUPPORTED; + } + /* Figure out IO and memory needs */ + for (cloop = 0x10; cloop <= 0x24; cloop += 4) { + temp_register = 0xFFFFFFFF; + + dbg("CND: bus=%d, devfn=%d, offset=%d\n", pci_bus->number, devfn, cloop); + rc = pci_bus_write_config_dword (pci_bus, devfn, cloop, temp_register); + + rc = pci_bus_read_config_dword (pci_bus, devfn, cloop, &temp_register); + dbg("CND: base = 0x%x\n", temp_register); + + if (temp_register) { /* If this register is implemented */ + if ((temp_register & 0x03L) == 0x01) { + /* Map IO */ + + /* set base = amount of IO space */ + base = temp_register & 0xFFFFFFFC; + base = ~base + 1; + + dbg("CND: length = 0x%x\n", base); + io_node = get_io_resource(&(resources->io_head), base); + dbg("Got io_node start = %8.8x, length = %8.8x next (%p)\n", + io_node->base, io_node->length, io_node->next); + dbg("func (%p) io_head (%p)\n", func, func->io_head); + + /* allocate the resource to the board */ + if (io_node) { + base = io_node->base; + + io_node->next = func->io_head; + func->io_head = io_node; + } else + return -ENOMEM; + } else if ((temp_register & 0x0BL) == 0x08) { + /* Map prefetchable memory */ + base = temp_register & 0xFFFFFFF0; + base = ~base + 1; + + dbg("CND: length = 0x%x\n", base); + p_mem_node = get_resource(&(resources->p_mem_head), base); + + /* allocate the resource to the board */ + if (p_mem_node) { + base = p_mem_node->base; + + p_mem_node->next = func->p_mem_head; + func->p_mem_head = p_mem_node; + } else + return -ENOMEM; + } else if ((temp_register & 0x0BL) == 0x00) { + /* Map memory */ + base = temp_register & 0xFFFFFFF0; + base = ~base + 1; + + dbg("CND: length = 0x%x\n", base); + mem_node = get_resource(&(resources->mem_head), base); + + /* allocate the resource to the board */ + if (mem_node) { + base = mem_node->base; + + mem_node->next = func->mem_head; + func->mem_head = mem_node; + } else + return -ENOMEM; + } else if ((temp_register & 0x0BL) == 0x04) { + /* Map memory */ + base = temp_register & 0xFFFFFFF0; + base = ~base + 1; + + dbg("CND: length = 0x%x\n", base); + mem_node = get_resource(&(resources->mem_head), base); + + /* allocate the resource to the board */ + if (mem_node) { + base = mem_node->base; + + mem_node->next = func->mem_head; + func->mem_head = mem_node; + } else + return -ENOMEM; + } else if ((temp_register & 0x0BL) == 0x06) { + /* Those bits are reserved, we can't handle this */ + return 1; + } else { + /* Requesting space below 1M */ + return NOT_ENOUGH_RESOURCES; + } + + rc = pci_bus_write_config_dword(pci_bus, devfn, cloop, base); + + /* Check for 64-bit base */ + if ((temp_register & 0x07L) == 0x04) { + cloop += 4; + + /* Upper 32 bits of address always zero + * on today's systems */ + /* FIXME this is probably not true on + * Alpha and ia64??? */ + base = 0; + rc = pci_bus_write_config_dword(pci_bus, devfn, cloop, base); + } + } + } /* End of base register loop */ + if (cpqhp_legacy_mode) { + /* Figure out which interrupt pin this function uses */ + rc = pci_bus_read_config_byte (pci_bus, devfn, + PCI_INTERRUPT_PIN, &temp_byte); + + /* If this function needs an interrupt and we are behind + * a bridge and the pin is tied to something that's + * alread mapped, set this one the same */ + if (temp_byte && resources->irqs && + (resources->irqs->valid_INT & + (0x01 << ((temp_byte + resources->irqs->barber_pole - 1) & 0x03)))) { + /* We have to share with something already set up */ + IRQ = resources->irqs->interrupt[(temp_byte + + resources->irqs->barber_pole - 1) & 0x03]; + } else { + /* Program IRQ based on card type */ + rc = pci_bus_read_config_byte (pci_bus, devfn, 0x0B, &class_code); + + if (class_code == PCI_BASE_CLASS_STORAGE) { + IRQ = cpqhp_disk_irq; + } else { + IRQ = cpqhp_nic_irq; + } + } + + /* IRQ Line */ + rc = pci_bus_write_config_byte (pci_bus, devfn, PCI_INTERRUPT_LINE, IRQ); + } + + if (!behind_bridge) { + rc = cpqhp_set_irq(func->bus, func->device, temp_byte + 0x09, IRQ); + if (rc) + return 1; + } else { + /* TBD - this code may also belong in the other clause + * of this If statement */ + resources->irqs->interrupt[(temp_byte + resources->irqs->barber_pole - 1) & 0x03] = IRQ; + resources->irqs->valid_INT |= 0x01 << (temp_byte + resources->irqs->barber_pole - 1) & 0x03; + } + + /* Latency Timer */ + temp_byte = 0x40; + rc = pci_bus_write_config_byte(pci_bus, devfn, + PCI_LATENCY_TIMER, temp_byte); + + /* Cache Line size */ + temp_byte = 0x08; + rc = pci_bus_write_config_byte(pci_bus, devfn, + PCI_CACHE_LINE_SIZE, temp_byte); + + /* disable ROM base Address */ + temp_dword = 0x00L; + rc = pci_bus_write_config_word(pci_bus, devfn, + PCI_ROM_ADDRESS, temp_dword); + + /* enable card */ + temp_word = 0x0157; /* = PCI_COMMAND_IO | + * PCI_COMMAND_MEMORY | + * PCI_COMMAND_MASTER | + * PCI_COMMAND_INVALIDATE | + * PCI_COMMAND_PARITY | + * PCI_COMMAND_SERR */ + rc = pci_bus_write_config_word (pci_bus, devfn, + PCI_COMMAND, temp_word); + } else { /* End of Not-A-Bridge else */ + /* It's some strange type of PCI adapter (Cardbus?) */ + return DEVICE_TYPE_NOT_SUPPORTED; + } + + func->configured = 1; + + return 0; +free_and_out: + cpqhp_destroy_resource_list (&temp_resources); + + return_resource(&(resources-> bus_head), hold_bus_node); + return_resource(&(resources-> io_head), hold_IO_node); + return_resource(&(resources-> mem_head), hold_mem_node); + return_resource(&(resources-> p_mem_head), hold_p_mem_node); + return rc; +} diff --git a/drivers/pci/hotplug/cpqphp_nvram.c b/drivers/pci/hotplug/cpqphp_nvram.c new file mode 100644 index 0000000..cb17488 --- /dev/null +++ b/drivers/pci/hotplug/cpqphp_nvram.c @@ -0,0 +1,666 @@ +/* + * Compaq Hot Plug Controller Driver + * + * Copyright (C) 1995,2001 Compaq Computer Corporation + * 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/module.h> +#include <linux/kernel.h> +#include <linux/types.h> +#include <linux/proc_fs.h> +#include <linux/slab.h> +#include <linux/workqueue.h> +#include <linux/pci.h> +#include <linux/pci_hotplug.h> +#include <linux/init.h> +#include <asm/uaccess.h> +#include "cpqphp.h" +#include "cpqphp_nvram.h" + + +#define ROM_INT15_PHY_ADDR 0x0FF859 +#define READ_EV 0xD8A4 +#define WRITE_EV 0xD8A5 + +struct register_foo { + union { + unsigned long lword; /* eax */ + unsigned short word; /* ax */ + + struct { + unsigned char low; /* al */ + unsigned char high; /* ah */ + } byte; + } data; + + unsigned char opcode; /* see below */ + unsigned long length; /* if the reg. is a pointer, how much data */ +} __attribute__ ((packed)); + +struct all_reg { + struct register_foo eax_reg; + struct register_foo ebx_reg; + struct register_foo ecx_reg; + struct register_foo edx_reg; + struct register_foo edi_reg; + struct register_foo esi_reg; + struct register_foo eflags_reg; +} __attribute__ ((packed)); + + +struct ev_hrt_header { + u8 Version; + u8 num_of_ctrl; + u8 next; +}; + +struct ev_hrt_ctrl { + u8 bus; + u8 device; + u8 function; + u8 mem_avail; + u8 p_mem_avail; + u8 io_avail; + u8 bus_avail; + u8 next; +}; + + +static u8 evbuffer_init; +static u8 evbuffer_length; +static u8 evbuffer[1024]; + +static void __iomem *compaq_int15_entry_point; + +static spinlock_t int15_lock; /* lock for ordering int15_bios_call() */ + + +/* This is a series of function that deals with + setting & getting the hotplug resource table in some environment variable. +*/ + +/* + * We really shouldn't be doing this unless there is a _very_ good reason to!!! + * greg k-h + */ + + +static u32 add_byte( u32 **p_buffer, u8 value, u32 *used, u32 *avail) +{ + u8 **tByte; + + if ((*used + 1) > *avail) + return(1); + + *((u8*)*p_buffer) = value; + tByte = (u8**)p_buffer; + (*tByte)++; + *used+=1; + return(0); +} + + +static u32 add_dword( u32 **p_buffer, u32 value, u32 *used, u32 *avail) +{ + if ((*used + 4) > *avail) + return(1); + + **p_buffer = value; + (*p_buffer)++; + *used+=4; + return(0); +} + + +/* + * check_for_compaq_ROM + * + * this routine verifies that the ROM OEM string is 'COMPAQ' + * + * returns 0 for non-Compaq ROM, 1 for Compaq ROM + */ +static int check_for_compaq_ROM (void __iomem *rom_start) +{ + u8 temp1, temp2, temp3, temp4, temp5, temp6; + int result = 0; + + temp1 = readb(rom_start + 0xffea + 0); + temp2 = readb(rom_start + 0xffea + 1); + temp3 = readb(rom_start + 0xffea + 2); + temp4 = readb(rom_start + 0xffea + 3); + temp5 = readb(rom_start + 0xffea + 4); + temp6 = readb(rom_start + 0xffea + 5); + if ((temp1 == 'C') && + (temp2 == 'O') && + (temp3 == 'M') && + (temp4 == 'P') && + (temp5 == 'A') && + (temp6 == 'Q')) { + result = 1; + } + dbg ("%s - returned %d\n", __func__, result); + return result; +} + + +static u32 access_EV (u16 operation, u8 *ev_name, u8 *buffer, u32 *buf_size) +{ + unsigned long flags; + int op = operation; + int ret_val; + + if (!compaq_int15_entry_point) + return -ENODEV; + + spin_lock_irqsave(&int15_lock, flags); + __asm__ ( + "xorl %%ebx,%%ebx\n" \ + "xorl %%edx,%%edx\n" \ + "pushf\n" \ + "push %%cs\n" \ + "cli\n" \ + "call *%6\n" + : "=c" (*buf_size), "=a" (ret_val) + : "a" (op), "c" (*buf_size), "S" (ev_name), + "D" (buffer), "m" (compaq_int15_entry_point) + : "%ebx", "%edx"); + spin_unlock_irqrestore(&int15_lock, flags); + + return((ret_val & 0xFF00) >> 8); +} + + +/* + * load_HRT + * + * Read the hot plug Resource Table from NVRAM + */ +static int load_HRT (void __iomem *rom_start) +{ + u32 available; + u32 temp_dword; + u8 temp_byte = 0xFF; + u32 rc; + + if (!check_for_compaq_ROM(rom_start)) { + return -ENODEV; + } + + available = 1024; + + // Now load the EV + temp_dword = available; + + rc = access_EV(READ_EV, "CQTHPS", evbuffer, &temp_dword); + + evbuffer_length = temp_dword; + + // We're maintaining the resource lists so write FF to invalidate old info + temp_dword = 1; + + rc = access_EV(WRITE_EV, "CQTHPS", &temp_byte, &temp_dword); + + return rc; +} + + +/* + * store_HRT + * + * Save the hot plug Resource Table in NVRAM + */ +static u32 store_HRT (void __iomem *rom_start) +{ + u32 *buffer; + u32 *pFill; + u32 usedbytes; + u32 available; + u32 temp_dword; + u32 rc; + u8 loop; + u8 numCtrl = 0; + struct controller *ctrl; + struct pci_resource *resNode; + struct ev_hrt_header *p_EV_header; + struct ev_hrt_ctrl *p_ev_ctrl; + + available = 1024; + + if (!check_for_compaq_ROM(rom_start)) { + return(1); + } + + buffer = (u32*) evbuffer; + + if (!buffer) + return(1); + + pFill = buffer; + usedbytes = 0; + + p_EV_header = (struct ev_hrt_header *) pFill; + + ctrl = cpqhp_ctrl_list; + + // The revision of this structure + rc = add_byte( &pFill, 1 + ctrl->push_flag, &usedbytes, &available); + if (rc) + return(rc); + + // The number of controllers + rc = add_byte( &pFill, 1, &usedbytes, &available); + if (rc) + return(rc); + + while (ctrl) { + p_ev_ctrl = (struct ev_hrt_ctrl *) pFill; + + numCtrl++; + + // The bus number + rc = add_byte( &pFill, ctrl->bus, &usedbytes, &available); + if (rc) + return(rc); + + // The device Number + rc = add_byte( &pFill, PCI_SLOT(ctrl->pci_dev->devfn), &usedbytes, &available); + if (rc) + return(rc); + + // The function Number + rc = add_byte( &pFill, PCI_FUNC(ctrl->pci_dev->devfn), &usedbytes, &available); + if (rc) + return(rc); + + // Skip the number of available entries + rc = add_dword( &pFill, 0, &usedbytes, &available); + if (rc) + return(rc); + + // Figure out memory Available + + resNode = ctrl->mem_head; + + loop = 0; + + while (resNode) { + loop ++; + + // base + rc = add_dword( &pFill, resNode->base, &usedbytes, &available); + if (rc) + return(rc); + + // length + rc = add_dword( &pFill, resNode->length, &usedbytes, &available); + if (rc) + return(rc); + + resNode = resNode->next; + } + + // Fill in the number of entries + p_ev_ctrl->mem_avail = loop; + + // Figure out prefetchable memory Available + + resNode = ctrl->p_mem_head; + + loop = 0; + + while (resNode) { + loop ++; + + // base + rc = add_dword( &pFill, resNode->base, &usedbytes, &available); + if (rc) + return(rc); + + // length + rc = add_dword( &pFill, resNode->length, &usedbytes, &available); + if (rc) + return(rc); + + resNode = resNode->next; + } + + // Fill in the number of entries + p_ev_ctrl->p_mem_avail = loop; + + // Figure out IO Available + + resNode = ctrl->io_head; + + loop = 0; + + while (resNode) { + loop ++; + + // base + rc = add_dword( &pFill, resNode->base, &usedbytes, &available); + if (rc) + return(rc); + + // length + rc = add_dword( &pFill, resNode->length, &usedbytes, &available); + if (rc) + return(rc); + + resNode = resNode->next; + } + + // Fill in the number of entries + p_ev_ctrl->io_avail = loop; + + // Figure out bus Available + + resNode = ctrl->bus_head; + + loop = 0; + + while (resNode) { + loop ++; + + // base + rc = add_dword( &pFill, resNode->base, &usedbytes, &available); + if (rc) + return(rc); + + // length + rc = add_dword( &pFill, resNode->length, &usedbytes, &available); + if (rc) + return(rc); + + resNode = resNode->next; + } + + // Fill in the number of entries + p_ev_ctrl->bus_avail = loop; + + ctrl = ctrl->next; + } + + p_EV_header->num_of_ctrl = numCtrl; + + // Now store the EV + + temp_dword = usedbytes; + + rc = access_EV(WRITE_EV, "CQTHPS", (u8*) buffer, &temp_dword); + + dbg("usedbytes = 0x%x, length = 0x%x\n", usedbytes, temp_dword); + + evbuffer_length = temp_dword; + + if (rc) { + err(msg_unable_to_save); + return(1); + } + + return(0); +} + + +void compaq_nvram_init (void __iomem *rom_start) +{ + if (rom_start) { + compaq_int15_entry_point = (rom_start + ROM_INT15_PHY_ADDR - ROM_PHY_ADDR); + } + dbg("int15 entry = %p\n", compaq_int15_entry_point); + + /* initialize our int15 lock */ + spin_lock_init(&int15_lock); +} + + +int compaq_nvram_load (void __iomem *rom_start, struct controller *ctrl) +{ + u8 bus, device, function; + u8 nummem, numpmem, numio, numbus; + u32 rc; + u8 *p_byte; + struct pci_resource *mem_node; + struct pci_resource *p_mem_node; + struct pci_resource *io_node; + struct pci_resource *bus_node; + struct ev_hrt_ctrl *p_ev_ctrl; + struct ev_hrt_header *p_EV_header; + + if (!evbuffer_init) { + // Read the resource list information in from NVRAM + if (load_HRT(rom_start)) + memset (evbuffer, 0, 1024); + + evbuffer_init = 1; + } + + // If we saved information in NVRAM, use it now + p_EV_header = (struct ev_hrt_header *) evbuffer; + + // The following code is for systems where version 1.0 of this + // driver has been loaded, but doesn't support the hardware. + // In that case, the driver would incorrectly store something + // in NVRAM. + if ((p_EV_header->Version == 2) || + ((p_EV_header->Version == 1) && !ctrl->push_flag)) { + p_byte = &(p_EV_header->next); + + p_ev_ctrl = (struct ev_hrt_ctrl *) &(p_EV_header->next); + + p_byte += 3; + + if (p_byte > ((u8*)p_EV_header + evbuffer_length)) + return 2; + + bus = p_ev_ctrl->bus; + device = p_ev_ctrl->device; + function = p_ev_ctrl->function; + + while ((bus != ctrl->bus) || + (device != PCI_SLOT(ctrl->pci_dev->devfn)) || + (function != PCI_FUNC(ctrl->pci_dev->devfn))) { + nummem = p_ev_ctrl->mem_avail; + numpmem = p_ev_ctrl->p_mem_avail; + numio = p_ev_ctrl->io_avail; + numbus = p_ev_ctrl->bus_avail; + + p_byte += 4; + + if (p_byte > ((u8*)p_EV_header + evbuffer_length)) + return 2; + + // Skip forward to the next entry + p_byte += (nummem + numpmem + numio + numbus) * 8; + + if (p_byte > ((u8*)p_EV_header + evbuffer_length)) + return 2; + + p_ev_ctrl = (struct ev_hrt_ctrl *) p_byte; + + p_byte += 3; + + if (p_byte > ((u8*)p_EV_header + evbuffer_length)) + return 2; + + bus = p_ev_ctrl->bus; + device = p_ev_ctrl->device; + function = p_ev_ctrl->function; + } + + nummem = p_ev_ctrl->mem_avail; + numpmem = p_ev_ctrl->p_mem_avail; + numio = p_ev_ctrl->io_avail; + numbus = p_ev_ctrl->bus_avail; + + p_byte += 4; + + if (p_byte > ((u8*)p_EV_header + evbuffer_length)) + return 2; + + while (nummem--) { + mem_node = kmalloc(sizeof(struct pci_resource), GFP_KERNEL); + + if (!mem_node) + break; + + mem_node->base = *(u32*)p_byte; + dbg("mem base = %8.8x\n",mem_node->base); + p_byte += 4; + + if (p_byte > ((u8*)p_EV_header + evbuffer_length)) { + kfree(mem_node); + return 2; + } + + mem_node->length = *(u32*)p_byte; + dbg("mem length = %8.8x\n",mem_node->length); + p_byte += 4; + + if (p_byte > ((u8*)p_EV_header + evbuffer_length)) { + kfree(mem_node); + return 2; + } + + mem_node->next = ctrl->mem_head; + ctrl->mem_head = mem_node; + } + + while (numpmem--) { + p_mem_node = kmalloc(sizeof(struct pci_resource), GFP_KERNEL); + + if (!p_mem_node) + break; + + p_mem_node->base = *(u32*)p_byte; + dbg("pre-mem base = %8.8x\n",p_mem_node->base); + p_byte += 4; + + if (p_byte > ((u8*)p_EV_header + evbuffer_length)) { + kfree(p_mem_node); + return 2; + } + + p_mem_node->length = *(u32*)p_byte; + dbg("pre-mem length = %8.8x\n",p_mem_node->length); + p_byte += 4; + + if (p_byte > ((u8*)p_EV_header + evbuffer_length)) { + kfree(p_mem_node); + return 2; + } + + p_mem_node->next = ctrl->p_mem_head; + ctrl->p_mem_head = p_mem_node; + } + + while (numio--) { + io_node = kmalloc(sizeof(struct pci_resource), GFP_KERNEL); + + if (!io_node) + break; + + io_node->base = *(u32*)p_byte; + dbg("io base = %8.8x\n",io_node->base); + p_byte += 4; + + if (p_byte > ((u8*)p_EV_header + evbuffer_length)) { + kfree(io_node); + return 2; + } + + io_node->length = *(u32*)p_byte; + dbg("io length = %8.8x\n",io_node->length); + p_byte += 4; + + if (p_byte > ((u8*)p_EV_header + evbuffer_length)) { + kfree(io_node); + return 2; + } + + io_node->next = ctrl->io_head; + ctrl->io_head = io_node; + } + + while (numbus--) { + bus_node = kmalloc(sizeof(struct pci_resource), GFP_KERNEL); + + if (!bus_node) + break; + + bus_node->base = *(u32*)p_byte; + p_byte += 4; + + if (p_byte > ((u8*)p_EV_header + evbuffer_length)) { + kfree(bus_node); + return 2; + } + + bus_node->length = *(u32*)p_byte; + p_byte += 4; + + if (p_byte > ((u8*)p_EV_header + evbuffer_length)) { + kfree(bus_node); + return 2; + } + + bus_node->next = ctrl->bus_head; + ctrl->bus_head = bus_node; + } + + // If all of the following fail, we don't have any resources for + // hot plug add + rc = 1; + rc &= cpqhp_resource_sort_and_combine(&(ctrl->mem_head)); + rc &= cpqhp_resource_sort_and_combine(&(ctrl->p_mem_head)); + rc &= cpqhp_resource_sort_and_combine(&(ctrl->io_head)); + rc &= cpqhp_resource_sort_and_combine(&(ctrl->bus_head)); + + if (rc) + return(rc); + } else { + if ((evbuffer[0] != 0) && (!ctrl->push_flag)) + return 1; + } + + return 0; +} + + +int compaq_nvram_store (void __iomem *rom_start) +{ + int rc = 1; + + if (rom_start == NULL) + return -ENODEV; + + if (evbuffer_init) { + rc = store_HRT(rom_start); + if (rc) { + err(msg_unable_to_save); + } + } + return rc; +} + diff --git a/drivers/pci/hotplug/cpqphp_nvram.h b/drivers/pci/hotplug/cpqphp_nvram.h new file mode 100644 index 0000000..e89c070 --- /dev/null +++ b/drivers/pci/hotplug/cpqphp_nvram.h @@ -0,0 +1,57 @@ +/* + * Compaq Hot Plug Controller Driver + * + * Copyright (C) 1995,2001 Compaq Computer Corporation + * Copyright (C) 2001 Greg Kroah-Hartman (greg@kroah.com) + * + * 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> + * + */ + +#ifndef _CPQPHP_NVRAM_H +#define _CPQPHP_NVRAM_H + +#ifndef CONFIG_HOTPLUG_PCI_COMPAQ_NVRAM + +static inline void compaq_nvram_init (void __iomem *rom_start) +{ + return; +} + +static inline int compaq_nvram_load (void __iomem *rom_start, struct controller *ctrl) +{ + return 0; +} + +static inline int compaq_nvram_store (void __iomem *rom_start) +{ + return 0; +} + +#else + +extern void compaq_nvram_init (void __iomem *rom_start); +extern int compaq_nvram_load (void __iomem *rom_start, struct controller *ctrl); +extern int compaq_nvram_store (void __iomem *rom_start); + +#endif + +#endif + diff --git a/drivers/pci/hotplug/cpqphp_pci.c b/drivers/pci/hotplug/cpqphp_pci.c new file mode 100644 index 0000000..0902193 --- /dev/null +++ b/drivers/pci/hotplug/cpqphp_pci.c @@ -0,0 +1,1564 @@ +/* + * Compaq Hot Plug Controller Driver + * + * Copyright (C) 1995,2001 Compaq Computer Corporation + * 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/module.h> +#include <linux/kernel.h> +#include <linux/types.h> +#include <linux/slab.h> +#include <linux/workqueue.h> +#include <linux/proc_fs.h> +#include <linux/pci.h> +#include <linux/pci_hotplug.h> +#include "../pci.h" +#include "cpqphp.h" +#include "cpqphp_nvram.h" +#include "../../../arch/x86/pci/pci.h" /* horrible hack showing how processor dependent we are... */ + + +u8 cpqhp_nic_irq; +u8 cpqhp_disk_irq; + +static u16 unused_IRQ; + +/* + * detect_HRT_floating_pointer + * + * find the Hot Plug Resource Table in the specified region of memory. + * + */ +static void __iomem *detect_HRT_floating_pointer(void __iomem *begin, void __iomem *end) +{ + void __iomem *fp; + void __iomem *endp; + u8 temp1, temp2, temp3, temp4; + int status = 0; + + endp = (end - sizeof(struct hrt) + 1); + + for (fp = begin; fp <= endp; fp += 16) { + temp1 = readb(fp + SIG0); + temp2 = readb(fp + SIG1); + temp3 = readb(fp + SIG2); + temp4 = readb(fp + SIG3); + if (temp1 == '$' && + temp2 == 'H' && + temp3 == 'R' && + temp4 == 'T') { + status = 1; + break; + } + } + + if (!status) + fp = NULL; + + dbg("Discovered Hotplug Resource Table at %p\n", fp); + return fp; +} + + +int cpqhp_configure_device (struct controller* ctrl, struct pci_func* func) +{ + unsigned char bus; + struct pci_bus *child; + int num; + + if (func->pci_dev == NULL) + func->pci_dev = pci_find_slot(func->bus, PCI_DEVFN(func->device, func->function)); + + /* No pci device, we need to create it then */ + if (func->pci_dev == NULL) { + dbg("INFO: pci_dev still null\n"); + + num = pci_scan_slot(ctrl->pci_dev->bus, PCI_DEVFN(func->device, func->function)); + if (num) + pci_bus_add_devices(ctrl->pci_dev->bus); + + func->pci_dev = pci_find_slot(func->bus, PCI_DEVFN(func->device, func->function)); + if (func->pci_dev == NULL) { + dbg("ERROR: pci_dev still null\n"); + return 0; + } + } + + if (func->pci_dev->hdr_type == PCI_HEADER_TYPE_BRIDGE) { + pci_read_config_byte(func->pci_dev, PCI_SECONDARY_BUS, &bus); + child = (struct pci_bus*) pci_add_new_bus(func->pci_dev->bus, (func->pci_dev), bus); + pci_do_scan_bus(child); + } + + return 0; +} + + +int cpqhp_unconfigure_device(struct pci_func* func) +{ + int j; + + dbg("%s: bus/dev/func = %x/%x/%x\n", __func__, func->bus, func->device, func->function); + + for (j=0; j<8 ; j++) { + struct pci_dev* temp = pci_find_slot(func->bus, PCI_DEVFN(func->device, j)); + if (temp) + pci_remove_bus_device(temp); + } + return 0; +} + +static int PCI_RefinedAccessConfig(struct pci_bus *bus, unsigned int devfn, u8 offset, u32 *value) +{ + u32 vendID = 0; + + if (pci_bus_read_config_dword (bus, devfn, PCI_VENDOR_ID, &vendID) == -1) + return -1; + if (vendID == 0xffffffff) + return -1; + return pci_bus_read_config_dword (bus, devfn, offset, value); +} + + +/* + * cpqhp_set_irq + * + * @bus_num: bus number of PCI device + * @dev_num: device number of PCI device + * @slot: pointer to u8 where slot number will be returned + */ +int cpqhp_set_irq (u8 bus_num, u8 dev_num, u8 int_pin, u8 irq_num) +{ + int rc = 0; + + if (cpqhp_legacy_mode) { + struct pci_dev *fakedev; + struct pci_bus *fakebus; + u16 temp_word; + + fakedev = kmalloc(sizeof(*fakedev), GFP_KERNEL); + fakebus = kmalloc(sizeof(*fakebus), GFP_KERNEL); + if (!fakedev || !fakebus) { + kfree(fakedev); + kfree(fakebus); + return -ENOMEM; + } + + fakedev->devfn = dev_num << 3; + fakedev->bus = fakebus; + fakebus->number = bus_num; + dbg("%s: dev %d, bus %d, pin %d, num %d\n", + __func__, dev_num, bus_num, int_pin, irq_num); + rc = pcibios_set_irq_routing(fakedev, int_pin - 0x0a, irq_num); + kfree(fakedev); + kfree(fakebus); + dbg("%s: rc %d\n", __func__, rc); + if (!rc) + return !rc; + + // set the Edge Level Control Register (ELCR) + temp_word = inb(0x4d0); + temp_word |= inb(0x4d1) << 8; + + temp_word |= 0x01 << irq_num; + + // This should only be for x86 as it sets the Edge Level Control Register + outb((u8) (temp_word & 0xFF), 0x4d0); + outb((u8) ((temp_word & 0xFF00) >> 8), 0x4d1); + rc = 0; + } + + return rc; +} + + +/* + * WTF??? This function isn't in the code, yet a function calls it, but the + * compiler optimizes it away? strange. Here as a placeholder to keep the + * compiler happy. + */ +static int PCI_ScanBusNonBridge (u8 bus, u8 device) +{ + return 0; +} + +static int PCI_ScanBusForNonBridge(struct controller *ctrl, u8 bus_num, u8 * dev_num) +{ + u16 tdevice; + u32 work; + u8 tbus; + + ctrl->pci_bus->number = bus_num; + + for (tdevice = 0; tdevice < 0xFF; tdevice++) { + //Scan for access first + if (PCI_RefinedAccessConfig(ctrl->pci_bus, tdevice, 0x08, &work) == -1) + continue; + dbg("Looking for nonbridge bus_num %d dev_num %d\n", bus_num, tdevice); + //Yep we got one. Not a bridge ? + if ((work >> 8) != PCI_TO_PCI_BRIDGE_CLASS) { + *dev_num = tdevice; + dbg("found it !\n"); + return 0; + } + } + for (tdevice = 0; tdevice < 0xFF; tdevice++) { + //Scan for access first + if (PCI_RefinedAccessConfig(ctrl->pci_bus, tdevice, 0x08, &work) == -1) + continue; + dbg("Looking for bridge bus_num %d dev_num %d\n", bus_num, tdevice); + //Yep we got one. bridge ? + if ((work >> 8) == PCI_TO_PCI_BRIDGE_CLASS) { + pci_bus_read_config_byte (ctrl->pci_bus, PCI_DEVFN(tdevice, 0), PCI_SECONDARY_BUS, &tbus); + dbg("Recurse on bus_num %d tdevice %d\n", tbus, tdevice); + if (PCI_ScanBusNonBridge(tbus, tdevice) == 0) + return 0; + } + } + + return -1; +} + + +static int PCI_GetBusDevHelper(struct controller *ctrl, u8 *bus_num, u8 *dev_num, u8 slot, u8 nobridge) +{ + struct irq_routing_table *PCIIRQRoutingInfoLength; + long len; + long loop; + u32 work; + + u8 tbus, tdevice, tslot; + + PCIIRQRoutingInfoLength = pcibios_get_irq_routing_table(); + if (!PCIIRQRoutingInfoLength) + return -1; + + len = (PCIIRQRoutingInfoLength->size - + sizeof(struct irq_routing_table)) / sizeof(struct irq_info); + // Make sure I got at least one entry + if (len == 0) { + kfree(PCIIRQRoutingInfoLength ); + return -1; + } + + for (loop = 0; loop < len; ++loop) { + tbus = PCIIRQRoutingInfoLength->slots[loop].bus; + tdevice = PCIIRQRoutingInfoLength->slots[loop].devfn; + tslot = PCIIRQRoutingInfoLength->slots[loop].slot; + + if (tslot == slot) { + *bus_num = tbus; + *dev_num = tdevice; + ctrl->pci_bus->number = tbus; + pci_bus_read_config_dword (ctrl->pci_bus, *dev_num, PCI_VENDOR_ID, &work); + if (!nobridge || (work == 0xffffffff)) { + kfree(PCIIRQRoutingInfoLength ); + return 0; + } + + dbg("bus_num %d devfn %d\n", *bus_num, *dev_num); + pci_bus_read_config_dword (ctrl->pci_bus, *dev_num, PCI_CLASS_REVISION, &work); + dbg("work >> 8 (%x) = BRIDGE (%x)\n", work >> 8, PCI_TO_PCI_BRIDGE_CLASS); + + if ((work >> 8) == PCI_TO_PCI_BRIDGE_CLASS) { + pci_bus_read_config_byte (ctrl->pci_bus, *dev_num, PCI_SECONDARY_BUS, &tbus); + dbg("Scan bus for Non Bridge: bus %d\n", tbus); + if (PCI_ScanBusForNonBridge(ctrl, tbus, dev_num) == 0) { + *bus_num = tbus; + kfree(PCIIRQRoutingInfoLength ); + return 0; + } + } else { + kfree(PCIIRQRoutingInfoLength ); + return 0; + } + + } + } + kfree(PCIIRQRoutingInfoLength ); + return -1; +} + + +int cpqhp_get_bus_dev (struct controller *ctrl, u8 * bus_num, u8 * dev_num, u8 slot) +{ + return PCI_GetBusDevHelper(ctrl, bus_num, dev_num, slot, 0); //plain (bridges allowed) +} + + +/* More PCI configuration routines; this time centered around hotplug controller */ + + +/* + * cpqhp_save_config + * + * Reads configuration for all slots in a PCI bus and saves info. + * + * Note: For non-hot plug busses, the slot # saved is the device # + * + * returns 0 if success + */ +int cpqhp_save_config(struct controller *ctrl, int busnumber, int is_hot_plug) +{ + long rc; + u8 class_code; + u8 header_type; + u32 ID; + u8 secondary_bus; + struct pci_func *new_slot; + int sub_bus; + int FirstSupported; + int LastSupported; + int max_functions; + int function; + u8 DevError; + int device = 0; + int cloop = 0; + int stop_it; + int index; + + // Decide which slots are supported + + if (is_hot_plug) { + //********************************* + // is_hot_plug is the slot mask + //********************************* + FirstSupported = is_hot_plug >> 4; + LastSupported = FirstSupported + (is_hot_plug & 0x0F) - 1; + } else { + FirstSupported = 0; + LastSupported = 0x1F; + } + + // Save PCI configuration space for all devices in supported slots + ctrl->pci_bus->number = busnumber; + for (device = FirstSupported; device <= LastSupported; device++) { + ID = 0xFFFFFFFF; + rc = pci_bus_read_config_dword (ctrl->pci_bus, PCI_DEVFN(device, 0), PCI_VENDOR_ID, &ID); + + if (ID != 0xFFFFFFFF) { // device in slot + rc = pci_bus_read_config_byte (ctrl->pci_bus, PCI_DEVFN(device, 0), 0x0B, &class_code); + if (rc) + return rc; + + rc = pci_bus_read_config_byte (ctrl->pci_bus, PCI_DEVFN(device, 0), PCI_HEADER_TYPE, &header_type); + if (rc) + return rc; + + // If multi-function device, set max_functions to 8 + if (header_type & 0x80) + max_functions = 8; + else + max_functions = 1; + + function = 0; + + do { + DevError = 0; + + if ((header_type & 0x7F) == PCI_HEADER_TYPE_BRIDGE) { // P-P Bridge + // Recurse the subordinate bus + // get the subordinate bus number + rc = pci_bus_read_config_byte (ctrl->pci_bus, PCI_DEVFN(device, function), PCI_SECONDARY_BUS, &secondary_bus); + if (rc) { + return rc; + } else { + sub_bus = (int) secondary_bus; + + // Save secondary bus cfg spc + // with this recursive call. + rc = cpqhp_save_config(ctrl, sub_bus, 0); + if (rc) + return rc; + ctrl->pci_bus->number = busnumber; + } + } + + index = 0; + new_slot = cpqhp_slot_find(busnumber, device, index++); + while (new_slot && + (new_slot->function != (u8) function)) + new_slot = cpqhp_slot_find(busnumber, device, index++); + + if (!new_slot) { + // Setup slot structure. + new_slot = cpqhp_slot_create(busnumber); + + if (new_slot == NULL) + return(1); + } + + new_slot->bus = (u8) busnumber; + new_slot->device = (u8) device; + new_slot->function = (u8) function; + new_slot->is_a_board = 1; + new_slot->switch_save = 0x10; + // In case of unsupported board + new_slot->status = DevError; + new_slot->pci_dev = pci_find_slot(new_slot->bus, (new_slot->device << 3) | new_slot->function); + + for (cloop = 0; cloop < 0x20; cloop++) { + rc = pci_bus_read_config_dword (ctrl->pci_bus, PCI_DEVFN(device, function), cloop << 2, (u32 *) & (new_slot-> config_space [cloop])); + if (rc) + return rc; + } + + function++; + + stop_it = 0; + + // this loop skips to the next present function + // reading in Class Code and Header type. + + while ((function < max_functions)&&(!stop_it)) { + rc = pci_bus_read_config_dword (ctrl->pci_bus, PCI_DEVFN(device, function), PCI_VENDOR_ID, &ID); + if (ID == 0xFFFFFFFF) { // nothing there. + function++; + } else { // Something there + rc = pci_bus_read_config_byte (ctrl->pci_bus, PCI_DEVFN(device, function), 0x0B, &class_code); + if (rc) + return rc; + + rc = pci_bus_read_config_byte (ctrl->pci_bus, PCI_DEVFN(device, function), PCI_HEADER_TYPE, &header_type); + if (rc) + return rc; + + stop_it++; + } + } + + } while (function < max_functions); + } // End of IF (device in slot?) + else if (is_hot_plug) { + // Setup slot structure with entry for empty slot + new_slot = cpqhp_slot_create(busnumber); + + if (new_slot == NULL) { + return(1); + } + + new_slot->bus = (u8) busnumber; + new_slot->device = (u8) device; + new_slot->function = 0; + new_slot->is_a_board = 0; + new_slot->presence_save = 0; + new_slot->switch_save = 0; + } + } // End of FOR loop + + return(0); +} + + +/* + * cpqhp_save_slot_config + * + * Saves configuration info for all PCI devices in a given slot + * including subordinate busses. + * + * returns 0 if success + */ +int cpqhp_save_slot_config (struct controller *ctrl, struct pci_func * new_slot) +{ + long rc; + u8 class_code; + u8 header_type; + u32 ID; + u8 secondary_bus; + int sub_bus; + int max_functions; + int function; + int cloop = 0; + int stop_it; + + ID = 0xFFFFFFFF; + + ctrl->pci_bus->number = new_slot->bus; + pci_bus_read_config_dword (ctrl->pci_bus, PCI_DEVFN(new_slot->device, 0), PCI_VENDOR_ID, &ID); + + if (ID != 0xFFFFFFFF) { // device in slot + pci_bus_read_config_byte (ctrl->pci_bus, PCI_DEVFN(new_slot->device, 0), 0x0B, &class_code); + pci_bus_read_config_byte (ctrl->pci_bus, PCI_DEVFN(new_slot->device, 0), PCI_HEADER_TYPE, &header_type); + + if (header_type & 0x80) // Multi-function device + max_functions = 8; + else + max_functions = 1; + + function = 0; + + do { + if ((header_type & 0x7F) == PCI_HEADER_TYPE_BRIDGE) { // PCI-PCI Bridge + // Recurse the subordinate bus + pci_bus_read_config_byte (ctrl->pci_bus, PCI_DEVFN(new_slot->device, function), PCI_SECONDARY_BUS, &secondary_bus); + + sub_bus = (int) secondary_bus; + + // Save the config headers for the secondary bus. + rc = cpqhp_save_config(ctrl, sub_bus, 0); + if (rc) + return(rc); + ctrl->pci_bus->number = new_slot->bus; + + } // End of IF + + new_slot->status = 0; + + for (cloop = 0; cloop < 0x20; cloop++) { + pci_bus_read_config_dword (ctrl->pci_bus, PCI_DEVFN(new_slot->device, function), cloop << 2, (u32 *) & (new_slot-> config_space [cloop])); + } + + function++; + + stop_it = 0; + + // this loop skips to the next present function + // reading in the Class Code and the Header type. + + while ((function < max_functions) && (!stop_it)) { + pci_bus_read_config_dword (ctrl->pci_bus, PCI_DEVFN(new_slot->device, function), PCI_VENDOR_ID, &ID); + + if (ID == 0xFFFFFFFF) { // nothing there. + function++; + } else { // Something there + pci_bus_read_config_byte (ctrl->pci_bus, PCI_DEVFN(new_slot->device, function), 0x0B, &class_code); + + pci_bus_read_config_byte (ctrl->pci_bus, PCI_DEVFN(new_slot->device, function), PCI_HEADER_TYPE, &header_type); + + stop_it++; + } + } + + } while (function < max_functions); + } // End of IF (device in slot?) + else { + return 2; + } + + return 0; +} + + +/* + * cpqhp_save_base_addr_length + * + * Saves the length of all base address registers for the + * specified slot. this is for hot plug REPLACE + * + * returns 0 if success + */ +int cpqhp_save_base_addr_length(struct controller *ctrl, struct pci_func * func) +{ + u8 cloop; + u8 header_type; + u8 secondary_bus; + u8 type; + int sub_bus; + u32 temp_register; + u32 base; + u32 rc; + struct pci_func *next; + int index = 0; + struct pci_bus *pci_bus = ctrl->pci_bus; + unsigned int devfn; + + func = cpqhp_slot_find(func->bus, func->device, index++); + + while (func != NULL) { + pci_bus->number = func->bus; + devfn = PCI_DEVFN(func->device, func->function); + + // Check for Bridge + pci_bus_read_config_byte (pci_bus, devfn, PCI_HEADER_TYPE, &header_type); + + if ((header_type & 0x7F) == PCI_HEADER_TYPE_BRIDGE) { + // PCI-PCI Bridge + pci_bus_read_config_byte (pci_bus, devfn, PCI_SECONDARY_BUS, &secondary_bus); + + sub_bus = (int) secondary_bus; + + next = cpqhp_slot_list[sub_bus]; + + while (next != NULL) { + rc = cpqhp_save_base_addr_length(ctrl, next); + if (rc) + return rc; + + next = next->next; + } + pci_bus->number = func->bus; + + //FIXME: this loop is duplicated in the non-bridge case. The two could be rolled together + // Figure out IO and memory base lengths + for (cloop = 0x10; cloop <= 0x14; cloop += 4) { + temp_register = 0xFFFFFFFF; + pci_bus_write_config_dword (pci_bus, devfn, cloop, temp_register); + pci_bus_read_config_dword (pci_bus, devfn, cloop, &base); + + if (base) { // If this register is implemented + if (base & 0x01L) { + // IO base + // set base = amount of IO space requested + base = base & 0xFFFFFFFE; + base = (~base) + 1; + + type = 1; + } else { + // memory base + base = base & 0xFFFFFFF0; + base = (~base) + 1; + + type = 0; + } + } else { + base = 0x0L; + type = 0; + } + + // Save information in slot structure + func->base_length[(cloop - 0x10) >> 2] = + base; + func->base_type[(cloop - 0x10) >> 2] = type; + + } // End of base register loop + + + } else if ((header_type & 0x7F) == 0x00) { // PCI-PCI Bridge + // Figure out IO and memory base lengths + for (cloop = 0x10; cloop <= 0x24; cloop += 4) { + temp_register = 0xFFFFFFFF; + pci_bus_write_config_dword (pci_bus, devfn, cloop, temp_register); + pci_bus_read_config_dword (pci_bus, devfn, cloop, &base); + + if (base) { // If this register is implemented + if (base & 0x01L) { + // IO base + // base = amount of IO space requested + base = base & 0xFFFFFFFE; + base = (~base) + 1; + + type = 1; + } else { + // memory base + // base = amount of memory space requested + base = base & 0xFFFFFFF0; + base = (~base) + 1; + + type = 0; + } + } else { + base = 0x0L; + type = 0; + } + + // Save information in slot structure + func->base_length[(cloop - 0x10) >> 2] = base; + func->base_type[(cloop - 0x10) >> 2] = type; + + } // End of base register loop + + } else { // Some other unknown header type + } + + // find the next device in this slot + func = cpqhp_slot_find(func->bus, func->device, index++); + } + + return(0); +} + + +/* + * cpqhp_save_used_resources + * + * Stores used resource information for existing boards. this is + * for boards that were in the system when this driver was loaded. + * this function is for hot plug ADD + * + * returns 0 if success + */ +int cpqhp_save_used_resources (struct controller *ctrl, struct pci_func * func) +{ + u8 cloop; + u8 header_type; + u8 secondary_bus; + u8 temp_byte; + u8 b_base; + u8 b_length; + u16 command; + u16 save_command; + u16 w_base; + u16 w_length; + u32 temp_register; + u32 save_base; + u32 base; + int index = 0; + struct pci_resource *mem_node; + struct pci_resource *p_mem_node; + struct pci_resource *io_node; + struct pci_resource *bus_node; + struct pci_bus *pci_bus = ctrl->pci_bus; + unsigned int devfn; + + func = cpqhp_slot_find(func->bus, func->device, index++); + + while ((func != NULL) && func->is_a_board) { + pci_bus->number = func->bus; + devfn = PCI_DEVFN(func->device, func->function); + + // Save the command register + pci_bus_read_config_word(pci_bus, devfn, PCI_COMMAND, &save_command); + + // disable card + command = 0x00; + pci_bus_write_config_word(pci_bus, devfn, PCI_COMMAND, command); + + // Check for Bridge + pci_bus_read_config_byte(pci_bus, devfn, PCI_HEADER_TYPE, &header_type); + + if ((header_type & 0x7F) == PCI_HEADER_TYPE_BRIDGE) { // PCI-PCI Bridge + // Clear Bridge Control Register + command = 0x00; + pci_bus_write_config_word(pci_bus, devfn, PCI_BRIDGE_CONTROL, command); + pci_bus_read_config_byte(pci_bus, devfn, PCI_SECONDARY_BUS, &secondary_bus); + pci_bus_read_config_byte(pci_bus, devfn, PCI_SUBORDINATE_BUS, &temp_byte); + + bus_node = kmalloc(sizeof(*bus_node), GFP_KERNEL); + if (!bus_node) + return -ENOMEM; + + bus_node->base = secondary_bus; + bus_node->length = temp_byte - secondary_bus + 1; + + bus_node->next = func->bus_head; + func->bus_head = bus_node; + + // Save IO base and Limit registers + pci_bus_read_config_byte(pci_bus, devfn, PCI_IO_BASE, &b_base); + pci_bus_read_config_byte(pci_bus, devfn, PCI_IO_LIMIT, &b_length); + + if ((b_base <= b_length) && (save_command & 0x01)) { + io_node = kmalloc(sizeof(*io_node), GFP_KERNEL); + if (!io_node) + return -ENOMEM; + + io_node->base = (b_base & 0xF0) << 8; + io_node->length = (b_length - b_base + 0x10) << 8; + + io_node->next = func->io_head; + func->io_head = io_node; + } + + // Save memory base and Limit registers + pci_bus_read_config_word(pci_bus, devfn, PCI_MEMORY_BASE, &w_base); + pci_bus_read_config_word(pci_bus, devfn, PCI_MEMORY_LIMIT, &w_length); + + if ((w_base <= w_length) && (save_command & 0x02)) { + mem_node = kmalloc(sizeof(*mem_node), GFP_KERNEL); + if (!mem_node) + return -ENOMEM; + + mem_node->base = w_base << 16; + mem_node->length = (w_length - w_base + 0x10) << 16; + + mem_node->next = func->mem_head; + func->mem_head = mem_node; + } + + // Save prefetchable memory base and Limit registers + pci_bus_read_config_word(pci_bus, devfn, PCI_PREF_MEMORY_BASE, &w_base); + pci_bus_read_config_word(pci_bus, devfn, PCI_PREF_MEMORY_LIMIT, &w_length); + + if ((w_base <= w_length) && (save_command & 0x02)) { + p_mem_node = kmalloc(sizeof(*p_mem_node), GFP_KERNEL); + if (!p_mem_node) + return -ENOMEM; + + p_mem_node->base = w_base << 16; + p_mem_node->length = (w_length - w_base + 0x10) << 16; + + p_mem_node->next = func->p_mem_head; + func->p_mem_head = p_mem_node; + } + // Figure out IO and memory base lengths + for (cloop = 0x10; cloop <= 0x14; cloop += 4) { + pci_bus_read_config_dword (pci_bus, devfn, cloop, &save_base); + + temp_register = 0xFFFFFFFF; + pci_bus_write_config_dword(pci_bus, devfn, cloop, temp_register); + pci_bus_read_config_dword(pci_bus, devfn, cloop, &base); + + temp_register = base; + + if (base) { // If this register is implemented + if (((base & 0x03L) == 0x01) + && (save_command & 0x01)) { + // IO base + // set temp_register = amount of IO space requested + temp_register = base & 0xFFFFFFFE; + temp_register = (~temp_register) + 1; + + io_node = kmalloc(sizeof(*io_node), + GFP_KERNEL); + if (!io_node) + return -ENOMEM; + + io_node->base = + save_base & (~0x03L); + io_node->length = temp_register; + + io_node->next = func->io_head; + func->io_head = io_node; + } else + if (((base & 0x0BL) == 0x08) + && (save_command & 0x02)) { + // prefetchable memory base + temp_register = base & 0xFFFFFFF0; + temp_register = (~temp_register) + 1; + + p_mem_node = kmalloc(sizeof(*p_mem_node), + GFP_KERNEL); + if (!p_mem_node) + return -ENOMEM; + + p_mem_node->base = save_base & (~0x0FL); + p_mem_node->length = temp_register; + + p_mem_node->next = func->p_mem_head; + func->p_mem_head = p_mem_node; + } else + if (((base & 0x0BL) == 0x00) + && (save_command & 0x02)) { + // prefetchable memory base + temp_register = base & 0xFFFFFFF0; + temp_register = (~temp_register) + 1; + + mem_node = kmalloc(sizeof(*mem_node), + GFP_KERNEL); + if (!mem_node) + return -ENOMEM; + + mem_node->base = save_base & (~0x0FL); + mem_node->length = temp_register; + + mem_node->next = func->mem_head; + func->mem_head = mem_node; + } else + return(1); + } + } // End of base register loop + } else if ((header_type & 0x7F) == 0x00) { // Standard header + // Figure out IO and memory base lengths + for (cloop = 0x10; cloop <= 0x24; cloop += 4) { + pci_bus_read_config_dword(pci_bus, devfn, cloop, &save_base); + + temp_register = 0xFFFFFFFF; + pci_bus_write_config_dword(pci_bus, devfn, cloop, temp_register); + pci_bus_read_config_dword(pci_bus, devfn, cloop, &base); + + temp_register = base; + + if (base) { // If this register is implemented + if (((base & 0x03L) == 0x01) + && (save_command & 0x01)) { + // IO base + // set temp_register = amount of IO space requested + temp_register = base & 0xFFFFFFFE; + temp_register = (~temp_register) + 1; + + io_node = kmalloc(sizeof(*io_node), + GFP_KERNEL); + if (!io_node) + return -ENOMEM; + + io_node->base = save_base & (~0x01L); + io_node->length = temp_register; + + io_node->next = func->io_head; + func->io_head = io_node; + } else + if (((base & 0x0BL) == 0x08) + && (save_command & 0x02)) { + // prefetchable memory base + temp_register = base & 0xFFFFFFF0; + temp_register = (~temp_register) + 1; + + p_mem_node = kmalloc(sizeof(*p_mem_node), + GFP_KERNEL); + if (!p_mem_node) + return -ENOMEM; + + p_mem_node->base = save_base & (~0x0FL); + p_mem_node->length = temp_register; + + p_mem_node->next = func->p_mem_head; + func->p_mem_head = p_mem_node; + } else + if (((base & 0x0BL) == 0x00) + && (save_command & 0x02)) { + // prefetchable memory base + temp_register = base & 0xFFFFFFF0; + temp_register = (~temp_register) + 1; + + mem_node = kmalloc(sizeof(*mem_node), + GFP_KERNEL); + if (!mem_node) + return -ENOMEM; + + mem_node->base = save_base & (~0x0FL); + mem_node->length = temp_register; + + mem_node->next = func->mem_head; + func->mem_head = mem_node; + } else + return(1); + } + } // End of base register loop + } else { // Some other unknown header type + } + + // find the next device in this slot + func = cpqhp_slot_find(func->bus, func->device, index++); + } + + return(0); +} + + +/* + * cpqhp_configure_board + * + * Copies saved configuration information to one slot. + * this is called recursively for bridge devices. + * this is for hot plug REPLACE! + * + * returns 0 if success + */ +int cpqhp_configure_board(struct controller *ctrl, struct pci_func * func) +{ + int cloop; + u8 header_type; + u8 secondary_bus; + int sub_bus; + struct pci_func *next; + u32 temp; + u32 rc; + int index = 0; + struct pci_bus *pci_bus = ctrl->pci_bus; + unsigned int devfn; + + func = cpqhp_slot_find(func->bus, func->device, index++); + + while (func != NULL) { + pci_bus->number = func->bus; + devfn = PCI_DEVFN(func->device, func->function); + + // Start at the top of config space so that the control + // registers are programmed last + for (cloop = 0x3C; cloop > 0; cloop -= 4) { + pci_bus_write_config_dword (pci_bus, devfn, cloop, func->config_space[cloop >> 2]); + } + + pci_bus_read_config_byte (pci_bus, devfn, PCI_HEADER_TYPE, &header_type); + + // If this is a bridge device, restore subordinate devices + if ((header_type & 0x7F) == PCI_HEADER_TYPE_BRIDGE) { // PCI-PCI Bridge + pci_bus_read_config_byte (pci_bus, devfn, PCI_SECONDARY_BUS, &secondary_bus); + + sub_bus = (int) secondary_bus; + + next = cpqhp_slot_list[sub_bus]; + + while (next != NULL) { + rc = cpqhp_configure_board(ctrl, next); + if (rc) + return rc; + + next = next->next; + } + } else { + + // Check all the base Address Registers to make sure + // they are the same. If not, the board is different. + + for (cloop = 16; cloop < 40; cloop += 4) { + pci_bus_read_config_dword (pci_bus, devfn, cloop, &temp); + + if (temp != func->config_space[cloop >> 2]) { + dbg("Config space compare failure!!! offset = %x\n", cloop); + dbg("bus = %x, device = %x, function = %x\n", func->bus, func->device, func->function); + dbg("temp = %x, config space = %x\n\n", temp, func->config_space[cloop >> 2]); + return 1; + } + } + } + + func->configured = 1; + + func = cpqhp_slot_find(func->bus, func->device, index++); + } + + return 0; +} + + +/* + * cpqhp_valid_replace + * + * this function checks to see if a board is the same as the + * one it is replacing. this check will detect if the device's + * vendor or device id's are the same + * + * returns 0 if the board is the same nonzero otherwise + */ +int cpqhp_valid_replace(struct controller *ctrl, struct pci_func * func) +{ + u8 cloop; + u8 header_type; + u8 secondary_bus; + u8 type; + u32 temp_register = 0; + u32 base; + u32 rc; + struct pci_func *next; + int index = 0; + struct pci_bus *pci_bus = ctrl->pci_bus; + unsigned int devfn; + + if (!func->is_a_board) + return(ADD_NOT_SUPPORTED); + + func = cpqhp_slot_find(func->bus, func->device, index++); + + while (func != NULL) { + pci_bus->number = func->bus; + devfn = PCI_DEVFN(func->device, func->function); + + pci_bus_read_config_dword (pci_bus, devfn, PCI_VENDOR_ID, &temp_register); + + // No adapter present + if (temp_register == 0xFFFFFFFF) + return(NO_ADAPTER_PRESENT); + + if (temp_register != func->config_space[0]) + return(ADAPTER_NOT_SAME); + + // Check for same revision number and class code + pci_bus_read_config_dword (pci_bus, devfn, PCI_CLASS_REVISION, &temp_register); + + // Adapter not the same + if (temp_register != func->config_space[0x08 >> 2]) + return(ADAPTER_NOT_SAME); + + // Check for Bridge + pci_bus_read_config_byte (pci_bus, devfn, PCI_HEADER_TYPE, &header_type); + + if ((header_type & 0x7F) == PCI_HEADER_TYPE_BRIDGE) { // PCI-PCI Bridge + // In order to continue checking, we must program the + // bus registers in the bridge to respond to accesses + // for it's subordinate bus(es) + + temp_register = func->config_space[0x18 >> 2]; + pci_bus_write_config_dword (pci_bus, devfn, PCI_PRIMARY_BUS, temp_register); + + secondary_bus = (temp_register >> 8) & 0xFF; + + next = cpqhp_slot_list[secondary_bus]; + + while (next != NULL) { + rc = cpqhp_valid_replace(ctrl, next); + if (rc) + return rc; + + next = next->next; + } + + } + // Check to see if it is a standard config header + else if ((header_type & 0x7F) == PCI_HEADER_TYPE_NORMAL) { + // Check subsystem vendor and ID + pci_bus_read_config_dword (pci_bus, devfn, PCI_SUBSYSTEM_VENDOR_ID, &temp_register); + + if (temp_register != func->config_space[0x2C >> 2]) { + // If it's a SMART-2 and the register isn't filled + // in, ignore the difference because + // they just have an old rev of the firmware + + if (!((func->config_space[0] == 0xAE100E11) + && (temp_register == 0x00L))) + return(ADAPTER_NOT_SAME); + } + // Figure out IO and memory base lengths + for (cloop = 0x10; cloop <= 0x24; cloop += 4) { + temp_register = 0xFFFFFFFF; + pci_bus_write_config_dword (pci_bus, devfn, cloop, temp_register); + pci_bus_read_config_dword (pci_bus, devfn, cloop, &base); + if (base) { // If this register is implemented + if (base & 0x01L) { + // IO base + // set base = amount of IO space requested + base = base & 0xFFFFFFFE; + base = (~base) + 1; + + type = 1; + } else { + // memory base + base = base & 0xFFFFFFF0; + base = (~base) + 1; + + type = 0; + } + } else { + base = 0x0L; + type = 0; + } + + // Check information in slot structure + if (func->base_length[(cloop - 0x10) >> 2] != base) + return(ADAPTER_NOT_SAME); + + if (func->base_type[(cloop - 0x10) >> 2] != type) + return(ADAPTER_NOT_SAME); + + } // End of base register loop + + } // End of (type 0 config space) else + else { + // this is not a type 0 or 1 config space header so + // we don't know how to do it + return(DEVICE_TYPE_NOT_SUPPORTED); + } + + // Get the next function + func = cpqhp_slot_find(func->bus, func->device, index++); + } + + + return 0; +} + + +/* + * cpqhp_find_available_resources + * + * Finds available memory, IO, and IRQ resources for programming + * devices which may be added to the system + * this function is for hot plug ADD! + * + * returns 0 if success + */ +int cpqhp_find_available_resources(struct controller *ctrl, void __iomem *rom_start) +{ + u8 temp; + u8 populated_slot; + u8 bridged_slot; + void __iomem *one_slot; + void __iomem *rom_resource_table; + struct pci_func *func = NULL; + int i = 10, index; + u32 temp_dword, rc; + struct pci_resource *mem_node; + struct pci_resource *p_mem_node; + struct pci_resource *io_node; + struct pci_resource *bus_node; + + rom_resource_table = detect_HRT_floating_pointer(rom_start, rom_start+0xffff); + dbg("rom_resource_table = %p\n", rom_resource_table); + + if (rom_resource_table == NULL) { + return -ENODEV; + } + // Sum all resources and setup resource maps + unused_IRQ = readl(rom_resource_table + UNUSED_IRQ); + dbg("unused_IRQ = %x\n", unused_IRQ); + + temp = 0; + while (unused_IRQ) { + if (unused_IRQ & 1) { + cpqhp_disk_irq = temp; + break; + } + unused_IRQ = unused_IRQ >> 1; + temp++; + } + + dbg("cpqhp_disk_irq= %d\n", cpqhp_disk_irq); + unused_IRQ = unused_IRQ >> 1; + temp++; + + while (unused_IRQ) { + if (unused_IRQ & 1) { + cpqhp_nic_irq = temp; + break; + } + unused_IRQ = unused_IRQ >> 1; + temp++; + } + + dbg("cpqhp_nic_irq= %d\n", cpqhp_nic_irq); + unused_IRQ = readl(rom_resource_table + PCIIRQ); + + temp = 0; + + if (!cpqhp_nic_irq) { + cpqhp_nic_irq = ctrl->cfgspc_irq; + } + + if (!cpqhp_disk_irq) { + cpqhp_disk_irq = ctrl->cfgspc_irq; + } + + dbg("cpqhp_disk_irq, cpqhp_nic_irq= %d, %d\n", cpqhp_disk_irq, cpqhp_nic_irq); + + rc = compaq_nvram_load(rom_start, ctrl); + if (rc) + return rc; + + one_slot = rom_resource_table + sizeof (struct hrt); + + i = readb(rom_resource_table + NUMBER_OF_ENTRIES); + dbg("number_of_entries = %d\n", i); + + if (!readb(one_slot + SECONDARY_BUS)) + return 1; + + dbg("dev|IO base|length|Mem base|length|Pre base|length|PB SB MB\n"); + + while (i && readb(one_slot + SECONDARY_BUS)) { + u8 dev_func = readb(one_slot + DEV_FUNC); + u8 primary_bus = readb(one_slot + PRIMARY_BUS); + u8 secondary_bus = readb(one_slot + SECONDARY_BUS); + u8 max_bus = readb(one_slot + MAX_BUS); + u16 io_base = readw(one_slot + IO_BASE); + u16 io_length = readw(one_slot + IO_LENGTH); + u16 mem_base = readw(one_slot + MEM_BASE); + u16 mem_length = readw(one_slot + MEM_LENGTH); + u16 pre_mem_base = readw(one_slot + PRE_MEM_BASE); + u16 pre_mem_length = readw(one_slot + PRE_MEM_LENGTH); + + dbg("%2.2x | %4.4x | %4.4x | %4.4x | %4.4x | %4.4x | %4.4x |%2.2x %2.2x %2.2x\n", + dev_func, io_base, io_length, mem_base, mem_length, pre_mem_base, pre_mem_length, + primary_bus, secondary_bus, max_bus); + + // If this entry isn't for our controller's bus, ignore it + if (primary_bus != ctrl->bus) { + i--; + one_slot += sizeof (struct slot_rt); + continue; + } + // find out if this entry is for an occupied slot + ctrl->pci_bus->number = primary_bus; + pci_bus_read_config_dword (ctrl->pci_bus, dev_func, PCI_VENDOR_ID, &temp_dword); + dbg("temp_D_word = %x\n", temp_dword); + + if (temp_dword != 0xFFFFFFFF) { + index = 0; + func = cpqhp_slot_find(primary_bus, dev_func >> 3, 0); + + while (func && (func->function != (dev_func & 0x07))) { + dbg("func = %p (bus, dev, fun) = (%d, %d, %d)\n", func, primary_bus, dev_func >> 3, index); + func = cpqhp_slot_find(primary_bus, dev_func >> 3, index++); + } + + // If we can't find a match, skip this table entry + if (!func) { + i--; + one_slot += sizeof (struct slot_rt); + continue; + } + // this may not work and shouldn't be used + if (secondary_bus != primary_bus) + bridged_slot = 1; + else + bridged_slot = 0; + + populated_slot = 1; + } else { + populated_slot = 0; + bridged_slot = 0; + } + + + // If we've got a valid IO base, use it + + temp_dword = io_base + io_length; + + if ((io_base) && (temp_dword < 0x10000)) { + io_node = kmalloc(sizeof(*io_node), GFP_KERNEL); + if (!io_node) + return -ENOMEM; + + io_node->base = io_base; + io_node->length = io_length; + + dbg("found io_node(base, length) = %x, %x\n", + io_node->base, io_node->length); + dbg("populated slot =%d \n", populated_slot); + if (!populated_slot) { + io_node->next = ctrl->io_head; + ctrl->io_head = io_node; + } else { + io_node->next = func->io_head; + func->io_head = io_node; + } + } + + // If we've got a valid memory base, use it + temp_dword = mem_base + mem_length; + if ((mem_base) && (temp_dword < 0x10000)) { + mem_node = kmalloc(sizeof(*mem_node), GFP_KERNEL); + if (!mem_node) + return -ENOMEM; + + mem_node->base = mem_base << 16; + + mem_node->length = mem_length << 16; + + dbg("found mem_node(base, length) = %x, %x\n", + mem_node->base, mem_node->length); + dbg("populated slot =%d \n", populated_slot); + if (!populated_slot) { + mem_node->next = ctrl->mem_head; + ctrl->mem_head = mem_node; + } else { + mem_node->next = func->mem_head; + func->mem_head = mem_node; + } + } + + // If we've got a valid prefetchable memory base, and + // the base + length isn't greater than 0xFFFF + temp_dword = pre_mem_base + pre_mem_length; + if ((pre_mem_base) && (temp_dword < 0x10000)) { + p_mem_node = kmalloc(sizeof(*p_mem_node), GFP_KERNEL); + if (!p_mem_node) + return -ENOMEM; + + p_mem_node->base = pre_mem_base << 16; + + p_mem_node->length = pre_mem_length << 16; + dbg("found p_mem_node(base, length) = %x, %x\n", + p_mem_node->base, p_mem_node->length); + dbg("populated slot =%d \n", populated_slot); + + if (!populated_slot) { + p_mem_node->next = ctrl->p_mem_head; + ctrl->p_mem_head = p_mem_node; + } else { + p_mem_node->next = func->p_mem_head; + func->p_mem_head = p_mem_node; + } + } + + // If we've got a valid bus number, use it + // The second condition is to ignore bus numbers on + // populated slots that don't have PCI-PCI bridges + if (secondary_bus && (secondary_bus != primary_bus)) { + bus_node = kmalloc(sizeof(*bus_node), GFP_KERNEL); + if (!bus_node) + return -ENOMEM; + + bus_node->base = secondary_bus; + bus_node->length = max_bus - secondary_bus + 1; + dbg("found bus_node(base, length) = %x, %x\n", + bus_node->base, bus_node->length); + dbg("populated slot =%d \n", populated_slot); + if (!populated_slot) { + bus_node->next = ctrl->bus_head; + ctrl->bus_head = bus_node; + } else { + bus_node->next = func->bus_head; + func->bus_head = bus_node; + } + } + + i--; + one_slot += sizeof (struct slot_rt); + } + + // If all of the following fail, we don't have any resources for + // hot plug add + rc = 1; + rc &= cpqhp_resource_sort_and_combine(&(ctrl->mem_head)); + rc &= cpqhp_resource_sort_and_combine(&(ctrl->p_mem_head)); + rc &= cpqhp_resource_sort_and_combine(&(ctrl->io_head)); + rc &= cpqhp_resource_sort_and_combine(&(ctrl->bus_head)); + + return rc; +} + + +/* + * cpqhp_return_board_resources + * + * this routine returns all resources allocated to a board to + * the available pool. + * + * returns 0 if success + */ +int cpqhp_return_board_resources(struct pci_func * func, struct resource_lists * resources) +{ + int rc = 0; + struct pci_resource *node; + struct pci_resource *t_node; + dbg("%s\n", __func__); + + if (!func) + return 1; + + node = func->io_head; + func->io_head = NULL; + while (node) { + t_node = node->next; + return_resource(&(resources->io_head), node); + node = t_node; + } + + node = func->mem_head; + func->mem_head = NULL; + while (node) { + t_node = node->next; + return_resource(&(resources->mem_head), node); + node = t_node; + } + + node = func->p_mem_head; + func->p_mem_head = NULL; + while (node) { + t_node = node->next; + return_resource(&(resources->p_mem_head), node); + node = t_node; + } + + node = func->bus_head; + func->bus_head = NULL; + while (node) { + t_node = node->next; + return_resource(&(resources->bus_head), node); + node = t_node; + } + + rc |= cpqhp_resource_sort_and_combine(&(resources->mem_head)); + rc |= cpqhp_resource_sort_and_combine(&(resources->p_mem_head)); + rc |= cpqhp_resource_sort_and_combine(&(resources->io_head)); + rc |= cpqhp_resource_sort_and_combine(&(resources->bus_head)); + + return rc; +} + + +/* + * cpqhp_destroy_resource_list + * + * Puts node back in the resource list pointed to by head + */ +void cpqhp_destroy_resource_list (struct resource_lists * resources) +{ + struct pci_resource *res, *tres; + + res = resources->io_head; + resources->io_head = NULL; + + while (res) { + tres = res; + res = res->next; + kfree(tres); + } + + res = resources->mem_head; + resources->mem_head = NULL; + + while (res) { + tres = res; + res = res->next; + kfree(tres); + } + + res = resources->p_mem_head; + resources->p_mem_head = NULL; + + while (res) { + tres = res; + res = res->next; + kfree(tres); + } + + res = resources->bus_head; + resources->bus_head = NULL; + + while (res) { + tres = res; + res = res->next; + kfree(tres); + } +} + + +/* + * cpqhp_destroy_board_resources + * + * Puts node back in the resource list pointed to by head + */ +void cpqhp_destroy_board_resources (struct pci_func * func) +{ + struct pci_resource *res, *tres; + + res = func->io_head; + func->io_head = NULL; + + while (res) { + tres = res; + res = res->next; + kfree(tres); + } + + res = func->mem_head; + func->mem_head = NULL; + + while (res) { + tres = res; + res = res->next; + kfree(tres); + } + + res = func->p_mem_head; + func->p_mem_head = NULL; + + while (res) { + tres = res; + res = res->next; + kfree(tres); + } + + res = func->bus_head; + func->bus_head = NULL; + + while (res) { + tres = res; + res = res->next; + kfree(tres); + } +} + diff --git a/drivers/pci/hotplug/cpqphp_sysfs.c b/drivers/pci/hotplug/cpqphp_sysfs.c new file mode 100644 index 0000000..a13abf5 --- /dev/null +++ b/drivers/pci/hotplug/cpqphp_sysfs.c @@ -0,0 +1,237 @@ +/* + * Compaq Hot Plug Controller Driver + * + * Copyright (C) 1995,2001 Compaq Computer Corporation + * Copyright (C) 2001,2003 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/module.h> +#include <linux/kernel.h> +#include <linux/types.h> +#include <linux/proc_fs.h> +#include <linux/workqueue.h> +#include <linux/pci.h> +#include <linux/pci_hotplug.h> +#include <linux/debugfs.h> +#include "cpqphp.h" + +static int show_ctrl (struct controller *ctrl, char *buf) +{ + char *out = buf; + int index; + struct pci_resource *res; + + out += sprintf(buf, "Free resources: memory\n"); + index = 11; + res = ctrl->mem_head; + while (res && index--) { + out += sprintf(out, "start = %8.8x, length = %8.8x\n", res->base, res->length); + res = res->next; + } + out += sprintf(out, "Free resources: prefetchable memory\n"); + index = 11; + res = ctrl->p_mem_head; + while (res && index--) { + out += sprintf(out, "start = %8.8x, length = %8.8x\n", res->base, res->length); + res = res->next; + } + out += sprintf(out, "Free resources: IO\n"); + index = 11; + res = ctrl->io_head; + while (res && index--) { + out += sprintf(out, "start = %8.8x, length = %8.8x\n", res->base, res->length); + res = res->next; + } + out += sprintf(out, "Free resources: bus numbers\n"); + index = 11; + res = ctrl->bus_head; + while (res && index--) { + out += sprintf(out, "start = %8.8x, length = %8.8x\n", res->base, res->length); + res = res->next; + } + + return out - buf; +} + +static int show_dev (struct controller *ctrl, char *buf) +{ + char * out = buf; + int index; + struct pci_resource *res; + struct pci_func *new_slot; + struct slot *slot; + + slot = ctrl->slot; + + while (slot) { + new_slot = cpqhp_slot_find(slot->bus, slot->device, 0); + if (!new_slot) + break; + out += sprintf(out, "assigned resources: memory\n"); + index = 11; + res = new_slot->mem_head; + while (res && index--) { + out += sprintf(out, "start = %8.8x, length = %8.8x\n", res->base, res->length); + res = res->next; + } + out += sprintf(out, "assigned resources: prefetchable memory\n"); + index = 11; + res = new_slot->p_mem_head; + while (res && index--) { + out += sprintf(out, "start = %8.8x, length = %8.8x\n", res->base, res->length); + res = res->next; + } + out += sprintf(out, "assigned resources: IO\n"); + index = 11; + res = new_slot->io_head; + while (res && index--) { + out += sprintf(out, "start = %8.8x, length = %8.8x\n", res->base, res->length); + res = res->next; + } + out += sprintf(out, "assigned resources: bus numbers\n"); + index = 11; + res = new_slot->bus_head; + while (res && index--) { + out += sprintf(out, "start = %8.8x, length = %8.8x\n", res->base, res->length); + res = res->next; + } + slot=slot->next; + } + + return out - buf; +} + +static int spew_debug_info(struct controller *ctrl, char *data, int size) +{ + int used; + + used = size - show_ctrl(ctrl, data); + used = (size - used) - show_dev(ctrl, &data[used]); + return used; +} + +struct ctrl_dbg { + int size; + char *data; + struct controller *ctrl; +}; + +#define MAX_OUTPUT (4*PAGE_SIZE) + +static int open(struct inode *inode, struct file *file) +{ + struct controller *ctrl = inode->i_private; + struct ctrl_dbg *dbg; + int retval = -ENOMEM; + + lock_kernel(); + dbg = kmalloc(sizeof(*dbg), GFP_KERNEL); + if (!dbg) + goto exit; + dbg->data = kmalloc(MAX_OUTPUT, GFP_KERNEL); + if (!dbg->data) { + kfree(dbg); + goto exit; + } + dbg->size = spew_debug_info(ctrl, dbg->data, MAX_OUTPUT); + file->private_data = dbg; + retval = 0; +exit: + unlock_kernel(); + return retval; +} + +static loff_t lseek(struct file *file, loff_t off, int whence) +{ + struct ctrl_dbg *dbg; + loff_t new = -1; + + lock_kernel(); + dbg = file->private_data; + + switch (whence) { + case 0: + new = off; + break; + case 1: + new = file->f_pos + off; + break; + } + if (new < 0 || new > dbg->size) { + unlock_kernel(); + return -EINVAL; + } + unlock_kernel(); + return (file->f_pos = new); +} + +static ssize_t read(struct file *file, char __user *buf, + size_t nbytes, loff_t *ppos) +{ + struct ctrl_dbg *dbg = file->private_data; + return simple_read_from_buffer(buf, nbytes, ppos, dbg->data, dbg->size); +} + +static int release(struct inode *inode, struct file *file) +{ + struct ctrl_dbg *dbg = file->private_data; + + kfree(dbg->data); + kfree(dbg); + return 0; +} + +static const struct file_operations debug_ops = { + .owner = THIS_MODULE, + .open = open, + .llseek = lseek, + .read = read, + .release = release, +}; + +static struct dentry *root; + +void cpqhp_initialize_debugfs(void) +{ + if (!root) + root = debugfs_create_dir("cpqhp", NULL); +} + +void cpqhp_shutdown_debugfs(void) +{ + debugfs_remove(root); +} + +void cpqhp_create_debugfs_files(struct controller *ctrl) +{ + ctrl->dentry = debugfs_create_file(ctrl->pci_dev->dev.bus_id, S_IRUGO, root, ctrl, &debug_ops); +} + +void cpqhp_remove_debugfs_files(struct controller *ctrl) +{ + if (ctrl->dentry) + debugfs_remove(ctrl->dentry); + ctrl->dentry = NULL; +} + diff --git a/drivers/pci/hotplug/fakephp.c b/drivers/pci/hotplug/fakephp.c new file mode 100644 index 0000000..3a2637a --- /dev/null +++ b/drivers/pci/hotplug/fakephp.c @@ -0,0 +1,384 @@ +/* + * Fake PCI Hot Plug Controller Driver + * + * Copyright (C) 2003 Greg Kroah-Hartman <greg@kroah.com> + * Copyright (C) 2003 IBM Corp. + * Copyright (C) 2003 Rolf Eike Beer <eike-kernel@sf-tec.de> + * + * Based on ideas and code from: + * Vladimir Kondratiev <vladimir.kondratiev@intel.com> + * Rolf Eike Beer <eike-kernel@sf-tec.de> + * + * 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, version 2 of the License. + * + * Send feedback to <greg@kroah.com> + */ + +/* + * + * This driver will "emulate" removing PCI devices from the system. If + * the "power" file is written to with "0" then the specified PCI device + * will be completely removed from the kernel. + * + * WARNING, this does NOT turn off the power to the PCI device. This is + * a "logical" removal, not a physical or electrical removal. + * + * Use this module at your own risk, you have been warned! + * + * Enabling PCI devices is left as an exercise for the reader... + * + */ +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/pci.h> +#include <linux/pci_hotplug.h> +#include <linux/init.h> +#include <linux/string.h> +#include <linux/slab.h> +#include <linux/workqueue.h> +#include "../pci.h" + +#if !defined(MODULE) + #define MY_NAME "fakephp" +#else + #define MY_NAME THIS_MODULE->name +#endif + +#define dbg(format, arg...) \ + do { \ + if (debug) \ + printk(KERN_DEBUG "%s: " format, \ + 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 DRIVER_AUTHOR "Greg Kroah-Hartman <greg@kroah.com>" +#define DRIVER_DESC "Fake PCI Hot Plug Controller Driver" + +struct dummy_slot { + struct list_head node; + struct hotplug_slot *slot; + struct pci_dev *dev; + struct work_struct remove_work; + unsigned long removed; +}; + +static int debug; +static int dup_slots; +static LIST_HEAD(slot_list); +static struct workqueue_struct *dummyphp_wq; + +static void pci_rescan_worker(struct work_struct *work); +static DECLARE_WORK(pci_rescan_work, pci_rescan_worker); + +static int enable_slot (struct hotplug_slot *slot); +static int disable_slot (struct hotplug_slot *slot); + +static struct hotplug_slot_ops dummy_hotplug_slot_ops = { + .owner = THIS_MODULE, + .enable_slot = enable_slot, + .disable_slot = disable_slot, +}; + +static void dummy_release(struct hotplug_slot *slot) +{ + struct dummy_slot *dslot = slot->private; + + list_del(&dslot->node); + kfree(dslot->slot->info); + kfree(dslot->slot); + pci_dev_put(dslot->dev); + kfree(dslot); +} + +#define SLOT_NAME_SIZE 8 + +static int add_slot(struct pci_dev *dev) +{ + struct dummy_slot *dslot; + struct hotplug_slot *slot; + char name[SLOT_NAME_SIZE]; + int retval = -ENOMEM; + static int count = 1; + + slot = kzalloc(sizeof(struct hotplug_slot), GFP_KERNEL); + if (!slot) + goto error; + + slot->info = kzalloc(sizeof(struct hotplug_slot_info), GFP_KERNEL); + if (!slot->info) + goto error_slot; + + slot->info->power_status = 1; + slot->info->max_bus_speed = PCI_SPEED_UNKNOWN; + slot->info->cur_bus_speed = PCI_SPEED_UNKNOWN; + + dslot = kzalloc(sizeof(struct dummy_slot), GFP_KERNEL); + if (!dslot) + goto error_info; + + if (dup_slots) + snprintf(name, SLOT_NAME_SIZE, "fake"); + else + snprintf(name, SLOT_NAME_SIZE, "fake%d", count++); + dbg("slot->name = %s\n", name); + slot->ops = &dummy_hotplug_slot_ops; + slot->release = &dummy_release; + slot->private = dslot; + + retval = pci_hp_register(slot, dev->bus, PCI_SLOT(dev->devfn), name); + if (retval) { + err("pci_hp_register failed with error %d\n", retval); + goto error_dslot; + } + + dbg("slot->name = %s\n", hotplug_slot_name(slot)); + dslot->slot = slot; + dslot->dev = pci_dev_get(dev); + list_add (&dslot->node, &slot_list); + return retval; + +error_dslot: + kfree(dslot); +error_info: + kfree(slot->info); +error_slot: + kfree(slot); +error: + return retval; +} + +static int __init pci_scan_buses(void) +{ + struct pci_dev *dev = NULL; + int lastslot = 0; + + while ((dev = pci_get_device(PCI_ANY_ID, PCI_ANY_ID, dev)) != NULL) { + if (PCI_FUNC(dev->devfn) > 0 && + lastslot == PCI_SLOT(dev->devfn)) + continue; + lastslot = PCI_SLOT(dev->devfn); + add_slot(dev); + } + + return 0; +} + +static void remove_slot(struct dummy_slot *dslot) +{ + int retval; + + dbg("removing slot %s\n", hotplug_slot_name(dslot->slot)); + retval = pci_hp_deregister(dslot->slot); + if (retval) + err("Problem unregistering a slot %s\n", + hotplug_slot_name(dslot->slot)); +} + +/* called from the single-threaded workqueue handler to remove a slot */ +static void remove_slot_worker(struct work_struct *work) +{ + struct dummy_slot *dslot = + container_of(work, struct dummy_slot, remove_work); + remove_slot(dslot); +} + +/** + * pci_rescan_slot - Rescan slot + * @temp: Device template. Should be set: bus and devfn. + * + * Tries hard not to re-enable already existing devices; + * also handles scanning of subfunctions. + */ +static void pci_rescan_slot(struct pci_dev *temp) +{ + struct pci_bus *bus = temp->bus; + struct pci_dev *dev; + int func; + int retval; + u8 hdr_type; + + if (!pci_read_config_byte(temp, PCI_HEADER_TYPE, &hdr_type)) { + temp->hdr_type = hdr_type & 0x7f; + if ((dev = pci_get_slot(bus, temp->devfn)) != NULL) + pci_dev_put(dev); + else { + dev = pci_scan_single_device(bus, temp->devfn); + if (dev) { + dbg("New device on %s function %x:%x\n", + bus->name, temp->devfn >> 3, + temp->devfn & 7); + retval = pci_bus_add_device(dev); + if (retval) + dev_err(&dev->dev, "error adding " + "device, continuing.\n"); + else + add_slot(dev); + } + } + /* multifunction device? */ + if (!(hdr_type & 0x80)) + return; + + /* continue scanning for other functions */ + for (func = 1, temp->devfn++; func < 8; func++, temp->devfn++) { + if (pci_read_config_byte(temp, PCI_HEADER_TYPE, &hdr_type)) + continue; + temp->hdr_type = hdr_type & 0x7f; + + if ((dev = pci_get_slot(bus, temp->devfn)) != NULL) + pci_dev_put(dev); + else { + dev = pci_scan_single_device(bus, temp->devfn); + if (dev) { + dbg("New device on %s function %x:%x\n", + bus->name, temp->devfn >> 3, + temp->devfn & 7); + retval = pci_bus_add_device(dev); + if (retval) + dev_err(&dev->dev, "error adding " + "device, continuing.\n"); + else + add_slot(dev); + } + } + } + } +} + + +/** + * pci_rescan_bus - Rescan PCI bus + * @bus: the PCI bus to rescan + * + * Call pci_rescan_slot for each possible function of the bus. + */ +static void pci_rescan_bus(const struct pci_bus *bus) +{ + unsigned int devfn; + struct pci_dev *dev; + dev = alloc_pci_dev(); + if (!dev) + return; + + dev->bus = (struct pci_bus*)bus; + dev->sysdata = bus->sysdata; + for (devfn = 0; devfn < 0x100; devfn += 8) { + dev->devfn = devfn; + pci_rescan_slot(dev); + } + kfree(dev); +} + +/* recursively scan all buses */ +static void pci_rescan_buses(const struct list_head *list) +{ + const struct list_head *l; + list_for_each(l,list) { + const struct pci_bus *b = pci_bus_b(l); + pci_rescan_bus(b); + pci_rescan_buses(&b->children); + } +} + +/* initiate rescan of all pci buses */ +static inline void pci_rescan(void) { + pci_rescan_buses(&pci_root_buses); +} + +/* called from the single-threaded workqueue handler to rescan all pci buses */ +static void pci_rescan_worker(struct work_struct *work) +{ + pci_rescan(); +} + +static int enable_slot(struct hotplug_slot *hotplug_slot) +{ + /* mis-use enable_slot for rescanning of the pci bus */ + cancel_work_sync(&pci_rescan_work); + queue_work(dummyphp_wq, &pci_rescan_work); + return 0; +} + +static int disable_slot(struct hotplug_slot *slot) +{ + struct dummy_slot *dslot; + struct pci_dev *dev; + int func; + + if (!slot) + return -ENODEV; + dslot = slot->private; + + dbg("%s - physical_slot = %s\n", __func__, hotplug_slot_name(slot)); + + for (func = 7; func >= 0; func--) { + dev = pci_get_slot(dslot->dev->bus, dslot->dev->devfn + func); + if (!dev) + continue; + + if (test_and_set_bit(0, &dslot->removed)) { + dbg("Slot already scheduled for removal\n"); + return -ENODEV; + } + + /* remove the device from the pci core */ + pci_remove_bus_device(dev); + + /* queue work item to blow away this sysfs entry and other + * parts. + */ + INIT_WORK(&dslot->remove_work, remove_slot_worker); + queue_work(dummyphp_wq, &dslot->remove_work); + + pci_dev_put(dev); + } + return 0; +} + +static void cleanup_slots (void) +{ + struct list_head *tmp; + struct list_head *next; + struct dummy_slot *dslot; + + destroy_workqueue(dummyphp_wq); + list_for_each_safe (tmp, next, &slot_list) { + dslot = list_entry (tmp, struct dummy_slot, node); + remove_slot(dslot); + } + +} + +static int __init dummyphp_init(void) +{ + info(DRIVER_DESC "\n"); + + dummyphp_wq = create_singlethread_workqueue(MY_NAME); + if (!dummyphp_wq) + return -ENOMEM; + + return pci_scan_buses(); +} + + +static void __exit dummyphp_exit(void) +{ + cleanup_slots(); +} + +module_init(dummyphp_init); +module_exit(dummyphp_exit); + +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); +module_param(debug, bool, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(debug, "Debugging mode enabled or not"); +module_param(dup_slots, bool, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(dup_slots, "Force duplicate slot names for debugging"); diff --git a/drivers/pci/hotplug/ibmphp.h b/drivers/pci/hotplug/ibmphp.h new file mode 100644 index 0000000..a8d391a --- /dev/null +++ b/drivers/pci/hotplug/ibmphp.h @@ -0,0 +1,760 @@ +#ifndef __IBMPHP_H +#define __IBMPHP_H + +/* + * IBM Hot Plug Controller Driver + * + * Written By: Jyoti Shah, Tong Yu, Irene Zubarev, IBM Corporation + * + * Copyright (C) 2001 Greg Kroah-Hartman (greg@kroah.com) + * Copyright (C) 2001-2003 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 <gregkh@us.ibm.com> + * + */ + +#include <linux/pci_hotplug.h> + +extern int ibmphp_debug; + +#if !defined(MODULE) + #define MY_NAME "ibmphpd" +#else + #define MY_NAME THIS_MODULE->name +#endif +#define debug(fmt, arg...) do { if (ibmphp_debug == 1) printk(KERN_DEBUG "%s: " fmt , MY_NAME , ## arg); } while (0) +#define debug_pci(fmt, arg...) do { if (ibmphp_debug) printk(KERN_DEBUG "%s: " 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) + + +/* EBDA stuff */ + +/*********************************************************** +* SLOT CAPABILITY * +***********************************************************/ + +#define EBDA_SLOT_133_MAX 0x20 +#define EBDA_SLOT_100_MAX 0x10 +#define EBDA_SLOT_66_MAX 0x02 +#define EBDA_SLOT_PCIX_CAP 0x08 + + +/************************************************************ +* RESOURE TYPE * +************************************************************/ + +#define EBDA_RSRC_TYPE_MASK 0x03 +#define EBDA_IO_RSRC_TYPE 0x00 +#define EBDA_MEM_RSRC_TYPE 0x01 +#define EBDA_PFM_RSRC_TYPE 0x03 +#define EBDA_RES_RSRC_TYPE 0x02 + + +/************************************************************* +* IO RESTRICTION TYPE * +*************************************************************/ + +#define EBDA_IO_RESTRI_MASK 0x0c +#define EBDA_NO_RESTRI 0x00 +#define EBDA_AVO_VGA_ADDR 0x04 +#define EBDA_AVO_VGA_ADDR_AND_ALIA 0x08 +#define EBDA_AVO_ISA_ADDR 0x0c + + +/************************************************************** +* DEVICE TYPE DEF * +**************************************************************/ + +#define EBDA_DEV_TYPE_MASK 0x10 +#define EBDA_PCI_DEV 0x10 +#define EBDA_NON_PCI_DEV 0x00 + + +/*************************************************************** +* PRIMARY DEF DEFINITION * +***************************************************************/ + +#define EBDA_PRI_DEF_MASK 0x20 +#define EBDA_PRI_PCI_BUS_INFO 0x20 +#define EBDA_NORM_DEV_RSRC_INFO 0x00 + + +//-------------------------------------------------------------- +// RIO TABLE DATA STRUCTURE +//-------------------------------------------------------------- + +struct rio_table_hdr { + u8 ver_num; + u8 scal_count; + u8 riodev_count; + u16 offset; +}; + +//------------------------------------------------------------- +// SCALABILITY DETAIL +//------------------------------------------------------------- + +struct scal_detail { + u8 node_id; + u32 cbar; + u8 port0_node_connect; + u8 port0_port_connect; + u8 port1_node_connect; + u8 port1_port_connect; + u8 port2_node_connect; + u8 port2_port_connect; + u8 chassis_num; +// struct list_head scal_detail_list; +}; + +//-------------------------------------------------------------- +// RIO DETAIL +//-------------------------------------------------------------- + +struct rio_detail { + u8 rio_node_id; + u32 bbar; + u8 rio_type; + u8 owner_id; + u8 port0_node_connect; + u8 port0_port_connect; + u8 port1_node_connect; + u8 port1_port_connect; + u8 first_slot_num; + u8 status; + u8 wpindex; + u8 chassis_num; + struct list_head rio_detail_list; +}; + +struct opt_rio { + u8 rio_type; + u8 chassis_num; + u8 first_slot_num; + u8 middle_num; + struct list_head opt_rio_list; +}; + +struct opt_rio_lo { + u8 rio_type; + u8 chassis_num; + u8 first_slot_num; + u8 middle_num; + u8 pack_count; + struct list_head opt_rio_lo_list; +}; + +/**************************************************************** +* HPC DESCRIPTOR NODE * +****************************************************************/ + +struct ebda_hpc_list { + u8 format; + u16 num_ctlrs; + short phys_addr; +// struct list_head ebda_hpc_list; +}; +/***************************************************************** +* IN HPC DATA STRUCTURE, THE ASSOCIATED SLOT AND BUS * +* STRUCTURE * +*****************************************************************/ + +struct ebda_hpc_slot { + u8 slot_num; + u32 slot_bus_num; + u8 ctl_index; + u8 slot_cap; +}; + +struct ebda_hpc_bus { + u32 bus_num; + u8 slots_at_33_conv; + u8 slots_at_66_conv; + u8 slots_at_66_pcix; + u8 slots_at_100_pcix; + u8 slots_at_133_pcix; +}; + + +/******************************************************************** +* THREE TYPE OF HOT PLUG CONTROLLER * +********************************************************************/ + +struct isa_ctlr_access { + u16 io_start; + u16 io_end; +}; + +struct pci_ctlr_access { + u8 bus; + u8 dev_fun; +}; + +struct wpeg_i2c_ctlr_access { + ulong wpegbbar; + u8 i2c_addr; +}; + +#define HPC_DEVICE_ID 0x0246 +#define HPC_SUBSYSTEM_ID 0x0247 +#define HPC_PCI_OFFSET 0x40 +/************************************************************************* +* RSTC DESCRIPTOR NODE * +*************************************************************************/ + +struct ebda_rsrc_list { + u8 format; + u16 num_entries; + u16 phys_addr; + struct ebda_rsrc_list *next; +}; + + +/*************************************************************************** +* PCI RSRC NODE * +***************************************************************************/ + +struct ebda_pci_rsrc { + u8 rsrc_type; + u8 bus_num; + u8 dev_fun; + u32 start_addr; + u32 end_addr; + u8 marked; /* for NVRAM */ + struct list_head ebda_pci_rsrc_list; +}; + + +/*********************************************************** +* BUS_INFO DATE STRUCTURE * +***********************************************************/ + +struct bus_info { + u8 slot_min; + u8 slot_max; + u8 slot_count; + u8 busno; + u8 controller_id; + u8 current_speed; + u8 current_bus_mode; + u8 index; + u8 slots_at_33_conv; + u8 slots_at_66_conv; + u8 slots_at_66_pcix; + u8 slots_at_100_pcix; + u8 slots_at_133_pcix; + struct list_head bus_info_list; +}; + + +/*********************************************************** +* GLOBAL VARIABLES * +***********************************************************/ +extern struct list_head ibmphp_ebda_pci_rsrc_head; +extern struct list_head ibmphp_slot_head; +/*********************************************************** +* FUNCTION PROTOTYPES * +***********************************************************/ + +extern void ibmphp_free_ebda_hpc_queue (void); +extern int ibmphp_access_ebda (void); +extern struct slot *ibmphp_get_slot_from_physical_num (u8); +extern int ibmphp_get_total_hp_slots (void); +extern void ibmphp_free_ibm_slot (struct slot *); +extern void ibmphp_free_bus_info_queue (void); +extern void ibmphp_free_ebda_pci_rsrc_queue (void); +extern struct bus_info *ibmphp_find_same_bus_num (u32); +extern int ibmphp_get_bus_index (u8); +extern u16 ibmphp_get_total_controllers (void); +extern int ibmphp_register_pci (void); + +/* passed parameters */ +#define MEM 0 +#define IO 1 +#define PFMEM 2 + +/* bit masks */ +#define RESTYPE 0x03 +#define IOMASK 0x00 /* will need to take its complement */ +#define MMASK 0x01 +#define PFMASK 0x03 +#define PCIDEVMASK 0x10 /* we should always have PCI devices */ +#define PRIMARYBUSMASK 0x20 + +/* pci specific defines */ +#define PCI_VENDOR_ID_NOTVALID 0xFFFF +#define PCI_HEADER_TYPE_MULTIDEVICE 0x80 +#define PCI_HEADER_TYPE_MULTIBRIDGE 0x81 + +#define LATENCY 0x64 +#define CACHE 64 +#define DEVICEENABLE 0x015F /* CPQ has 0x0157 */ + +#define IOBRIDGE 0x1000 /* 4k */ +#define MEMBRIDGE 0x100000 /* 1M */ + +/* irqs */ +#define SCSI_IRQ 0x09 +#define LAN_IRQ 0x0A +#define OTHER_IRQ 0x0B + +/* Data Structures */ + +/* type is of the form x x xx xx + * | | | |_ 00 - I/O, 01 - Memory, 11 - PFMemory + * | | - 00 - No Restrictions, 01 - Avoid VGA, 10 - Avoid + * | | VGA and their aliases, 11 - Avoid ISA + * | - 1 - PCI device, 0 - non pci device + * - 1 - Primary PCI Bus Information (0 if Normal device) + * the IO restrictions [2:3] are only for primary buses + */ + + +/* we need this struct because there could be several resource blocks + * allocated per primary bus in the EBDA + */ +struct range_node { + int rangeno; + u32 start; + u32 end; + struct range_node *next; +}; + +struct bus_node { + u8 busno; + int noIORanges; + struct range_node *rangeIO; + int noMemRanges; + struct range_node *rangeMem; + int noPFMemRanges; + struct range_node *rangePFMem; + int needIOUpdate; + int needMemUpdate; + int needPFMemUpdate; + struct resource_node *firstIO; /* first IO resource on the Bus */ + struct resource_node *firstMem; /* first memory resource on the Bus */ + struct resource_node *firstPFMem; /* first prefetchable memory resource on the Bus */ + struct resource_node *firstPFMemFromMem; /* when run out of pfmem available, taking from Mem */ + struct list_head bus_list; +}; + +struct resource_node { + int rangeno; + u8 busno; + u8 devfunc; + u32 start; + u32 end; + u32 len; + int type; /* MEM, IO, PFMEM */ + u8 fromMem; /* this is to indicate that the range is from + * from the Memory bucket rather than from PFMem */ + struct resource_node *next; + struct resource_node *nextRange; /* for the other mem range on bus */ +}; + +struct res_needed { + u32 mem; + u32 pfmem; + u32 io; + u8 not_correct; /* needed for return */ + int devices[32]; /* for device numbers behind this bridge */ +}; + +/* functions */ + +extern int ibmphp_rsrc_init (void); +extern int ibmphp_add_resource (struct resource_node *); +extern int ibmphp_remove_resource (struct resource_node *); +extern int ibmphp_find_resource (struct bus_node *, u32, struct resource_node **, int); +extern int ibmphp_check_resource (struct resource_node *, u8); +extern int ibmphp_remove_bus (struct bus_node *, u8); +extern void ibmphp_free_resources (void); +extern int ibmphp_add_pfmem_from_mem (struct resource_node *); +extern struct bus_node *ibmphp_find_res_bus (u8); +extern void ibmphp_print_test (void); /* for debugging purposes */ + +extern void ibmphp_hpc_initvars (void); +extern int ibmphp_hpc_readslot (struct slot *, u8, u8 *); +extern int ibmphp_hpc_writeslot (struct slot *, u8); +extern void ibmphp_lock_operations (void); +extern void ibmphp_unlock_operations (void); +extern int ibmphp_hpc_start_poll_thread (void); +extern void ibmphp_hpc_stop_poll_thread (void); + +//---------------------------------------------------------------------------- + + +//---------------------------------------------------------------------------- +// HPC return codes +//---------------------------------------------------------------------------- +#define HPC_ERROR 0xFF + +//----------------------------------------------------------------------------- +// BUS INFO +//----------------------------------------------------------------------------- +#define BUS_SPEED 0x30 +#define BUS_MODE 0x40 +#define BUS_MODE_PCIX 0x01 +#define BUS_MODE_PCI 0x00 +#define BUS_SPEED_2 0x20 +#define BUS_SPEED_1 0x10 +#define BUS_SPEED_33 0x00 +#define BUS_SPEED_66 0x01 +#define BUS_SPEED_100 0x02 +#define BUS_SPEED_133 0x03 +#define BUS_SPEED_66PCIX 0x04 +#define BUS_SPEED_66UNKNOWN 0x05 +#define BUS_STATUS_AVAILABLE 0x01 +#define BUS_CONTROL_AVAILABLE 0x02 +#define SLOT_LATCH_REGS_SUPPORTED 0x10 + +#define PRGM_MODEL_REV_LEVEL 0xF0 +#define MAX_ADAPTER_NONE 0x09 + +//---------------------------------------------------------------------------- +// HPC 'write' operations/commands +//---------------------------------------------------------------------------- +// Command Code State Write to reg +// Machine at index +//------------------------- ---- ------- ------------ +#define HPC_CTLR_ENABLEIRQ 0x00 // N 15 +#define HPC_CTLR_DISABLEIRQ 0x01 // N 15 +#define HPC_SLOT_OFF 0x02 // Y 0-14 +#define HPC_SLOT_ON 0x03 // Y 0-14 +#define HPC_SLOT_ATTNOFF 0x04 // N 0-14 +#define HPC_SLOT_ATTNON 0x05 // N 0-14 +#define HPC_CTLR_CLEARIRQ 0x06 // N 15 +#define HPC_CTLR_RESET 0x07 // Y 15 +#define HPC_CTLR_IRQSTEER 0x08 // N 15 +#define HPC_BUS_33CONVMODE 0x09 // Y 31-34 +#define HPC_BUS_66CONVMODE 0x0A // Y 31-34 +#define HPC_BUS_66PCIXMODE 0x0B // Y 31-34 +#define HPC_BUS_100PCIXMODE 0x0C // Y 31-34 +#define HPC_BUS_133PCIXMODE 0x0D // Y 31-34 +#define HPC_ALLSLOT_OFF 0x11 // Y 15 +#define HPC_ALLSLOT_ON 0x12 // Y 15 +#define HPC_SLOT_BLINKLED 0x13 // N 0-14 + +//---------------------------------------------------------------------------- +// read commands +//---------------------------------------------------------------------------- +#define READ_SLOTSTATUS 0x01 +#define READ_EXTSLOTSTATUS 0x02 +#define READ_BUSSTATUS 0x03 +#define READ_CTLRSTATUS 0x04 +#define READ_ALLSTAT 0x05 +#define READ_ALLSLOT 0x06 +#define READ_SLOTLATCHLOWREG 0x07 +#define READ_REVLEVEL 0x08 +#define READ_HPCOPTIONS 0x09 +//---------------------------------------------------------------------------- +// slot status +//---------------------------------------------------------------------------- +#define HPC_SLOT_POWER 0x01 +#define HPC_SLOT_CONNECT 0x02 +#define HPC_SLOT_ATTN 0x04 +#define HPC_SLOT_PRSNT2 0x08 +#define HPC_SLOT_PRSNT1 0x10 +#define HPC_SLOT_PWRGD 0x20 +#define HPC_SLOT_BUS_SPEED 0x40 +#define HPC_SLOT_LATCH 0x80 + +//---------------------------------------------------------------------------- +// HPC_SLOT_POWER status return codes +//---------------------------------------------------------------------------- +#define HPC_SLOT_POWER_OFF 0x00 +#define HPC_SLOT_POWER_ON 0x01 + +//---------------------------------------------------------------------------- +// HPC_SLOT_CONNECT status return codes +//---------------------------------------------------------------------------- +#define HPC_SLOT_CONNECTED 0x00 +#define HPC_SLOT_DISCONNECTED 0x01 + +//---------------------------------------------------------------------------- +// HPC_SLOT_ATTN status return codes +//---------------------------------------------------------------------------- +#define HPC_SLOT_ATTN_OFF 0x00 +#define HPC_SLOT_ATTN_ON 0x01 +#define HPC_SLOT_ATTN_BLINK 0x02 + +//---------------------------------------------------------------------------- +// HPC_SLOT_PRSNT status return codes +//---------------------------------------------------------------------------- +#define HPC_SLOT_EMPTY 0x00 +#define HPC_SLOT_PRSNT_7 0x01 +#define HPC_SLOT_PRSNT_15 0x02 +#define HPC_SLOT_PRSNT_25 0x03 + +//---------------------------------------------------------------------------- +// HPC_SLOT_PWRGD status return codes +//---------------------------------------------------------------------------- +#define HPC_SLOT_PWRGD_FAULT_NONE 0x00 +#define HPC_SLOT_PWRGD_GOOD 0x01 + +//---------------------------------------------------------------------------- +// HPC_SLOT_BUS_SPEED status return codes +//---------------------------------------------------------------------------- +#define HPC_SLOT_BUS_SPEED_OK 0x00 +#define HPC_SLOT_BUS_SPEED_MISM 0x01 + +//---------------------------------------------------------------------------- +// HPC_SLOT_LATCH status return codes +//---------------------------------------------------------------------------- +#define HPC_SLOT_LATCH_OPEN 0x01 // NOTE : in PCI spec bit off = open +#define HPC_SLOT_LATCH_CLOSED 0x00 // NOTE : in PCI spec bit on = closed + + +//---------------------------------------------------------------------------- +// extended slot status +//---------------------------------------------------------------------------- +#define HPC_SLOT_PCIX 0x01 +#define HPC_SLOT_SPEED1 0x02 +#define HPC_SLOT_SPEED2 0x04 +#define HPC_SLOT_BLINK_ATTN 0x08 +#define HPC_SLOT_RSRVD1 0x10 +#define HPC_SLOT_RSRVD2 0x20 +#define HPC_SLOT_BUS_MODE 0x40 +#define HPC_SLOT_RSRVD3 0x80 + +//---------------------------------------------------------------------------- +// HPC_XSLOT_PCIX_CAP status return codes +//---------------------------------------------------------------------------- +#define HPC_SLOT_PCIX_NO 0x00 +#define HPC_SLOT_PCIX_YES 0x01 + +//---------------------------------------------------------------------------- +// HPC_XSLOT_SPEED status return codes +//---------------------------------------------------------------------------- +#define HPC_SLOT_SPEED_33 0x00 +#define HPC_SLOT_SPEED_66 0x01 +#define HPC_SLOT_SPEED_133 0x02 + +//---------------------------------------------------------------------------- +// HPC_XSLOT_ATTN_BLINK status return codes +//---------------------------------------------------------------------------- +#define HPC_SLOT_ATTN_BLINK_OFF 0x00 +#define HPC_SLOT_ATTN_BLINK_ON 0x01 + +//---------------------------------------------------------------------------- +// HPC_XSLOT_BUS_MODE status return codes +//---------------------------------------------------------------------------- +#define HPC_SLOT_BUS_MODE_OK 0x00 +#define HPC_SLOT_BUS_MODE_MISM 0x01 + +//---------------------------------------------------------------------------- +// Controller status +//---------------------------------------------------------------------------- +#define HPC_CTLR_WORKING 0x01 +#define HPC_CTLR_FINISHED 0x02 +#define HPC_CTLR_RESULT0 0x04 +#define HPC_CTLR_RESULT1 0x08 +#define HPC_CTLR_RESULE2 0x10 +#define HPC_CTLR_RESULT3 0x20 +#define HPC_CTLR_IRQ_ROUTG 0x40 +#define HPC_CTLR_IRQ_PENDG 0x80 + +//---------------------------------------------------------------------------- +// HPC_CTLR_WROKING status return codes +//---------------------------------------------------------------------------- +#define HPC_CTLR_WORKING_NO 0x00 +#define HPC_CTLR_WORKING_YES 0x01 + +//---------------------------------------------------------------------------- +// HPC_CTLR_FINISHED status return codes +//---------------------------------------------------------------------------- +#define HPC_CTLR_FINISHED_NO 0x00 +#define HPC_CTLR_FINISHED_YES 0x01 + +//---------------------------------------------------------------------------- +// HPC_CTLR_RESULT status return codes +//---------------------------------------------------------------------------- +#define HPC_CTLR_RESULT_SUCCESS 0x00 +#define HPC_CTLR_RESULT_FAILED 0x01 +#define HPC_CTLR_RESULT_RSVD 0x02 +#define HPC_CTLR_RESULT_NORESP 0x03 + + +//---------------------------------------------------------------------------- +// macro for slot info +//---------------------------------------------------------------------------- +#define SLOT_POWER(s) ((u8) ((s & HPC_SLOT_POWER) \ + ? HPC_SLOT_POWER_ON : HPC_SLOT_POWER_OFF)) + +#define SLOT_CONNECT(s) ((u8) ((s & HPC_SLOT_CONNECT) \ + ? HPC_SLOT_DISCONNECTED : HPC_SLOT_CONNECTED)) + +#define SLOT_ATTN(s,es) ((u8) ((es & HPC_SLOT_BLINK_ATTN) \ + ? HPC_SLOT_ATTN_BLINK \ + : ((s & HPC_SLOT_ATTN) ? HPC_SLOT_ATTN_ON : HPC_SLOT_ATTN_OFF))) + +#define SLOT_PRESENT(s) ((u8) ((s & HPC_SLOT_PRSNT1) \ + ? ((s & HPC_SLOT_PRSNT2) ? HPC_SLOT_EMPTY : HPC_SLOT_PRSNT_15) \ + : ((s & HPC_SLOT_PRSNT2) ? HPC_SLOT_PRSNT_25 : HPC_SLOT_PRSNT_7))) + +#define SLOT_PWRGD(s) ((u8) ((s & HPC_SLOT_PWRGD) \ + ? HPC_SLOT_PWRGD_GOOD : HPC_SLOT_PWRGD_FAULT_NONE)) + +#define SLOT_BUS_SPEED(s) ((u8) ((s & HPC_SLOT_BUS_SPEED) \ + ? HPC_SLOT_BUS_SPEED_MISM : HPC_SLOT_BUS_SPEED_OK)) + +#define SLOT_LATCH(s) ((u8) ((s & HPC_SLOT_LATCH) \ + ? HPC_SLOT_LATCH_CLOSED : HPC_SLOT_LATCH_OPEN)) + +#define SLOT_PCIX(es) ((u8) ((es & HPC_SLOT_PCIX) \ + ? HPC_SLOT_PCIX_YES : HPC_SLOT_PCIX_NO)) + +#define SLOT_SPEED(es) ((u8) ((es & HPC_SLOT_SPEED2) \ + ? ((es & HPC_SLOT_SPEED1) ? HPC_SLOT_SPEED_133 \ + : HPC_SLOT_SPEED_66) \ + : HPC_SLOT_SPEED_33)) + +#define SLOT_BUS_MODE(es) ((u8) ((es & HPC_SLOT_BUS_MODE) \ + ? HPC_SLOT_BUS_MODE_MISM : HPC_SLOT_BUS_MODE_OK)) + +//-------------------------------------------------------------------------- +// macro for bus info +//--------------------------------------------------------------------------- +#define CURRENT_BUS_SPEED(s) ((u8) (s & BUS_SPEED_2) \ + ? ((s & BUS_SPEED_1) ? BUS_SPEED_133 : BUS_SPEED_100) \ + : ((s & BUS_SPEED_1) ? BUS_SPEED_66 : BUS_SPEED_33)) + +#define CURRENT_BUS_MODE(s) ((u8) (s & BUS_MODE) ? BUS_MODE_PCIX : BUS_MODE_PCI) + +#define READ_BUS_STATUS(s) ((u8) (s->options & BUS_STATUS_AVAILABLE)) + +#define READ_BUS_MODE(s) ((s->revision & PRGM_MODEL_REV_LEVEL) >= 0x20) + +#define SET_BUS_STATUS(s) ((u8) (s->options & BUS_CONTROL_AVAILABLE)) + +#define READ_SLOT_LATCH(s) ((u8) (s->options & SLOT_LATCH_REGS_SUPPORTED)) + +//---------------------------------------------------------------------------- +// macro for controller info +//---------------------------------------------------------------------------- +#define CTLR_WORKING(c) ((u8) ((c & HPC_CTLR_WORKING) \ + ? HPC_CTLR_WORKING_YES : HPC_CTLR_WORKING_NO)) +#define CTLR_FINISHED(c) ((u8) ((c & HPC_CTLR_FINISHED) \ + ? HPC_CTLR_FINISHED_YES : HPC_CTLR_FINISHED_NO)) +#define CTLR_RESULT(c) ((u8) ((c & HPC_CTLR_RESULT1) \ + ? ((c & HPC_CTLR_RESULT0) ? HPC_CTLR_RESULT_NORESP \ + : HPC_CTLR_RESULT_RSVD) \ + : ((c & HPC_CTLR_RESULT0) ? HPC_CTLR_RESULT_FAILED \ + : HPC_CTLR_RESULT_SUCCESS))) + +// command that affect the state machine of HPC +#define NEEDTOCHECK_CMDSTATUS(c) ((c == HPC_SLOT_OFF) || \ + (c == HPC_SLOT_ON) || \ + (c == HPC_CTLR_RESET) || \ + (c == HPC_BUS_33CONVMODE) || \ + (c == HPC_BUS_66CONVMODE) || \ + (c == HPC_BUS_66PCIXMODE) || \ + (c == HPC_BUS_100PCIXMODE) || \ + (c == HPC_BUS_133PCIXMODE) || \ + (c == HPC_ALLSLOT_OFF) || \ + (c == HPC_ALLSLOT_ON)) + + +/* Core part of the driver */ + +#define ENABLE 1 +#define DISABLE 0 + +#define CARD_INFO 0x07 +#define PCIX133 0x07 +#define PCIX66 0x05 +#define PCI66 0x04 + +extern struct pci_bus *ibmphp_pci_bus; + +/* Variables */ + +struct pci_func { + struct pci_dev *dev; /* from the OS */ + u8 busno; + u8 device; + u8 function; + struct resource_node *io[6]; + struct resource_node *mem[6]; + struct resource_node *pfmem[6]; + struct pci_func *next; + int devices[32]; /* for bridge config */ + u8 irq[4]; /* for interrupt config */ + u8 bus; /* flag for unconfiguring, to say if PPB */ +}; + +struct slot { + u8 bus; + u8 device; + u8 number; + u8 real_physical_slot_num; + u32 capabilities; + u8 supported_speed; + u8 supported_bus_mode; + u8 flag; /* this is for disable slot and polling */ + u8 ctlr_index; + struct hotplug_slot *hotplug_slot; + struct controller *ctrl; + struct pci_func *func; + u8 irq[4]; + int bit_mode; /* 0 = 32, 1 = 64 */ + struct bus_info *bus_on; + struct list_head ibm_slot_list; + u8 status; + u8 ext_status; + u8 busstatus; +}; + +struct controller { + struct ebda_hpc_slot *slots; + struct ebda_hpc_bus *buses; + struct pci_dev *ctrl_dev; /* in case where controller is PCI */ + u8 starting_slot_num; /* starting and ending slot #'s this ctrl controls*/ + u8 ending_slot_num; + u8 revision; + u8 options; /* which options HPC supports */ + u8 status; + u8 ctlr_id; + u8 slot_count; + u8 bus_count; + u8 ctlr_relative_id; + u32 irq; + union { + struct isa_ctlr_access isa_ctlr; + struct pci_ctlr_access pci_ctlr; + struct wpeg_i2c_ctlr_access wpeg_ctlr; + } u; + u8 ctlr_type; + struct list_head ebda_hpc_list; +}; + +/* Functions */ + +extern int ibmphp_init_devno (struct slot **); /* This function is called from EBDA, so we need it not be static */ +extern int ibmphp_do_disable_slot (struct slot *slot_cur); +extern int ibmphp_update_slot_info (struct slot *); /* This function is called from HPC, so we need it to not be be static */ +extern int ibmphp_configure_card (struct pci_func *, u8); +extern int ibmphp_unconfigure_card (struct slot **, int); +extern struct hotplug_slot_ops ibmphp_hotplug_slot_ops; + +#endif //__IBMPHP_H + diff --git a/drivers/pci/hotplug/ibmphp_core.c b/drivers/pci/hotplug/ibmphp_core.c new file mode 100644 index 0000000..633e743 --- /dev/null +++ b/drivers/pci/hotplug/ibmphp_core.c @@ -0,0 +1,1421 @@ +/* + * IBM Hot Plug Controller Driver + * + * Written By: Chuck Cole, Jyoti Shah, Tong Yu, Irene Zubarev, IBM Corporation + * + * Copyright (C) 2001,2003 Greg Kroah-Hartman (greg@kroah.com) + * Copyright (C) 2001-2003 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 <gregkh@us.ibm.com> + * + */ + +#include <linux/init.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/pci.h> +#include <linux/interrupt.h> +#include <linux/delay.h> +#include <linux/wait.h> +#include "../pci.h" +#include "../../../arch/x86/pci/pci.h" /* for struct irq_routing_table */ +#include "ibmphp.h" + +#define attn_on(sl) ibmphp_hpc_writeslot (sl, HPC_SLOT_ATTNON) +#define attn_off(sl) ibmphp_hpc_writeslot (sl, HPC_SLOT_ATTNOFF) +#define attn_LED_blink(sl) ibmphp_hpc_writeslot (sl, HPC_SLOT_BLINKLED) +#define get_ctrl_revision(sl, rev) ibmphp_hpc_readslot (sl, READ_REVLEVEL, rev) +#define get_hpc_options(sl, opt) ibmphp_hpc_readslot (sl, READ_HPCOPTIONS, opt) + +#define DRIVER_VERSION "0.6" +#define DRIVER_DESC "IBM Hot Plug PCI Controller Driver" + +int ibmphp_debug; + +static int debug; +module_param(debug, bool, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC (debug, "Debugging mode enabled or not"); +MODULE_LICENSE ("GPL"); +MODULE_DESCRIPTION (DRIVER_DESC); + +struct pci_bus *ibmphp_pci_bus; +static int max_slots; + +static int irqs[16]; /* PIC mode IRQ's we're using so far (in case MPS + * tables don't provide default info for empty slots */ + +static int init_flag; + +/* +static int get_max_adapter_speed_1 (struct hotplug_slot *, u8 *, u8); + +static inline int get_max_adapter_speed (struct hotplug_slot *hs, u8 *value) +{ + return get_max_adapter_speed_1 (hs, value, 1); +} +*/ +static inline int get_cur_bus_info(struct slot **sl) +{ + int rc = 1; + struct slot * slot_cur = *sl; + + debug("options = %x\n", slot_cur->ctrl->options); + debug("revision = %x\n", slot_cur->ctrl->revision); + + if (READ_BUS_STATUS(slot_cur->ctrl)) + rc = ibmphp_hpc_readslot(slot_cur, READ_BUSSTATUS, NULL); + + if (rc) + return rc; + + slot_cur->bus_on->current_speed = CURRENT_BUS_SPEED(slot_cur->busstatus); + if (READ_BUS_MODE(slot_cur->ctrl)) + slot_cur->bus_on->current_bus_mode = + CURRENT_BUS_MODE(slot_cur->busstatus); + else + slot_cur->bus_on->current_bus_mode = 0xFF; + + debug("busstatus = %x, bus_speed = %x, bus_mode = %x\n", + slot_cur->busstatus, + slot_cur->bus_on->current_speed, + slot_cur->bus_on->current_bus_mode); + + *sl = slot_cur; + return 0; +} + +static inline int slot_update(struct slot **sl) +{ + int rc; + rc = ibmphp_hpc_readslot(*sl, READ_ALLSTAT, NULL); + if (rc) + return rc; + if (!init_flag) + rc = get_cur_bus_info(sl); + return rc; +} + +static int __init get_max_slots (void) +{ + struct slot * slot_cur; + struct list_head * tmp; + u8 slot_count = 0; + + list_for_each(tmp, &ibmphp_slot_head) { + slot_cur = list_entry(tmp, struct slot, ibm_slot_list); + /* sometimes the hot-pluggable slots start with 4 (not always from 1) */ + slot_count = max(slot_count, slot_cur->number); + } + return slot_count; +} + +/* This routine will put the correct slot->device information per slot. It's + * called from initialization of the slot structures. It will also assign + * interrupt numbers per each slot. + * Parameters: struct slot + * Returns 0 or errors + */ +int ibmphp_init_devno(struct slot **cur_slot) +{ + struct irq_routing_table *rtable; + int len; + int loop; + int i; + + rtable = pcibios_get_irq_routing_table(); + if (!rtable) { + err("no BIOS routing table...\n"); + return -ENOMEM; + } + + len = (rtable->size - sizeof(struct irq_routing_table)) / + sizeof(struct irq_info); + + if (!len) { + kfree(rtable); + return -1; + } + for (loop = 0; loop < len; loop++) { + if ((*cur_slot)->number == rtable->slots[loop].slot) { + if ((*cur_slot)->bus == rtable->slots[loop].bus) { + (*cur_slot)->device = PCI_SLOT(rtable->slots[loop].devfn); + for (i = 0; i < 4; i++) + (*cur_slot)->irq[i] = IO_APIC_get_PCI_irq_vector((int) (*cur_slot)->bus, + (int) (*cur_slot)->device, i); + + debug("(*cur_slot)->irq[0] = %x\n", + (*cur_slot)->irq[0]); + debug("(*cur_slot)->irq[1] = %x\n", + (*cur_slot)->irq[1]); + debug("(*cur_slot)->irq[2] = %x\n", + (*cur_slot)->irq[2]); + debug("(*cur_slot)->irq[3] = %x\n", + (*cur_slot)->irq[3]); + + debug("rtable->exlusive_irqs = %x\n", + rtable->exclusive_irqs); + debug("rtable->slots[loop].irq[0].bitmap = %x\n", + rtable->slots[loop].irq[0].bitmap); + debug("rtable->slots[loop].irq[1].bitmap = %x\n", + rtable->slots[loop].irq[1].bitmap); + debug("rtable->slots[loop].irq[2].bitmap = %x\n", + rtable->slots[loop].irq[2].bitmap); + debug("rtable->slots[loop].irq[3].bitmap = %x\n", + rtable->slots[loop].irq[3].bitmap); + + debug("rtable->slots[loop].irq[0].link = %x\n", + rtable->slots[loop].irq[0].link); + debug("rtable->slots[loop].irq[1].link = %x\n", + rtable->slots[loop].irq[1].link); + debug("rtable->slots[loop].irq[2].link = %x\n", + rtable->slots[loop].irq[2].link); + debug("rtable->slots[loop].irq[3].link = %x\n", + rtable->slots[loop].irq[3].link); + debug("end of init_devno\n"); + kfree(rtable); + return 0; + } + } + } + + kfree(rtable); + return -1; +} + +static inline int power_on(struct slot *slot_cur) +{ + u8 cmd = HPC_SLOT_ON; + int retval; + + retval = ibmphp_hpc_writeslot(slot_cur, cmd); + if (retval) { + err("power on failed\n"); + return retval; + } + if (CTLR_RESULT(slot_cur->ctrl->status)) { + err("command not completed successfully in power_on\n"); + return -EIO; + } + msleep(3000); /* For ServeRAID cards, and some 66 PCI */ + return 0; +} + +static inline int power_off(struct slot *slot_cur) +{ + u8 cmd = HPC_SLOT_OFF; + int retval; + + retval = ibmphp_hpc_writeslot(slot_cur, cmd); + if (retval) { + err("power off failed\n"); + return retval; + } + if (CTLR_RESULT(slot_cur->ctrl->status)) { + err("command not completed successfully in power_off\n"); + retval = -EIO; + } + return retval; +} + +static int set_attention_status(struct hotplug_slot *hotplug_slot, u8 value) +{ + int rc = 0; + struct slot *pslot; + u8 cmd = 0x00; /* avoid compiler warning */ + + debug("set_attention_status - Entry hotplug_slot[%lx] value[%x]\n", + (ulong) hotplug_slot, value); + ibmphp_lock_operations(); + + + if (hotplug_slot) { + switch (value) { + case HPC_SLOT_ATTN_OFF: + cmd = HPC_SLOT_ATTNOFF; + break; + case HPC_SLOT_ATTN_ON: + cmd = HPC_SLOT_ATTNON; + break; + case HPC_SLOT_ATTN_BLINK: + cmd = HPC_SLOT_BLINKLED; + break; + default: + rc = -ENODEV; + err("set_attention_status - Error : invalid input [%x]\n", + value); + break; + } + if (rc == 0) { + pslot = hotplug_slot->private; + if (pslot) + rc = ibmphp_hpc_writeslot(pslot, cmd); + else + rc = -ENODEV; + } + } else + rc = -ENODEV; + + ibmphp_unlock_operations(); + + debug("set_attention_status - Exit rc[%d]\n", rc); + return rc; +} + +static int get_attention_status(struct hotplug_slot *hotplug_slot, u8 * value) +{ + int rc = -ENODEV; + struct slot *pslot; + struct slot myslot; + + debug("get_attention_status - Entry hotplug_slot[%lx] pvalue[%lx]\n", + (ulong) hotplug_slot, (ulong) value); + + ibmphp_lock_operations(); + if (hotplug_slot) { + pslot = hotplug_slot->private; + if (pslot) { + memcpy(&myslot, pslot, sizeof(struct slot)); + rc = ibmphp_hpc_readslot(pslot, READ_SLOTSTATUS, + &(myslot.status)); + if (!rc) + rc = ibmphp_hpc_readslot(pslot, + READ_EXTSLOTSTATUS, + &(myslot.ext_status)); + if (!rc) + *value = SLOT_ATTN(myslot.status, + myslot.ext_status); + } + } + + ibmphp_unlock_operations(); + debug("get_attention_status - Exit rc[%d] value[%x]\n", rc, *value); + return rc; +} + +static int get_latch_status(struct hotplug_slot *hotplug_slot, u8 * value) +{ + int rc = -ENODEV; + struct slot *pslot; + struct slot myslot; + + debug("get_latch_status - Entry hotplug_slot[%lx] pvalue[%lx]\n", + (ulong) hotplug_slot, (ulong) value); + ibmphp_lock_operations(); + if (hotplug_slot) { + pslot = hotplug_slot->private; + if (pslot) { + memcpy(&myslot, pslot, sizeof(struct slot)); + rc = ibmphp_hpc_readslot(pslot, READ_SLOTSTATUS, + &(myslot.status)); + if (!rc) + *value = SLOT_LATCH(myslot.status); + } + } + + ibmphp_unlock_operations(); + debug("get_latch_status - Exit rc[%d] rc[%x] value[%x]\n", + rc, rc, *value); + return rc; +} + + +static int get_power_status(struct hotplug_slot *hotplug_slot, u8 * value) +{ + int rc = -ENODEV; + struct slot *pslot; + struct slot myslot; + + debug("get_power_status - Entry hotplug_slot[%lx] pvalue[%lx]\n", + (ulong) hotplug_slot, (ulong) value); + ibmphp_lock_operations(); + if (hotplug_slot) { + pslot = hotplug_slot->private; + if (pslot) { + memcpy(&myslot, pslot, sizeof(struct slot)); + rc = ibmphp_hpc_readslot(pslot, READ_SLOTSTATUS, + &(myslot.status)); + if (!rc) + *value = SLOT_PWRGD(myslot.status); + } + } + + ibmphp_unlock_operations(); + debug("get_power_status - Exit rc[%d] rc[%x] value[%x]\n", + rc, rc, *value); + return rc; +} + +static int get_adapter_present(struct hotplug_slot *hotplug_slot, u8 * value) +{ + int rc = -ENODEV; + struct slot *pslot; + u8 present; + struct slot myslot; + + debug("get_adapter_status - Entry hotplug_slot[%lx] pvalue[%lx]\n", + (ulong) hotplug_slot, (ulong) value); + ibmphp_lock_operations(); + if (hotplug_slot) { + pslot = hotplug_slot->private; + if (pslot) { + memcpy(&myslot, pslot, sizeof(struct slot)); + rc = ibmphp_hpc_readslot(pslot, READ_SLOTSTATUS, + &(myslot.status)); + if (!rc) { + present = SLOT_PRESENT(myslot.status); + if (present == HPC_SLOT_EMPTY) + *value = 0; + else + *value = 1; + } + } + } + + ibmphp_unlock_operations(); + debug("get_adapter_present - Exit rc[%d] value[%x]\n", rc, *value); + return rc; +} + +static int get_max_bus_speed(struct hotplug_slot *hotplug_slot, enum pci_bus_speed *value) +{ + int rc = -ENODEV; + struct slot *pslot; + u8 mode = 0; + + debug("%s - Entry hotplug_slot[%p] pvalue[%p]\n", __func__, + hotplug_slot, value); + + ibmphp_lock_operations(); + + if (hotplug_slot) { + pslot = hotplug_slot->private; + if (pslot) { + rc = 0; + mode = pslot->supported_bus_mode; + *value = pslot->supported_speed; + switch (*value) { + case BUS_SPEED_33: + break; + case BUS_SPEED_66: + if (mode == BUS_MODE_PCIX) + *value += 0x01; + break; + case BUS_SPEED_100: + case BUS_SPEED_133: + *value = pslot->supported_speed + 0x01; + break; + default: + /* Note (will need to change): there would be soon 256, 512 also */ + rc = -ENODEV; + } + } + } + + ibmphp_unlock_operations(); + debug("%s - Exit rc[%d] value[%x]\n", __func__, rc, *value); + return rc; +} + +static int get_cur_bus_speed(struct hotplug_slot *hotplug_slot, enum pci_bus_speed *value) +{ + int rc = -ENODEV; + struct slot *pslot; + u8 mode = 0; + + debug("%s - Entry hotplug_slot[%p] pvalue[%p]\n", __func__, + hotplug_slot, value); + + ibmphp_lock_operations(); + + if (hotplug_slot) { + pslot = hotplug_slot->private; + if (pslot) { + rc = get_cur_bus_info(&pslot); + if (!rc) { + mode = pslot->bus_on->current_bus_mode; + *value = pslot->bus_on->current_speed; + switch (*value) { + case BUS_SPEED_33: + break; + case BUS_SPEED_66: + if (mode == BUS_MODE_PCIX) + *value += 0x01; + else if (mode == BUS_MODE_PCI) + ; + else + *value = PCI_SPEED_UNKNOWN; + break; + case BUS_SPEED_100: + case BUS_SPEED_133: + *value += 0x01; + break; + default: + /* Note of change: there would also be 256, 512 soon */ + rc = -ENODEV; + } + } + } + } + + ibmphp_unlock_operations(); + debug("%s - Exit rc[%d] value[%x]\n", __func__, rc, *value); + return rc; +} + +/* +static int get_max_adapter_speed_1(struct hotplug_slot *hotplug_slot, u8 * value, u8 flag) +{ + int rc = -ENODEV; + struct slot *pslot; + struct slot myslot; + + debug("get_max_adapter_speed_1 - Entry hotplug_slot[%lx] pvalue[%lx]\n", + (ulong)hotplug_slot, (ulong) value); + + if (flag) + ibmphp_lock_operations(); + + if (hotplug_slot && value) { + pslot = hotplug_slot->private; + if (pslot) { + memcpy(&myslot, pslot, sizeof(struct slot)); + rc = ibmphp_hpc_readslot(pslot, READ_SLOTSTATUS, + &(myslot.status)); + + if (!(SLOT_LATCH (myslot.status)) && + (SLOT_PRESENT (myslot.status))) { + rc = ibmphp_hpc_readslot(pslot, + READ_EXTSLOTSTATUS, + &(myslot.ext_status)); + if (!rc) + *value = SLOT_SPEED(myslot.ext_status); + } else + *value = MAX_ADAPTER_NONE; + } + } + + if (flag) + ibmphp_unlock_operations(); + + debug("get_max_adapter_speed_1 - Exit rc[%d] value[%x]\n", rc, *value); + return rc; +} + +static int get_bus_name(struct hotplug_slot *hotplug_slot, char * value) +{ + int rc = -ENODEV; + struct slot *pslot = NULL; + + debug("get_bus_name - Entry hotplug_slot[%lx]\n", (ulong)hotplug_slot); + + ibmphp_lock_operations(); + + if (hotplug_slot) { + pslot = hotplug_slot->private; + if (pslot) { + rc = 0; + snprintf(value, 100, "Bus %x", pslot->bus); + } + } else + rc = -ENODEV; + + ibmphp_unlock_operations(); + debug("get_bus_name - Exit rc[%d] value[%x]\n", rc, *value); + return rc; +} +*/ + +/**************************************************************************** + * This routine will initialize the ops data structure used in the validate + * function. It will also power off empty slots that are powered on since BIOS + * leaves those on, albeit disconnected + ****************************************************************************/ +static int __init init_ops(void) +{ + struct slot *slot_cur; + struct list_head *tmp; + int retval; + int rc; + + list_for_each(tmp, &ibmphp_slot_head) { + slot_cur = list_entry(tmp, struct slot, ibm_slot_list); + + if (!slot_cur) + return -ENODEV; + + debug("BEFORE GETTING SLOT STATUS, slot # %x\n", + slot_cur->number); + if (slot_cur->ctrl->revision == 0xFF) + if (get_ctrl_revision(slot_cur, + &slot_cur->ctrl->revision)) + return -1; + + if (slot_cur->bus_on->current_speed == 0xFF) + if (get_cur_bus_info(&slot_cur)) + return -1; + + if (slot_cur->ctrl->options == 0xFF) + if (get_hpc_options(slot_cur, &slot_cur->ctrl->options)) + return -1; + + retval = slot_update(&slot_cur); + if (retval) + return retval; + + debug("status = %x\n", slot_cur->status); + debug("ext_status = %x\n", slot_cur->ext_status); + debug("SLOT_POWER = %x\n", SLOT_POWER(slot_cur->status)); + debug("SLOT_PRESENT = %x\n", SLOT_PRESENT(slot_cur->status)); + debug("SLOT_LATCH = %x\n", SLOT_LATCH(slot_cur->status)); + + if ((SLOT_PWRGD(slot_cur->status)) && + !(SLOT_PRESENT(slot_cur->status)) && + !(SLOT_LATCH(slot_cur->status))) { + debug("BEFORE POWER OFF COMMAND\n"); + rc = power_off(slot_cur); + if (rc) + return rc; + + /* retval = slot_update(&slot_cur); + * if (retval) + * return retval; + * ibmphp_update_slot_info(slot_cur); + */ + } + } + init_flag = 0; + return 0; +} + +/* This operation will check whether the slot is within the bounds and + * the operation is valid to perform on that slot + * Parameters: slot, operation + * Returns: 0 or error codes + */ +static int validate(struct slot *slot_cur, int opn) +{ + int number; + int retval; + + if (!slot_cur) + return -ENODEV; + number = slot_cur->number; + if ((number > max_slots) || (number < 0)) + return -EBADSLT; + debug("slot_number in validate is %d\n", slot_cur->number); + + retval = slot_update(&slot_cur); + if (retval) + return retval; + + switch (opn) { + case ENABLE: + if (!(SLOT_PWRGD(slot_cur->status)) && + (SLOT_PRESENT(slot_cur->status)) && + !(SLOT_LATCH(slot_cur->status))) + return 0; + break; + case DISABLE: + if ((SLOT_PWRGD(slot_cur->status)) && + (SLOT_PRESENT(slot_cur->status)) && + !(SLOT_LATCH(slot_cur->status))) + return 0; + break; + default: + break; + } + err("validate failed....\n"); + return -EINVAL; +} + +/**************************************************************************** + * This routine is for updating the data structures in the hotplug core + * Parameters: struct slot + * Returns: 0 or error + ****************************************************************************/ +int ibmphp_update_slot_info(struct slot *slot_cur) +{ + struct hotplug_slot_info *info; + int rc; + u8 bus_speed; + u8 mode; + + info = kmalloc(sizeof(struct hotplug_slot_info), GFP_KERNEL); + if (!info) { + err("out of system memory\n"); + return -ENOMEM; + } + + info->power_status = SLOT_PWRGD(slot_cur->status); + info->attention_status = SLOT_ATTN(slot_cur->status, + slot_cur->ext_status); + info->latch_status = SLOT_LATCH(slot_cur->status); + if (!SLOT_PRESENT(slot_cur->status)) { + info->adapter_status = 0; +/* info->max_adapter_speed_status = MAX_ADAPTER_NONE; */ + } else { + info->adapter_status = 1; +/* get_max_adapter_speed_1(slot_cur->hotplug_slot, + &info->max_adapter_speed_status, 0); */ + } + + bus_speed = slot_cur->bus_on->current_speed; + mode = slot_cur->bus_on->current_bus_mode; + + switch (bus_speed) { + case BUS_SPEED_33: + break; + case BUS_SPEED_66: + if (mode == BUS_MODE_PCIX) + bus_speed += 0x01; + else if (mode == BUS_MODE_PCI) + ; + else + bus_speed = PCI_SPEED_UNKNOWN; + break; + case BUS_SPEED_100: + case BUS_SPEED_133: + bus_speed += 0x01; + break; + default: + bus_speed = PCI_SPEED_UNKNOWN; + } + + info->cur_bus_speed = bus_speed; + info->max_bus_speed = slot_cur->hotplug_slot->info->max_bus_speed; + // To do: bus_names + + rc = pci_hp_change_slot_info(slot_cur->hotplug_slot, info); + kfree(info); + return rc; +} + + +/****************************************************************************** + * This function will return the pci_func, given bus and devfunc, or NULL. It + * is called from visit routines + ******************************************************************************/ + +static struct pci_func *ibm_slot_find(u8 busno, u8 device, u8 function) +{ + struct pci_func *func_cur; + struct slot *slot_cur; + struct list_head * tmp; + list_for_each(tmp, &ibmphp_slot_head) { + slot_cur = list_entry(tmp, struct slot, ibm_slot_list); + if (slot_cur->func) { + func_cur = slot_cur->func; + while (func_cur) { + if ((func_cur->busno == busno) && + (func_cur->device == device) && + (func_cur->function == function)) + return func_cur; + func_cur = func_cur->next; + } + } + } + return NULL; +} + +/************************************************************* + * This routine frees up memory used by struct slot, including + * the pointers to pci_func, bus, hotplug_slot, controller, + * and deregistering from the hotplug core + *************************************************************/ +static void free_slots(void) +{ + struct slot *slot_cur; + struct list_head * tmp; + struct list_head * next; + + debug("%s -- enter\n", __func__); + + list_for_each_safe(tmp, next, &ibmphp_slot_head) { + slot_cur = list_entry(tmp, struct slot, ibm_slot_list); + pci_hp_deregister(slot_cur->hotplug_slot); + } + debug("%s -- exit\n", __func__); +} + +static void ibm_unconfigure_device(struct pci_func *func) +{ + struct pci_dev *temp; + u8 j; + + debug("inside %s\n", __func__); + debug("func->device = %x, func->function = %x\n", + func->device, func->function); + debug("func->device << 3 | 0x0 = %x\n", func->device << 3 | 0x0); + + for (j = 0; j < 0x08; j++) { + temp = pci_get_bus_and_slot(func->busno, (func->device << 3) | j); + if (temp) { + pci_remove_bus_device(temp); + pci_dev_put(temp); + } + } + pci_dev_put(func->dev); +} + +/* + * The following function is to fix kernel bug regarding + * getting bus entries, here we manually add those primary + * bus entries to kernel bus structure whenever apply + */ +static u8 bus_structure_fixup(u8 busno) +{ + struct pci_bus *bus; + struct pci_dev *dev; + u16 l; + + if (pci_find_bus(0, busno) || !(ibmphp_find_same_bus_num(busno))) + return 1; + + bus = kmalloc(sizeof(*bus), GFP_KERNEL); + if (!bus) { + err("%s - out of memory\n", __func__); + return 1; + } + dev = kmalloc(sizeof(*dev), GFP_KERNEL); + if (!dev) { + kfree(bus); + err("%s - out of memory\n", __func__); + return 1; + } + + bus->number = busno; + bus->ops = ibmphp_pci_bus->ops; + dev->bus = bus; + for (dev->devfn = 0; dev->devfn < 256; dev->devfn += 8) { + if (!pci_read_config_word(dev, PCI_VENDOR_ID, &l) && + (l != 0x0000) && (l != 0xffff)) { + debug("%s - Inside bus_struture_fixup()\n", + __func__); + pci_scan_bus(busno, ibmphp_pci_bus->ops, NULL); + break; + } + } + + kfree(dev); + kfree(bus); + + return 0; +} + +static int ibm_configure_device(struct pci_func *func) +{ + unsigned char bus; + struct pci_bus *child; + int num; + int flag = 0; /* this is to make sure we don't double scan the bus, + for bridged devices primarily */ + + if (!(bus_structure_fixup(func->busno))) + flag = 1; + if (func->dev == NULL) + func->dev = pci_get_bus_and_slot(func->busno, + PCI_DEVFN(func->device, func->function)); + + if (func->dev == NULL) { + struct pci_bus *bus = pci_find_bus(0, func->busno); + if (!bus) + return 0; + + num = pci_scan_slot(bus, + PCI_DEVFN(func->device, func->function)); + if (num) + pci_bus_add_devices(bus); + + func->dev = pci_get_bus_and_slot(func->busno, + PCI_DEVFN(func->device, func->function)); + if (func->dev == NULL) { + err("ERROR... : pci_dev still NULL\n"); + return 0; + } + } + if (!(flag) && (func->dev->hdr_type == PCI_HEADER_TYPE_BRIDGE)) { + pci_read_config_byte(func->dev, PCI_SECONDARY_BUS, &bus); + child = pci_add_new_bus(func->dev->bus, func->dev, bus); + pci_do_scan_bus(child); + } + + return 0; +} + +/******************************************************* + * Returns whether the bus is empty or not + *******************************************************/ +static int is_bus_empty(struct slot * slot_cur) +{ + int rc; + struct slot * tmp_slot; + u8 i = slot_cur->bus_on->slot_min; + + while (i <= slot_cur->bus_on->slot_max) { + if (i == slot_cur->number) { + i++; + continue; + } + tmp_slot = ibmphp_get_slot_from_physical_num(i); + if (!tmp_slot) + return 0; + rc = slot_update(&tmp_slot); + if (rc) + return 0; + if (SLOT_PRESENT(tmp_slot->status) && + SLOT_PWRGD(tmp_slot->status)) + return 0; + i++; + } + return 1; +} + +/*********************************************************** + * If the HPC permits and the bus currently empty, tries to set the + * bus speed and mode at the maximum card and bus capability + * Parameters: slot + * Returns: bus is set (0) or error code + ***********************************************************/ +static int set_bus(struct slot * slot_cur) +{ + int rc; + u8 speed; + u8 cmd = 0x0; + int retval; + static struct pci_device_id ciobx[] = { + { PCI_DEVICE(PCI_VENDOR_ID_SERVERWORKS, 0x0101) }, + { }, + }; + + debug("%s - entry slot # %d\n", __func__, slot_cur->number); + if (SET_BUS_STATUS(slot_cur->ctrl) && is_bus_empty(slot_cur)) { + rc = slot_update(&slot_cur); + if (rc) + return rc; + speed = SLOT_SPEED(slot_cur->ext_status); + debug("ext_status = %x, speed = %x\n", slot_cur->ext_status, speed); + switch (speed) { + case HPC_SLOT_SPEED_33: + cmd = HPC_BUS_33CONVMODE; + break; + case HPC_SLOT_SPEED_66: + if (SLOT_PCIX(slot_cur->ext_status)) { + if ((slot_cur->supported_speed >= BUS_SPEED_66) && + (slot_cur->supported_bus_mode == BUS_MODE_PCIX)) + cmd = HPC_BUS_66PCIXMODE; + else if (!SLOT_BUS_MODE(slot_cur->ext_status)) + /* if max slot/bus capability is 66 pci + and there's no bus mode mismatch, then + the adapter supports 66 pci */ + cmd = HPC_BUS_66CONVMODE; + else + cmd = HPC_BUS_33CONVMODE; + } else { + if (slot_cur->supported_speed >= BUS_SPEED_66) + cmd = HPC_BUS_66CONVMODE; + else + cmd = HPC_BUS_33CONVMODE; + } + break; + case HPC_SLOT_SPEED_133: + switch (slot_cur->supported_speed) { + case BUS_SPEED_33: + cmd = HPC_BUS_33CONVMODE; + break; + case BUS_SPEED_66: + if (slot_cur->supported_bus_mode == BUS_MODE_PCIX) + cmd = HPC_BUS_66PCIXMODE; + else + cmd = HPC_BUS_66CONVMODE; + break; + case BUS_SPEED_100: + cmd = HPC_BUS_100PCIXMODE; + break; + case BUS_SPEED_133: + /* This is to take care of the bug in CIOBX chip */ + if (pci_dev_present(ciobx)) + ibmphp_hpc_writeslot(slot_cur, + HPC_BUS_100PCIXMODE); + cmd = HPC_BUS_133PCIXMODE; + break; + default: + err("Wrong bus speed\n"); + return -ENODEV; + } + break; + default: + err("wrong slot speed\n"); + return -ENODEV; + } + debug("setting bus speed for slot %d, cmd %x\n", + slot_cur->number, cmd); + retval = ibmphp_hpc_writeslot(slot_cur, cmd); + if (retval) { + err("setting bus speed failed\n"); + return retval; + } + if (CTLR_RESULT(slot_cur->ctrl->status)) { + err("command not completed successfully in set_bus\n"); + return -EIO; + } + } + /* This is for x440, once Brandon fixes the firmware, + will not need this delay */ + msleep(1000); + debug("%s -Exit\n", __func__); + return 0; +} + +/* This routine checks the bus limitations that the slot is on from the BIOS. + * This is used in deciding whether or not to power up the slot. + * (electrical/spec limitations. For example, >1 133 MHz or >2 66 PCI cards on + * same bus) + * Parameters: slot + * Returns: 0 = no limitations, -EINVAL = exceeded limitations on the bus + */ +static int check_limitations(struct slot *slot_cur) +{ + u8 i; + struct slot * tmp_slot; + u8 count = 0; + u8 limitation = 0; + + for (i = slot_cur->bus_on->slot_min; i <= slot_cur->bus_on->slot_max; i++) { + tmp_slot = ibmphp_get_slot_from_physical_num(i); + if (!tmp_slot) + return -ENODEV; + if ((SLOT_PWRGD(tmp_slot->status)) && + !(SLOT_CONNECT(tmp_slot->status))) + count++; + } + get_cur_bus_info(&slot_cur); + switch (slot_cur->bus_on->current_speed) { + case BUS_SPEED_33: + limitation = slot_cur->bus_on->slots_at_33_conv; + break; + case BUS_SPEED_66: + if (slot_cur->bus_on->current_bus_mode == BUS_MODE_PCIX) + limitation = slot_cur->bus_on->slots_at_66_pcix; + else + limitation = slot_cur->bus_on->slots_at_66_conv; + break; + case BUS_SPEED_100: + limitation = slot_cur->bus_on->slots_at_100_pcix; + break; + case BUS_SPEED_133: + limitation = slot_cur->bus_on->slots_at_133_pcix; + break; + } + + if ((count + 1) > limitation) + return -EINVAL; + return 0; +} + +static inline void print_card_capability(struct slot *slot_cur) +{ + info("capability of the card is "); + if ((slot_cur->ext_status & CARD_INFO) == PCIX133) + info(" 133 MHz PCI-X\n"); + else if ((slot_cur->ext_status & CARD_INFO) == PCIX66) + info(" 66 MHz PCI-X\n"); + else if ((slot_cur->ext_status & CARD_INFO) == PCI66) + info(" 66 MHz PCI\n"); + else + info(" 33 MHz PCI\n"); + +} + +/* This routine will power on the slot, configure the device(s) and find the + * drivers for them. + * Parameters: hotplug_slot + * Returns: 0 or failure codes + */ +static int enable_slot(struct hotplug_slot *hs) +{ + int rc, i, rcpr; + struct slot *slot_cur; + u8 function; + struct pci_func *tmp_func; + + ibmphp_lock_operations(); + + debug("ENABLING SLOT........\n"); + slot_cur = hs->private; + + if ((rc = validate(slot_cur, ENABLE))) { + err("validate function failed\n"); + goto error_nopower; + } + + attn_LED_blink(slot_cur); + + rc = set_bus(slot_cur); + if (rc) { + err("was not able to set the bus\n"); + goto error_nopower; + } + + /*-----------------debugging------------------------------*/ + get_cur_bus_info(&slot_cur); + debug("the current bus speed right after set_bus = %x\n", + slot_cur->bus_on->current_speed); + /*----------------------------------------------------------*/ + + rc = check_limitations(slot_cur); + if (rc) { + err("Adding this card exceeds the limitations of this bus.\n"); + err("(i.e., >1 133MHz cards running on same bus, or " + ">2 66 PCI cards running on same bus.\n"); + err("Try hot-adding into another bus\n"); + rc = -EINVAL; + goto error_nopower; + } + + rc = power_on(slot_cur); + + if (rc) { + err("something wrong when powering up... please see below for details\n"); + /* need to turn off before on, otherwise, blinking overwrites */ + attn_off(slot_cur); + attn_on(slot_cur); + if (slot_update(&slot_cur)) { + attn_off(slot_cur); + attn_on(slot_cur); + rc = -ENODEV; + goto exit; + } + /* Check to see the error of why it failed */ + if ((SLOT_POWER(slot_cur->status)) && + !(SLOT_PWRGD(slot_cur->status))) + err("power fault occurred trying to power up\n"); + else if (SLOT_BUS_SPEED(slot_cur->status)) { + err("bus speed mismatch occurred. please check " + "current bus speed and card capability\n"); + print_card_capability(slot_cur); + } else if (SLOT_BUS_MODE(slot_cur->ext_status)) { + err("bus mode mismatch occurred. please check " + "current bus mode and card capability\n"); + print_card_capability(slot_cur); + } + ibmphp_update_slot_info(slot_cur); + goto exit; + } + debug("after power_on\n"); + /*-----------------------debugging---------------------------*/ + get_cur_bus_info(&slot_cur); + debug("the current bus speed right after power_on = %x\n", + slot_cur->bus_on->current_speed); + /*----------------------------------------------------------*/ + + rc = slot_update(&slot_cur); + if (rc) + goto error_power; + + rc = -EINVAL; + if (SLOT_POWER(slot_cur->status) && !(SLOT_PWRGD(slot_cur->status))) { + err("power fault occurred trying to power up...\n"); + goto error_power; + } + if (SLOT_POWER(slot_cur->status) && (SLOT_BUS_SPEED(slot_cur->status))) { + err("bus speed mismatch occurred. please check current bus " + "speed and card capability\n"); + print_card_capability(slot_cur); + goto error_power; + } + /* Don't think this case will happen after above checks... + * but just in case, for paranoia sake */ + if (!(SLOT_POWER(slot_cur->status))) { + err("power on failed...\n"); + goto error_power; + } + + slot_cur->func = kzalloc(sizeof(struct pci_func), GFP_KERNEL); + if (!slot_cur->func) { + /* We cannot do update_slot_info here, since no memory for + * kmalloc n.e.ways, and update_slot_info allocates some */ + err("out of system memory\n"); + rc = -ENOMEM; + goto error_power; + } + slot_cur->func->busno = slot_cur->bus; + slot_cur->func->device = slot_cur->device; + for (i = 0; i < 4; i++) + slot_cur->func->irq[i] = slot_cur->irq[i]; + + debug("b4 configure_card, slot_cur->bus = %x, slot_cur->device = %x\n", + slot_cur->bus, slot_cur->device); + + if (ibmphp_configure_card(slot_cur->func, slot_cur->number)) { + err("configure_card was unsuccessful...\n"); + /* true because don't need to actually deallocate resources, + * just remove references */ + ibmphp_unconfigure_card(&slot_cur, 1); + debug("after unconfigure_card\n"); + slot_cur->func = NULL; + rc = -ENOMEM; + goto error_power; + } + + function = 0x00; + do { + tmp_func = ibm_slot_find(slot_cur->bus, slot_cur->func->device, + function++); + if (tmp_func && !(tmp_func->dev)) + ibm_configure_device(tmp_func); + } while (tmp_func); + + attn_off(slot_cur); + if (slot_update(&slot_cur)) { + rc = -EFAULT; + goto exit; + } + ibmphp_print_test(); + rc = ibmphp_update_slot_info(slot_cur); +exit: + ibmphp_unlock_operations(); + return rc; + +error_nopower: + attn_off(slot_cur); /* need to turn off if was blinking b4 */ + attn_on(slot_cur); +error_cont: + rcpr = slot_update(&slot_cur); + if (rcpr) { + rc = rcpr; + goto exit; + } + ibmphp_update_slot_info(slot_cur); + goto exit; + +error_power: + attn_off(slot_cur); /* need to turn off if was blinking b4 */ + attn_on(slot_cur); + rcpr = power_off(slot_cur); + if (rcpr) { + rc = rcpr; + goto exit; + } + goto error_cont; +} + +/************************************************************** +* HOT REMOVING ADAPTER CARD * +* INPUT: POINTER TO THE HOTPLUG SLOT STRUCTURE * +* OUTPUT: SUCCESS 0 ; FAILURE: UNCONFIGURE , VALIDATE * + DISABLE POWER , * +**************************************************************/ +static int ibmphp_disable_slot(struct hotplug_slot *hotplug_slot) +{ + struct slot *slot = hotplug_slot->private; + int rc; + + ibmphp_lock_operations(); + rc = ibmphp_do_disable_slot(slot); + ibmphp_unlock_operations(); + return rc; +} + +int ibmphp_do_disable_slot(struct slot *slot_cur) +{ + int rc; + u8 flag; + + debug("DISABLING SLOT...\n"); + + if ((slot_cur == NULL) || (slot_cur->ctrl == NULL)) { + return -ENODEV; + } + + flag = slot_cur->flag; + slot_cur->flag = 1; + + if (flag == 1) { + rc = validate(slot_cur, DISABLE); + /* checking if powered off already & valid slot # */ + if (rc) + goto error; + } + attn_LED_blink(slot_cur); + + if (slot_cur->func == NULL) { + /* We need this for fncs's that were there on bootup */ + slot_cur->func = kzalloc(sizeof(struct pci_func), GFP_KERNEL); + if (!slot_cur->func) { + err("out of system memory\n"); + rc = -ENOMEM; + goto error; + } + slot_cur->func->busno = slot_cur->bus; + slot_cur->func->device = slot_cur->device; + } + + ibm_unconfigure_device(slot_cur->func); + + /* If we got here from latch suddenly opening on operating card or + a power fault, there's no power to the card, so cannot + read from it to determine what resources it occupied. This operation + is forbidden anyhow. The best we can do is remove it from kernel + lists at least */ + + if (!flag) { + attn_off(slot_cur); + return 0; + } + + rc = ibmphp_unconfigure_card(&slot_cur, 0); + slot_cur->func = NULL; + debug("in disable_slot. after unconfigure_card\n"); + if (rc) { + err("could not unconfigure card.\n"); + goto error; + } + + rc = ibmphp_hpc_writeslot(slot_cur, HPC_SLOT_OFF); + if (rc) + goto error; + + attn_off(slot_cur); + rc = slot_update(&slot_cur); + if (rc) + goto exit; + + rc = ibmphp_update_slot_info(slot_cur); + ibmphp_print_test(); +exit: + return rc; + +error: + /* Need to turn off if was blinking b4 */ + attn_off(slot_cur); + attn_on(slot_cur); + if (slot_update(&slot_cur)) { + rc = -EFAULT; + goto exit; + } + if (flag) + ibmphp_update_slot_info(slot_cur); + goto exit; +} + +struct hotplug_slot_ops ibmphp_hotplug_slot_ops = { + .owner = THIS_MODULE, + .set_attention_status = set_attention_status, + .enable_slot = enable_slot, + .disable_slot = ibmphp_disable_slot, + .hardware_test = NULL, + .get_power_status = get_power_status, + .get_attention_status = get_attention_status, + .get_latch_status = get_latch_status, + .get_adapter_status = get_adapter_present, + .get_max_bus_speed = get_max_bus_speed, + .get_cur_bus_speed = get_cur_bus_speed, +/* .get_max_adapter_speed = get_max_adapter_speed, + .get_bus_name_status = get_bus_name, +*/ +}; + +static void ibmphp_unload(void) +{ + free_slots(); + debug("after slots\n"); + ibmphp_free_resources(); + debug("after resources\n"); + ibmphp_free_bus_info_queue(); + debug("after bus info\n"); + ibmphp_free_ebda_hpc_queue(); + debug("after ebda hpc\n"); + ibmphp_free_ebda_pci_rsrc_queue(); + debug("after ebda pci rsrc\n"); + kfree(ibmphp_pci_bus); +} + +static int __init ibmphp_init(void) +{ + struct pci_bus *bus; + int i = 0; + int rc = 0; + + init_flag = 1; + + info(DRIVER_DESC " version: " DRIVER_VERSION "\n"); + + ibmphp_pci_bus = kmalloc(sizeof(*ibmphp_pci_bus), GFP_KERNEL); + if (!ibmphp_pci_bus) { + err("out of memory\n"); + rc = -ENOMEM; + goto exit; + } + + bus = pci_find_bus(0, 0); + if (!bus) { + err("Can't find the root pci bus, can not continue\n"); + rc = -ENODEV; + goto error; + } + memcpy(ibmphp_pci_bus, bus, sizeof(*ibmphp_pci_bus)); + + ibmphp_debug = debug; + + ibmphp_hpc_initvars(); + + for (i = 0; i < 16; i++) + irqs[i] = 0; + + if ((rc = ibmphp_access_ebda())) + goto error; + debug("after ibmphp_access_ebda()\n"); + + if ((rc = ibmphp_rsrc_init())) + goto error; + debug("AFTER Resource & EBDA INITIALIZATIONS\n"); + + max_slots = get_max_slots(); + + if ((rc = ibmphp_register_pci())) + goto error; + + if (init_ops()) { + rc = -ENODEV; + goto error; + } + + ibmphp_print_test(); + if ((rc = ibmphp_hpc_start_poll_thread())) { + goto error; + } + +exit: + return rc; + +error: + ibmphp_unload(); + goto exit; +} + +static void __exit ibmphp_exit(void) +{ + ibmphp_hpc_stop_poll_thread(); + debug("after polling\n"); + ibmphp_unload(); + debug("done\n"); +} + +module_init(ibmphp_init); diff --git a/drivers/pci/hotplug/ibmphp_ebda.c b/drivers/pci/hotplug/ibmphp_ebda.c new file mode 100644 index 0000000..c1abac8 --- /dev/null +++ b/drivers/pci/hotplug/ibmphp_ebda.c @@ -0,0 +1,1196 @@ +/* + * IBM Hot Plug Controller Driver + * + * Written By: Tong Yu, IBM Corporation + * + * Copyright (C) 2001,2003 Greg Kroah-Hartman (greg@kroah.com) + * Copyright (C) 2001-2003 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 <gregkh@us.ibm.com> + * + */ + +#include <linux/module.h> +#include <linux/errno.h> +#include <linux/mm.h> +#include <linux/slab.h> +#include <linux/pci.h> +#include <linux/list.h> +#include <linux/init.h> +#include "ibmphp.h" + +/* + * POST builds data blocks(in this data block definition, a char-1 + * byte, short(or word)-2 byte, long(dword)-4 byte) in the Extended + * BIOS Data Area which describe the configuration of the hot-plug + * controllers and resources used by the PCI Hot-Plug devices. + * + * This file walks EBDA, maps data block from physical addr, + * reconstruct linked lists about all system resource(MEM, PFM, IO) + * already assigned by POST, as well as linked lists about hot plug + * controllers (ctlr#, slot#, bus&slot features...) + */ + +/* Global lists */ +LIST_HEAD (ibmphp_ebda_pci_rsrc_head); +LIST_HEAD (ibmphp_slot_head); + +/* Local variables */ +static struct ebda_hpc_list *hpc_list_ptr; +static struct ebda_rsrc_list *rsrc_list_ptr; +static struct rio_table_hdr *rio_table_ptr = NULL; +static LIST_HEAD (ebda_hpc_head); +static LIST_HEAD (bus_info_head); +static LIST_HEAD (rio_vg_head); +static LIST_HEAD (rio_lo_head); +static LIST_HEAD (opt_vg_head); +static LIST_HEAD (opt_lo_head); +static void __iomem *io_mem; + +/* Local functions */ +static int ebda_rsrc_controller (void); +static int ebda_rsrc_rsrc (void); +static int ebda_rio_table (void); + +static struct ebda_hpc_list * __init alloc_ebda_hpc_list (void) +{ + return kzalloc(sizeof(struct ebda_hpc_list), GFP_KERNEL); +} + +static struct controller *alloc_ebda_hpc (u32 slot_count, u32 bus_count) +{ + struct controller *controller; + struct ebda_hpc_slot *slots; + struct ebda_hpc_bus *buses; + + controller = kzalloc(sizeof(struct controller), GFP_KERNEL); + if (!controller) + goto error; + + slots = kcalloc(slot_count, sizeof(struct ebda_hpc_slot), GFP_KERNEL); + if (!slots) + goto error_contr; + controller->slots = slots; + + buses = kcalloc(bus_count, sizeof(struct ebda_hpc_bus), GFP_KERNEL); + if (!buses) + goto error_slots; + controller->buses = buses; + + return controller; +error_slots: + kfree(controller->slots); +error_contr: + kfree(controller); +error: + return NULL; +} + +static void free_ebda_hpc (struct controller *controller) +{ + kfree (controller->slots); + kfree (controller->buses); + kfree (controller); +} + +static struct ebda_rsrc_list * __init alloc_ebda_rsrc_list (void) +{ + return kzalloc(sizeof(struct ebda_rsrc_list), GFP_KERNEL); +} + +static struct ebda_pci_rsrc *alloc_ebda_pci_rsrc (void) +{ + return kzalloc(sizeof(struct ebda_pci_rsrc), GFP_KERNEL); +} + +static void __init print_bus_info (void) +{ + struct bus_info *ptr; + + list_for_each_entry(ptr, &bus_info_head, bus_info_list) { + debug ("%s - slot_min = %x\n", __func__, ptr->slot_min); + debug ("%s - slot_max = %x\n", __func__, ptr->slot_max); + debug ("%s - slot_count = %x\n", __func__, ptr->slot_count); + debug ("%s - bus# = %x\n", __func__, ptr->busno); + debug ("%s - current_speed = %x\n", __func__, ptr->current_speed); + debug ("%s - controller_id = %x\n", __func__, ptr->controller_id); + + debug ("%s - slots_at_33_conv = %x\n", __func__, ptr->slots_at_33_conv); + debug ("%s - slots_at_66_conv = %x\n", __func__, ptr->slots_at_66_conv); + debug ("%s - slots_at_66_pcix = %x\n", __func__, ptr->slots_at_66_pcix); + debug ("%s - slots_at_100_pcix = %x\n", __func__, ptr->slots_at_100_pcix); + debug ("%s - slots_at_133_pcix = %x\n", __func__, ptr->slots_at_133_pcix); + + } +} + +static void print_lo_info (void) +{ + struct rio_detail *ptr; + debug ("print_lo_info ----\n"); + list_for_each_entry(ptr, &rio_lo_head, rio_detail_list) { + debug ("%s - rio_node_id = %x\n", __func__, ptr->rio_node_id); + debug ("%s - rio_type = %x\n", __func__, ptr->rio_type); + debug ("%s - owner_id = %x\n", __func__, ptr->owner_id); + debug ("%s - first_slot_num = %x\n", __func__, ptr->first_slot_num); + debug ("%s - wpindex = %x\n", __func__, ptr->wpindex); + debug ("%s - chassis_num = %x\n", __func__, ptr->chassis_num); + + } +} + +static void print_vg_info (void) +{ + struct rio_detail *ptr; + debug ("%s ---\n", __func__); + list_for_each_entry(ptr, &rio_vg_head, rio_detail_list) { + debug ("%s - rio_node_id = %x\n", __func__, ptr->rio_node_id); + debug ("%s - rio_type = %x\n", __func__, ptr->rio_type); + debug ("%s - owner_id = %x\n", __func__, ptr->owner_id); + debug ("%s - first_slot_num = %x\n", __func__, ptr->first_slot_num); + debug ("%s - wpindex = %x\n", __func__, ptr->wpindex); + debug ("%s - chassis_num = %x\n", __func__, ptr->chassis_num); + + } +} + +static void __init print_ebda_pci_rsrc (void) +{ + struct ebda_pci_rsrc *ptr; + + list_for_each_entry(ptr, &ibmphp_ebda_pci_rsrc_head, ebda_pci_rsrc_list) { + debug ("%s - rsrc type: %x bus#: %x dev_func: %x start addr: %x end addr: %x\n", + __func__, ptr->rsrc_type ,ptr->bus_num, ptr->dev_fun,ptr->start_addr, ptr->end_addr); + } +} + +static void __init print_ibm_slot (void) +{ + struct slot *ptr; + + list_for_each_entry(ptr, &ibmphp_slot_head, ibm_slot_list) { + debug ("%s - slot_number: %x\n", __func__, ptr->number); + } +} + +static void __init print_opt_vg (void) +{ + struct opt_rio *ptr; + debug ("%s ---\n", __func__); + list_for_each_entry(ptr, &opt_vg_head, opt_rio_list) { + debug ("%s - rio_type %x\n", __func__, ptr->rio_type); + debug ("%s - chassis_num: %x\n", __func__, ptr->chassis_num); + debug ("%s - first_slot_num: %x\n", __func__, ptr->first_slot_num); + debug ("%s - middle_num: %x\n", __func__, ptr->middle_num); + } +} + +static void __init print_ebda_hpc (void) +{ + struct controller *hpc_ptr; + u16 index; + + list_for_each_entry(hpc_ptr, &ebda_hpc_head, ebda_hpc_list) { + for (index = 0; index < hpc_ptr->slot_count; index++) { + debug ("%s - physical slot#: %x\n", __func__, hpc_ptr->slots[index].slot_num); + debug ("%s - pci bus# of the slot: %x\n", __func__, hpc_ptr->slots[index].slot_bus_num); + debug ("%s - index into ctlr addr: %x\n", __func__, hpc_ptr->slots[index].ctl_index); + debug ("%s - cap of the slot: %x\n", __func__, hpc_ptr->slots[index].slot_cap); + } + + for (index = 0; index < hpc_ptr->bus_count; index++) { + debug ("%s - bus# of each bus controlled by this ctlr: %x\n", __func__, hpc_ptr->buses[index].bus_num); + } + + debug ("%s - type of hpc: %x\n", __func__, hpc_ptr->ctlr_type); + switch (hpc_ptr->ctlr_type) { + case 1: + debug ("%s - bus: %x\n", __func__, hpc_ptr->u.pci_ctlr.bus); + debug ("%s - dev_fun: %x\n", __func__, hpc_ptr->u.pci_ctlr.dev_fun); + debug ("%s - irq: %x\n", __func__, hpc_ptr->irq); + break; + + case 0: + debug ("%s - io_start: %x\n", __func__, hpc_ptr->u.isa_ctlr.io_start); + debug ("%s - io_end: %x\n", __func__, hpc_ptr->u.isa_ctlr.io_end); + debug ("%s - irq: %x\n", __func__, hpc_ptr->irq); + break; + + case 2: + case 4: + debug ("%s - wpegbbar: %lx\n", __func__, hpc_ptr->u.wpeg_ctlr.wpegbbar); + debug ("%s - i2c_addr: %x\n", __func__, hpc_ptr->u.wpeg_ctlr.i2c_addr); + debug ("%s - irq: %x\n", __func__, hpc_ptr->irq); + break; + } + } +} + +int __init ibmphp_access_ebda (void) +{ + u8 format, num_ctlrs, rio_complete, hs_complete; + u16 ebda_seg, num_entries, next_offset, offset, blk_id, sub_addr, re, rc_id, re_id, base; + int rc = 0; + + + rio_complete = 0; + hs_complete = 0; + + io_mem = ioremap ((0x40 << 4) + 0x0e, 2); + if (!io_mem ) + return -ENOMEM; + ebda_seg = readw (io_mem); + iounmap (io_mem); + debug ("returned ebda segment: %x\n", ebda_seg); + + io_mem = ioremap(ebda_seg<<4, 1024); + if (!io_mem ) + return -ENOMEM; + next_offset = 0x180; + + for (;;) { + offset = next_offset; + next_offset = readw (io_mem + offset); /* offset of next blk */ + + offset += 2; + if (next_offset == 0) /* 0 indicate it's last blk */ + break; + blk_id = readw (io_mem + offset); /* this blk id */ + + offset += 2; + /* check if it is hot swap block or rio block */ + if (blk_id != 0x4853 && blk_id != 0x4752) + continue; + /* found hs table */ + if (blk_id == 0x4853) { + debug ("now enter hot swap block---\n"); + debug ("hot blk id: %x\n", blk_id); + format = readb (io_mem + offset); + + offset += 1; + if (format != 4) + goto error_nodev; + debug ("hot blk format: %x\n", format); + /* hot swap sub blk */ + base = offset; + + sub_addr = base; + re = readw (io_mem + sub_addr); /* next sub blk */ + + sub_addr += 2; + rc_id = readw (io_mem + sub_addr); /* sub blk id */ + + sub_addr += 2; + if (rc_id != 0x5243) + goto error_nodev; + /* rc sub blk signature */ + num_ctlrs = readb (io_mem + sub_addr); + + sub_addr += 1; + hpc_list_ptr = alloc_ebda_hpc_list (); + if (!hpc_list_ptr) { + rc = -ENOMEM; + goto out; + } + hpc_list_ptr->format = format; + hpc_list_ptr->num_ctlrs = num_ctlrs; + hpc_list_ptr->phys_addr = sub_addr; /* offset of RSRC_CONTROLLER blk */ + debug ("info about hpc descriptor---\n"); + debug ("hot blk format: %x\n", format); + debug ("num of controller: %x\n", num_ctlrs); + debug ("offset of hpc data structure enteries: %x\n ", sub_addr); + + sub_addr = base + re; /* re sub blk */ + /* FIXME: rc is never used/checked */ + rc = readw (io_mem + sub_addr); /* next sub blk */ + + sub_addr += 2; + re_id = readw (io_mem + sub_addr); /* sub blk id */ + + sub_addr += 2; + if (re_id != 0x5245) + goto error_nodev; + + /* signature of re */ + num_entries = readw (io_mem + sub_addr); + + sub_addr += 2; /* offset of RSRC_ENTRIES blk */ + rsrc_list_ptr = alloc_ebda_rsrc_list (); + if (!rsrc_list_ptr ) { + rc = -ENOMEM; + goto out; + } + rsrc_list_ptr->format = format; + rsrc_list_ptr->num_entries = num_entries; + rsrc_list_ptr->phys_addr = sub_addr; + + debug ("info about rsrc descriptor---\n"); + debug ("format: %x\n", format); + debug ("num of rsrc: %x\n", num_entries); + debug ("offset of rsrc data structure enteries: %x\n ", sub_addr); + + hs_complete = 1; + } else { + /* found rio table, blk_id == 0x4752 */ + debug ("now enter io table ---\n"); + debug ("rio blk id: %x\n", blk_id); + + rio_table_ptr = kzalloc(sizeof(struct rio_table_hdr), GFP_KERNEL); + if (!rio_table_ptr) + return -ENOMEM; + rio_table_ptr->ver_num = readb (io_mem + offset); + rio_table_ptr->scal_count = readb (io_mem + offset + 1); + rio_table_ptr->riodev_count = readb (io_mem + offset + 2); + rio_table_ptr->offset = offset +3 ; + + debug("info about rio table hdr ---\n"); + debug("ver_num: %x\nscal_count: %x\nriodev_count: %x\noffset of rio table: %x\n ", + rio_table_ptr->ver_num, rio_table_ptr->scal_count, + rio_table_ptr->riodev_count, rio_table_ptr->offset); + + rio_complete = 1; + } + } + + if (!hs_complete && !rio_complete) + goto error_nodev; + + if (rio_table_ptr) { + if (rio_complete && rio_table_ptr->ver_num == 3) { + rc = ebda_rio_table (); + if (rc) + goto out; + } + } + rc = ebda_rsrc_controller (); + if (rc) + goto out; + + rc = ebda_rsrc_rsrc (); + goto out; +error_nodev: + rc = -ENODEV; +out: + iounmap (io_mem); + return rc; +} + +/* + * map info of scalability details and rio details from physical address + */ +static int __init ebda_rio_table (void) +{ + u16 offset; + u8 i; + struct rio_detail *rio_detail_ptr; + + offset = rio_table_ptr->offset; + offset += 12 * rio_table_ptr->scal_count; + + // we do concern about rio details + for (i = 0; i < rio_table_ptr->riodev_count; i++) { + rio_detail_ptr = kzalloc(sizeof(struct rio_detail), GFP_KERNEL); + if (!rio_detail_ptr) + return -ENOMEM; + rio_detail_ptr->rio_node_id = readb (io_mem + offset); + rio_detail_ptr->bbar = readl (io_mem + offset + 1); + rio_detail_ptr->rio_type = readb (io_mem + offset + 5); + rio_detail_ptr->owner_id = readb (io_mem + offset + 6); + rio_detail_ptr->port0_node_connect = readb (io_mem + offset + 7); + rio_detail_ptr->port0_port_connect = readb (io_mem + offset + 8); + rio_detail_ptr->port1_node_connect = readb (io_mem + offset + 9); + rio_detail_ptr->port1_port_connect = readb (io_mem + offset + 10); + rio_detail_ptr->first_slot_num = readb (io_mem + offset + 11); + rio_detail_ptr->status = readb (io_mem + offset + 12); + rio_detail_ptr->wpindex = readb (io_mem + offset + 13); + rio_detail_ptr->chassis_num = readb (io_mem + offset + 14); +// debug ("rio_node_id: %x\nbbar: %x\nrio_type: %x\nowner_id: %x\nport0_node: %x\nport0_port: %x\nport1_node: %x\nport1_port: %x\nfirst_slot_num: %x\nstatus: %x\n", rio_detail_ptr->rio_node_id, rio_detail_ptr->bbar, rio_detail_ptr->rio_type, rio_detail_ptr->owner_id, rio_detail_ptr->port0_node_connect, rio_detail_ptr->port0_port_connect, rio_detail_ptr->port1_node_connect, rio_detail_ptr->port1_port_connect, rio_detail_ptr->first_slot_num, rio_detail_ptr->status); + //create linked list of chassis + if (rio_detail_ptr->rio_type == 4 || rio_detail_ptr->rio_type == 5) + list_add (&rio_detail_ptr->rio_detail_list, &rio_vg_head); + //create linked list of expansion box + else if (rio_detail_ptr->rio_type == 6 || rio_detail_ptr->rio_type == 7) + list_add (&rio_detail_ptr->rio_detail_list, &rio_lo_head); + else + // not in my concern + kfree (rio_detail_ptr); + offset += 15; + } + print_lo_info (); + print_vg_info (); + return 0; +} + +/* + * reorganizing linked list of chassis + */ +static struct opt_rio *search_opt_vg (u8 chassis_num) +{ + struct opt_rio *ptr; + list_for_each_entry(ptr, &opt_vg_head, opt_rio_list) { + if (ptr->chassis_num == chassis_num) + return ptr; + } + return NULL; +} + +static int __init combine_wpg_for_chassis (void) +{ + struct opt_rio *opt_rio_ptr = NULL; + struct rio_detail *rio_detail_ptr = NULL; + + list_for_each_entry(rio_detail_ptr, &rio_vg_head, rio_detail_list) { + opt_rio_ptr = search_opt_vg (rio_detail_ptr->chassis_num); + if (!opt_rio_ptr) { + opt_rio_ptr = kzalloc(sizeof(struct opt_rio), GFP_KERNEL); + if (!opt_rio_ptr) + return -ENOMEM; + opt_rio_ptr->rio_type = rio_detail_ptr->rio_type; + opt_rio_ptr->chassis_num = rio_detail_ptr->chassis_num; + opt_rio_ptr->first_slot_num = rio_detail_ptr->first_slot_num; + opt_rio_ptr->middle_num = rio_detail_ptr->first_slot_num; + list_add (&opt_rio_ptr->opt_rio_list, &opt_vg_head); + } else { + opt_rio_ptr->first_slot_num = min (opt_rio_ptr->first_slot_num, rio_detail_ptr->first_slot_num); + opt_rio_ptr->middle_num = max (opt_rio_ptr->middle_num, rio_detail_ptr->first_slot_num); + } + } + print_opt_vg (); + return 0; +} + +/* + * reorganizing linked list of expansion box + */ +static struct opt_rio_lo *search_opt_lo (u8 chassis_num) +{ + struct opt_rio_lo *ptr; + list_for_each_entry(ptr, &opt_lo_head, opt_rio_lo_list) { + if (ptr->chassis_num == chassis_num) + return ptr; + } + return NULL; +} + +static int combine_wpg_for_expansion (void) +{ + struct opt_rio_lo *opt_rio_lo_ptr = NULL; + struct rio_detail *rio_detail_ptr = NULL; + + list_for_each_entry(rio_detail_ptr, &rio_lo_head, rio_detail_list) { + opt_rio_lo_ptr = search_opt_lo (rio_detail_ptr->chassis_num); + if (!opt_rio_lo_ptr) { + opt_rio_lo_ptr = kzalloc(sizeof(struct opt_rio_lo), GFP_KERNEL); + if (!opt_rio_lo_ptr) + return -ENOMEM; + opt_rio_lo_ptr->rio_type = rio_detail_ptr->rio_type; + opt_rio_lo_ptr->chassis_num = rio_detail_ptr->chassis_num; + opt_rio_lo_ptr->first_slot_num = rio_detail_ptr->first_slot_num; + opt_rio_lo_ptr->middle_num = rio_detail_ptr->first_slot_num; + opt_rio_lo_ptr->pack_count = 1; + + list_add (&opt_rio_lo_ptr->opt_rio_lo_list, &opt_lo_head); + } else { + opt_rio_lo_ptr->first_slot_num = min (opt_rio_lo_ptr->first_slot_num, rio_detail_ptr->first_slot_num); + opt_rio_lo_ptr->middle_num = max (opt_rio_lo_ptr->middle_num, rio_detail_ptr->first_slot_num); + opt_rio_lo_ptr->pack_count = 2; + } + } + return 0; +} + + +/* Since we don't know the max slot number per each chassis, hence go + * through the list of all chassis to find out the range + * Arguments: slot_num, 1st slot number of the chassis we think we are on, + * var (0 = chassis, 1 = expansion box) + */ +static int first_slot_num (u8 slot_num, u8 first_slot, u8 var) +{ + struct opt_rio *opt_vg_ptr = NULL; + struct opt_rio_lo *opt_lo_ptr = NULL; + int rc = 0; + + if (!var) { + list_for_each_entry(opt_vg_ptr, &opt_vg_head, opt_rio_list) { + if ((first_slot < opt_vg_ptr->first_slot_num) && (slot_num >= opt_vg_ptr->first_slot_num)) { + rc = -ENODEV; + break; + } + } + } else { + list_for_each_entry(opt_lo_ptr, &opt_lo_head, opt_rio_lo_list) { + if ((first_slot < opt_lo_ptr->first_slot_num) && (slot_num >= opt_lo_ptr->first_slot_num)) { + rc = -ENODEV; + break; + } + } + } + return rc; +} + +static struct opt_rio_lo * find_rxe_num (u8 slot_num) +{ + struct opt_rio_lo *opt_lo_ptr; + + list_for_each_entry(opt_lo_ptr, &opt_lo_head, opt_rio_lo_list) { + //check to see if this slot_num belongs to expansion box + if ((slot_num >= opt_lo_ptr->first_slot_num) && (!first_slot_num (slot_num, opt_lo_ptr->first_slot_num, 1))) + return opt_lo_ptr; + } + return NULL; +} + +static struct opt_rio * find_chassis_num (u8 slot_num) +{ + struct opt_rio *opt_vg_ptr; + + list_for_each_entry(opt_vg_ptr, &opt_vg_head, opt_rio_list) { + //check to see if this slot_num belongs to chassis + if ((slot_num >= opt_vg_ptr->first_slot_num) && (!first_slot_num (slot_num, opt_vg_ptr->first_slot_num, 0))) + return opt_vg_ptr; + } + return NULL; +} + +/* This routine will find out how many slots are in the chassis, so that + * the slot numbers for rxe100 would start from 1, and not from 7, or 6 etc + */ +static u8 calculate_first_slot (u8 slot_num) +{ + u8 first_slot = 1; + struct slot * slot_cur; + + list_for_each_entry(slot_cur, &ibmphp_slot_head, ibm_slot_list) { + if (slot_cur->ctrl) { + if ((slot_cur->ctrl->ctlr_type != 4) && (slot_cur->ctrl->ending_slot_num > first_slot) && (slot_num > slot_cur->ctrl->ending_slot_num)) + first_slot = slot_cur->ctrl->ending_slot_num; + } + } + return first_slot + 1; + +} + +#define SLOT_NAME_SIZE 30 + +static char *create_file_name (struct slot * slot_cur) +{ + struct opt_rio *opt_vg_ptr = NULL; + struct opt_rio_lo *opt_lo_ptr = NULL; + static char str[SLOT_NAME_SIZE]; + int which = 0; /* rxe = 1, chassis = 0 */ + u8 number = 1; /* either chassis or rxe # */ + u8 first_slot = 1; + u8 slot_num; + u8 flag = 0; + + if (!slot_cur) { + err ("Structure passed is empty\n"); + return NULL; + } + + slot_num = slot_cur->number; + + memset (str, 0, sizeof(str)); + + if (rio_table_ptr) { + if (rio_table_ptr->ver_num == 3) { + opt_vg_ptr = find_chassis_num (slot_num); + opt_lo_ptr = find_rxe_num (slot_num); + } + } + if (opt_vg_ptr) { + if (opt_lo_ptr) { + if ((slot_num - opt_vg_ptr->first_slot_num) > (slot_num - opt_lo_ptr->first_slot_num)) { + number = opt_lo_ptr->chassis_num; + first_slot = opt_lo_ptr->first_slot_num; + which = 1; /* it is RXE */ + } else { + first_slot = opt_vg_ptr->first_slot_num; + number = opt_vg_ptr->chassis_num; + which = 0; + } + } else { + first_slot = opt_vg_ptr->first_slot_num; + number = opt_vg_ptr->chassis_num; + which = 0; + } + ++flag; + } else if (opt_lo_ptr) { + number = opt_lo_ptr->chassis_num; + first_slot = opt_lo_ptr->first_slot_num; + which = 1; + ++flag; + } else if (rio_table_ptr) { + if (rio_table_ptr->ver_num == 3) { + /* if both NULL and we DO have correct RIO table in BIOS */ + return NULL; + } + } + if (!flag) { + if (slot_cur->ctrl->ctlr_type == 4) { + first_slot = calculate_first_slot (slot_num); + which = 1; + } else { + which = 0; + } + } + + sprintf(str, "%s%dslot%d", + which == 0 ? "chassis" : "rxe", + number, slot_num - first_slot + 1); + return str; +} + +static int fillslotinfo(struct hotplug_slot *hotplug_slot) +{ + struct slot *slot; + int rc = 0; + + if (!hotplug_slot || !hotplug_slot->private) + return -EINVAL; + + slot = hotplug_slot->private; + rc = ibmphp_hpc_readslot(slot, READ_ALLSTAT, NULL); + if (rc) + return rc; + + // power - enabled:1 not:0 + hotplug_slot->info->power_status = SLOT_POWER(slot->status); + + // attention - off:0, on:1, blinking:2 + hotplug_slot->info->attention_status = SLOT_ATTN(slot->status, slot->ext_status); + + // latch - open:1 closed:0 + hotplug_slot->info->latch_status = SLOT_LATCH(slot->status); + + // pci board - present:1 not:0 + if (SLOT_PRESENT (slot->status)) + hotplug_slot->info->adapter_status = 1; + else + hotplug_slot->info->adapter_status = 0; +/* + if (slot->bus_on->supported_bus_mode + && (slot->bus_on->supported_speed == BUS_SPEED_66)) + hotplug_slot->info->max_bus_speed_status = BUS_SPEED_66PCIX; + else + hotplug_slot->info->max_bus_speed_status = slot->bus_on->supported_speed; +*/ + + return rc; +} + +static void release_slot(struct hotplug_slot *hotplug_slot) +{ + struct slot *slot; + + if (!hotplug_slot || !hotplug_slot->private) + return; + + slot = hotplug_slot->private; + kfree(slot->hotplug_slot->info); + kfree(slot->hotplug_slot); + slot->ctrl = NULL; + slot->bus_on = NULL; + + /* we don't want to actually remove the resources, since free_resources will do just that */ + ibmphp_unconfigure_card(&slot, -1); + + kfree (slot); +} + +static struct pci_driver ibmphp_driver; + +/* + * map info (ctlr-id, slot count, slot#.. bus count, bus#, ctlr type...) of + * each hpc from physical address to a list of hot plug controllers based on + * hpc descriptors. + */ +static int __init ebda_rsrc_controller (void) +{ + u16 addr, addr_slot, addr_bus; + u8 ctlr_id, temp, bus_index; + u16 ctlr, slot, bus; + u16 slot_num, bus_num, index; + struct hotplug_slot *hp_slot_ptr; + struct controller *hpc_ptr; + struct ebda_hpc_bus *bus_ptr; + struct ebda_hpc_slot *slot_ptr; + struct bus_info *bus_info_ptr1, *bus_info_ptr2; + int rc; + struct slot *tmp_slot; + char name[SLOT_NAME_SIZE]; + + addr = hpc_list_ptr->phys_addr; + for (ctlr = 0; ctlr < hpc_list_ptr->num_ctlrs; ctlr++) { + bus_index = 1; + ctlr_id = readb (io_mem + addr); + addr += 1; + slot_num = readb (io_mem + addr); + + addr += 1; + addr_slot = addr; /* offset of slot structure */ + addr += (slot_num * 4); + + bus_num = readb (io_mem + addr); + + addr += 1; + addr_bus = addr; /* offset of bus */ + addr += (bus_num * 9); /* offset of ctlr_type */ + temp = readb (io_mem + addr); + + addr += 1; + /* init hpc structure */ + hpc_ptr = alloc_ebda_hpc (slot_num, bus_num); + if (!hpc_ptr ) { + rc = -ENOMEM; + goto error_no_hpc; + } + hpc_ptr->ctlr_id = ctlr_id; + hpc_ptr->ctlr_relative_id = ctlr; + hpc_ptr->slot_count = slot_num; + hpc_ptr->bus_count = bus_num; + debug ("now enter ctlr data struture ---\n"); + debug ("ctlr id: %x\n", ctlr_id); + debug ("ctlr_relative_id: %x\n", hpc_ptr->ctlr_relative_id); + debug ("count of slots controlled by this ctlr: %x\n", slot_num); + debug ("count of buses controlled by this ctlr: %x\n", bus_num); + + /* init slot structure, fetch slot, bus, cap... */ + slot_ptr = hpc_ptr->slots; + for (slot = 0; slot < slot_num; slot++) { + slot_ptr->slot_num = readb (io_mem + addr_slot); + slot_ptr->slot_bus_num = readb (io_mem + addr_slot + slot_num); + slot_ptr->ctl_index = readb (io_mem + addr_slot + 2*slot_num); + slot_ptr->slot_cap = readb (io_mem + addr_slot + 3*slot_num); + + // create bus_info lined list --- if only one slot per bus: slot_min = slot_max + + bus_info_ptr2 = ibmphp_find_same_bus_num (slot_ptr->slot_bus_num); + if (!bus_info_ptr2) { + bus_info_ptr1 = kzalloc(sizeof(struct bus_info), GFP_KERNEL); + if (!bus_info_ptr1) { + rc = -ENOMEM; + goto error_no_hp_slot; + } + bus_info_ptr1->slot_min = slot_ptr->slot_num; + bus_info_ptr1->slot_max = slot_ptr->slot_num; + bus_info_ptr1->slot_count += 1; + bus_info_ptr1->busno = slot_ptr->slot_bus_num; + bus_info_ptr1->index = bus_index++; + bus_info_ptr1->current_speed = 0xff; + bus_info_ptr1->current_bus_mode = 0xff; + + bus_info_ptr1->controller_id = hpc_ptr->ctlr_id; + + list_add_tail (&bus_info_ptr1->bus_info_list, &bus_info_head); + + } else { + bus_info_ptr2->slot_min = min (bus_info_ptr2->slot_min, slot_ptr->slot_num); + bus_info_ptr2->slot_max = max (bus_info_ptr2->slot_max, slot_ptr->slot_num); + bus_info_ptr2->slot_count += 1; + + } + + // end of creating the bus_info linked list + + slot_ptr++; + addr_slot += 1; + } + + /* init bus structure */ + bus_ptr = hpc_ptr->buses; + for (bus = 0; bus < bus_num; bus++) { + bus_ptr->bus_num = readb (io_mem + addr_bus + bus); + bus_ptr->slots_at_33_conv = readb (io_mem + addr_bus + bus_num + 8 * bus); + bus_ptr->slots_at_66_conv = readb (io_mem + addr_bus + bus_num + 8 * bus + 1); + + bus_ptr->slots_at_66_pcix = readb (io_mem + addr_bus + bus_num + 8 * bus + 2); + + bus_ptr->slots_at_100_pcix = readb (io_mem + addr_bus + bus_num + 8 * bus + 3); + + bus_ptr->slots_at_133_pcix = readb (io_mem + addr_bus + bus_num + 8 * bus + 4); + + bus_info_ptr2 = ibmphp_find_same_bus_num (bus_ptr->bus_num); + if (bus_info_ptr2) { + bus_info_ptr2->slots_at_33_conv = bus_ptr->slots_at_33_conv; + bus_info_ptr2->slots_at_66_conv = bus_ptr->slots_at_66_conv; + bus_info_ptr2->slots_at_66_pcix = bus_ptr->slots_at_66_pcix; + bus_info_ptr2->slots_at_100_pcix = bus_ptr->slots_at_100_pcix; + bus_info_ptr2->slots_at_133_pcix = bus_ptr->slots_at_133_pcix; + } + bus_ptr++; + } + + hpc_ptr->ctlr_type = temp; + + switch (hpc_ptr->ctlr_type) { + case 1: + hpc_ptr->u.pci_ctlr.bus = readb (io_mem + addr); + hpc_ptr->u.pci_ctlr.dev_fun = readb (io_mem + addr + 1); + hpc_ptr->irq = readb (io_mem + addr + 2); + addr += 3; + debug ("ctrl bus = %x, ctlr devfun = %x, irq = %x\n", + hpc_ptr->u.pci_ctlr.bus, + hpc_ptr->u.pci_ctlr.dev_fun, hpc_ptr->irq); + break; + + case 0: + hpc_ptr->u.isa_ctlr.io_start = readw (io_mem + addr); + hpc_ptr->u.isa_ctlr.io_end = readw (io_mem + addr + 2); + if (!request_region (hpc_ptr->u.isa_ctlr.io_start, + (hpc_ptr->u.isa_ctlr.io_end - hpc_ptr->u.isa_ctlr.io_start + 1), + "ibmphp")) { + rc = -ENODEV; + goto error_no_hp_slot; + } + hpc_ptr->irq = readb (io_mem + addr + 4); + addr += 5; + break; + + case 2: + case 4: + hpc_ptr->u.wpeg_ctlr.wpegbbar = readl (io_mem + addr); + hpc_ptr->u.wpeg_ctlr.i2c_addr = readb (io_mem + addr + 4); + hpc_ptr->irq = readb (io_mem + addr + 5); + addr += 6; + break; + default: + rc = -ENODEV; + goto error_no_hp_slot; + } + + //reorganize chassis' linked list + combine_wpg_for_chassis (); + combine_wpg_for_expansion (); + hpc_ptr->revision = 0xff; + hpc_ptr->options = 0xff; + hpc_ptr->starting_slot_num = hpc_ptr->slots[0].slot_num; + hpc_ptr->ending_slot_num = hpc_ptr->slots[slot_num-1].slot_num; + + // register slots with hpc core as well as create linked list of ibm slot + for (index = 0; index < hpc_ptr->slot_count; index++) { + + hp_slot_ptr = kzalloc(sizeof(*hp_slot_ptr), GFP_KERNEL); + if (!hp_slot_ptr) { + rc = -ENOMEM; + goto error_no_hp_slot; + } + + hp_slot_ptr->info = kzalloc(sizeof(struct hotplug_slot_info), GFP_KERNEL); + if (!hp_slot_ptr->info) { + rc = -ENOMEM; + goto error_no_hp_info; + } + + tmp_slot = kzalloc(sizeof(*tmp_slot), GFP_KERNEL); + if (!tmp_slot) { + rc = -ENOMEM; + goto error_no_slot; + } + + tmp_slot->flag = 1; + + tmp_slot->capabilities = hpc_ptr->slots[index].slot_cap; + if ((hpc_ptr->slots[index].slot_cap & EBDA_SLOT_133_MAX) == EBDA_SLOT_133_MAX) + tmp_slot->supported_speed = 3; + else if ((hpc_ptr->slots[index].slot_cap & EBDA_SLOT_100_MAX) == EBDA_SLOT_100_MAX) + tmp_slot->supported_speed = 2; + else if ((hpc_ptr->slots[index].slot_cap & EBDA_SLOT_66_MAX) == EBDA_SLOT_66_MAX) + tmp_slot->supported_speed = 1; + + if ((hpc_ptr->slots[index].slot_cap & EBDA_SLOT_PCIX_CAP) == EBDA_SLOT_PCIX_CAP) + tmp_slot->supported_bus_mode = 1; + else + tmp_slot->supported_bus_mode = 0; + + + tmp_slot->bus = hpc_ptr->slots[index].slot_bus_num; + + bus_info_ptr1 = ibmphp_find_same_bus_num (hpc_ptr->slots[index].slot_bus_num); + if (!bus_info_ptr1) { + kfree(tmp_slot); + rc = -ENODEV; + goto error; + } + tmp_slot->bus_on = bus_info_ptr1; + bus_info_ptr1 = NULL; + tmp_slot->ctrl = hpc_ptr; + + tmp_slot->ctlr_index = hpc_ptr->slots[index].ctl_index; + tmp_slot->number = hpc_ptr->slots[index].slot_num; + tmp_slot->hotplug_slot = hp_slot_ptr; + + hp_slot_ptr->private = tmp_slot; + hp_slot_ptr->release = release_slot; + + rc = fillslotinfo(hp_slot_ptr); + if (rc) + goto error; + + rc = ibmphp_init_devno ((struct slot **) &hp_slot_ptr->private); + if (rc) + goto error; + hp_slot_ptr->ops = &ibmphp_hotplug_slot_ops; + + // end of registering ibm slot with hotplug core + + list_add (& ((struct slot *)(hp_slot_ptr->private))->ibm_slot_list, &ibmphp_slot_head); + } + + print_bus_info (); + list_add (&hpc_ptr->ebda_hpc_list, &ebda_hpc_head ); + + } /* each hpc */ + + list_for_each_entry(tmp_slot, &ibmphp_slot_head, ibm_slot_list) { + snprintf(name, SLOT_NAME_SIZE, "%s", create_file_name(tmp_slot)); + pci_hp_register(tmp_slot->hotplug_slot, + pci_find_bus(0, tmp_slot->bus), tmp_slot->device, name); + } + + print_ebda_hpc (); + print_ibm_slot (); + return 0; + +error: + kfree (hp_slot_ptr->private); +error_no_slot: + kfree (hp_slot_ptr->info); +error_no_hp_info: + kfree (hp_slot_ptr); +error_no_hp_slot: + free_ebda_hpc (hpc_ptr); +error_no_hpc: + iounmap (io_mem); + return rc; +} + +/* + * map info (bus, devfun, start addr, end addr..) of i/o, memory, + * pfm from the physical addr to a list of resource. + */ +static int __init ebda_rsrc_rsrc (void) +{ + u16 addr; + short rsrc; + u8 type, rsrc_type; + struct ebda_pci_rsrc *rsrc_ptr; + + addr = rsrc_list_ptr->phys_addr; + debug ("now entering rsrc land\n"); + debug ("offset of rsrc: %x\n", rsrc_list_ptr->phys_addr); + + for (rsrc = 0; rsrc < rsrc_list_ptr->num_entries; rsrc++) { + type = readb (io_mem + addr); + + addr += 1; + rsrc_type = type & EBDA_RSRC_TYPE_MASK; + + if (rsrc_type == EBDA_IO_RSRC_TYPE) { + rsrc_ptr = alloc_ebda_pci_rsrc (); + if (!rsrc_ptr) { + iounmap (io_mem); + return -ENOMEM; + } + rsrc_ptr->rsrc_type = type; + + rsrc_ptr->bus_num = readb (io_mem + addr); + rsrc_ptr->dev_fun = readb (io_mem + addr + 1); + rsrc_ptr->start_addr = readw (io_mem + addr + 2); + rsrc_ptr->end_addr = readw (io_mem + addr + 4); + addr += 6; + + debug ("rsrc from io type ----\n"); + debug ("rsrc type: %x bus#: %x dev_func: %x start addr: %x end addr: %x\n", + rsrc_ptr->rsrc_type, rsrc_ptr->bus_num, rsrc_ptr->dev_fun, rsrc_ptr->start_addr, rsrc_ptr->end_addr); + + list_add (&rsrc_ptr->ebda_pci_rsrc_list, &ibmphp_ebda_pci_rsrc_head); + } + + if (rsrc_type == EBDA_MEM_RSRC_TYPE || rsrc_type == EBDA_PFM_RSRC_TYPE) { + rsrc_ptr = alloc_ebda_pci_rsrc (); + if (!rsrc_ptr ) { + iounmap (io_mem); + return -ENOMEM; + } + rsrc_ptr->rsrc_type = type; + + rsrc_ptr->bus_num = readb (io_mem + addr); + rsrc_ptr->dev_fun = readb (io_mem + addr + 1); + rsrc_ptr->start_addr = readl (io_mem + addr + 2); + rsrc_ptr->end_addr = readl (io_mem + addr + 6); + addr += 10; + + debug ("rsrc from mem or pfm ---\n"); + debug ("rsrc type: %x bus#: %x dev_func: %x start addr: %x end addr: %x\n", + rsrc_ptr->rsrc_type, rsrc_ptr->bus_num, rsrc_ptr->dev_fun, rsrc_ptr->start_addr, rsrc_ptr->end_addr); + + list_add (&rsrc_ptr->ebda_pci_rsrc_list, &ibmphp_ebda_pci_rsrc_head); + } + } + kfree (rsrc_list_ptr); + rsrc_list_ptr = NULL; + print_ebda_pci_rsrc (); + return 0; +} + +u16 ibmphp_get_total_controllers (void) +{ + return hpc_list_ptr->num_ctlrs; +} + +struct slot *ibmphp_get_slot_from_physical_num (u8 physical_num) +{ + struct slot *slot; + + list_for_each_entry(slot, &ibmphp_slot_head, ibm_slot_list) { + if (slot->number == physical_num) + return slot; + } + return NULL; +} + +/* To find: + * - the smallest slot number + * - the largest slot number + * - the total number of the slots based on each bus + * (if only one slot per bus slot_min = slot_max ) + */ +struct bus_info *ibmphp_find_same_bus_num (u32 num) +{ + struct bus_info *ptr; + + list_for_each_entry(ptr, &bus_info_head, bus_info_list) { + if (ptr->busno == num) + return ptr; + } + return NULL; +} + +/* Finding relative bus number, in order to map corresponding + * bus register + */ +int ibmphp_get_bus_index (u8 num) +{ + struct bus_info *ptr; + + list_for_each_entry(ptr, &bus_info_head, bus_info_list) { + if (ptr->busno == num) + return ptr->index; + } + return -ENODEV; +} + +void ibmphp_free_bus_info_queue (void) +{ + struct bus_info *bus_info; + struct list_head *list; + struct list_head *next; + + list_for_each_safe (list, next, &bus_info_head ) { + bus_info = list_entry (list, struct bus_info, bus_info_list); + kfree (bus_info); + } +} + +void ibmphp_free_ebda_hpc_queue (void) +{ + struct controller *controller = NULL; + struct list_head *list; + struct list_head *next; + int pci_flag = 0; + + list_for_each_safe (list, next, &ebda_hpc_head) { + controller = list_entry (list, struct controller, ebda_hpc_list); + if (controller->ctlr_type == 0) + release_region (controller->u.isa_ctlr.io_start, (controller->u.isa_ctlr.io_end - controller->u.isa_ctlr.io_start + 1)); + else if ((controller->ctlr_type == 1) && (!pci_flag)) { + ++pci_flag; + pci_unregister_driver (&ibmphp_driver); + } + free_ebda_hpc (controller); + } +} + +void ibmphp_free_ebda_pci_rsrc_queue (void) +{ + struct ebda_pci_rsrc *resource; + struct list_head *list; + struct list_head *next; + + list_for_each_safe (list, next, &ibmphp_ebda_pci_rsrc_head) { + resource = list_entry (list, struct ebda_pci_rsrc, ebda_pci_rsrc_list); + kfree (resource); + resource = NULL; + } +} + +static struct pci_device_id id_table[] = { + { + .vendor = PCI_VENDOR_ID_IBM, + .device = HPC_DEVICE_ID, + .subvendor = PCI_VENDOR_ID_IBM, + .subdevice = HPC_SUBSYSTEM_ID, + .class = ((PCI_CLASS_SYSTEM_PCI_HOTPLUG << 8) | 0x00), + }, {} +}; + +MODULE_DEVICE_TABLE(pci, id_table); + +static int ibmphp_probe (struct pci_dev *, const struct pci_device_id *); +static struct pci_driver ibmphp_driver = { + .name = "ibmphp", + .id_table = id_table, + .probe = ibmphp_probe, +}; + +int ibmphp_register_pci (void) +{ + struct controller *ctrl; + int rc = 0; + + list_for_each_entry(ctrl, &ebda_hpc_head, ebda_hpc_list) { + if (ctrl->ctlr_type == 1) { + rc = pci_register_driver(&ibmphp_driver); + break; + } + } + return rc; +} +static int ibmphp_probe (struct pci_dev * dev, const struct pci_device_id *ids) +{ + struct controller *ctrl; + + debug ("inside ibmphp_probe\n"); + + list_for_each_entry(ctrl, &ebda_hpc_head, ebda_hpc_list) { + if (ctrl->ctlr_type == 1) { + if ((dev->devfn == ctrl->u.pci_ctlr.dev_fun) && (dev->bus->number == ctrl->u.pci_ctlr.bus)) { + ctrl->ctrl_dev = dev; + debug ("found device!!!\n"); + debug ("dev->device = %x, dev->subsystem_device = %x\n", dev->device, dev->subsystem_device); + return 0; + } + } + } + return -ENODEV; +} + diff --git a/drivers/pci/hotplug/ibmphp_hpc.c b/drivers/pci/hotplug/ibmphp_hpc.c new file mode 100644 index 0000000..83f337c --- /dev/null +++ b/drivers/pci/hotplug/ibmphp_hpc.c @@ -0,0 +1,1130 @@ +/* + * IBM Hot Plug Controller Driver + * + * Written By: Jyoti Shah, IBM Corporation + * + * Copyright (C) 2001-2003 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 <gregkh@us.ibm.com> + * <jshah@us.ibm.com> + * + */ + +#include <linux/wait.h> +#include <linux/time.h> +#include <linux/delay.h> +#include <linux/module.h> +#include <linux/pci.h> +#include <linux/init.h> +#include <linux/mutex.h> +#include <linux/sched.h> +#include <linux/kthread.h> +#include "ibmphp.h" + +static int to_debug = 0; +#define debug_polling(fmt, arg...) do { if (to_debug) debug (fmt, arg); } while (0) + +//---------------------------------------------------------------------------- +// timeout values +//---------------------------------------------------------------------------- +#define CMD_COMPLETE_TOUT_SEC 60 // give HPC 60 sec to finish cmd +#define HPC_CTLR_WORKING_TOUT 60 // give HPC 60 sec to finish cmd +#define HPC_GETACCESS_TIMEOUT 60 // seconds +#define POLL_INTERVAL_SEC 2 // poll HPC every 2 seconds +#define POLL_LATCH_CNT 5 // poll latch 5 times, then poll slots + +//---------------------------------------------------------------------------- +// Winnipeg Architected Register Offsets +//---------------------------------------------------------------------------- +#define WPG_I2CMBUFL_OFFSET 0x08 // I2C Message Buffer Low +#define WPG_I2CMOSUP_OFFSET 0x10 // I2C Master Operation Setup Reg +#define WPG_I2CMCNTL_OFFSET 0x20 // I2C Master Control Register +#define WPG_I2CPARM_OFFSET 0x40 // I2C Parameter Register +#define WPG_I2CSTAT_OFFSET 0x70 // I2C Status Register + +//---------------------------------------------------------------------------- +// Winnipeg Store Type commands (Add this commands to the register offset) +//---------------------------------------------------------------------------- +#define WPG_I2C_AND 0x1000 // I2C AND operation +#define WPG_I2C_OR 0x2000 // I2C OR operation + +//---------------------------------------------------------------------------- +// Command set for I2C Master Operation Setup Register +//---------------------------------------------------------------------------- +#define WPG_READATADDR_MASK 0x00010000 // read,bytes,I2C shifted,index +#define WPG_WRITEATADDR_MASK 0x40010000 // write,bytes,I2C shifted,index +#define WPG_READDIRECT_MASK 0x10010000 +#define WPG_WRITEDIRECT_MASK 0x60010000 + + +//---------------------------------------------------------------------------- +// bit masks for I2C Master Control Register +//---------------------------------------------------------------------------- +#define WPG_I2CMCNTL_STARTOP_MASK 0x00000002 // Start the Operation + +//---------------------------------------------------------------------------- +// +//---------------------------------------------------------------------------- +#define WPG_I2C_IOREMAP_SIZE 0x2044 // size of linear address interval + +//---------------------------------------------------------------------------- +// command index +//---------------------------------------------------------------------------- +#define WPG_1ST_SLOT_INDEX 0x01 // index - 1st slot for ctlr +#define WPG_CTLR_INDEX 0x0F // index - ctlr +#define WPG_1ST_EXTSLOT_INDEX 0x10 // index - 1st ext slot for ctlr +#define WPG_1ST_BUS_INDEX 0x1F // index - 1st bus for ctlr + +//---------------------------------------------------------------------------- +// macro utilities +//---------------------------------------------------------------------------- +// if bits 20,22,25,26,27,29,30 are OFF return 1 +#define HPC_I2CSTATUS_CHECK(s) ((u8)((s & 0x00000A76) ? 0 : 1)) + +//---------------------------------------------------------------------------- +// global variables +//---------------------------------------------------------------------------- +static struct mutex sem_hpcaccess; // lock access to HPC +static struct semaphore semOperations; // lock all operations and + // access to data structures +static struct semaphore sem_exit; // make sure polling thread goes away +static struct task_struct *ibmphp_poll_thread; +//---------------------------------------------------------------------------- +// local function prototypes +//---------------------------------------------------------------------------- +static u8 i2c_ctrl_read (struct controller *, void __iomem *, u8); +static u8 i2c_ctrl_write (struct controller *, void __iomem *, u8, u8); +static u8 hpc_writecmdtoindex (u8, u8); +static u8 hpc_readcmdtoindex (u8, u8); +static void get_hpc_access (void); +static void free_hpc_access (void); +static int poll_hpc(void *data); +static int process_changeinstatus (struct slot *, struct slot *); +static int process_changeinlatch (u8, u8, struct controller *); +static int hpc_wait_ctlr_notworking (int, struct controller *, void __iomem *, u8 *); +//---------------------------------------------------------------------------- + + +/*---------------------------------------------------------------------- +* Name: ibmphp_hpc_initvars +* +* Action: initialize semaphores and variables +*---------------------------------------------------------------------*/ +void __init ibmphp_hpc_initvars (void) +{ + debug ("%s - Entry\n", __func__); + + mutex_init(&sem_hpcaccess); + init_MUTEX (&semOperations); + init_MUTEX_LOCKED (&sem_exit); + to_debug = 0; + + debug ("%s - Exit\n", __func__); +} + +/*---------------------------------------------------------------------- +* Name: i2c_ctrl_read +* +* Action: read from HPC over I2C +* +*---------------------------------------------------------------------*/ +static u8 i2c_ctrl_read (struct controller *ctlr_ptr, void __iomem *WPGBbar, u8 index) +{ + u8 status; + int i; + void __iomem *wpg_addr; // base addr + offset + unsigned long wpg_data; // data to/from WPG LOHI format + unsigned long ultemp; + unsigned long data; // actual data HILO format + + debug_polling ("%s - Entry WPGBbar[%p] index[%x] \n", __func__, WPGBbar, index); + + //-------------------------------------------------------------------- + // READ - step 1 + // read at address, byte length, I2C address (shifted), index + // or read direct, byte length, index + if (ctlr_ptr->ctlr_type == 0x02) { + data = WPG_READATADDR_MASK; + // fill in I2C address + ultemp = (unsigned long)ctlr_ptr->u.wpeg_ctlr.i2c_addr; + ultemp = ultemp >> 1; + data |= (ultemp << 8); + + // fill in index + data |= (unsigned long)index; + } else if (ctlr_ptr->ctlr_type == 0x04) { + data = WPG_READDIRECT_MASK; + + // fill in index + ultemp = (unsigned long)index; + ultemp = ultemp << 8; + data |= ultemp; + } else { + err ("this controller type is not supported \n"); + return HPC_ERROR; + } + + wpg_data = swab32 (data); // swap data before writing + wpg_addr = WPGBbar + WPG_I2CMOSUP_OFFSET; + writel (wpg_data, wpg_addr); + + //-------------------------------------------------------------------- + // READ - step 2 : clear the message buffer + data = 0x00000000; + wpg_data = swab32 (data); + wpg_addr = WPGBbar + WPG_I2CMBUFL_OFFSET; + writel (wpg_data, wpg_addr); + + //-------------------------------------------------------------------- + // READ - step 3 : issue start operation, I2C master control bit 30:ON + // 2020 : [20] OR operation at [20] offset 0x20 + data = WPG_I2CMCNTL_STARTOP_MASK; + wpg_data = swab32 (data); + wpg_addr = WPGBbar + WPG_I2CMCNTL_OFFSET + WPG_I2C_OR; + writel (wpg_data, wpg_addr); + + //-------------------------------------------------------------------- + // READ - step 4 : wait until start operation bit clears + i = CMD_COMPLETE_TOUT_SEC; + while (i) { + msleep(10); + wpg_addr = WPGBbar + WPG_I2CMCNTL_OFFSET; + wpg_data = readl (wpg_addr); + data = swab32 (wpg_data); + if (!(data & WPG_I2CMCNTL_STARTOP_MASK)) + break; + i--; + } + if (i == 0) { + debug ("%s - Error : WPG timeout\n", __func__); + return HPC_ERROR; + } + //-------------------------------------------------------------------- + // READ - step 5 : read I2C status register + i = CMD_COMPLETE_TOUT_SEC; + while (i) { + msleep(10); + wpg_addr = WPGBbar + WPG_I2CSTAT_OFFSET; + wpg_data = readl (wpg_addr); + data = swab32 (wpg_data); + if (HPC_I2CSTATUS_CHECK (data)) + break; + i--; + } + if (i == 0) { + debug ("ctrl_read - Exit Error:I2C timeout\n"); + return HPC_ERROR; + } + + //-------------------------------------------------------------------- + // READ - step 6 : get DATA + wpg_addr = WPGBbar + WPG_I2CMBUFL_OFFSET; + wpg_data = readl (wpg_addr); + data = swab32 (wpg_data); + + status = (u8) data; + + debug_polling ("%s - Exit index[%x] status[%x]\n", __func__, index, status); + + return (status); +} + +/*---------------------------------------------------------------------- +* Name: i2c_ctrl_write +* +* Action: write to HPC over I2C +* +* Return 0 or error codes +*---------------------------------------------------------------------*/ +static u8 i2c_ctrl_write (struct controller *ctlr_ptr, void __iomem *WPGBbar, u8 index, u8 cmd) +{ + u8 rc; + void __iomem *wpg_addr; // base addr + offset + unsigned long wpg_data; // data to/from WPG LOHI format + unsigned long ultemp; + unsigned long data; // actual data HILO format + int i; + + debug_polling ("%s - Entry WPGBbar[%p] index[%x] cmd[%x]\n", __func__, WPGBbar, index, cmd); + + rc = 0; + //-------------------------------------------------------------------- + // WRITE - step 1 + // write at address, byte length, I2C address (shifted), index + // or write direct, byte length, index + data = 0x00000000; + + if (ctlr_ptr->ctlr_type == 0x02) { + data = WPG_WRITEATADDR_MASK; + // fill in I2C address + ultemp = (unsigned long)ctlr_ptr->u.wpeg_ctlr.i2c_addr; + ultemp = ultemp >> 1; + data |= (ultemp << 8); + + // fill in index + data |= (unsigned long)index; + } else if (ctlr_ptr->ctlr_type == 0x04) { + data = WPG_WRITEDIRECT_MASK; + + // fill in index + ultemp = (unsigned long)index; + ultemp = ultemp << 8; + data |= ultemp; + } else { + err ("this controller type is not supported \n"); + return HPC_ERROR; + } + + wpg_data = swab32 (data); // swap data before writing + wpg_addr = WPGBbar + WPG_I2CMOSUP_OFFSET; + writel (wpg_data, wpg_addr); + + //-------------------------------------------------------------------- + // WRITE - step 2 : clear the message buffer + data = 0x00000000 | (unsigned long)cmd; + wpg_data = swab32 (data); + wpg_addr = WPGBbar + WPG_I2CMBUFL_OFFSET; + writel (wpg_data, wpg_addr); + + //-------------------------------------------------------------------- + // WRITE - step 3 : issue start operation,I2C master control bit 30:ON + // 2020 : [20] OR operation at [20] offset 0x20 + data = WPG_I2CMCNTL_STARTOP_MASK; + wpg_data = swab32 (data); + wpg_addr = WPGBbar + WPG_I2CMCNTL_OFFSET + WPG_I2C_OR; + writel (wpg_data, wpg_addr); + + //-------------------------------------------------------------------- + // WRITE - step 4 : wait until start operation bit clears + i = CMD_COMPLETE_TOUT_SEC; + while (i) { + msleep(10); + wpg_addr = WPGBbar + WPG_I2CMCNTL_OFFSET; + wpg_data = readl (wpg_addr); + data = swab32 (wpg_data); + if (!(data & WPG_I2CMCNTL_STARTOP_MASK)) + break; + i--; + } + if (i == 0) { + debug ("%s - Exit Error:WPG timeout\n", __func__); + rc = HPC_ERROR; + } + + //-------------------------------------------------------------------- + // WRITE - step 5 : read I2C status register + i = CMD_COMPLETE_TOUT_SEC; + while (i) { + msleep(10); + wpg_addr = WPGBbar + WPG_I2CSTAT_OFFSET; + wpg_data = readl (wpg_addr); + data = swab32 (wpg_data); + if (HPC_I2CSTATUS_CHECK (data)) + break; + i--; + } + if (i == 0) { + debug ("ctrl_read - Error : I2C timeout\n"); + rc = HPC_ERROR; + } + + debug_polling ("%s Exit rc[%x]\n", __func__, rc); + return (rc); +} + +//------------------------------------------------------------ +// Read from ISA type HPC +//------------------------------------------------------------ +static u8 isa_ctrl_read (struct controller *ctlr_ptr, u8 offset) +{ + u16 start_address; + u16 end_address; + u8 data; + + start_address = ctlr_ptr->u.isa_ctlr.io_start; + end_address = ctlr_ptr->u.isa_ctlr.io_end; + data = inb (start_address + offset); + return data; +} + +//-------------------------------------------------------------- +// Write to ISA type HPC +//-------------------------------------------------------------- +static void isa_ctrl_write (struct controller *ctlr_ptr, u8 offset, u8 data) +{ + u16 start_address; + u16 port_address; + + start_address = ctlr_ptr->u.isa_ctlr.io_start; + port_address = start_address + (u16) offset; + outb (data, port_address); +} + +static u8 pci_ctrl_read (struct controller *ctrl, u8 offset) +{ + u8 data = 0x00; + debug ("inside pci_ctrl_read\n"); + if (ctrl->ctrl_dev) + pci_read_config_byte (ctrl->ctrl_dev, HPC_PCI_OFFSET + offset, &data); + return data; +} + +static u8 pci_ctrl_write (struct controller *ctrl, u8 offset, u8 data) +{ + u8 rc = -ENODEV; + debug ("inside pci_ctrl_write\n"); + if (ctrl->ctrl_dev) { + pci_write_config_byte (ctrl->ctrl_dev, HPC_PCI_OFFSET + offset, data); + rc = 0; + } + return rc; +} + +static u8 ctrl_read (struct controller *ctlr, void __iomem *base, u8 offset) +{ + u8 rc; + switch (ctlr->ctlr_type) { + case 0: + rc = isa_ctrl_read (ctlr, offset); + break; + case 1: + rc = pci_ctrl_read (ctlr, offset); + break; + case 2: + case 4: + rc = i2c_ctrl_read (ctlr, base, offset); + break; + default: + return -ENODEV; + } + return rc; +} + +static u8 ctrl_write (struct controller *ctlr, void __iomem *base, u8 offset, u8 data) +{ + u8 rc = 0; + switch (ctlr->ctlr_type) { + case 0: + isa_ctrl_write(ctlr, offset, data); + break; + case 1: + rc = pci_ctrl_write (ctlr, offset, data); + break; + case 2: + case 4: + rc = i2c_ctrl_write(ctlr, base, offset, data); + break; + default: + return -ENODEV; + } + return rc; +} +/*---------------------------------------------------------------------- +* Name: hpc_writecmdtoindex() +* +* Action: convert a write command to proper index within a controller +* +* Return index, HPC_ERROR +*---------------------------------------------------------------------*/ +static u8 hpc_writecmdtoindex (u8 cmd, u8 index) +{ + u8 rc; + + switch (cmd) { + case HPC_CTLR_ENABLEIRQ: // 0x00.N.15 + case HPC_CTLR_CLEARIRQ: // 0x06.N.15 + case HPC_CTLR_RESET: // 0x07.N.15 + case HPC_CTLR_IRQSTEER: // 0x08.N.15 + case HPC_CTLR_DISABLEIRQ: // 0x01.N.15 + case HPC_ALLSLOT_ON: // 0x11.N.15 + case HPC_ALLSLOT_OFF: // 0x12.N.15 + rc = 0x0F; + break; + + case HPC_SLOT_OFF: // 0x02.Y.0-14 + case HPC_SLOT_ON: // 0x03.Y.0-14 + case HPC_SLOT_ATTNOFF: // 0x04.N.0-14 + case HPC_SLOT_ATTNON: // 0x05.N.0-14 + case HPC_SLOT_BLINKLED: // 0x13.N.0-14 + rc = index; + break; + + case HPC_BUS_33CONVMODE: + case HPC_BUS_66CONVMODE: + case HPC_BUS_66PCIXMODE: + case HPC_BUS_100PCIXMODE: + case HPC_BUS_133PCIXMODE: + rc = index + WPG_1ST_BUS_INDEX - 1; + break; + + default: + err ("hpc_writecmdtoindex - Error invalid cmd[%x]\n", cmd); + rc = HPC_ERROR; + } + + return rc; +} + +/*---------------------------------------------------------------------- +* Name: hpc_readcmdtoindex() +* +* Action: convert a read command to proper index within a controller +* +* Return index, HPC_ERROR +*---------------------------------------------------------------------*/ +static u8 hpc_readcmdtoindex (u8 cmd, u8 index) +{ + u8 rc; + + switch (cmd) { + case READ_CTLRSTATUS: + rc = 0x0F; + break; + case READ_SLOTSTATUS: + case READ_ALLSTAT: + rc = index; + break; + case READ_EXTSLOTSTATUS: + rc = index + WPG_1ST_EXTSLOT_INDEX; + break; + case READ_BUSSTATUS: + rc = index + WPG_1ST_BUS_INDEX - 1; + break; + case READ_SLOTLATCHLOWREG: + rc = 0x28; + break; + case READ_REVLEVEL: + rc = 0x25; + break; + case READ_HPCOPTIONS: + rc = 0x27; + break; + default: + rc = HPC_ERROR; + } + return rc; +} + +/*---------------------------------------------------------------------- +* Name: HPCreadslot() +* +* Action: issue a READ command to HPC +* +* Input: pslot - cannot be NULL for READ_ALLSTAT +* pstatus - can be NULL for READ_ALLSTAT +* +* Return 0 or error codes +*---------------------------------------------------------------------*/ +int ibmphp_hpc_readslot (struct slot * pslot, u8 cmd, u8 * pstatus) +{ + void __iomem *wpg_bbar = NULL; + struct controller *ctlr_ptr; + struct list_head *pslotlist; + u8 index, status; + int rc = 0; + int busindex; + + debug_polling ("%s - Entry pslot[%p] cmd[%x] pstatus[%p]\n", __func__, pslot, cmd, pstatus); + + if ((pslot == NULL) + || ((pstatus == NULL) && (cmd != READ_ALLSTAT) && (cmd != READ_BUSSTATUS))) { + rc = -EINVAL; + err ("%s - Error invalid pointer, rc[%d]\n", __func__, rc); + return rc; + } + + if (cmd == READ_BUSSTATUS) { + busindex = ibmphp_get_bus_index (pslot->bus); + if (busindex < 0) { + rc = -EINVAL; + err ("%s - Exit Error:invalid bus, rc[%d]\n", __func__, rc); + return rc; + } else + index = (u8) busindex; + } else + index = pslot->ctlr_index; + + index = hpc_readcmdtoindex (cmd, index); + + if (index == HPC_ERROR) { + rc = -EINVAL; + err ("%s - Exit Error:invalid index, rc[%d]\n", __func__, rc); + return rc; + } + + ctlr_ptr = pslot->ctrl; + + get_hpc_access (); + + //-------------------------------------------------------------------- + // map physical address to logical address + //-------------------------------------------------------------------- + if ((ctlr_ptr->ctlr_type == 2) || (ctlr_ptr->ctlr_type == 4)) + wpg_bbar = ioremap (ctlr_ptr->u.wpeg_ctlr.wpegbbar, WPG_I2C_IOREMAP_SIZE); + + //-------------------------------------------------------------------- + // check controller status before reading + //-------------------------------------------------------------------- + rc = hpc_wait_ctlr_notworking (HPC_CTLR_WORKING_TOUT, ctlr_ptr, wpg_bbar, &status); + if (!rc) { + switch (cmd) { + case READ_ALLSTAT: + // update the slot structure + pslot->ctrl->status = status; + pslot->status = ctrl_read (ctlr_ptr, wpg_bbar, index); + rc = hpc_wait_ctlr_notworking (HPC_CTLR_WORKING_TOUT, ctlr_ptr, wpg_bbar, + &status); + if (!rc) + pslot->ext_status = ctrl_read (ctlr_ptr, wpg_bbar, index + WPG_1ST_EXTSLOT_INDEX); + + break; + + case READ_SLOTSTATUS: + // DO NOT update the slot structure + *pstatus = ctrl_read (ctlr_ptr, wpg_bbar, index); + break; + + case READ_EXTSLOTSTATUS: + // DO NOT update the slot structure + *pstatus = ctrl_read (ctlr_ptr, wpg_bbar, index); + break; + + case READ_CTLRSTATUS: + // DO NOT update the slot structure + *pstatus = status; + break; + + case READ_BUSSTATUS: + pslot->busstatus = ctrl_read (ctlr_ptr, wpg_bbar, index); + break; + case READ_REVLEVEL: + *pstatus = ctrl_read (ctlr_ptr, wpg_bbar, index); + break; + case READ_HPCOPTIONS: + *pstatus = ctrl_read (ctlr_ptr, wpg_bbar, index); + break; + case READ_SLOTLATCHLOWREG: + // DO NOT update the slot structure + *pstatus = ctrl_read (ctlr_ptr, wpg_bbar, index); + break; + + // Not used + case READ_ALLSLOT: + list_for_each (pslotlist, &ibmphp_slot_head) { + pslot = list_entry (pslotlist, struct slot, ibm_slot_list); + index = pslot->ctlr_index; + rc = hpc_wait_ctlr_notworking (HPC_CTLR_WORKING_TOUT, ctlr_ptr, + wpg_bbar, &status); + if (!rc) { + pslot->status = ctrl_read (ctlr_ptr, wpg_bbar, index); + rc = hpc_wait_ctlr_notworking (HPC_CTLR_WORKING_TOUT, + ctlr_ptr, wpg_bbar, &status); + if (!rc) + pslot->ext_status = + ctrl_read (ctlr_ptr, wpg_bbar, + index + WPG_1ST_EXTSLOT_INDEX); + } else { + err ("%s - Error ctrl_read failed\n", __func__); + rc = -EINVAL; + break; + } + } + break; + default: + rc = -EINVAL; + break; + } + } + //-------------------------------------------------------------------- + // cleanup + //-------------------------------------------------------------------- + + // remove physical to logical address mapping + if ((ctlr_ptr->ctlr_type == 2) || (ctlr_ptr->ctlr_type == 4)) + iounmap (wpg_bbar); + + free_hpc_access (); + + debug_polling ("%s - Exit rc[%d]\n", __func__, rc); + return rc; +} + +/*---------------------------------------------------------------------- +* Name: ibmphp_hpc_writeslot() +* +* Action: issue a WRITE command to HPC +*---------------------------------------------------------------------*/ +int ibmphp_hpc_writeslot (struct slot * pslot, u8 cmd) +{ + void __iomem *wpg_bbar = NULL; + struct controller *ctlr_ptr; + u8 index, status; + int busindex; + u8 done; + int rc = 0; + int timeout; + + debug_polling ("%s - Entry pslot[%p] cmd[%x]\n", __func__, pslot, cmd); + if (pslot == NULL) { + rc = -EINVAL; + err ("%s - Error Exit rc[%d]\n", __func__, rc); + return rc; + } + + if ((cmd == HPC_BUS_33CONVMODE) || (cmd == HPC_BUS_66CONVMODE) || + (cmd == HPC_BUS_66PCIXMODE) || (cmd == HPC_BUS_100PCIXMODE) || + (cmd == HPC_BUS_133PCIXMODE)) { + busindex = ibmphp_get_bus_index (pslot->bus); + if (busindex < 0) { + rc = -EINVAL; + err ("%s - Exit Error:invalid bus, rc[%d]\n", __func__, rc); + return rc; + } else + index = (u8) busindex; + } else + index = pslot->ctlr_index; + + index = hpc_writecmdtoindex (cmd, index); + + if (index == HPC_ERROR) { + rc = -EINVAL; + err ("%s - Error Exit rc[%d]\n", __func__, rc); + return rc; + } + + ctlr_ptr = pslot->ctrl; + + get_hpc_access (); + + //-------------------------------------------------------------------- + // map physical address to logical address + //-------------------------------------------------------------------- + if ((ctlr_ptr->ctlr_type == 2) || (ctlr_ptr->ctlr_type == 4)) { + wpg_bbar = ioremap (ctlr_ptr->u.wpeg_ctlr.wpegbbar, WPG_I2C_IOREMAP_SIZE); + + debug ("%s - ctlr id[%x] physical[%lx] logical[%lx] i2c[%x]\n", __func__, + ctlr_ptr->ctlr_id, (ulong) (ctlr_ptr->u.wpeg_ctlr.wpegbbar), (ulong) wpg_bbar, + ctlr_ptr->u.wpeg_ctlr.i2c_addr); + } + //-------------------------------------------------------------------- + // check controller status before writing + //-------------------------------------------------------------------- + rc = hpc_wait_ctlr_notworking (HPC_CTLR_WORKING_TOUT, ctlr_ptr, wpg_bbar, &status); + if (!rc) { + + ctrl_write (ctlr_ptr, wpg_bbar, index, cmd); + + //-------------------------------------------------------------------- + // check controller is still not working on the command + //-------------------------------------------------------------------- + timeout = CMD_COMPLETE_TOUT_SEC; + done = 0; + while (!done) { + rc = hpc_wait_ctlr_notworking (HPC_CTLR_WORKING_TOUT, ctlr_ptr, wpg_bbar, + &status); + if (!rc) { + if (NEEDTOCHECK_CMDSTATUS (cmd)) { + if (CTLR_FINISHED (status) == HPC_CTLR_FINISHED_YES) + done = 1; + } else + done = 1; + } + if (!done) { + msleep(1000); + if (timeout < 1) { + done = 1; + err ("%s - Error command complete timeout\n", __func__); + rc = -EFAULT; + } else + timeout--; + } + } + ctlr_ptr->status = status; + } + // cleanup + + // remove physical to logical address mapping + if ((ctlr_ptr->ctlr_type == 2) || (ctlr_ptr->ctlr_type == 4)) + iounmap (wpg_bbar); + free_hpc_access (); + + debug_polling ("%s - Exit rc[%d]\n", __func__, rc); + return rc; +} + +/*---------------------------------------------------------------------- +* Name: get_hpc_access() +* +* Action: make sure only one process can access HPC at one time +*---------------------------------------------------------------------*/ +static void get_hpc_access (void) +{ + mutex_lock(&sem_hpcaccess); +} + +/*---------------------------------------------------------------------- +* Name: free_hpc_access() +*---------------------------------------------------------------------*/ +void free_hpc_access (void) +{ + mutex_unlock(&sem_hpcaccess); +} + +/*---------------------------------------------------------------------- +* Name: ibmphp_lock_operations() +* +* Action: make sure only one process can change the data structure +*---------------------------------------------------------------------*/ +void ibmphp_lock_operations (void) +{ + down (&semOperations); + to_debug = 1; +} + +/*---------------------------------------------------------------------- +* Name: ibmphp_unlock_operations() +*---------------------------------------------------------------------*/ +void ibmphp_unlock_operations (void) +{ + debug ("%s - Entry\n", __func__); + up (&semOperations); + to_debug = 0; + debug ("%s - Exit\n", __func__); +} + +/*---------------------------------------------------------------------- +* Name: poll_hpc() +*---------------------------------------------------------------------*/ +#define POLL_LATCH_REGISTER 0 +#define POLL_SLOTS 1 +#define POLL_SLEEP 2 +static int poll_hpc(void *data) +{ + struct slot myslot; + struct slot *pslot = NULL; + struct list_head *pslotlist; + int rc; + int poll_state = POLL_LATCH_REGISTER; + u8 oldlatchlow = 0x00; + u8 curlatchlow = 0x00; + int poll_count = 0; + u8 ctrl_count = 0x00; + + debug ("%s - Entry\n", __func__); + + while (!kthread_should_stop()) { + /* try to get the lock to do some kind of hardware access */ + down (&semOperations); + + switch (poll_state) { + case POLL_LATCH_REGISTER: + oldlatchlow = curlatchlow; + ctrl_count = 0x00; + list_for_each (pslotlist, &ibmphp_slot_head) { + if (ctrl_count >= ibmphp_get_total_controllers()) + break; + pslot = list_entry (pslotlist, struct slot, ibm_slot_list); + if (pslot->ctrl->ctlr_relative_id == ctrl_count) { + ctrl_count++; + if (READ_SLOT_LATCH (pslot->ctrl)) { + rc = ibmphp_hpc_readslot (pslot, + READ_SLOTLATCHLOWREG, + &curlatchlow); + if (oldlatchlow != curlatchlow) + process_changeinlatch (oldlatchlow, + curlatchlow, + pslot->ctrl); + } + } + } + ++poll_count; + poll_state = POLL_SLEEP; + break; + case POLL_SLOTS: + list_for_each (pslotlist, &ibmphp_slot_head) { + pslot = list_entry (pslotlist, struct slot, ibm_slot_list); + // make a copy of the old status + memcpy ((void *) &myslot, (void *) pslot, + sizeof (struct slot)); + rc = ibmphp_hpc_readslot (pslot, READ_ALLSTAT, NULL); + if ((myslot.status != pslot->status) + || (myslot.ext_status != pslot->ext_status)) + process_changeinstatus (pslot, &myslot); + } + ctrl_count = 0x00; + list_for_each (pslotlist, &ibmphp_slot_head) { + if (ctrl_count >= ibmphp_get_total_controllers()) + break; + pslot = list_entry (pslotlist, struct slot, ibm_slot_list); + if (pslot->ctrl->ctlr_relative_id == ctrl_count) { + ctrl_count++; + if (READ_SLOT_LATCH (pslot->ctrl)) + rc = ibmphp_hpc_readslot (pslot, + READ_SLOTLATCHLOWREG, + &curlatchlow); + } + } + ++poll_count; + poll_state = POLL_SLEEP; + break; + case POLL_SLEEP: + /* don't sleep with a lock on the hardware */ + up (&semOperations); + msleep(POLL_INTERVAL_SEC * 1000); + + if (kthread_should_stop()) + break; + + down (&semOperations); + + if (poll_count >= POLL_LATCH_CNT) { + poll_count = 0; + poll_state = POLL_SLOTS; + } else + poll_state = POLL_LATCH_REGISTER; + break; + } + /* give up the hardware semaphore */ + up (&semOperations); + /* sleep for a short time just for good measure */ + msleep(100); + } + up (&sem_exit); + debug ("%s - Exit\n", __func__); + return 0; +} + + +/*---------------------------------------------------------------------- +* Name: process_changeinstatus +* +* Action: compare old and new slot status, process the change in status +* +* Input: pointer to slot struct, old slot struct +* +* Return 0 or error codes +* Value: +* +* Side +* Effects: None. +* +* Notes: +*---------------------------------------------------------------------*/ +static int process_changeinstatus (struct slot *pslot, struct slot *poldslot) +{ + u8 status; + int rc = 0; + u8 disable = 0; + u8 update = 0; + + debug ("process_changeinstatus - Entry pslot[%p], poldslot[%p]\n", pslot, poldslot); + + // bit 0 - HPC_SLOT_POWER + if ((pslot->status & 0x01) != (poldslot->status & 0x01)) + update = 1; + + // bit 1 - HPC_SLOT_CONNECT + // ignore + + // bit 2 - HPC_SLOT_ATTN + if ((pslot->status & 0x04) != (poldslot->status & 0x04)) + update = 1; + + // bit 3 - HPC_SLOT_PRSNT2 + // bit 4 - HPC_SLOT_PRSNT1 + if (((pslot->status & 0x08) != (poldslot->status & 0x08)) + || ((pslot->status & 0x10) != (poldslot->status & 0x10))) + update = 1; + + // bit 5 - HPC_SLOT_PWRGD + if ((pslot->status & 0x20) != (poldslot->status & 0x20)) + // OFF -> ON: ignore, ON -> OFF: disable slot + if ((poldslot->status & 0x20) && (SLOT_CONNECT (poldslot->status) == HPC_SLOT_CONNECTED) && (SLOT_PRESENT (poldslot->status))) + disable = 1; + + // bit 6 - HPC_SLOT_BUS_SPEED + // ignore + + // bit 7 - HPC_SLOT_LATCH + if ((pslot->status & 0x80) != (poldslot->status & 0x80)) { + update = 1; + // OPEN -> CLOSE + if (pslot->status & 0x80) { + if (SLOT_PWRGD (pslot->status)) { + // power goes on and off after closing latch + // check again to make sure power is still ON + msleep(1000); + rc = ibmphp_hpc_readslot (pslot, READ_SLOTSTATUS, &status); + if (SLOT_PWRGD (status)) + update = 1; + else // overwrite power in pslot to OFF + pslot->status &= ~HPC_SLOT_POWER; + } + } + // CLOSE -> OPEN + else if ((SLOT_PWRGD (poldslot->status) == HPC_SLOT_PWRGD_GOOD) + && (SLOT_CONNECT (poldslot->status) == HPC_SLOT_CONNECTED) && (SLOT_PRESENT (poldslot->status))) { + disable = 1; + } + // else - ignore + } + // bit 4 - HPC_SLOT_BLINK_ATTN + if ((pslot->ext_status & 0x08) != (poldslot->ext_status & 0x08)) + update = 1; + + if (disable) { + debug ("process_changeinstatus - disable slot\n"); + pslot->flag = 0; + rc = ibmphp_do_disable_slot (pslot); + } + + if (update || disable) { + ibmphp_update_slot_info (pslot); + } + + debug ("%s - Exit rc[%d] disable[%x] update[%x]\n", __func__, rc, disable, update); + + return rc; +} + +/*---------------------------------------------------------------------- +* Name: process_changeinlatch +* +* Action: compare old and new latch reg status, process the change +* +* Input: old and current latch register status +* +* Return 0 or error codes +* Value: +*---------------------------------------------------------------------*/ +static int process_changeinlatch (u8 old, u8 new, struct controller *ctrl) +{ + struct slot myslot, *pslot; + u8 i; + u8 mask; + int rc = 0; + + debug ("%s - Entry old[%x], new[%x]\n", __func__, old, new); + // bit 0 reserved, 0 is LSB, check bit 1-6 for 6 slots + + for (i = ctrl->starting_slot_num; i <= ctrl->ending_slot_num; i++) { + mask = 0x01 << i; + if ((mask & old) != (mask & new)) { + pslot = ibmphp_get_slot_from_physical_num (i); + if (pslot) { + memcpy ((void *) &myslot, (void *) pslot, sizeof (struct slot)); + rc = ibmphp_hpc_readslot (pslot, READ_ALLSTAT, NULL); + debug ("%s - call process_changeinstatus for slot[%d]\n", __func__, i); + process_changeinstatus (pslot, &myslot); + } else { + rc = -EINVAL; + err ("%s - Error bad pointer for slot[%d]\n", __func__, i); + } + } + } + debug ("%s - Exit rc[%d]\n", __func__, rc); + return rc; +} + +/*---------------------------------------------------------------------- +* Name: ibmphp_hpc_start_poll_thread +* +* Action: start polling thread +*---------------------------------------------------------------------*/ +int __init ibmphp_hpc_start_poll_thread (void) +{ + debug ("%s - Entry\n", __func__); + + ibmphp_poll_thread = kthread_run(poll_hpc, NULL, "hpc_poll"); + if (IS_ERR(ibmphp_poll_thread)) { + err ("%s - Error, thread not started\n", __func__); + return PTR_ERR(ibmphp_poll_thread); + } + return 0; +} + +/*---------------------------------------------------------------------- +* Name: ibmphp_hpc_stop_poll_thread +* +* Action: stop polling thread and cleanup +*---------------------------------------------------------------------*/ +void __exit ibmphp_hpc_stop_poll_thread (void) +{ + debug ("%s - Entry\n", __func__); + + kthread_stop(ibmphp_poll_thread); + debug ("before locking operations \n"); + ibmphp_lock_operations (); + debug ("after locking operations \n"); + + // wait for poll thread to exit + debug ("before sem_exit down \n"); + down (&sem_exit); + debug ("after sem_exit down \n"); + + // cleanup + debug ("before free_hpc_access \n"); + free_hpc_access (); + debug ("after free_hpc_access \n"); + ibmphp_unlock_operations (); + debug ("after unlock operations \n"); + up (&sem_exit); + debug ("after sem exit up\n"); + + debug ("%s - Exit\n", __func__); +} + +/*---------------------------------------------------------------------- +* Name: hpc_wait_ctlr_notworking +* +* Action: wait until the controller is in a not working state +* +* Return 0, HPC_ERROR +* Value: +*---------------------------------------------------------------------*/ +static int hpc_wait_ctlr_notworking (int timeout, struct controller *ctlr_ptr, void __iomem *wpg_bbar, + u8 * pstatus) +{ + int rc = 0; + u8 done = 0; + + debug_polling ("hpc_wait_ctlr_notworking - Entry timeout[%d]\n", timeout); + + while (!done) { + *pstatus = ctrl_read (ctlr_ptr, wpg_bbar, WPG_CTLR_INDEX); + if (*pstatus == HPC_ERROR) { + rc = HPC_ERROR; + done = 1; + } + if (CTLR_WORKING (*pstatus) == HPC_CTLR_WORKING_NO) + done = 1; + if (!done) { + msleep(1000); + if (timeout < 1) { + done = 1; + err ("HPCreadslot - Error ctlr timeout\n"); + rc = HPC_ERROR; + } else + timeout--; + } + } + debug_polling ("hpc_wait_ctlr_notworking - Exit rc[%x] status[%x]\n", rc, *pstatus); + return rc; +} diff --git a/drivers/pci/hotplug/ibmphp_pci.c b/drivers/pci/hotplug/ibmphp_pci.c new file mode 100644 index 0000000..7b09e16 --- /dev/null +++ b/drivers/pci/hotplug/ibmphp_pci.c @@ -0,0 +1,1729 @@ +/* + * IBM Hot Plug Controller Driver + * + * Written By: Irene Zubarev, IBM Corporation + * + * Copyright (C) 2001 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 <gregkh@us.ibm.com> + * + */ + +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/pci.h> +#include <linux/list.h> +#include "ibmphp.h" + + +static int configure_device(struct pci_func *); +static int configure_bridge(struct pci_func **, u8); +static struct res_needed *scan_behind_bridge(struct pci_func *, u8); +static int add_new_bus (struct bus_node *, struct resource_node *, struct resource_node *, struct resource_node *, u8); +static u8 find_sec_number (u8 primary_busno, u8 slotno); + +/* + * NOTE..... If BIOS doesn't provide default routing, we assign: + * 9 for SCSI, 10 for LAN adapters, and 11 for everything else. + * If adapter is bridged, then we assign 11 to it and devices behind it. + * We also assign the same irq numbers for multi function devices. + * These are PIC mode, so shouldn't matter n.e.ways (hopefully) + */ +static void assign_alt_irq (struct pci_func * cur_func, u8 class_code) +{ + int j; + for (j = 0; j < 4; j++) { + if (cur_func->irq[j] == 0xff) { + switch (class_code) { + case PCI_BASE_CLASS_STORAGE: + cur_func->irq[j] = SCSI_IRQ; + break; + case PCI_BASE_CLASS_NETWORK: + cur_func->irq[j] = LAN_IRQ; + break; + default: + cur_func->irq[j] = OTHER_IRQ; + break; + } + } + } +} + +/* + * Configures the device to be added (will allocate needed resources if it + * can), the device can be a bridge or a regular pci device, can also be + * multi-functional + * + * Input: function to be added + * + * TO DO: The error case with Multifunction device or multi function bridge, + * if there is an error, will need to go through all previous functions and + * unconfigure....or can add some code into unconfigure_card.... + */ +int ibmphp_configure_card (struct pci_func *func, u8 slotno) +{ + u16 vendor_id; + u32 class; + u8 class_code; + u8 hdr_type, device, sec_number; + u8 function; + struct pci_func *newfunc; /* for multi devices */ + struct pci_func *cur_func, *prev_func; + int rc, i, j; + int cleanup_count; + u8 flag; + u8 valid_device = 0x00; /* to see if we are able to read from card any device info at all */ + + debug ("inside configure_card, func->busno = %x\n", func->busno); + + device = func->device; + cur_func = func; + + /* We only get bus and device from IRQ routing table. So at this point, + * func->busno is correct, and func->device contains only device (at the 5 + * highest bits) + */ + + /* For every function on the card */ + for (function = 0x00; function < 0x08; function++) { + unsigned int devfn = PCI_DEVFN(device, function); + ibmphp_pci_bus->number = cur_func->busno; + + cur_func->function = function; + + debug ("inside the loop, cur_func->busno = %x, cur_func->device = %x, cur_func->funcion = %x\n", + cur_func->busno, cur_func->device, cur_func->function); + + pci_bus_read_config_word (ibmphp_pci_bus, devfn, PCI_VENDOR_ID, &vendor_id); + + debug ("vendor_id is %x\n", vendor_id); + if (vendor_id != PCI_VENDOR_ID_NOTVALID) { + /* found correct device!!! */ + debug ("found valid device, vendor_id = %x\n", vendor_id); + + ++valid_device; + + /* header: x x x x x x x x + * | |___________|=> 1=PPB bridge, 0=normal device, 2=CardBus Bridge + * |_=> 0 = single function device, 1 = multi-function device + */ + + pci_bus_read_config_byte (ibmphp_pci_bus, devfn, PCI_HEADER_TYPE, &hdr_type); + pci_bus_read_config_dword (ibmphp_pci_bus, devfn, PCI_CLASS_REVISION, &class); + + class_code = class >> 24; + debug ("hrd_type = %x, class = %x, class_code %x\n", hdr_type, class, class_code); + class >>= 8; /* to take revision out, class = class.subclass.prog i/f */ + if (class == PCI_CLASS_NOT_DEFINED_VGA) { + err ("The device %x is VGA compatible and as is not supported for hot plugging. " + "Please choose another device.\n", cur_func->device); + return -ENODEV; + } else if (class == PCI_CLASS_DISPLAY_VGA) { + err ("The device %x is not supported for hot plugging. " + "Please choose another device.\n", cur_func->device); + return -ENODEV; + } + switch (hdr_type) { + case PCI_HEADER_TYPE_NORMAL: + debug ("single device case.... vendor id = %x, hdr_type = %x, class = %x\n", vendor_id, hdr_type, class); + assign_alt_irq (cur_func, class_code); + if ((rc = configure_device (cur_func)) < 0) { + /* We need to do this in case some other BARs were properly inserted */ + err ("was not able to configure devfunc %x on bus %x.\n", + cur_func->device, cur_func->busno); + cleanup_count = 6; + goto error; + } + cur_func->next = NULL; + function = 0x8; + break; + case PCI_HEADER_TYPE_MULTIDEVICE: + assign_alt_irq (cur_func, class_code); + if ((rc = configure_device (cur_func)) < 0) { + /* We need to do this in case some other BARs were properly inserted */ + err ("was not able to configure devfunc %x on bus %x...bailing out\n", + cur_func->device, cur_func->busno); + cleanup_count = 6; + goto error; + } + newfunc = kzalloc(sizeof(*newfunc), GFP_KERNEL); + if (!newfunc) { + err ("out of system memory\n"); + return -ENOMEM; + } + newfunc->busno = cur_func->busno; + newfunc->device = device; + cur_func->next = newfunc; + cur_func = newfunc; + for (j = 0; j < 4; j++) + newfunc->irq[j] = cur_func->irq[j]; + break; + case PCI_HEADER_TYPE_MULTIBRIDGE: + class >>= 8; + if (class != PCI_CLASS_BRIDGE_PCI) { + err ("This %x is not PCI-to-PCI bridge, and as is not supported for hot-plugging. " + "Please insert another card.\n", cur_func->device); + return -ENODEV; + } + assign_alt_irq (cur_func, class_code); + rc = configure_bridge (&cur_func, slotno); + if (rc == -ENODEV) { + err ("You chose to insert Single Bridge, or nested bridges, this is not supported...\n"); + err ("Bus %x, devfunc %x\n", cur_func->busno, cur_func->device); + return rc; + } + if (rc) { + /* We need to do this in case some other BARs were properly inserted */ + err ("was not able to hot-add PPB properly.\n"); + func->bus = 1; /* To indicate to the unconfigure function that this is a PPB */ + cleanup_count = 2; + goto error; + } + + pci_bus_read_config_byte (ibmphp_pci_bus, devfn, PCI_SECONDARY_BUS, &sec_number); + flag = 0; + for (i = 0; i < 32; i++) { + if (func->devices[i]) { + newfunc = kzalloc(sizeof(*newfunc), GFP_KERNEL); + if (!newfunc) { + err ("out of system memory\n"); + return -ENOMEM; + } + newfunc->busno = sec_number; + newfunc->device = (u8) i; + for (j = 0; j < 4; j++) + newfunc->irq[j] = cur_func->irq[j]; + + if (flag) { + for (prev_func = cur_func; prev_func->next; prev_func = prev_func->next) ; + prev_func->next = newfunc; + } else + cur_func->next = newfunc; + + rc = ibmphp_configure_card (newfunc, slotno); + /* This could only happen if kmalloc failed */ + if (rc) { + /* We need to do this in case bridge itself got configured properly, but devices behind it failed */ + func->bus = 1; /* To indicate to the unconfigure function that this is a PPB */ + cleanup_count = 2; + goto error; + } + flag = 1; + } + } + + newfunc = kzalloc(sizeof(*newfunc), GFP_KERNEL); + if (!newfunc) { + err ("out of system memory\n"); + return -ENOMEM; + } + newfunc->busno = cur_func->busno; + newfunc->device = device; + for (j = 0; j < 4; j++) + newfunc->irq[j] = cur_func->irq[j]; + for (prev_func = cur_func; prev_func->next; prev_func = prev_func->next) ; + prev_func->next = newfunc; + cur_func = newfunc; + break; + case PCI_HEADER_TYPE_BRIDGE: + class >>= 8; + debug ("class now is %x\n", class); + if (class != PCI_CLASS_BRIDGE_PCI) { + err ("This %x is not PCI-to-PCI bridge, and as is not supported for hot-plugging. " + "Please insert another card.\n", cur_func->device); + return -ENODEV; + } + + assign_alt_irq (cur_func, class_code); + + debug ("cur_func->busno b4 configure_bridge is %x\n", cur_func->busno); + rc = configure_bridge (&cur_func, slotno); + if (rc == -ENODEV) { + err ("You chose to insert Single Bridge, or nested bridges, this is not supported...\n"); + err ("Bus %x, devfunc %x\n", cur_func->busno, cur_func->device); + return rc; + } + if (rc) { + /* We need to do this in case some other BARs were properly inserted */ + func->bus = 1; /* To indicate to the unconfigure function that this is a PPB */ + err ("was not able to hot-add PPB properly.\n"); + cleanup_count = 2; + goto error; + } + debug ("cur_func->busno = %x, device = %x, function = %x\n", + cur_func->busno, device, function); + pci_bus_read_config_byte (ibmphp_pci_bus, devfn, PCI_SECONDARY_BUS, &sec_number); + debug ("after configuring bridge..., sec_number = %x\n", sec_number); + flag = 0; + for (i = 0; i < 32; i++) { + if (func->devices[i]) { + debug ("inside for loop, device is %x\n", i); + newfunc = kzalloc(sizeof(*newfunc), GFP_KERNEL); + if (!newfunc) { + err (" out of system memory\n"); + return -ENOMEM; + } + newfunc->busno = sec_number; + newfunc->device = (u8) i; + for (j = 0; j < 4; j++) + newfunc->irq[j] = cur_func->irq[j]; + + if (flag) { + for (prev_func = cur_func; prev_func->next; prev_func = prev_func->next) ; + prev_func->next = newfunc; + } else + cur_func->next = newfunc; + + rc = ibmphp_configure_card (newfunc, slotno); + + /* Again, this case should not happen... For complete paranoia, will need to call remove_bus */ + if (rc) { + /* We need to do this in case some other BARs were properly inserted */ + func->bus = 1; /* To indicate to the unconfigure function that this is a PPB */ + cleanup_count = 2; + goto error; + } + flag = 1; + } + } + + function = 0x8; + break; + default: + err ("MAJOR PROBLEM!!!!, header type not supported? %x\n", hdr_type); + return -ENXIO; + break; + } /* end of switch */ + } /* end of valid device */ + } /* end of for */ + + if (!valid_device) { + err ("Cannot find any valid devices on the card. Or unable to read from card.\n"); + return -ENODEV; + } + + return 0; + +error: + for (i = 0; i < cleanup_count; i++) { + if (cur_func->io[i]) { + ibmphp_remove_resource (cur_func->io[i]); + cur_func->io[i] = NULL; + } else if (cur_func->pfmem[i]) { + ibmphp_remove_resource (cur_func->pfmem[i]); + cur_func->pfmem[i] = NULL; + } else if (cur_func->mem[i]) { + ibmphp_remove_resource (cur_func->mem[i]); + cur_func->mem[i] = NULL; + } + } + return rc; +} + +/* + * This function configures the pci BARs of a single device. + * Input: pointer to the pci_func + * Output: configured PCI, 0, or error + */ +static int configure_device (struct pci_func *func) +{ + u32 bar[6]; + u32 address[] = { + PCI_BASE_ADDRESS_0, + PCI_BASE_ADDRESS_1, + PCI_BASE_ADDRESS_2, + PCI_BASE_ADDRESS_3, + PCI_BASE_ADDRESS_4, + PCI_BASE_ADDRESS_5, + 0 + }; + u8 irq; + int count; + int len[6]; + struct resource_node *io[6]; + struct resource_node *mem[6]; + struct resource_node *mem_tmp; + struct resource_node *pfmem[6]; + unsigned int devfn; + + debug ("%s - inside\n", __func__); + + devfn = PCI_DEVFN(func->device, func->function); + ibmphp_pci_bus->number = func->busno; + + for (count = 0; address[count]; count++) { /* for 6 BARs */ + + /* not sure if i need this. per scott, said maybe need smth like this + if devices don't adhere 100% to the spec, so don't want to write + to the reserved bits + + pcibios_read_config_byte(cur_func->busno, cur_func->device, + PCI_BASE_ADDRESS_0 + 4 * count, &tmp); + if (tmp & 0x01) // IO + pcibios_write_config_dword(cur_func->busno, cur_func->device, + PCI_BASE_ADDRESS_0 + 4 * count, 0xFFFFFFFD); + else // Memory + pcibios_write_config_dword(cur_func->busno, cur_func->device, + PCI_BASE_ADDRESS_0 + 4 * count, 0xFFFFFFFF); + */ + pci_bus_write_config_dword (ibmphp_pci_bus, devfn, address[count], 0xFFFFFFFF); + pci_bus_read_config_dword (ibmphp_pci_bus, devfn, address[count], &bar[count]); + + if (!bar[count]) /* This BAR is not implemented */ + continue; + + debug ("Device %x BAR %d wants %x\n", func->device, count, bar[count]); + + if (bar[count] & PCI_BASE_ADDRESS_SPACE_IO) { + /* This is IO */ + debug ("inside IO SPACE\n"); + + len[count] = bar[count] & 0xFFFFFFFC; + len[count] = ~len[count] + 1; + + debug ("len[count] in IO %x, count %d\n", len[count], count); + + io[count] = kzalloc(sizeof(struct resource_node), GFP_KERNEL); + + if (!io[count]) { + err ("out of system memory\n"); + return -ENOMEM; + } + io[count]->type = IO; + io[count]->busno = func->busno; + io[count]->devfunc = PCI_DEVFN(func->device, func->function); + io[count]->len = len[count]; + if (ibmphp_check_resource(io[count], 0) == 0) { + ibmphp_add_resource (io[count]); + func->io[count] = io[count]; + } else { + err ("cannot allocate requested io for bus %x device %x function %x len %x\n", + func->busno, func->device, func->function, len[count]); + kfree (io[count]); + return -EIO; + } + pci_bus_write_config_dword (ibmphp_pci_bus, devfn, address[count], func->io[count]->start); + + /* _______________This is for debugging purposes only_____________________ */ + debug ("b4 writing, the IO address is %x\n", func->io[count]->start); + pci_bus_read_config_dword (ibmphp_pci_bus, devfn, address[count], &bar[count]); + debug ("after writing.... the start address is %x\n", bar[count]); + /* _________________________________________________________________________*/ + + } else { + /* This is Memory */ + if (bar[count] & PCI_BASE_ADDRESS_MEM_PREFETCH) { + /* pfmem */ + debug ("PFMEM SPACE\n"); + + len[count] = bar[count] & 0xFFFFFFF0; + len[count] = ~len[count] + 1; + + debug ("len[count] in PFMEM %x, count %d\n", len[count], count); + + pfmem[count] = kzalloc(sizeof(struct resource_node), GFP_KERNEL); + if (!pfmem[count]) { + err ("out of system memory\n"); + return -ENOMEM; + } + pfmem[count]->type = PFMEM; + pfmem[count]->busno = func->busno; + pfmem[count]->devfunc = PCI_DEVFN(func->device, + func->function); + pfmem[count]->len = len[count]; + pfmem[count]->fromMem = 0; + if (ibmphp_check_resource (pfmem[count], 0) == 0) { + ibmphp_add_resource (pfmem[count]); + func->pfmem[count] = pfmem[count]; + } else { + mem_tmp = kzalloc(sizeof(*mem_tmp), GFP_KERNEL); + if (!mem_tmp) { + err ("out of system memory\n"); + kfree (pfmem[count]); + return -ENOMEM; + } + mem_tmp->type = MEM; + mem_tmp->busno = pfmem[count]->busno; + mem_tmp->devfunc = pfmem[count]->devfunc; + mem_tmp->len = pfmem[count]->len; + debug ("there's no pfmem... going into mem.\n"); + if (ibmphp_check_resource (mem_tmp, 0) == 0) { + ibmphp_add_resource (mem_tmp); + pfmem[count]->fromMem = 1; + pfmem[count]->rangeno = mem_tmp->rangeno; + pfmem[count]->start = mem_tmp->start; + pfmem[count]->end = mem_tmp->end; + ibmphp_add_pfmem_from_mem (pfmem[count]); + func->pfmem[count] = pfmem[count]; + } else { + err ("cannot allocate requested pfmem for bus %x, device %x, len %x\n", + func->busno, func->device, len[count]); + kfree (mem_tmp); + kfree (pfmem[count]); + return -EIO; + } + } + + pci_bus_write_config_dword (ibmphp_pci_bus, devfn, address[count], func->pfmem[count]->start); + + /*_______________This is for debugging purposes only______________________________*/ + debug ("b4 writing, start address is %x\n", func->pfmem[count]->start); + pci_bus_read_config_dword (ibmphp_pci_bus, devfn, address[count], &bar[count]); + debug ("after writing, start address is %x\n", bar[count]); + /*_________________________________________________________________________________*/ + + if (bar[count] & PCI_BASE_ADDRESS_MEM_TYPE_64) { /* takes up another dword */ + debug ("inside the mem 64 case, count %d\n", count); + count += 1; + /* on the 2nd dword, write all 0s, since we can't handle them n.e.ways */ + pci_bus_write_config_dword (ibmphp_pci_bus, devfn, address[count], 0x00000000); + } + } else { + /* regular memory */ + debug ("REGULAR MEM SPACE\n"); + + len[count] = bar[count] & 0xFFFFFFF0; + len[count] = ~len[count] + 1; + + debug ("len[count] in Mem %x, count %d\n", len[count], count); + + mem[count] = kzalloc(sizeof(struct resource_node), GFP_KERNEL); + if (!mem[count]) { + err ("out of system memory\n"); + return -ENOMEM; + } + mem[count]->type = MEM; + mem[count]->busno = func->busno; + mem[count]->devfunc = PCI_DEVFN(func->device, + func->function); + mem[count]->len = len[count]; + if (ibmphp_check_resource (mem[count], 0) == 0) { + ibmphp_add_resource (mem[count]); + func->mem[count] = mem[count]; + } else { + err ("cannot allocate requested mem for bus %x, device %x, len %x\n", + func->busno, func->device, len[count]); + kfree (mem[count]); + return -EIO; + } + pci_bus_write_config_dword (ibmphp_pci_bus, devfn, address[count], func->mem[count]->start); + /* _______________________This is for debugging purposes only _______________________*/ + debug ("b4 writing, start address is %x\n", func->mem[count]->start); + pci_bus_read_config_dword (ibmphp_pci_bus, devfn, address[count], &bar[count]); + debug ("after writing, the address is %x\n", bar[count]); + /* __________________________________________________________________________________*/ + + if (bar[count] & PCI_BASE_ADDRESS_MEM_TYPE_64) { + /* takes up another dword */ + debug ("inside mem 64 case, reg. mem, count %d\n", count); + count += 1; + /* on the 2nd dword, write all 0s, since we can't handle them n.e.ways */ + pci_bus_write_config_dword (ibmphp_pci_bus, devfn, address[count], 0x00000000); + } + } + } /* end of mem */ + } /* end of for */ + + func->bus = 0; /* To indicate that this is not a PPB */ + pci_bus_read_config_byte (ibmphp_pci_bus, devfn, PCI_INTERRUPT_PIN, &irq); + if ((irq > 0x00) && (irq < 0x05)) + pci_bus_write_config_byte (ibmphp_pci_bus, devfn, PCI_INTERRUPT_LINE, func->irq[irq - 1]); + + pci_bus_write_config_byte (ibmphp_pci_bus, devfn, PCI_CACHE_LINE_SIZE, CACHE); + pci_bus_write_config_byte (ibmphp_pci_bus, devfn, PCI_LATENCY_TIMER, LATENCY); + + pci_bus_write_config_dword (ibmphp_pci_bus, devfn, PCI_ROM_ADDRESS, 0x00L); + pci_bus_write_config_word (ibmphp_pci_bus, devfn, PCI_COMMAND, DEVICEENABLE); + + return 0; +} + +/****************************************************************************** + * This routine configures a PCI-2-PCI bridge and the functions behind it + * Parameters: pci_func + * Returns: + ******************************************************************************/ +static int configure_bridge (struct pci_func **func_passed, u8 slotno) +{ + int count; + int i; + int rc; + u8 sec_number; + u8 io_base; + u16 pfmem_base; + u32 bar[2]; + u32 len[2]; + u8 flag_io = 0; + u8 flag_mem = 0; + u8 flag_pfmem = 0; + u8 need_io_upper = 0; + u8 need_pfmem_upper = 0; + struct res_needed *amount_needed = NULL; + struct resource_node *io = NULL; + struct resource_node *bus_io[2] = {NULL, NULL}; + struct resource_node *mem = NULL; + struct resource_node *bus_mem[2] = {NULL, NULL}; + struct resource_node *mem_tmp = NULL; + struct resource_node *pfmem = NULL; + struct resource_node *bus_pfmem[2] = {NULL, NULL}; + struct bus_node *bus; + u32 address[] = { + PCI_BASE_ADDRESS_0, + PCI_BASE_ADDRESS_1, + 0 + }; + struct pci_func *func = *func_passed; + unsigned int devfn; + u8 irq; + int retval; + + debug ("%s - enter\n", __func__); + + devfn = PCI_DEVFN(func->function, func->device); + ibmphp_pci_bus->number = func->busno; + + /* Configuring necessary info for the bridge so that we could see the devices + * behind it + */ + + pci_bus_write_config_byte (ibmphp_pci_bus, devfn, PCI_PRIMARY_BUS, func->busno); + + /* _____________________For debugging purposes only __________________________ + pci_bus_config_byte (ibmphp_pci_bus, devfn, PCI_PRIMARY_BUS, &pri_number); + debug ("primary # written into the bridge is %x\n", pri_number); + ___________________________________________________________________________*/ + + /* in EBDA, only get allocated 1 additional bus # per slot */ + sec_number = find_sec_number (func->busno, slotno); + if (sec_number == 0xff) { + err ("cannot allocate secondary bus number for the bridged device\n"); + return -EINVAL; + } + + debug ("after find_sec_number, the number we got is %x\n", sec_number); + debug ("AFTER FIND_SEC_NUMBER, func->busno IS %x\n", func->busno); + + pci_bus_write_config_byte (ibmphp_pci_bus, devfn, PCI_SECONDARY_BUS, sec_number); + + /* __________________For debugging purposes only __________________________________ + pci_bus_read_config_byte (ibmphp_pci_bus, devfn, PCI_SECONDARY_BUS, &sec_number); + debug ("sec_number after write/read is %x\n", sec_number); + ________________________________________________________________________________*/ + + pci_bus_write_config_byte (ibmphp_pci_bus, devfn, PCI_SUBORDINATE_BUS, sec_number); + + /* __________________For debugging purposes only ____________________________________ + pci_bus_read_config_byte (ibmphp_pci_bus, devfn, PCI_SUBORDINATE_BUS, &sec_number); + debug ("subordinate number after write/read is %x\n", sec_number); + __________________________________________________________________________________*/ + + pci_bus_write_config_byte (ibmphp_pci_bus, devfn, PCI_CACHE_LINE_SIZE, CACHE); + pci_bus_write_config_byte (ibmphp_pci_bus, devfn, PCI_LATENCY_TIMER, LATENCY); + pci_bus_write_config_byte (ibmphp_pci_bus, devfn, PCI_SEC_LATENCY_TIMER, LATENCY); + + debug ("func->busno is %x\n", func->busno); + debug ("sec_number after writing is %x\n", sec_number); + + + /* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !!!!!!!!!!!!!!!NEED TO ADD!!! FAST BACK-TO-BACK ENABLE!!!!!!!!!!!!!!!!!!!! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!*/ + + + /* First we need to allocate mem/io for the bridge itself in case it needs it */ + for (count = 0; address[count]; count++) { /* for 2 BARs */ + pci_bus_write_config_dword (ibmphp_pci_bus, devfn, address[count], 0xFFFFFFFF); + pci_bus_read_config_dword (ibmphp_pci_bus, devfn, address[count], &bar[count]); + + if (!bar[count]) { + /* This BAR is not implemented */ + debug ("so we come here then, eh?, count = %d\n", count); + continue; + } + // tmp_bar = bar[count]; + + debug ("Bar %d wants %x\n", count, bar[count]); + + if (bar[count] & PCI_BASE_ADDRESS_SPACE_IO) { + /* This is IO */ + len[count] = bar[count] & 0xFFFFFFFC; + len[count] = ~len[count] + 1; + + debug ("len[count] in IO = %x\n", len[count]); + + bus_io[count] = kzalloc(sizeof(struct resource_node), GFP_KERNEL); + + if (!bus_io[count]) { + err ("out of system memory\n"); + retval = -ENOMEM; + goto error; + } + bus_io[count]->type = IO; + bus_io[count]->busno = func->busno; + bus_io[count]->devfunc = PCI_DEVFN(func->device, + func->function); + bus_io[count]->len = len[count]; + if (ibmphp_check_resource (bus_io[count], 0) == 0) { + ibmphp_add_resource (bus_io[count]); + func->io[count] = bus_io[count]; + } else { + err ("cannot allocate requested io for bus %x, device %x, len %x\n", + func->busno, func->device, len[count]); + kfree (bus_io[count]); + return -EIO; + } + + pci_bus_write_config_dword (ibmphp_pci_bus, devfn, address[count], func->io[count]->start); + + } else { + /* This is Memory */ + if (bar[count] & PCI_BASE_ADDRESS_MEM_PREFETCH) { + /* pfmem */ + len[count] = bar[count] & 0xFFFFFFF0; + len[count] = ~len[count] + 1; + + debug ("len[count] in PFMEM = %x\n", len[count]); + + bus_pfmem[count] = kzalloc(sizeof(struct resource_node), GFP_KERNEL); + if (!bus_pfmem[count]) { + err ("out of system memory\n"); + retval = -ENOMEM; + goto error; + } + bus_pfmem[count]->type = PFMEM; + bus_pfmem[count]->busno = func->busno; + bus_pfmem[count]->devfunc = PCI_DEVFN(func->device, + func->function); + bus_pfmem[count]->len = len[count]; + bus_pfmem[count]->fromMem = 0; + if (ibmphp_check_resource (bus_pfmem[count], 0) == 0) { + ibmphp_add_resource (bus_pfmem[count]); + func->pfmem[count] = bus_pfmem[count]; + } else { + mem_tmp = kzalloc(sizeof(*mem_tmp), GFP_KERNEL); + if (!mem_tmp) { + err ("out of system memory\n"); + retval = -ENOMEM; + goto error; + } + mem_tmp->type = MEM; + mem_tmp->busno = bus_pfmem[count]->busno; + mem_tmp->devfunc = bus_pfmem[count]->devfunc; + mem_tmp->len = bus_pfmem[count]->len; + if (ibmphp_check_resource (mem_tmp, 0) == 0) { + ibmphp_add_resource (mem_tmp); + bus_pfmem[count]->fromMem = 1; + bus_pfmem[count]->rangeno = mem_tmp->rangeno; + ibmphp_add_pfmem_from_mem (bus_pfmem[count]); + func->pfmem[count] = bus_pfmem[count]; + } else { + err ("cannot allocate requested pfmem for bus %x, device %x, len %x\n", + func->busno, func->device, len[count]); + kfree (mem_tmp); + kfree (bus_pfmem[count]); + return -EIO; + } + } + + pci_bus_write_config_dword (ibmphp_pci_bus, devfn, address[count], func->pfmem[count]->start); + + if (bar[count] & PCI_BASE_ADDRESS_MEM_TYPE_64) { + /* takes up another dword */ + count += 1; + /* on the 2nd dword, write all 0s, since we can't handle them n.e.ways */ + pci_bus_write_config_dword (ibmphp_pci_bus, devfn, address[count], 0x00000000); + + } + } else { + /* regular memory */ + len[count] = bar[count] & 0xFFFFFFF0; + len[count] = ~len[count] + 1; + + debug ("len[count] in Memory is %x\n", len[count]); + + bus_mem[count] = kzalloc(sizeof(struct resource_node), GFP_KERNEL); + if (!bus_mem[count]) { + err ("out of system memory\n"); + retval = -ENOMEM; + goto error; + } + bus_mem[count]->type = MEM; + bus_mem[count]->busno = func->busno; + bus_mem[count]->devfunc = PCI_DEVFN(func->device, + func->function); + bus_mem[count]->len = len[count]; + if (ibmphp_check_resource (bus_mem[count], 0) == 0) { + ibmphp_add_resource (bus_mem[count]); + func->mem[count] = bus_mem[count]; + } else { + err ("cannot allocate requested mem for bus %x, device %x, len %x\n", + func->busno, func->device, len[count]); + kfree (bus_mem[count]); + return -EIO; + } + + pci_bus_write_config_dword (ibmphp_pci_bus, devfn, address[count], func->mem[count]->start); + + if (bar[count] & PCI_BASE_ADDRESS_MEM_TYPE_64) { + /* takes up another dword */ + count += 1; + /* on the 2nd dword, write all 0s, since we can't handle them n.e.ways */ + pci_bus_write_config_dword (ibmphp_pci_bus, devfn, address[count], 0x00000000); + + } + } + } /* end of mem */ + } /* end of for */ + + /* Now need to see how much space the devices behind the bridge needed */ + amount_needed = scan_behind_bridge (func, sec_number); + if (amount_needed == NULL) + return -ENOMEM; + + ibmphp_pci_bus->number = func->busno; + debug ("after coming back from scan_behind_bridge\n"); + debug ("amount_needed->not_correct = %x\n", amount_needed->not_correct); + debug ("amount_needed->io = %x\n", amount_needed->io); + debug ("amount_needed->mem = %x\n", amount_needed->mem); + debug ("amount_needed->pfmem = %x\n", amount_needed->pfmem); + + if (amount_needed->not_correct) { + debug ("amount_needed is not correct\n"); + for (count = 0; address[count]; count++) { + /* for 2 BARs */ + if (bus_io[count]) { + ibmphp_remove_resource (bus_io[count]); + func->io[count] = NULL; + } else if (bus_pfmem[count]) { + ibmphp_remove_resource (bus_pfmem[count]); + func->pfmem[count] = NULL; + } else if (bus_mem[count]) { + ibmphp_remove_resource (bus_mem[count]); + func->mem[count] = NULL; + } + } + kfree (amount_needed); + return -ENODEV; + } + + if (!amount_needed->io) { + debug ("it doesn't want IO?\n"); + flag_io = 1; + } else { + debug ("it wants %x IO behind the bridge\n", amount_needed->io); + io = kzalloc(sizeof(*io), GFP_KERNEL); + + if (!io) { + err ("out of system memory\n"); + retval = -ENOMEM; + goto error; + } + io->type = IO; + io->busno = func->busno; + io->devfunc = PCI_DEVFN(func->device, func->function); + io->len = amount_needed->io; + if (ibmphp_check_resource (io, 1) == 0) { + debug ("were we able to add io\n"); + ibmphp_add_resource (io); + flag_io = 1; + } + } + + if (!amount_needed->mem) { + debug ("it doesn't want n.e.memory?\n"); + flag_mem = 1; + } else { + debug ("it wants %x memory behind the bridge\n", amount_needed->mem); + mem = kzalloc(sizeof(*mem), GFP_KERNEL); + if (!mem) { + err ("out of system memory\n"); + retval = -ENOMEM; + goto error; + } + mem->type = MEM; + mem->busno = func->busno; + mem->devfunc = PCI_DEVFN(func->device, func->function); + mem->len = amount_needed->mem; + if (ibmphp_check_resource (mem, 1) == 0) { + ibmphp_add_resource (mem); + flag_mem = 1; + debug ("were we able to add mem\n"); + } + } + + if (!amount_needed->pfmem) { + debug ("it doesn't want n.e.pfmem mem?\n"); + flag_pfmem = 1; + } else { + debug ("it wants %x pfmemory behind the bridge\n", amount_needed->pfmem); + pfmem = kzalloc(sizeof(*pfmem), GFP_KERNEL); + if (!pfmem) { + err ("out of system memory\n"); + retval = -ENOMEM; + goto error; + } + pfmem->type = PFMEM; + pfmem->busno = func->busno; + pfmem->devfunc = PCI_DEVFN(func->device, func->function); + pfmem->len = amount_needed->pfmem; + pfmem->fromMem = 0; + if (ibmphp_check_resource (pfmem, 1) == 0) { + ibmphp_add_resource (pfmem); + flag_pfmem = 1; + } else { + mem_tmp = kzalloc(sizeof(*mem_tmp), GFP_KERNEL); + if (!mem_tmp) { + err ("out of system memory\n"); + retval = -ENOMEM; + goto error; + } + mem_tmp->type = MEM; + mem_tmp->busno = pfmem->busno; + mem_tmp->devfunc = pfmem->devfunc; + mem_tmp->len = pfmem->len; + if (ibmphp_check_resource (mem_tmp, 1) == 0) { + ibmphp_add_resource (mem_tmp); + pfmem->fromMem = 1; + pfmem->rangeno = mem_tmp->rangeno; + ibmphp_add_pfmem_from_mem (pfmem); + flag_pfmem = 1; + } + } + } + + debug ("b4 if (flag_io && flag_mem && flag_pfmem)\n"); + debug ("flag_io = %x, flag_mem = %x, flag_pfmem = %x\n", flag_io, flag_mem, flag_pfmem); + + if (flag_io && flag_mem && flag_pfmem) { + /* If on bootup, there was a bridged card in this slot, + * then card was removed and ibmphp got unloaded and loaded + * back again, there's no way for us to remove the bus + * struct, so no need to kmalloc, can use existing node + */ + bus = ibmphp_find_res_bus (sec_number); + if (!bus) { + bus = kzalloc(sizeof(*bus), GFP_KERNEL); + if (!bus) { + err ("out of system memory\n"); + retval = -ENOMEM; + goto error; + } + bus->busno = sec_number; + debug ("b4 adding new bus\n"); + rc = add_new_bus (bus, io, mem, pfmem, func->busno); + } else if (!(bus->rangeIO) && !(bus->rangeMem) && !(bus->rangePFMem)) + rc = add_new_bus (bus, io, mem, pfmem, 0xFF); + else { + err ("expected bus structure not empty?\n"); + retval = -EIO; + goto error; + } + if (rc) { + if (rc == -ENOMEM) { + ibmphp_remove_bus (bus, func->busno); + kfree (amount_needed); + return rc; + } + retval = rc; + goto error; + } + pci_bus_read_config_byte (ibmphp_pci_bus, devfn, PCI_IO_BASE, &io_base); + pci_bus_read_config_word (ibmphp_pci_bus, devfn, PCI_PREF_MEMORY_BASE, &pfmem_base); + + if ((io_base & PCI_IO_RANGE_TYPE_MASK) == PCI_IO_RANGE_TYPE_32) { + debug ("io 32\n"); + need_io_upper = 1; + } + if ((pfmem_base & PCI_PREF_RANGE_TYPE_MASK) == PCI_PREF_RANGE_TYPE_64) { + debug ("pfmem 64\n"); + need_pfmem_upper = 1; + } + + if (bus->noIORanges) { + pci_bus_write_config_byte (ibmphp_pci_bus, devfn, PCI_IO_BASE, 0x00 | bus->rangeIO->start >> 8); + pci_bus_write_config_byte (ibmphp_pci_bus, devfn, PCI_IO_LIMIT, 0x00 | bus->rangeIO->end >> 8); + + /* _______________This is for debugging purposes only ____________________ + pci_bus_read_config_byte (ibmphp_pci_bus, devfn, PCI_IO_BASE, &temp); + debug ("io_base = %x\n", (temp & PCI_IO_RANGE_TYPE_MASK) << 8); + pci_bus_read_config_byte (ibmphp_pci_bus, devfn, PCI_IO_LIMIT, &temp); + debug ("io_limit = %x\n", (temp & PCI_IO_RANGE_TYPE_MASK) << 8); + ________________________________________________________________________*/ + + if (need_io_upper) { /* since can't support n.e.ways */ + pci_bus_write_config_word (ibmphp_pci_bus, devfn, PCI_IO_BASE_UPPER16, 0x0000); + pci_bus_write_config_word (ibmphp_pci_bus, devfn, PCI_IO_LIMIT_UPPER16, 0x0000); + } + } else { + pci_bus_write_config_byte (ibmphp_pci_bus, devfn, PCI_IO_BASE, 0x00); + pci_bus_write_config_byte (ibmphp_pci_bus, devfn, PCI_IO_LIMIT, 0x00); + } + + if (bus->noMemRanges) { + pci_bus_write_config_word (ibmphp_pci_bus, devfn, PCI_MEMORY_BASE, 0x0000 | bus->rangeMem->start >> 16); + pci_bus_write_config_word (ibmphp_pci_bus, devfn, PCI_MEMORY_LIMIT, 0x0000 | bus->rangeMem->end >> 16); + + /* ____________________This is for debugging purposes only ________________________ + pci_bus_read_config_word (ibmphp_pci_bus, devfn, PCI_MEMORY_BASE, &temp); + debug ("mem_base = %x\n", (temp & PCI_MEMORY_RANGE_TYPE_MASK) << 16); + pci_bus_read_config_word (ibmphp_pci_bus, devfn, PCI_MEMORY_LIMIT, &temp); + debug ("mem_limit = %x\n", (temp & PCI_MEMORY_RANGE_TYPE_MASK) << 16); + __________________________________________________________________________________*/ + + } else { + pci_bus_write_config_word (ibmphp_pci_bus, devfn, PCI_MEMORY_BASE, 0xffff); + pci_bus_write_config_word (ibmphp_pci_bus, devfn, PCI_MEMORY_LIMIT, 0x0000); + } + if (bus->noPFMemRanges) { + pci_bus_write_config_word (ibmphp_pci_bus, devfn, PCI_PREF_MEMORY_BASE, 0x0000 | bus->rangePFMem->start >> 16); + pci_bus_write_config_word (ibmphp_pci_bus, devfn, PCI_PREF_MEMORY_LIMIT, 0x0000 | bus->rangePFMem->end >> 16); + + /* __________________________This is for debugging purposes only _______________________ + pci_bus_read_config_word (ibmphp_pci_bus, devfn, PCI_PREF_MEMORY_BASE, &temp); + debug ("pfmem_base = %x", (temp & PCI_MEMORY_RANGE_TYPE_MASK) << 16); + pci_bus_read_config_word (ibmphp_pci_bus, devfn, PCI_PREF_MEMORY_LIMIT, &temp); + debug ("pfmem_limit = %x\n", (temp & PCI_MEMORY_RANGE_TYPE_MASK) << 16); + ______________________________________________________________________________________*/ + + if (need_pfmem_upper) { /* since can't support n.e.ways */ + pci_bus_write_config_dword (ibmphp_pci_bus, devfn, PCI_PREF_BASE_UPPER32, 0x00000000); + pci_bus_write_config_dword (ibmphp_pci_bus, devfn, PCI_PREF_LIMIT_UPPER32, 0x00000000); + } + } else { + pci_bus_write_config_word (ibmphp_pci_bus, devfn, PCI_PREF_MEMORY_BASE, 0xffff); + pci_bus_write_config_word (ibmphp_pci_bus, devfn, PCI_PREF_MEMORY_LIMIT, 0x0000); + } + + debug ("b4 writing control information\n"); + + pci_bus_read_config_byte (ibmphp_pci_bus, devfn, PCI_INTERRUPT_PIN, &irq); + if ((irq > 0x00) && (irq < 0x05)) + pci_bus_write_config_byte (ibmphp_pci_bus, devfn, PCI_INTERRUPT_LINE, func->irq[irq - 1]); + /* + pci_bus_write_config_byte (ibmphp_pci_bus, devfn, PCI_BRIDGE_CONTROL, ctrl); + pci_bus_write_config_byte (ibmphp_pci_bus, devfn, PCI_BRIDGE_CONTROL, PCI_BRIDGE_CTL_PARITY); + pci_bus_write_config_byte (ibmphp_pci_bus, devfn, PCI_BRIDGE_CONTROL, PCI_BRIDGE_CTL_SERR); + */ + + pci_bus_write_config_word (ibmphp_pci_bus, devfn, PCI_COMMAND, DEVICEENABLE); + pci_bus_write_config_word (ibmphp_pci_bus, devfn, PCI_BRIDGE_CONTROL, 0x07); + for (i = 0; i < 32; i++) { + if (amount_needed->devices[i]) { + debug ("device where devices[i] is 1 = %x\n", i); + func->devices[i] = 1; + } + } + func->bus = 1; /* For unconfiguring, to indicate it's PPB */ + func_passed = &func; + debug ("func->busno b4 returning is %x\n", func->busno); + debug ("func->busno b4 returning in the other structure is %x\n", (*func_passed)->busno); + kfree (amount_needed); + return 0; + } else { + err ("Configuring bridge was unsuccessful...\n"); + mem_tmp = NULL; + retval = -EIO; + goto error; + } + +error: + kfree(amount_needed); + if (pfmem) + ibmphp_remove_resource (pfmem); + if (io) + ibmphp_remove_resource (io); + if (mem) + ibmphp_remove_resource (mem); + for (i = 0; i < 2; i++) { /* for 2 BARs */ + if (bus_io[i]) { + ibmphp_remove_resource (bus_io[i]); + func->io[i] = NULL; + } else if (bus_pfmem[i]) { + ibmphp_remove_resource (bus_pfmem[i]); + func->pfmem[i] = NULL; + } else if (bus_mem[i]) { + ibmphp_remove_resource (bus_mem[i]); + func->mem[i] = NULL; + } + } + return retval; +} + +/***************************************************************************** + * This function adds up the amount of resources needed behind the PPB bridge + * and passes it to the configure_bridge function + * Input: bridge function + * Ouput: amount of resources needed + *****************************************************************************/ +static struct res_needed *scan_behind_bridge (struct pci_func * func, u8 busno) +{ + int count, len[6]; + u16 vendor_id; + u8 hdr_type; + u8 device, function; + unsigned int devfn; + int howmany = 0; /*this is to see if there are any devices behind the bridge */ + + u32 bar[6], class; + u32 address[] = { + PCI_BASE_ADDRESS_0, + PCI_BASE_ADDRESS_1, + PCI_BASE_ADDRESS_2, + PCI_BASE_ADDRESS_3, + PCI_BASE_ADDRESS_4, + PCI_BASE_ADDRESS_5, + 0 + }; + struct res_needed *amount; + + amount = kzalloc(sizeof(*amount), GFP_KERNEL); + if (amount == NULL) + return NULL; + + ibmphp_pci_bus->number = busno; + + debug ("the bus_no behind the bridge is %x\n", busno); + debug ("scanning devices behind the bridge...\n"); + for (device = 0; device < 32; device++) { + amount->devices[device] = 0; + for (function = 0; function < 8; function++) { + devfn = PCI_DEVFN(device, function); + + pci_bus_read_config_word (ibmphp_pci_bus, devfn, PCI_VENDOR_ID, &vendor_id); + + if (vendor_id != PCI_VENDOR_ID_NOTVALID) { + /* found correct device!!! */ + howmany++; + + pci_bus_read_config_byte (ibmphp_pci_bus, devfn, PCI_HEADER_TYPE, &hdr_type); + pci_bus_read_config_dword (ibmphp_pci_bus, devfn, PCI_CLASS_REVISION, &class); + + debug ("hdr_type behind the bridge is %x\n", hdr_type); + if (hdr_type & PCI_HEADER_TYPE_BRIDGE) { + err ("embedded bridges not supported for hot-plugging.\n"); + amount->not_correct = 1; + return amount; + } + + class >>= 8; /* to take revision out, class = class.subclass.prog i/f */ + if (class == PCI_CLASS_NOT_DEFINED_VGA) { + err ("The device %x is VGA compatible and as is not supported for hot plugging. " + "Please choose another device.\n", device); + amount->not_correct = 1; + return amount; + } else if (class == PCI_CLASS_DISPLAY_VGA) { + err ("The device %x is not supported for hot plugging. " + "Please choose another device.\n", device); + amount->not_correct = 1; + return amount; + } + + amount->devices[device] = 1; + + for (count = 0; address[count]; count++) { + /* for 6 BARs */ + /* + pci_bus_read_config_byte (ibmphp_pci_bus, devfn, address[count], &tmp); + if (tmp & 0x01) // IO + pci_bus_write_config_dword (ibmphp_pci_bus, devfn, address[count], 0xFFFFFFFD); + else // MEMORY + pci_bus_write_config_dword (ibmphp_pci_bus, devfn, address[count], 0xFFFFFFFF); + */ + pci_bus_write_config_dword (ibmphp_pci_bus, devfn, address[count], 0xFFFFFFFF); + pci_bus_read_config_dword (ibmphp_pci_bus, devfn, address[count], &bar[count]); + + debug ("what is bar[count]? %x, count = %d\n", bar[count], count); + + if (!bar[count]) /* This BAR is not implemented */ + continue; + + //tmp_bar = bar[count]; + + debug ("count %d device %x function %x wants %x resources\n", count, device, function, bar[count]); + + if (bar[count] & PCI_BASE_ADDRESS_SPACE_IO) { + /* This is IO */ + len[count] = bar[count] & 0xFFFFFFFC; + len[count] = ~len[count] + 1; + amount->io += len[count]; + } else { + /* This is Memory */ + if (bar[count] & PCI_BASE_ADDRESS_MEM_PREFETCH) { + /* pfmem */ + len[count] = bar[count] & 0xFFFFFFF0; + len[count] = ~len[count] + 1; + amount->pfmem += len[count]; + if (bar[count] & PCI_BASE_ADDRESS_MEM_TYPE_64) + /* takes up another dword */ + count += 1; + + } else { + /* regular memory */ + len[count] = bar[count] & 0xFFFFFFF0; + len[count] = ~len[count] + 1; + amount->mem += len[count]; + if (bar[count] & PCI_BASE_ADDRESS_MEM_TYPE_64) { + /* takes up another dword */ + count += 1; + } + } + } + } /* end for */ + } /* end if (valid) */ + } /* end for */ + } /* end for */ + + if (!howmany) + amount->not_correct = 1; + else + amount->not_correct = 0; + if ((amount->io) && (amount->io < IOBRIDGE)) + amount->io = IOBRIDGE; + if ((amount->mem) && (amount->mem < MEMBRIDGE)) + amount->mem = MEMBRIDGE; + if ((amount->pfmem) && (amount->pfmem < MEMBRIDGE)) + amount->pfmem = MEMBRIDGE; + return amount; +} + +/* The following 3 unconfigure_boot_ routines deal with the case when we had the card + * upon bootup in the system, since we don't allocate func to such case, we need to read + * the start addresses from pci config space and then find the corresponding entries in + * our resource lists. The functions return either 0, -ENODEV, or -1 (general failure) + * Change: we also call these functions even if we configured the card ourselves (i.e., not + * the bootup case), since it should work same way + */ +static int unconfigure_boot_device (u8 busno, u8 device, u8 function) +{ + u32 start_address; + u32 address[] = { + PCI_BASE_ADDRESS_0, + PCI_BASE_ADDRESS_1, + PCI_BASE_ADDRESS_2, + PCI_BASE_ADDRESS_3, + PCI_BASE_ADDRESS_4, + PCI_BASE_ADDRESS_5, + 0 + }; + int count; + struct resource_node *io; + struct resource_node *mem; + struct resource_node *pfmem; + struct bus_node *bus; + u32 end_address; + u32 temp_end; + u32 size; + u32 tmp_address; + unsigned int devfn; + + debug ("%s - enter\n", __func__); + + bus = ibmphp_find_res_bus (busno); + if (!bus) { + debug ("cannot find corresponding bus.\n"); + return -EINVAL; + } + + devfn = PCI_DEVFN(device, function); + ibmphp_pci_bus->number = busno; + for (count = 0; address[count]; count++) { /* for 6 BARs */ + pci_bus_read_config_dword (ibmphp_pci_bus, devfn, address[count], &start_address); + + /* We can do this here, b/c by that time the device driver of the card has been stopped */ + + pci_bus_write_config_dword (ibmphp_pci_bus, devfn, address[count], 0xFFFFFFFF); + pci_bus_read_config_dword (ibmphp_pci_bus, devfn, address[count], &size); + pci_bus_write_config_dword (ibmphp_pci_bus, devfn, address[count], start_address); + + debug ("start_address is %x\n", start_address); + debug ("busno, device, function %x %x %x\n", busno, device, function); + if (!size) { + /* This BAR is not implemented */ + debug ("is this bar no implemented?, count = %d\n", count); + continue; + } + tmp_address = start_address; + if (start_address & PCI_BASE_ADDRESS_SPACE_IO) { + /* This is IO */ + start_address &= PCI_BASE_ADDRESS_IO_MASK; + size = size & 0xFFFFFFFC; + size = ~size + 1; + end_address = start_address + size - 1; + if (ibmphp_find_resource (bus, start_address, &io, IO) < 0) { + err ("cannot find corresponding IO resource to remove\n"); + return -EIO; + } + debug ("io->start = %x\n", io->start); + temp_end = io->end; + start_address = io->end + 1; + ibmphp_remove_resource (io); + /* This is needed b/c of the old I/O restrictions in the BIOS */ + while (temp_end < end_address) { + if (ibmphp_find_resource (bus, start_address, &io, IO) < 0) { + err ("cannot find corresponding IO resource to remove\n"); + return -EIO; + } + debug ("io->start = %x\n", io->start); + temp_end = io->end; + start_address = io->end + 1; + ibmphp_remove_resource (io); + } + + /* ????????? DO WE NEED TO WRITE ANYTHING INTO THE PCI CONFIG SPACE BACK ?????????? */ + } else { + /* This is Memory */ + if (start_address & PCI_BASE_ADDRESS_MEM_PREFETCH) { + /* pfmem */ + debug ("start address of pfmem is %x\n", start_address); + start_address &= PCI_BASE_ADDRESS_MEM_MASK; + + if (ibmphp_find_resource (bus, start_address, &pfmem, PFMEM) < 0) { + err ("cannot find corresponding PFMEM resource to remove\n"); + return -EIO; + } + if (pfmem) { + debug ("pfmem->start = %x\n", pfmem->start); + + ibmphp_remove_resource(pfmem); + } + } else { + /* regular memory */ + debug ("start address of mem is %x\n", start_address); + start_address &= PCI_BASE_ADDRESS_MEM_MASK; + + if (ibmphp_find_resource (bus, start_address, &mem, MEM) < 0) { + err ("cannot find corresponding MEM resource to remove\n"); + return -EIO; + } + if (mem) { + debug ("mem->start = %x\n", mem->start); + + ibmphp_remove_resource(mem); + } + } + if (tmp_address & PCI_BASE_ADDRESS_MEM_TYPE_64) { + /* takes up another dword */ + count += 1; + } + } /* end of mem */ + } /* end of for */ + + return 0; +} + +static int unconfigure_boot_bridge (u8 busno, u8 device, u8 function) +{ + int count; + int bus_no, pri_no, sub_no, sec_no = 0; + u32 start_address, tmp_address; + u8 sec_number, sub_number, pri_number; + struct resource_node *io = NULL; + struct resource_node *mem = NULL; + struct resource_node *pfmem = NULL; + struct bus_node *bus; + u32 address[] = { + PCI_BASE_ADDRESS_0, + PCI_BASE_ADDRESS_1, + 0 + }; + unsigned int devfn; + + devfn = PCI_DEVFN(device, function); + ibmphp_pci_bus->number = busno; + bus_no = (int) busno; + debug ("busno is %x\n", busno); + pci_bus_read_config_byte (ibmphp_pci_bus, devfn, PCI_PRIMARY_BUS, &pri_number); + debug ("%s - busno = %x, primary_number = %x\n", __func__, busno, pri_number); + + pci_bus_read_config_byte (ibmphp_pci_bus, devfn, PCI_SECONDARY_BUS, &sec_number); + debug ("sec_number is %x\n", sec_number); + sec_no = (int) sec_number; + pri_no = (int) pri_number; + if (pri_no != bus_no) { + err ("primary numbers in our structures and pci config space don't match.\n"); + return -EINVAL; + } + + pci_bus_read_config_byte (ibmphp_pci_bus, devfn, PCI_SUBORDINATE_BUS, &sub_number); + sub_no = (int) sub_number; + debug ("sub_no is %d, sec_no is %d\n", sub_no, sec_no); + if (sec_no != sub_number) { + err ("there're more buses behind this bridge. Hot removal is not supported. Please choose another card\n"); + return -ENODEV; + } + + bus = ibmphp_find_res_bus (sec_number); + if (!bus) { + err ("cannot find Bus structure for the bridged device\n"); + return -EINVAL; + } + debug("bus->busno is %x\n", bus->busno); + debug("sec_number is %x\n", sec_number); + + ibmphp_remove_bus (bus, busno); + + for (count = 0; address[count]; count++) { + /* for 2 BARs */ + pci_bus_read_config_dword (ibmphp_pci_bus, devfn, address[count], &start_address); + + if (!start_address) { + /* This BAR is not implemented */ + continue; + } + + tmp_address = start_address; + + if (start_address & PCI_BASE_ADDRESS_SPACE_IO) { + /* This is IO */ + start_address &= PCI_BASE_ADDRESS_IO_MASK; + if (ibmphp_find_resource (bus, start_address, &io, IO) < 0) { + err ("cannot find corresponding IO resource to remove\n"); + return -EIO; + } + if (io) + debug ("io->start = %x\n", io->start); + + ibmphp_remove_resource (io); + + /* ????????? DO WE NEED TO WRITE ANYTHING INTO THE PCI CONFIG SPACE BACK ?????????? */ + } else { + /* This is Memory */ + if (start_address & PCI_BASE_ADDRESS_MEM_PREFETCH) { + /* pfmem */ + start_address &= PCI_BASE_ADDRESS_MEM_MASK; + if (ibmphp_find_resource (bus, start_address, &pfmem, PFMEM) < 0) { + err ("cannot find corresponding PFMEM resource to remove\n"); + return -EINVAL; + } + if (pfmem) { + debug ("pfmem->start = %x\n", pfmem->start); + + ibmphp_remove_resource(pfmem); + } + } else { + /* regular memory */ + start_address &= PCI_BASE_ADDRESS_MEM_MASK; + if (ibmphp_find_resource (bus, start_address, &mem, MEM) < 0) { + err ("cannot find corresponding MEM resource to remove\n"); + return -EINVAL; + } + if (mem) { + debug ("mem->start = %x\n", mem->start); + + ibmphp_remove_resource(mem); + } + } + if (tmp_address & PCI_BASE_ADDRESS_MEM_TYPE_64) { + /* takes up another dword */ + count += 1; + } + } /* end of mem */ + } /* end of for */ + debug ("%s - exiting, returning success\n", __func__); + return 0; +} + +static int unconfigure_boot_card (struct slot *slot_cur) +{ + u16 vendor_id; + u32 class; + u8 hdr_type; + u8 device; + u8 busno; + u8 function; + int rc; + unsigned int devfn; + u8 valid_device = 0x00; /* To see if we are ever able to find valid device and read it */ + + debug ("%s - enter\n", __func__); + + device = slot_cur->device; + busno = slot_cur->bus; + + debug ("b4 for loop, device is %x\n", device); + /* For every function on the card */ + for (function = 0x0; function < 0x08; function++) { + devfn = PCI_DEVFN(device, function); + ibmphp_pci_bus->number = busno; + + pci_bus_read_config_word (ibmphp_pci_bus, devfn, PCI_VENDOR_ID, &vendor_id); + + if (vendor_id != PCI_VENDOR_ID_NOTVALID) { + /* found correct device!!! */ + ++valid_device; + + debug ("%s - found correct device\n", __func__); + + /* header: x x x x x x x x + * | |___________|=> 1=PPB bridge, 0=normal device, 2=CardBus Bridge + * |_=> 0 = single function device, 1 = multi-function device + */ + + pci_bus_read_config_byte (ibmphp_pci_bus, devfn, PCI_HEADER_TYPE, &hdr_type); + pci_bus_read_config_dword (ibmphp_pci_bus, devfn, PCI_CLASS_REVISION, &class); + + debug ("hdr_type %x, class %x\n", hdr_type, class); + class >>= 8; /* to take revision out, class = class.subclass.prog i/f */ + if (class == PCI_CLASS_NOT_DEFINED_VGA) { + err ("The device %x function %x is VGA compatible and is not supported for hot removing. " + "Please choose another device.\n", device, function); + return -ENODEV; + } else if (class == PCI_CLASS_DISPLAY_VGA) { + err ("The device %x function %x is not supported for hot removing. " + "Please choose another device.\n", device, function); + return -ENODEV; + } + + switch (hdr_type) { + case PCI_HEADER_TYPE_NORMAL: + rc = unconfigure_boot_device (busno, device, function); + if (rc) { + err ("was not able to unconfigure device %x func %x on bus %x. bailing out...\n", + device, function, busno); + return rc; + } + function = 0x8; + break; + case PCI_HEADER_TYPE_MULTIDEVICE: + rc = unconfigure_boot_device (busno, device, function); + if (rc) { + err ("was not able to unconfigure device %x func %x on bus %x. bailing out...\n", + device, function, busno); + return rc; + } + break; + case PCI_HEADER_TYPE_BRIDGE: + class >>= 8; + if (class != PCI_CLASS_BRIDGE_PCI) { + err ("This device %x function %x is not PCI-to-PCI bridge, " + "and is not supported for hot-removing. " + "Please try another card.\n", device, function); + return -ENODEV; + } + rc = unconfigure_boot_bridge (busno, device, function); + if (rc != 0) { + err ("was not able to hot-remove PPB properly.\n"); + return rc; + } + + function = 0x8; + break; + case PCI_HEADER_TYPE_MULTIBRIDGE: + class >>= 8; + if (class != PCI_CLASS_BRIDGE_PCI) { + err ("This device %x function %x is not PCI-to-PCI bridge, " + "and is not supported for hot-removing. " + "Please try another card.\n", device, function); + return -ENODEV; + } + rc = unconfigure_boot_bridge (busno, device, function); + if (rc != 0) { + err ("was not able to hot-remove PPB properly.\n"); + return rc; + } + break; + default: + err ("MAJOR PROBLEM!!!! Cannot read device's header\n"); + return -1; + break; + } /* end of switch */ + } /* end of valid device */ + } /* end of for */ + + if (!valid_device) { + err ("Could not find device to unconfigure. Or could not read the card.\n"); + return -1; + } + return 0; +} + +/* + * free the resources of the card (multi, single, or bridged) + * Parameters: slot, flag to say if this is for removing entire module or just + * unconfiguring the device + * TO DO: will probably need to add some code in case there was some resource, + * to remove it... this is from when we have errors in the configure_card... + * !!!!!!!!!!!!!!!!!!!!!!!!!FOR BUSES!!!!!!!!!!!! + * Returns: 0, -1, -ENODEV + */ +int ibmphp_unconfigure_card (struct slot **slot_cur, int the_end) +{ + int i; + int count; + int rc; + struct slot *sl = *slot_cur; + struct pci_func *cur_func = NULL; + struct pci_func *temp_func; + + debug ("%s - enter\n", __func__); + + if (!the_end) { + /* Need to unconfigure the card */ + rc = unconfigure_boot_card (sl); + if ((rc == -ENODEV) || (rc == -EIO) || (rc == -EINVAL)) { + /* In all other cases, will still need to get rid of func structure if it exists */ + return rc; + } + } + + if (sl->func) { + cur_func = sl->func; + while (cur_func) { + /* TO DO: WILL MOST LIKELY NEED TO GET RID OF THE BUS STRUCTURE FROM RESOURCES AS WELL */ + if (cur_func->bus) { + /* in other words, it's a PPB */ + count = 2; + } else { + count = 6; + } + + for (i = 0; i < count; i++) { + if (cur_func->io[i]) { + debug ("io[%d] exists\n", i); + if (the_end > 0) + ibmphp_remove_resource (cur_func->io[i]); + cur_func->io[i] = NULL; + } + if (cur_func->mem[i]) { + debug ("mem[%d] exists\n", i); + if (the_end > 0) + ibmphp_remove_resource (cur_func->mem[i]); + cur_func->mem[i] = NULL; + } + if (cur_func->pfmem[i]) { + debug ("pfmem[%d] exists\n", i); + if (the_end > 0) + ibmphp_remove_resource (cur_func->pfmem[i]); + cur_func->pfmem[i] = NULL; + } + } + + temp_func = cur_func->next; + kfree (cur_func); + cur_func = temp_func; + } + } + + sl->func = NULL; + *slot_cur = sl; + debug ("%s - exit\n", __func__); + return 0; +} + +/* + * add a new bus resulting from hot-plugging a PPB bridge with devices + * + * Input: bus and the amount of resources needed (we know we can assign those, + * since they've been checked already + * Output: bus added to the correct spot + * 0, -1, error + */ +static int add_new_bus (struct bus_node *bus, struct resource_node *io, struct resource_node *mem, struct resource_node *pfmem, u8 parent_busno) +{ + struct range_node *io_range = NULL; + struct range_node *mem_range = NULL; + struct range_node *pfmem_range = NULL; + struct bus_node *cur_bus = NULL; + + /* Trying to find the parent bus number */ + if (parent_busno != 0xFF) { + cur_bus = ibmphp_find_res_bus (parent_busno); + if (!cur_bus) { + err ("strange, cannot find bus which is supposed to be at the system... something is terribly wrong...\n"); + return -ENODEV; + } + + list_add (&bus->bus_list, &cur_bus->bus_list); + } + if (io) { + io_range = kzalloc(sizeof(*io_range), GFP_KERNEL); + if (!io_range) { + err ("out of system memory\n"); + return -ENOMEM; + } + io_range->start = io->start; + io_range->end = io->end; + io_range->rangeno = 1; + bus->noIORanges = 1; + bus->rangeIO = io_range; + } + if (mem) { + mem_range = kzalloc(sizeof(*mem_range), GFP_KERNEL); + if (!mem_range) { + err ("out of system memory\n"); + return -ENOMEM; + } + mem_range->start = mem->start; + mem_range->end = mem->end; + mem_range->rangeno = 1; + bus->noMemRanges = 1; + bus->rangeMem = mem_range; + } + if (pfmem) { + pfmem_range = kzalloc(sizeof(*pfmem_range), GFP_KERNEL); + if (!pfmem_range) { + err ("out of system memory\n"); + return -ENOMEM; + } + pfmem_range->start = pfmem->start; + pfmem_range->end = pfmem->end; + pfmem_range->rangeno = 1; + bus->noPFMemRanges = 1; + bus->rangePFMem = pfmem_range; + } + return 0; +} + +/* + * find the 1st available bus number for PPB to set as its secondary bus + * Parameters: bus_number of the primary bus + * Returns: bus_number of the secondary bus or 0xff in case of failure + */ +static u8 find_sec_number (u8 primary_busno, u8 slotno) +{ + int min, max; + u8 busno; + struct bus_info *bus; + struct bus_node *bus_cur; + + bus = ibmphp_find_same_bus_num (primary_busno); + if (!bus) { + err ("cannot get slot range of the bus from the BIOS\n"); + return 0xff; + } + max = bus->slot_max; + min = bus->slot_min; + if ((slotno > max) || (slotno < min)) { + err ("got the wrong range\n"); + return 0xff; + } + busno = (u8) (slotno - (u8) min); + busno += primary_busno + 0x01; + bus_cur = ibmphp_find_res_bus (busno); + /* either there is no such bus number, or there are no ranges, which + * can only happen if we removed the bridged device in previous load + * of the driver, and now only have the skeleton bus struct + */ + if ((!bus_cur) || (!(bus_cur->rangeIO) && !(bus_cur->rangeMem) && !(bus_cur->rangePFMem))) + return busno; + return 0xff; +} + diff --git a/drivers/pci/hotplug/ibmphp_res.c b/drivers/pci/hotplug/ibmphp_res.c new file mode 100644 index 0000000..ec73294 --- /dev/null +++ b/drivers/pci/hotplug/ibmphp_res.c @@ -0,0 +1,2145 @@ +/* + * IBM Hot Plug Controller Driver + * + * Written By: Irene Zubarev, IBM Corporation + * + * Copyright (C) 2001 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 <gregkh@us.ibm.com> + * + */ + +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/pci.h> +#include <linux/list.h> +#include <linux/init.h> +#include "ibmphp.h" + +static int flags = 0; /* for testing */ + +static void update_resources (struct bus_node *bus_cur, int type, int rangeno); +static int once_over (void); +static int remove_ranges (struct bus_node *, struct bus_node *); +static int update_bridge_ranges (struct bus_node **); +static int add_range (int type, struct range_node *, struct bus_node *); +static void fix_resources (struct bus_node *); +static struct bus_node *find_bus_wprev (u8, struct bus_node **, u8); + +static LIST_HEAD(gbuses); + +static struct bus_node * __init alloc_error_bus (struct ebda_pci_rsrc * curr, u8 busno, int flag) +{ + struct bus_node * newbus; + + if (!(curr) && !(flag)) { + err ("NULL pointer passed\n"); + return NULL; + } + + newbus = kzalloc(sizeof(struct bus_node), GFP_KERNEL); + if (!newbus) { + err ("out of system memory\n"); + return NULL; + } + + if (flag) + newbus->busno = busno; + else + newbus->busno = curr->bus_num; + list_add_tail (&newbus->bus_list, &gbuses); + return newbus; +} + +static struct resource_node * __init alloc_resources (struct ebda_pci_rsrc * curr) +{ + struct resource_node *rs; + + if (!curr) { + err ("NULL passed to allocate\n"); + return NULL; + } + + rs = kzalloc(sizeof(struct resource_node), GFP_KERNEL); + if (!rs) { + err ("out of system memory\n"); + return NULL; + } + rs->busno = curr->bus_num; + rs->devfunc = curr->dev_fun; + rs->start = curr->start_addr; + rs->end = curr->end_addr; + rs->len = curr->end_addr - curr->start_addr + 1; + return rs; +} + +static int __init alloc_bus_range (struct bus_node **new_bus, struct range_node **new_range, struct ebda_pci_rsrc *curr, int flag, u8 first_bus) +{ + struct bus_node * newbus; + struct range_node *newrange; + u8 num_ranges = 0; + + if (first_bus) { + newbus = kzalloc(sizeof(struct bus_node), GFP_KERNEL); + if (!newbus) { + err ("out of system memory.\n"); + return -ENOMEM; + } + newbus->busno = curr->bus_num; + } else { + newbus = *new_bus; + switch (flag) { + case MEM: + num_ranges = newbus->noMemRanges; + break; + case PFMEM: + num_ranges = newbus->noPFMemRanges; + break; + case IO: + num_ranges = newbus->noIORanges; + break; + } + } + + newrange = kzalloc(sizeof(struct range_node), GFP_KERNEL); + if (!newrange) { + if (first_bus) + kfree (newbus); + err ("out of system memory\n"); + return -ENOMEM; + } + newrange->start = curr->start_addr; + newrange->end = curr->end_addr; + + if (first_bus || (!num_ranges)) + newrange->rangeno = 1; + else { + /* need to insert our range */ + add_range (flag, newrange, newbus); + debug ("%d resource Primary Bus inserted on bus %x [%x - %x]\n", flag, newbus->busno, newrange->start, newrange->end); + } + + switch (flag) { + case MEM: + newbus->rangeMem = newrange; + if (first_bus) + newbus->noMemRanges = 1; + else { + debug ("First Memory Primary on bus %x, [%x - %x]\n", newbus->busno, newrange->start, newrange->end); + ++newbus->noMemRanges; + fix_resources (newbus); + } + break; + case IO: + newbus->rangeIO = newrange; + if (first_bus) + newbus->noIORanges = 1; + else { + debug ("First IO Primary on bus %x, [%x - %x]\n", newbus->busno, newrange->start, newrange->end); + ++newbus->noIORanges; + fix_resources (newbus); + } + break; + case PFMEM: + newbus->rangePFMem = newrange; + if (first_bus) + newbus->noPFMemRanges = 1; + else { + debug ("1st PFMemory Primary on Bus %x [%x - %x]\n", newbus->busno, newrange->start, newrange->end); + ++newbus->noPFMemRanges; + fix_resources (newbus); + } + + break; + } + + *new_bus = newbus; + *new_range = newrange; + return 0; +} + + +/* Notes: + * 1. The ranges are ordered. The buses are not ordered. (First come) + * + * 2. If cannot allocate out of PFMem range, allocate from Mem ranges. PFmemFromMem + * are not sorted. (no need since use mem node). To not change the entire code, we + * also add mem node whenever this case happens so as not to change + * ibmphp_check_mem_resource etc (and since it really is taking Mem resource) + */ + +/***************************************************************************** + * This is the Resource Management initialization function. It will go through + * the Resource list taken from EBDA and fill in this module's data structures + * + * THIS IS NOT TAKING INTO CONSIDERATION IO RESTRICTIONS OF PRIMARY BUSES, + * SINCE WE'RE GOING TO ASSUME FOR NOW WE DON'T HAVE THOSE ON OUR BUSES FOR NOW + * + * Input: ptr to the head of the resource list from EBDA + * Output: 0, -1 or error codes + ***************************************************************************/ +int __init ibmphp_rsrc_init (void) +{ + struct ebda_pci_rsrc *curr; + struct range_node *newrange = NULL; + struct bus_node *newbus = NULL; + struct bus_node *bus_cur; + struct bus_node *bus_prev; + struct list_head *tmp; + struct resource_node *new_io = NULL; + struct resource_node *new_mem = NULL; + struct resource_node *new_pfmem = NULL; + int rc; + struct list_head *tmp_ebda; + + list_for_each (tmp_ebda, &ibmphp_ebda_pci_rsrc_head) { + curr = list_entry (tmp_ebda, struct ebda_pci_rsrc, ebda_pci_rsrc_list); + if (!(curr->rsrc_type & PCIDEVMASK)) { + /* EBDA still lists non PCI devices, so ignore... */ + debug ("this is not a PCI DEVICE in rsrc_init, please take care\n"); + // continue; + } + + /* this is a primary bus resource */ + if (curr->rsrc_type & PRIMARYBUSMASK) { + /* memory */ + if ((curr->rsrc_type & RESTYPE) == MMASK) { + /* no bus structure exists in place yet */ + if (list_empty (&gbuses)) { + if ((rc = alloc_bus_range (&newbus, &newrange, curr, MEM, 1))) + return rc; + list_add_tail (&newbus->bus_list, &gbuses); + debug ("gbuses = NULL, Memory Primary Bus %x [%x - %x]\n", newbus->busno, newrange->start, newrange->end); + } else { + bus_cur = find_bus_wprev (curr->bus_num, &bus_prev, 1); + /* found our bus */ + if (bus_cur) { + rc = alloc_bus_range (&bus_cur, &newrange, curr, MEM, 0); + if (rc) + return rc; + } else { + /* went through all the buses and didn't find ours, need to create a new bus node */ + if ((rc = alloc_bus_range (&newbus, &newrange, curr, MEM, 1))) + return rc; + + list_add_tail (&newbus->bus_list, &gbuses); + debug ("New Bus, Memory Primary Bus %x [%x - %x]\n", newbus->busno, newrange->start, newrange->end); + } + } + } else if ((curr->rsrc_type & RESTYPE) == PFMASK) { + /* prefetchable memory */ + if (list_empty (&gbuses)) { + /* no bus structure exists in place yet */ + if ((rc = alloc_bus_range (&newbus, &newrange, curr, PFMEM, 1))) + return rc; + list_add_tail (&newbus->bus_list, &gbuses); + debug ("gbuses = NULL, PFMemory Primary Bus %x [%x - %x]\n", newbus->busno, newrange->start, newrange->end); + } else { + bus_cur = find_bus_wprev (curr->bus_num, &bus_prev, 1); + if (bus_cur) { + /* found our bus */ + rc = alloc_bus_range (&bus_cur, &newrange, curr, PFMEM, 0); + if (rc) + return rc; + } else { + /* went through all the buses and didn't find ours, need to create a new bus node */ + if ((rc = alloc_bus_range (&newbus, &newrange, curr, PFMEM, 1))) + return rc; + list_add_tail (&newbus->bus_list, &gbuses); + debug ("1st Bus, PFMemory Primary Bus %x [%x - %x]\n", newbus->busno, newrange->start, newrange->end); + } + } + } else if ((curr->rsrc_type & RESTYPE) == IOMASK) { + /* IO */ + if (list_empty (&gbuses)) { + /* no bus structure exists in place yet */ + if ((rc = alloc_bus_range (&newbus, &newrange, curr, IO, 1))) + return rc; + list_add_tail (&newbus->bus_list, &gbuses); + debug ("gbuses = NULL, IO Primary Bus %x [%x - %x]\n", newbus->busno, newrange->start, newrange->end); + } else { + bus_cur = find_bus_wprev (curr->bus_num, &bus_prev, 1); + if (bus_cur) { + rc = alloc_bus_range (&bus_cur, &newrange, curr, IO, 0); + if (rc) + return rc; + } else { + /* went through all the buses and didn't find ours, need to create a new bus node */ + if ((rc = alloc_bus_range (&newbus, &newrange, curr, IO, 1))) + return rc; + list_add_tail (&newbus->bus_list, &gbuses); + debug ("1st Bus, IO Primary Bus %x [%x - %x]\n", newbus->busno, newrange->start, newrange->end); + } + } + + } else { + ; /* type is reserved WHAT TO DO IN THIS CASE??? + NOTHING TO DO??? */ + } + } else { + /* regular pci device resource */ + if ((curr->rsrc_type & RESTYPE) == MMASK) { + /* Memory resource */ + new_mem = alloc_resources (curr); + if (!new_mem) + return -ENOMEM; + new_mem->type = MEM; + /* + * if it didn't find the bus, means PCI dev + * came b4 the Primary Bus info, so need to + * create a bus rangeno becomes a problem... + * assign a -1 and then update once the range + * actually appears... + */ + if (ibmphp_add_resource (new_mem) < 0) { + newbus = alloc_error_bus (curr, 0, 0); + if (!newbus) + return -ENOMEM; + newbus->firstMem = new_mem; + ++newbus->needMemUpdate; + new_mem->rangeno = -1; + } + debug ("Memory resource for device %x, bus %x, [%x - %x]\n", new_mem->devfunc, new_mem->busno, new_mem->start, new_mem->end); + + } else if ((curr->rsrc_type & RESTYPE) == PFMASK) { + /* PFMemory resource */ + new_pfmem = alloc_resources (curr); + if (!new_pfmem) + return -ENOMEM; + new_pfmem->type = PFMEM; + new_pfmem->fromMem = 0; + if (ibmphp_add_resource (new_pfmem) < 0) { + newbus = alloc_error_bus (curr, 0, 0); + if (!newbus) + return -ENOMEM; + newbus->firstPFMem = new_pfmem; + ++newbus->needPFMemUpdate; + new_pfmem->rangeno = -1; + } + + debug ("PFMemory resource for device %x, bus %x, [%x - %x]\n", new_pfmem->devfunc, new_pfmem->busno, new_pfmem->start, new_pfmem->end); + } else if ((curr->rsrc_type & RESTYPE) == IOMASK) { + /* IO resource */ + new_io = alloc_resources (curr); + if (!new_io) + return -ENOMEM; + new_io->type = IO; + + /* + * if it didn't find the bus, means PCI dev + * came b4 the Primary Bus info, so need to + * create a bus rangeno becomes a problem... + * Can assign a -1 and then update once the + * range actually appears... + */ + if (ibmphp_add_resource (new_io) < 0) { + newbus = alloc_error_bus (curr, 0, 0); + if (!newbus) + return -ENOMEM; + newbus->firstIO = new_io; + ++newbus->needIOUpdate; + new_io->rangeno = -1; + } + debug ("IO resource for device %x, bus %x, [%x - %x]\n", new_io->devfunc, new_io->busno, new_io->start, new_io->end); + } + } + } + + list_for_each (tmp, &gbuses) { + bus_cur = list_entry (tmp, struct bus_node, bus_list); + /* This is to get info about PPB resources, since EBDA doesn't put this info into the primary bus info */ + rc = update_bridge_ranges (&bus_cur); + if (rc) + return rc; + } + rc = once_over (); /* This is to align ranges (so no -1) */ + if (rc) + return rc; + return 0; +} + +/******************************************************************************** + * This function adds a range into a sorted list of ranges per bus for a particular + * range type, it then calls another routine to update the range numbers on the + * pci devices' resources for the appropriate resource + * + * Input: type of the resource, range to add, current bus + * Output: 0 or -1, bus and range ptrs + ********************************************************************************/ +static int add_range (int type, struct range_node *range, struct bus_node *bus_cur) +{ + struct range_node *range_cur = NULL; + struct range_node *range_prev; + int count = 0, i_init; + int noRanges = 0; + + switch (type) { + case MEM: + range_cur = bus_cur->rangeMem; + noRanges = bus_cur->noMemRanges; + break; + case PFMEM: + range_cur = bus_cur->rangePFMem; + noRanges = bus_cur->noPFMemRanges; + break; + case IO: + range_cur = bus_cur->rangeIO; + noRanges = bus_cur->noIORanges; + break; + } + + range_prev = NULL; + while (range_cur) { + if (range->start < range_cur->start) + break; + range_prev = range_cur; + range_cur = range_cur->next; + count = count + 1; + } + if (!count) { + /* our range will go at the beginning of the list */ + switch (type) { + case MEM: + bus_cur->rangeMem = range; + break; + case PFMEM: + bus_cur->rangePFMem = range; + break; + case IO: + bus_cur->rangeIO = range; + break; + } + range->next = range_cur; + range->rangeno = 1; + i_init = 0; + } else if (!range_cur) { + /* our range will go at the end of the list */ + range->next = NULL; + range_prev->next = range; + range->rangeno = range_prev->rangeno + 1; + return 0; + } else { + /* the range is in the middle */ + range_prev->next = range; + range->next = range_cur; + range->rangeno = range_cur->rangeno; + i_init = range_prev->rangeno; + } + + for (count = i_init; count < noRanges; ++count) { + ++range_cur->rangeno; + range_cur = range_cur->next; + } + + update_resources (bus_cur, type, i_init + 1); + return 0; +} + +/******************************************************************************* + * This routine goes through the list of resources of type 'type' and updates + * the range numbers that they correspond to. It was called from add_range fnc + * + * Input: bus, type of the resource, the rangeno starting from which to update + ******************************************************************************/ +static void update_resources (struct bus_node *bus_cur, int type, int rangeno) +{ + struct resource_node *res = NULL; + u8 eol = 0; /* end of list indicator */ + + switch (type) { + case MEM: + if (bus_cur->firstMem) + res = bus_cur->firstMem; + break; + case PFMEM: + if (bus_cur->firstPFMem) + res = bus_cur->firstPFMem; + break; + case IO: + if (bus_cur->firstIO) + res = bus_cur->firstIO; + break; + } + + if (res) { + while (res) { + if (res->rangeno == rangeno) + break; + if (res->next) + res = res->next; + else if (res->nextRange) + res = res->nextRange; + else { + eol = 1; + break; + } + } + + if (!eol) { + /* found the range */ + while (res) { + ++res->rangeno; + res = res->next; + } + } + } +} + +static void fix_me (struct resource_node *res, struct bus_node *bus_cur, struct range_node *range) +{ + char * str = ""; + switch (res->type) { + case IO: + str = "io"; + break; + case MEM: + str = "mem"; + break; + case PFMEM: + str = "pfmem"; + break; + } + + while (res) { + if (res->rangeno == -1) { + while (range) { + if ((res->start >= range->start) && (res->end <= range->end)) { + res->rangeno = range->rangeno; + debug ("%s->rangeno in fix_resources is %d\n", str, res->rangeno); + switch (res->type) { + case IO: + --bus_cur->needIOUpdate; + break; + case MEM: + --bus_cur->needMemUpdate; + break; + case PFMEM: + --bus_cur->needPFMemUpdate; + break; + } + break; + } + range = range->next; + } + } + if (res->next) + res = res->next; + else + res = res->nextRange; + } + +} + +/***************************************************************************** + * This routine reassigns the range numbers to the resources that had a -1 + * This case can happen only if upon initialization, resources taken by pci dev + * appear in EBDA before the resources allocated for that bus, since we don't + * know the range, we assign -1, and this routine is called after a new range + * is assigned to see the resources with unknown range belong to the added range + * + * Input: current bus + * Output: none, list of resources for that bus are fixed if can be + *******************************************************************************/ +static void fix_resources (struct bus_node *bus_cur) +{ + struct range_node *range; + struct resource_node *res; + + debug ("%s - bus_cur->busno = %d\n", __func__, bus_cur->busno); + + if (bus_cur->needIOUpdate) { + res = bus_cur->firstIO; + range = bus_cur->rangeIO; + fix_me (res, bus_cur, range); + } + if (bus_cur->needMemUpdate) { + res = bus_cur->firstMem; + range = bus_cur->rangeMem; + fix_me (res, bus_cur, range); + } + if (bus_cur->needPFMemUpdate) { + res = bus_cur->firstPFMem; + range = bus_cur->rangePFMem; + fix_me (res, bus_cur, range); + } +} + +/******************************************************************************* + * This routine adds a resource to the list of resources to the appropriate bus + * based on their resource type and sorted by their starting addresses. It assigns + * the ptrs to next and nextRange if needed. + * + * Input: resource ptr + * Output: ptrs assigned (to the node) + * 0 or -1 + *******************************************************************************/ +int ibmphp_add_resource (struct resource_node *res) +{ + struct resource_node *res_cur; + struct resource_node *res_prev; + struct bus_node *bus_cur; + struct range_node *range_cur = NULL; + struct resource_node *res_start = NULL; + + debug ("%s - enter\n", __func__); + + if (!res) { + err ("NULL passed to add\n"); + return -ENODEV; + } + + bus_cur = find_bus_wprev (res->busno, NULL, 0); + + if (!bus_cur) { + /* didn't find a bus, smth's wrong!!! */ + debug ("no bus in the system, either pci_dev's wrong or allocation failed\n"); + return -ENODEV; + } + + /* Normal case */ + switch (res->type) { + case IO: + range_cur = bus_cur->rangeIO; + res_start = bus_cur->firstIO; + break; + case MEM: + range_cur = bus_cur->rangeMem; + res_start = bus_cur->firstMem; + break; + case PFMEM: + range_cur = bus_cur->rangePFMem; + res_start = bus_cur->firstPFMem; + break; + default: + err ("cannot read the type of the resource to add... problem\n"); + return -EINVAL; + } + while (range_cur) { + if ((res->start >= range_cur->start) && (res->end <= range_cur->end)) { + res->rangeno = range_cur->rangeno; + break; + } + range_cur = range_cur->next; + } + + /* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + * this is again the case of rangeno = -1 + * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + */ + + if (!range_cur) { + switch (res->type) { + case IO: + ++bus_cur->needIOUpdate; + break; + case MEM: + ++bus_cur->needMemUpdate; + break; + case PFMEM: + ++bus_cur->needPFMemUpdate; + break; + } + res->rangeno = -1; + } + + debug ("The range is %d\n", res->rangeno); + if (!res_start) { + /* no first{IO,Mem,Pfmem} on the bus, 1st IO/Mem/Pfmem resource ever */ + switch (res->type) { + case IO: + bus_cur->firstIO = res; + break; + case MEM: + bus_cur->firstMem = res; + break; + case PFMEM: + bus_cur->firstPFMem = res; + break; + } + res->next = NULL; + res->nextRange = NULL; + } else { + res_cur = res_start; + res_prev = NULL; + + debug ("res_cur->rangeno is %d\n", res_cur->rangeno); + + while (res_cur) { + if (res_cur->rangeno >= res->rangeno) + break; + res_prev = res_cur; + if (res_cur->next) + res_cur = res_cur->next; + else + res_cur = res_cur->nextRange; + } + + if (!res_cur) { + /* at the end of the resource list */ + debug ("i should be here, [%x - %x]\n", res->start, res->end); + res_prev->nextRange = res; + res->next = NULL; + res->nextRange = NULL; + } else if (res_cur->rangeno == res->rangeno) { + /* in the same range */ + while (res_cur) { + if (res->start < res_cur->start) + break; + res_prev = res_cur; + res_cur = res_cur->next; + } + if (!res_cur) { + /* the last resource in this range */ + res_prev->next = res; + res->next = NULL; + res->nextRange = res_prev->nextRange; + res_prev->nextRange = NULL; + } else if (res->start < res_cur->start) { + /* at the beginning or middle of the range */ + if (!res_prev) { + switch (res->type) { + case IO: + bus_cur->firstIO = res; + break; + case MEM: + bus_cur->firstMem = res; + break; + case PFMEM: + bus_cur->firstPFMem = res; + break; + } + } else if (res_prev->rangeno == res_cur->rangeno) + res_prev->next = res; + else + res_prev->nextRange = res; + + res->next = res_cur; + res->nextRange = NULL; + } + } else { + /* this is the case where it is 1st occurrence of the range */ + if (!res_prev) { + /* at the beginning of the resource list */ + res->next = NULL; + switch (res->type) { + case IO: + res->nextRange = bus_cur->firstIO; + bus_cur->firstIO = res; + break; + case MEM: + res->nextRange = bus_cur->firstMem; + bus_cur->firstMem = res; + break; + case PFMEM: + res->nextRange = bus_cur->firstPFMem; + bus_cur->firstPFMem = res; + break; + } + } else if (res_cur->rangeno > res->rangeno) { + /* in the middle of the resource list */ + res_prev->nextRange = res; + res->next = NULL; + res->nextRange = res_cur; + } + } + } + + debug ("%s - exit\n", __func__); + return 0; +} + +/**************************************************************************** + * This routine will remove the resource from the list of resources + * + * Input: io, mem, and/or pfmem resource to be deleted + * Ouput: modified resource list + * 0 or error code + ****************************************************************************/ +int ibmphp_remove_resource (struct resource_node *res) +{ + struct bus_node *bus_cur; + struct resource_node *res_cur = NULL; + struct resource_node *res_prev; + struct resource_node *mem_cur; + char * type = ""; + + if (!res) { + err ("resource to remove is NULL\n"); + return -ENODEV; + } + + bus_cur = find_bus_wprev (res->busno, NULL, 0); + + if (!bus_cur) { + err ("cannot find corresponding bus of the io resource to remove " + "bailing out...\n"); + return -ENODEV; + } + + switch (res->type) { + case IO: + res_cur = bus_cur->firstIO; + type = "io"; + break; + case MEM: + res_cur = bus_cur->firstMem; + type = "mem"; + break; + case PFMEM: + res_cur = bus_cur->firstPFMem; + type = "pfmem"; + break; + default: + err ("unknown type for resource to remove\n"); + return -EINVAL; + } + res_prev = NULL; + + while (res_cur) { + if ((res_cur->start == res->start) && (res_cur->end == res->end)) + break; + res_prev = res_cur; + if (res_cur->next) + res_cur = res_cur->next; + else + res_cur = res_cur->nextRange; + } + + if (!res_cur) { + if (res->type == PFMEM) { + /* + * case where pfmem might be in the PFMemFromMem list + * so will also need to remove the corresponding mem + * entry + */ + res_cur = bus_cur->firstPFMemFromMem; + res_prev = NULL; + + while (res_cur) { + if ((res_cur->start == res->start) && (res_cur->end == res->end)) { + mem_cur = bus_cur->firstMem; + while (mem_cur) { + if ((mem_cur->start == res_cur->start) + && (mem_cur->end == res_cur->end)) + break; + if (mem_cur->next) + mem_cur = mem_cur->next; + else + mem_cur = mem_cur->nextRange; + } + if (!mem_cur) { + err ("cannot find corresponding mem node for pfmem...\n"); + return -EINVAL; + } + + ibmphp_remove_resource (mem_cur); + if (!res_prev) + bus_cur->firstPFMemFromMem = res_cur->next; + else + res_prev->next = res_cur->next; + kfree (res_cur); + return 0; + } + res_prev = res_cur; + if (res_cur->next) + res_cur = res_cur->next; + else + res_cur = res_cur->nextRange; + } + if (!res_cur) { + err ("cannot find pfmem to delete...\n"); + return -EINVAL; + } + } else { + err ("the %s resource is not in the list to be deleted...\n", type); + return -EINVAL; + } + } + if (!res_prev) { + /* first device to be deleted */ + if (res_cur->next) { + switch (res->type) { + case IO: + bus_cur->firstIO = res_cur->next; + break; + case MEM: + bus_cur->firstMem = res_cur->next; + break; + case PFMEM: + bus_cur->firstPFMem = res_cur->next; + break; + } + } else if (res_cur->nextRange) { + switch (res->type) { + case IO: + bus_cur->firstIO = res_cur->nextRange; + break; + case MEM: + bus_cur->firstMem = res_cur->nextRange; + break; + case PFMEM: + bus_cur->firstPFMem = res_cur->nextRange; + break; + } + } else { + switch (res->type) { + case IO: + bus_cur->firstIO = NULL; + break; + case MEM: + bus_cur->firstMem = NULL; + break; + case PFMEM: + bus_cur->firstPFMem = NULL; + break; + } + } + kfree (res_cur); + return 0; + } else { + if (res_cur->next) { + if (res_prev->rangeno == res_cur->rangeno) + res_prev->next = res_cur->next; + else + res_prev->nextRange = res_cur->next; + } else if (res_cur->nextRange) { + res_prev->next = NULL; + res_prev->nextRange = res_cur->nextRange; + } else { + res_prev->next = NULL; + res_prev->nextRange = NULL; + } + kfree (res_cur); + return 0; + } + + return 0; +} + +static struct range_node * find_range (struct bus_node *bus_cur, struct resource_node * res) +{ + struct range_node * range = NULL; + + switch (res->type) { + case IO: + range = bus_cur->rangeIO; + break; + case MEM: + range = bus_cur->rangeMem; + break; + case PFMEM: + range = bus_cur->rangePFMem; + break; + default: + err ("cannot read resource type in find_range\n"); + } + + while (range) { + if (res->rangeno == range->rangeno) + break; + range = range->next; + } + return range; +} + +/***************************************************************************** + * This routine will check to make sure the io/mem/pfmem->len that the device asked for + * can fit w/i our list of available IO/MEM/PFMEM resources. If cannot, returns -EINVAL, + * otherwise, returns 0 + * + * Input: resource + * Ouput: the correct start and end address are inputted into the resource node, + * 0 or -EINVAL + *****************************************************************************/ +int ibmphp_check_resource (struct resource_node *res, u8 bridge) +{ + struct bus_node *bus_cur; + struct range_node *range = NULL; + struct resource_node *res_prev; + struct resource_node *res_cur = NULL; + u32 len_cur = 0, start_cur = 0, len_tmp = 0; + int noranges = 0; + u32 tmp_start; /* this is to make sure start address is divisible by the length needed */ + u32 tmp_divide; + u8 flag = 0; + + if (!res) + return -EINVAL; + + if (bridge) { + /* The rules for bridges are different, 4K divisible for IO, 1M for (pf)mem*/ + if (res->type == IO) + tmp_divide = IOBRIDGE; + else + tmp_divide = MEMBRIDGE; + } else + tmp_divide = res->len; + + bus_cur = find_bus_wprev (res->busno, NULL, 0); + + if (!bus_cur) { + /* didn't find a bus, smth's wrong!!! */ + debug ("no bus in the system, either pci_dev's wrong or allocation failed\n"); + return -EINVAL; + } + + debug ("%s - enter\n", __func__); + debug ("bus_cur->busno is %d\n", bus_cur->busno); + + /* This is a quick fix to not mess up with the code very much. i.e., + * 2000-2fff, len = 1000, but when we compare, we need it to be fff */ + res->len -= 1; + + switch (res->type) { + case IO: + res_cur = bus_cur->firstIO; + noranges = bus_cur->noIORanges; + break; + case MEM: + res_cur = bus_cur->firstMem; + noranges = bus_cur->noMemRanges; + break; + case PFMEM: + res_cur = bus_cur->firstPFMem; + noranges = bus_cur->noPFMemRanges; + break; + default: + err ("wrong type of resource to check\n"); + return -EINVAL; + } + res_prev = NULL; + + while (res_cur) { + range = find_range (bus_cur, res_cur); + debug ("%s - rangeno = %d\n", __func__, res_cur->rangeno); + + if (!range) { + err ("no range for the device exists... bailing out...\n"); + return -EINVAL; + } + + /* found our range */ + if (!res_prev) { + /* first time in the loop */ + if ((res_cur->start != range->start) && ((len_tmp = res_cur->start - 1 - range->start) >= res->len)) { + debug ("len_tmp = %x\n", len_tmp); + + if ((len_tmp < len_cur) || (len_cur == 0)) { + + if ((range->start % tmp_divide) == 0) { + /* just perfect, starting address is divisible by length */ + flag = 1; + len_cur = len_tmp; + start_cur = range->start; + } else { + /* Needs adjusting */ + tmp_start = range->start; + flag = 0; + + while ((len_tmp = res_cur->start - 1 - tmp_start) >= res->len) { + if ((tmp_start % tmp_divide) == 0) { + flag = 1; + len_cur = len_tmp; + start_cur = tmp_start; + break; + } + tmp_start += tmp_divide - tmp_start % tmp_divide; + if (tmp_start >= res_cur->start - 1) + break; + } + } + + if (flag && len_cur == res->len) { + debug ("but we are not here, right?\n"); + res->start = start_cur; + res->len += 1; /* To restore the balance */ + res->end = res->start + res->len - 1; + return 0; + } + } + } + } + if (!res_cur->next) { + /* last device on the range */ + if ((range->end != res_cur->end) && ((len_tmp = range->end - (res_cur->end + 1)) >= res->len)) { + debug ("len_tmp = %x\n", len_tmp); + if ((len_tmp < len_cur) || (len_cur == 0)) { + + if (((res_cur->end + 1) % tmp_divide) == 0) { + /* just perfect, starting address is divisible by length */ + flag = 1; + len_cur = len_tmp; + start_cur = res_cur->end + 1; + } else { + /* Needs adjusting */ + tmp_start = res_cur->end + 1; + flag = 0; + + while ((len_tmp = range->end - tmp_start) >= res->len) { + if ((tmp_start % tmp_divide) == 0) { + flag = 1; + len_cur = len_tmp; + start_cur = tmp_start; + break; + } + tmp_start += tmp_divide - tmp_start % tmp_divide; + if (tmp_start >= range->end) + break; + } + } + if (flag && len_cur == res->len) { + res->start = start_cur; + res->len += 1; /* To restore the balance */ + res->end = res->start + res->len - 1; + return 0; + } + } + } + } + + if (res_prev) { + if (res_prev->rangeno != res_cur->rangeno) { + /* 1st device on this range */ + if ((res_cur->start != range->start) && + ((len_tmp = res_cur->start - 1 - range->start) >= res->len)) { + if ((len_tmp < len_cur) || (len_cur == 0)) { + if ((range->start % tmp_divide) == 0) { + /* just perfect, starting address is divisible by length */ + flag = 1; + len_cur = len_tmp; + start_cur = range->start; + } else { + /* Needs adjusting */ + tmp_start = range->start; + flag = 0; + + while ((len_tmp = res_cur->start - 1 - tmp_start) >= res->len) { + if ((tmp_start % tmp_divide) == 0) { + flag = 1; + len_cur = len_tmp; + start_cur = tmp_start; + break; + } + tmp_start += tmp_divide - tmp_start % tmp_divide; + if (tmp_start >= res_cur->start - 1) + break; + } + } + + if (flag && len_cur == res->len) { + res->start = start_cur; + res->len += 1; /* To restore the balance */ + res->end = res->start + res->len - 1; + return 0; + } + } + } + } else { + /* in the same range */ + if ((len_tmp = res_cur->start - 1 - res_prev->end - 1) >= res->len) { + if ((len_tmp < len_cur) || (len_cur == 0)) { + if (((res_prev->end + 1) % tmp_divide) == 0) { + /* just perfect, starting address's divisible by length */ + flag = 1; + len_cur = len_tmp; + start_cur = res_prev->end + 1; + } else { + /* Needs adjusting */ + tmp_start = res_prev->end + 1; + flag = 0; + + while ((len_tmp = res_cur->start - 1 - tmp_start) >= res->len) { + if ((tmp_start % tmp_divide) == 0) { + flag = 1; + len_cur = len_tmp; + start_cur = tmp_start; + break; + } + tmp_start += tmp_divide - tmp_start % tmp_divide; + if (tmp_start >= res_cur->start - 1) + break; + } + } + + if (flag && len_cur == res->len) { + res->start = start_cur; + res->len += 1; /* To restore the balance */ + res->end = res->start + res->len - 1; + return 0; + } + } + } + } + } + /* end if (res_prev) */ + res_prev = res_cur; + if (res_cur->next) + res_cur = res_cur->next; + else + res_cur = res_cur->nextRange; + } /* end of while */ + + + if (!res_prev) { + /* 1st device ever */ + /* need to find appropriate range */ + switch (res->type) { + case IO: + range = bus_cur->rangeIO; + break; + case MEM: + range = bus_cur->rangeMem; + break; + case PFMEM: + range = bus_cur->rangePFMem; + break; + } + while (range) { + if ((len_tmp = range->end - range->start) >= res->len) { + if ((len_tmp < len_cur) || (len_cur == 0)) { + if ((range->start % tmp_divide) == 0) { + /* just perfect, starting address's divisible by length */ + flag = 1; + len_cur = len_tmp; + start_cur = range->start; + } else { + /* Needs adjusting */ + tmp_start = range->start; + flag = 0; + + while ((len_tmp = range->end - tmp_start) >= res->len) { + if ((tmp_start % tmp_divide) == 0) { + flag = 1; + len_cur = len_tmp; + start_cur = tmp_start; + break; + } + tmp_start += tmp_divide - tmp_start % tmp_divide; + if (tmp_start >= range->end) + break; + } + } + + if (flag && len_cur == res->len) { + res->start = start_cur; + res->len += 1; /* To restore the balance */ + res->end = res->start + res->len - 1; + return 0; + } + } + } + range = range->next; + } /* end of while */ + + if ((!range) && (len_cur == 0)) { + /* have gone through the list of devices and ranges and haven't found n.e.thing */ + err ("no appropriate range.. bailing out...\n"); + return -EINVAL; + } else if (len_cur) { + res->start = start_cur; + res->len += 1; /* To restore the balance */ + res->end = res->start + res->len - 1; + return 0; + } + } + + if (!res_cur) { + debug ("prev->rangeno = %d, noranges = %d\n", res_prev->rangeno, noranges); + if (res_prev->rangeno < noranges) { + /* if there're more ranges out there to check */ + switch (res->type) { + case IO: + range = bus_cur->rangeIO; + break; + case MEM: + range = bus_cur->rangeMem; + break; + case PFMEM: + range = bus_cur->rangePFMem; + break; + } + while (range) { + if ((len_tmp = range->end - range->start) >= res->len) { + if ((len_tmp < len_cur) || (len_cur == 0)) { + if ((range->start % tmp_divide) == 0) { + /* just perfect, starting address's divisible by length */ + flag = 1; + len_cur = len_tmp; + start_cur = range->start; + } else { + /* Needs adjusting */ + tmp_start = range->start; + flag = 0; + + while ((len_tmp = range->end - tmp_start) >= res->len) { + if ((tmp_start % tmp_divide) == 0) { + flag = 1; + len_cur = len_tmp; + start_cur = tmp_start; + break; + } + tmp_start += tmp_divide - tmp_start % tmp_divide; + if (tmp_start >= range->end) + break; + } + } + + if (flag && len_cur == res->len) { + res->start = start_cur; + res->len += 1; /* To restore the balance */ + res->end = res->start + res->len - 1; + return 0; + } + } + } + range = range->next; + } /* end of while */ + + if ((!range) && (len_cur == 0)) { + /* have gone through the list of devices and ranges and haven't found n.e.thing */ + err ("no appropriate range.. bailing out...\n"); + return -EINVAL; + } else if (len_cur) { + res->start = start_cur; + res->len += 1; /* To restore the balance */ + res->end = res->start + res->len - 1; + return 0; + } + } else { + /* no more ranges to check on */ + if (len_cur) { + res->start = start_cur; + res->len += 1; /* To restore the balance */ + res->end = res->start + res->len - 1; + return 0; + } else { + /* have gone through the list of devices and haven't found n.e.thing */ + err ("no appropriate range.. bailing out...\n"); + return -EINVAL; + } + } + } /* end if(!res_cur) */ + return -EINVAL; +} + +/******************************************************************************** + * This routine is called from remove_card if the card contained PPB. + * It will remove all the resources on the bus as well as the bus itself + * Input: Bus + * Ouput: 0, -ENODEV + ********************************************************************************/ +int ibmphp_remove_bus (struct bus_node *bus, u8 parent_busno) +{ + struct resource_node *res_cur; + struct resource_node *res_tmp; + struct bus_node *prev_bus; + int rc; + + prev_bus = find_bus_wprev (parent_busno, NULL, 0); + + if (!prev_bus) { + debug ("something terribly wrong. Cannot find parent bus to the one to remove\n"); + return -ENODEV; + } + + debug ("In ibmphp_remove_bus... prev_bus->busno is %x\n", prev_bus->busno); + + rc = remove_ranges (bus, prev_bus); + if (rc) + return rc; + + if (bus->firstIO) { + res_cur = bus->firstIO; + while (res_cur) { + res_tmp = res_cur; + if (res_cur->next) + res_cur = res_cur->next; + else + res_cur = res_cur->nextRange; + kfree (res_tmp); + res_tmp = NULL; + } + bus->firstIO = NULL; + } + if (bus->firstMem) { + res_cur = bus->firstMem; + while (res_cur) { + res_tmp = res_cur; + if (res_cur->next) + res_cur = res_cur->next; + else + res_cur = res_cur->nextRange; + kfree (res_tmp); + res_tmp = NULL; + } + bus->firstMem = NULL; + } + if (bus->firstPFMem) { + res_cur = bus->firstPFMem; + while (res_cur) { + res_tmp = res_cur; + if (res_cur->next) + res_cur = res_cur->next; + else + res_cur = res_cur->nextRange; + kfree (res_tmp); + res_tmp = NULL; + } + bus->firstPFMem = NULL; + } + + if (bus->firstPFMemFromMem) { + res_cur = bus->firstPFMemFromMem; + while (res_cur) { + res_tmp = res_cur; + res_cur = res_cur->next; + + kfree (res_tmp); + res_tmp = NULL; + } + bus->firstPFMemFromMem = NULL; + } + + list_del (&bus->bus_list); + kfree (bus); + return 0; +} + +/****************************************************************************** + * This routine deletes the ranges from a given bus, and the entries from the + * parent's bus in the resources + * Input: current bus, previous bus + * Output: 0, -EINVAL + ******************************************************************************/ +static int remove_ranges (struct bus_node *bus_cur, struct bus_node *bus_prev) +{ + struct range_node *range_cur; + struct range_node *range_tmp; + int i; + struct resource_node *res = NULL; + + if (bus_cur->noIORanges) { + range_cur = bus_cur->rangeIO; + for (i = 0; i < bus_cur->noIORanges; i++) { + if (ibmphp_find_resource (bus_prev, range_cur->start, &res, IO) < 0) + return -EINVAL; + ibmphp_remove_resource (res); + + range_tmp = range_cur; + range_cur = range_cur->next; + kfree (range_tmp); + range_tmp = NULL; + } + bus_cur->rangeIO = NULL; + } + if (bus_cur->noMemRanges) { + range_cur = bus_cur->rangeMem; + for (i = 0; i < bus_cur->noMemRanges; i++) { + if (ibmphp_find_resource (bus_prev, range_cur->start, &res, MEM) < 0) + return -EINVAL; + + ibmphp_remove_resource (res); + range_tmp = range_cur; + range_cur = range_cur->next; + kfree (range_tmp); + range_tmp = NULL; + } + bus_cur->rangeMem = NULL; + } + if (bus_cur->noPFMemRanges) { + range_cur = bus_cur->rangePFMem; + for (i = 0; i < bus_cur->noPFMemRanges; i++) { + if (ibmphp_find_resource (bus_prev, range_cur->start, &res, PFMEM) < 0) + return -EINVAL; + + ibmphp_remove_resource (res); + range_tmp = range_cur; + range_cur = range_cur->next; + kfree (range_tmp); + range_tmp = NULL; + } + bus_cur->rangePFMem = NULL; + } + return 0; +} + +/* + * find the resource node in the bus + * Input: Resource needed, start address of the resource, type of resource + */ +int ibmphp_find_resource (struct bus_node *bus, u32 start_address, struct resource_node **res, int flag) +{ + struct resource_node *res_cur = NULL; + char * type = ""; + + if (!bus) { + err ("The bus passed in NULL to find resource\n"); + return -ENODEV; + } + + switch (flag) { + case IO: + res_cur = bus->firstIO; + type = "io"; + break; + case MEM: + res_cur = bus->firstMem; + type = "mem"; + break; + case PFMEM: + res_cur = bus->firstPFMem; + type = "pfmem"; + break; + default: + err ("wrong type of flag\n"); + return -EINVAL; + } + + while (res_cur) { + if (res_cur->start == start_address) { + *res = res_cur; + break; + } + if (res_cur->next) + res_cur = res_cur->next; + else + res_cur = res_cur->nextRange; + } + + if (!res_cur) { + if (flag == PFMEM) { + res_cur = bus->firstPFMemFromMem; + while (res_cur) { + if (res_cur->start == start_address) { + *res = res_cur; + break; + } + res_cur = res_cur->next; + } + if (!res_cur) { + debug ("SOS...cannot find %s resource in the bus.\n", type); + return -EINVAL; + } + } else { + debug ("SOS... cannot find %s resource in the bus.\n", type); + return -EINVAL; + } + } + + if (*res) + debug ("*res->start = %x\n", (*res)->start); + + return 0; +} + +/*********************************************************************** + * This routine will free the resource structures used by the + * system. It is called from cleanup routine for the module + * Parameters: none + * Returns: none + ***********************************************************************/ +void ibmphp_free_resources (void) +{ + struct bus_node *bus_cur = NULL; + struct bus_node *bus_tmp; + struct range_node *range_cur; + struct range_node *range_tmp; + struct resource_node *res_cur; + struct resource_node *res_tmp; + struct list_head *tmp; + struct list_head *next; + int i = 0; + flags = 1; + + list_for_each_safe (tmp, next, &gbuses) { + bus_cur = list_entry (tmp, struct bus_node, bus_list); + if (bus_cur->noIORanges) { + range_cur = bus_cur->rangeIO; + for (i = 0; i < bus_cur->noIORanges; i++) { + if (!range_cur) + break; + range_tmp = range_cur; + range_cur = range_cur->next; + kfree (range_tmp); + range_tmp = NULL; + } + } + if (bus_cur->noMemRanges) { + range_cur = bus_cur->rangeMem; + for (i = 0; i < bus_cur->noMemRanges; i++) { + if (!range_cur) + break; + range_tmp = range_cur; + range_cur = range_cur->next; + kfree (range_tmp); + range_tmp = NULL; + } + } + if (bus_cur->noPFMemRanges) { + range_cur = bus_cur->rangePFMem; + for (i = 0; i < bus_cur->noPFMemRanges; i++) { + if (!range_cur) + break; + range_tmp = range_cur; + range_cur = range_cur->next; + kfree (range_tmp); + range_tmp = NULL; + } + } + + if (bus_cur->firstIO) { + res_cur = bus_cur->firstIO; + while (res_cur) { + res_tmp = res_cur; + if (res_cur->next) + res_cur = res_cur->next; + else + res_cur = res_cur->nextRange; + kfree (res_tmp); + res_tmp = NULL; + } + bus_cur->firstIO = NULL; + } + if (bus_cur->firstMem) { + res_cur = bus_cur->firstMem; + while (res_cur) { + res_tmp = res_cur; + if (res_cur->next) + res_cur = res_cur->next; + else + res_cur = res_cur->nextRange; + kfree (res_tmp); + res_tmp = NULL; + } + bus_cur->firstMem = NULL; + } + if (bus_cur->firstPFMem) { + res_cur = bus_cur->firstPFMem; + while (res_cur) { + res_tmp = res_cur; + if (res_cur->next) + res_cur = res_cur->next; + else + res_cur = res_cur->nextRange; + kfree (res_tmp); + res_tmp = NULL; + } + bus_cur->firstPFMem = NULL; + } + + if (bus_cur->firstPFMemFromMem) { + res_cur = bus_cur->firstPFMemFromMem; + while (res_cur) { + res_tmp = res_cur; + res_cur = res_cur->next; + + kfree (res_tmp); + res_tmp = NULL; + } + bus_cur->firstPFMemFromMem = NULL; + } + + bus_tmp = bus_cur; + list_del (&bus_cur->bus_list); + kfree (bus_tmp); + bus_tmp = NULL; + } +} + +/********************************************************************************* + * This function will go over the PFmem resources to check if the EBDA allocated + * pfmem out of memory buckets of the bus. If so, it will change the range numbers + * and a flag to indicate that this resource is out of memory. It will also move the + * Pfmem out of the pfmem resource list to the PFMemFromMem list, and will create + * a new Mem node + * This routine is called right after initialization + *******************************************************************************/ +static int __init once_over (void) +{ + struct resource_node *pfmem_cur; + struct resource_node *pfmem_prev; + struct resource_node *mem; + struct bus_node *bus_cur; + struct list_head *tmp; + + list_for_each (tmp, &gbuses) { + bus_cur = list_entry (tmp, struct bus_node, bus_list); + if ((!bus_cur->rangePFMem) && (bus_cur->firstPFMem)) { + for (pfmem_cur = bus_cur->firstPFMem, pfmem_prev = NULL; pfmem_cur; pfmem_prev = pfmem_cur, pfmem_cur = pfmem_cur->next) { + pfmem_cur->fromMem = 1; + if (pfmem_prev) + pfmem_prev->next = pfmem_cur->next; + else + bus_cur->firstPFMem = pfmem_cur->next; + + if (!bus_cur->firstPFMemFromMem) + pfmem_cur->next = NULL; + else + /* we don't need to sort PFMemFromMem since we're using mem node for + all the real work anyways, so just insert at the beginning of the + list + */ + pfmem_cur->next = bus_cur->firstPFMemFromMem; + + bus_cur->firstPFMemFromMem = pfmem_cur; + + mem = kzalloc(sizeof(struct resource_node), GFP_KERNEL); + if (!mem) { + err ("out of system memory\n"); + return -ENOMEM; + } + mem->type = MEM; + mem->busno = pfmem_cur->busno; + mem->devfunc = pfmem_cur->devfunc; + mem->start = pfmem_cur->start; + mem->end = pfmem_cur->end; + mem->len = pfmem_cur->len; + if (ibmphp_add_resource (mem) < 0) + err ("Trouble...trouble... EBDA allocated pfmem from mem, but system doesn't display it has this space... unless not PCI device...\n"); + pfmem_cur->rangeno = mem->rangeno; + } /* end for pfmem */ + } /* end if */ + } /* end list_for_each bus */ + return 0; +} + +int ibmphp_add_pfmem_from_mem (struct resource_node *pfmem) +{ + struct bus_node *bus_cur = find_bus_wprev (pfmem->busno, NULL, 0); + + if (!bus_cur) { + err ("cannot find bus of pfmem to add...\n"); + return -ENODEV; + } + + if (bus_cur->firstPFMemFromMem) + pfmem->next = bus_cur->firstPFMemFromMem; + else + pfmem->next = NULL; + + bus_cur->firstPFMemFromMem = pfmem; + + return 0; +} + +/* This routine just goes through the buses to see if the bus already exists. + * It is called from ibmphp_find_sec_number, to find out a secondary bus number for + * bridged cards + * Parameters: bus_number + * Returns: Bus pointer or NULL + */ +struct bus_node *ibmphp_find_res_bus (u8 bus_number) +{ + return find_bus_wprev (bus_number, NULL, 0); +} + +static struct bus_node *find_bus_wprev (u8 bus_number, struct bus_node **prev, u8 flag) +{ + struct bus_node *bus_cur; + struct list_head *tmp; + struct list_head *tmp_prev; + + list_for_each (tmp, &gbuses) { + tmp_prev = tmp->prev; + bus_cur = list_entry (tmp, struct bus_node, bus_list); + if (flag) + *prev = list_entry (tmp_prev, struct bus_node, bus_list); + if (bus_cur->busno == bus_number) + return bus_cur; + } + + return NULL; +} + +void ibmphp_print_test (void) +{ + int i = 0; + struct bus_node *bus_cur = NULL; + struct range_node *range; + struct resource_node *res; + struct list_head *tmp; + + debug_pci ("*****************START**********************\n"); + + if ((!list_empty(&gbuses)) && flags) { + err ("The GBUSES is not NULL?!?!?!?!?\n"); + return; + } + + list_for_each (tmp, &gbuses) { + bus_cur = list_entry (tmp, struct bus_node, bus_list); + debug_pci ("This is bus # %d. There are\n", bus_cur->busno); + debug_pci ("IORanges = %d\t", bus_cur->noIORanges); + debug_pci ("MemRanges = %d\t", bus_cur->noMemRanges); + debug_pci ("PFMemRanges = %d\n", bus_cur->noPFMemRanges); + debug_pci ("The IO Ranges are as follows:\n"); + if (bus_cur->rangeIO) { + range = bus_cur->rangeIO; + for (i = 0; i < bus_cur->noIORanges; i++) { + debug_pci ("rangeno is %d\n", range->rangeno); + debug_pci ("[%x - %x]\n", range->start, range->end); + range = range->next; + } + } + + debug_pci ("The Mem Ranges are as follows:\n"); + if (bus_cur->rangeMem) { + range = bus_cur->rangeMem; + for (i = 0; i < bus_cur->noMemRanges; i++) { + debug_pci ("rangeno is %d\n", range->rangeno); + debug_pci ("[%x - %x]\n", range->start, range->end); + range = range->next; + } + } + + debug_pci ("The PFMem Ranges are as follows:\n"); + + if (bus_cur->rangePFMem) { + range = bus_cur->rangePFMem; + for (i = 0; i < bus_cur->noPFMemRanges; i++) { + debug_pci ("rangeno is %d\n", range->rangeno); + debug_pci ("[%x - %x]\n", range->start, range->end); + range = range->next; + } + } + + debug_pci ("The resources on this bus are as follows\n"); + + debug_pci ("IO...\n"); + if (bus_cur->firstIO) { + res = bus_cur->firstIO; + while (res) { + debug_pci ("The range # is %d\n", res->rangeno); + debug_pci ("The bus, devfnc is %d, %x\n", res->busno, res->devfunc); + debug_pci ("[%x - %x], len=%x\n", res->start, res->end, res->len); + if (res->next) + res = res->next; + else if (res->nextRange) + res = res->nextRange; + else + break; + } + } + debug_pci ("Mem...\n"); + if (bus_cur->firstMem) { + res = bus_cur->firstMem; + while (res) { + debug_pci ("The range # is %d\n", res->rangeno); + debug_pci ("The bus, devfnc is %d, %x\n", res->busno, res->devfunc); + debug_pci ("[%x - %x], len=%x\n", res->start, res->end, res->len); + if (res->next) + res = res->next; + else if (res->nextRange) + res = res->nextRange; + else + break; + } + } + debug_pci ("PFMem...\n"); + if (bus_cur->firstPFMem) { + res = bus_cur->firstPFMem; + while (res) { + debug_pci ("The range # is %d\n", res->rangeno); + debug_pci ("The bus, devfnc is %d, %x\n", res->busno, res->devfunc); + debug_pci ("[%x - %x], len=%x\n", res->start, res->end, res->len); + if (res->next) + res = res->next; + else if (res->nextRange) + res = res->nextRange; + else + break; + } + } + + debug_pci ("PFMemFromMem...\n"); + if (bus_cur->firstPFMemFromMem) { + res = bus_cur->firstPFMemFromMem; + while (res) { + debug_pci ("The range # is %d\n", res->rangeno); + debug_pci ("The bus, devfnc is %d, %x\n", res->busno, res->devfunc); + debug_pci ("[%x - %x], len=%x\n", res->start, res->end, res->len); + res = res->next; + } + } + } + debug_pci ("***********************END***********************\n"); +} + +static int range_exists_already (struct range_node * range, struct bus_node * bus_cur, u8 type) +{ + struct range_node * range_cur = NULL; + switch (type) { + case IO: + range_cur = bus_cur->rangeIO; + break; + case MEM: + range_cur = bus_cur->rangeMem; + break; + case PFMEM: + range_cur = bus_cur->rangePFMem; + break; + default: + err ("wrong type passed to find out if range already exists\n"); + return -ENODEV; + } + + while (range_cur) { + if ((range_cur->start == range->start) && (range_cur->end == range->end)) + return 1; + range_cur = range_cur->next; + } + + return 0; +} + +/* This routine will read the windows for any PPB we have and update the + * range info for the secondary bus, and will also input this info into + * primary bus, since BIOS doesn't. This is for PPB that are in the system + * on bootup. For bridged cards that were added during previous load of the + * driver, only the ranges and the bus structure are added, the devices are + * added from NVRAM + * Input: primary busno + * Returns: none + * Note: this function doesn't take into account IO restrictions etc, + * so will only work for bridges with no video/ISA devices behind them It + * also will not work for onboard PPB's that can have more than 1 *bus + * behind them All these are TO DO. + * Also need to add more error checkings... (from fnc returns etc) + */ +static int __init update_bridge_ranges (struct bus_node **bus) +{ + u8 sec_busno, device, function, hdr_type, start_io_address, end_io_address; + u16 vendor_id, upper_io_start, upper_io_end, start_mem_address, end_mem_address; + u32 start_address, end_address, upper_start, upper_end; + struct bus_node *bus_sec; + struct bus_node *bus_cur; + struct resource_node *io; + struct resource_node *mem; + struct resource_node *pfmem; + struct range_node *range; + unsigned int devfn; + + bus_cur = *bus; + if (!bus_cur) + return -ENODEV; + ibmphp_pci_bus->number = bus_cur->busno; + + debug ("inside %s\n", __func__); + debug ("bus_cur->busno = %x\n", bus_cur->busno); + + for (device = 0; device < 32; device++) { + for (function = 0x00; function < 0x08; function++) { + devfn = PCI_DEVFN(device, function); + pci_bus_read_config_word (ibmphp_pci_bus, devfn, PCI_VENDOR_ID, &vendor_id); + + if (vendor_id != PCI_VENDOR_ID_NOTVALID) { + /* found correct device!!! */ + pci_bus_read_config_byte (ibmphp_pci_bus, devfn, PCI_HEADER_TYPE, &hdr_type); + + switch (hdr_type) { + case PCI_HEADER_TYPE_NORMAL: + function = 0x8; + break; + case PCI_HEADER_TYPE_MULTIDEVICE: + break; + case PCI_HEADER_TYPE_BRIDGE: + function = 0x8; + case PCI_HEADER_TYPE_MULTIBRIDGE: + /* We assume here that only 1 bus behind the bridge + TO DO: add functionality for several: + temp = secondary; + while (temp < subordinate) { + ... + temp++; + } + */ + pci_bus_read_config_byte (ibmphp_pci_bus, devfn, PCI_SECONDARY_BUS, &sec_busno); + bus_sec = find_bus_wprev (sec_busno, NULL, 0); + /* this bus structure doesn't exist yet, PPB was configured during previous loading of ibmphp */ + if (!bus_sec) { + bus_sec = alloc_error_bus (NULL, sec_busno, 1); + /* the rest will be populated during NVRAM call */ + return 0; + } + pci_bus_read_config_byte (ibmphp_pci_bus, devfn, PCI_IO_BASE, &start_io_address); + pci_bus_read_config_byte (ibmphp_pci_bus, devfn, PCI_IO_LIMIT, &end_io_address); + pci_bus_read_config_word (ibmphp_pci_bus, devfn, PCI_IO_BASE_UPPER16, &upper_io_start); + pci_bus_read_config_word (ibmphp_pci_bus, devfn, PCI_IO_LIMIT_UPPER16, &upper_io_end); + start_address = (start_io_address & PCI_IO_RANGE_MASK) << 8; + start_address |= (upper_io_start << 16); + end_address = (end_io_address & PCI_IO_RANGE_MASK) << 8; + end_address |= (upper_io_end << 16); + + if ((start_address) && (start_address <= end_address)) { + range = kzalloc(sizeof(struct range_node), GFP_KERNEL); + if (!range) { + err ("out of system memory\n"); + return -ENOMEM; + } + range->start = start_address; + range->end = end_address + 0xfff; + + if (bus_sec->noIORanges > 0) { + if (!range_exists_already (range, bus_sec, IO)) { + add_range (IO, range, bus_sec); + ++bus_sec->noIORanges; + } else { + kfree (range); + range = NULL; + } + } else { + /* 1st IO Range on the bus */ + range->rangeno = 1; + bus_sec->rangeIO = range; + ++bus_sec->noIORanges; + } + fix_resources (bus_sec); + + if (ibmphp_find_resource (bus_cur, start_address, &io, IO)) { + io = kzalloc(sizeof(struct resource_node), GFP_KERNEL); + if (!io) { + kfree (range); + err ("out of system memory\n"); + return -ENOMEM; + } + io->type = IO; + io->busno = bus_cur->busno; + io->devfunc = ((device << 3) | (function & 0x7)); + io->start = start_address; + io->end = end_address + 0xfff; + io->len = io->end - io->start + 1; + ibmphp_add_resource (io); + } + } + + pci_bus_read_config_word (ibmphp_pci_bus, devfn, PCI_MEMORY_BASE, &start_mem_address); + pci_bus_read_config_word (ibmphp_pci_bus, devfn, PCI_MEMORY_LIMIT, &end_mem_address); + + start_address = 0x00000000 | (start_mem_address & PCI_MEMORY_RANGE_MASK) << 16; + end_address = 0x00000000 | (end_mem_address & PCI_MEMORY_RANGE_MASK) << 16; + + if ((start_address) && (start_address <= end_address)) { + + range = kzalloc(sizeof(struct range_node), GFP_KERNEL); + if (!range) { + err ("out of system memory\n"); + return -ENOMEM; + } + range->start = start_address; + range->end = end_address + 0xfffff; + + if (bus_sec->noMemRanges > 0) { + if (!range_exists_already (range, bus_sec, MEM)) { + add_range (MEM, range, bus_sec); + ++bus_sec->noMemRanges; + } else { + kfree (range); + range = NULL; + } + } else { + /* 1st Mem Range on the bus */ + range->rangeno = 1; + bus_sec->rangeMem = range; + ++bus_sec->noMemRanges; + } + + fix_resources (bus_sec); + + if (ibmphp_find_resource (bus_cur, start_address, &mem, MEM)) { + mem = kzalloc(sizeof(struct resource_node), GFP_KERNEL); + if (!mem) { + kfree (range); + err ("out of system memory\n"); + return -ENOMEM; + } + mem->type = MEM; + mem->busno = bus_cur->busno; + mem->devfunc = ((device << 3) | (function & 0x7)); + mem->start = start_address; + mem->end = end_address + 0xfffff; + mem->len = mem->end - mem->start + 1; + ibmphp_add_resource (mem); + } + } + pci_bus_read_config_word (ibmphp_pci_bus, devfn, PCI_PREF_MEMORY_BASE, &start_mem_address); + pci_bus_read_config_word (ibmphp_pci_bus, devfn, PCI_PREF_MEMORY_LIMIT, &end_mem_address); + pci_bus_read_config_dword (ibmphp_pci_bus, devfn, PCI_PREF_BASE_UPPER32, &upper_start); + pci_bus_read_config_dword (ibmphp_pci_bus, devfn, PCI_PREF_LIMIT_UPPER32, &upper_end); + start_address = 0x00000000 | (start_mem_address & PCI_MEMORY_RANGE_MASK) << 16; + end_address = 0x00000000 | (end_mem_address & PCI_MEMORY_RANGE_MASK) << 16; +#if BITS_PER_LONG == 64 + start_address |= ((long) upper_start) << 32; + end_address |= ((long) upper_end) << 32; +#endif + + if ((start_address) && (start_address <= end_address)) { + + range = kzalloc(sizeof(struct range_node), GFP_KERNEL); + if (!range) { + err ("out of system memory\n"); + return -ENOMEM; + } + range->start = start_address; + range->end = end_address + 0xfffff; + + if (bus_sec->noPFMemRanges > 0) { + if (!range_exists_already (range, bus_sec, PFMEM)) { + add_range (PFMEM, range, bus_sec); + ++bus_sec->noPFMemRanges; + } else { + kfree (range); + range = NULL; + } + } else { + /* 1st PFMem Range on the bus */ + range->rangeno = 1; + bus_sec->rangePFMem = range; + ++bus_sec->noPFMemRanges; + } + + fix_resources (bus_sec); + if (ibmphp_find_resource (bus_cur, start_address, &pfmem, PFMEM)) { + pfmem = kzalloc(sizeof(struct resource_node), GFP_KERNEL); + if (!pfmem) { + kfree (range); + err ("out of system memory\n"); + return -ENOMEM; + } + pfmem->type = PFMEM; + pfmem->busno = bus_cur->busno; + pfmem->devfunc = ((device << 3) | (function & 0x7)); + pfmem->start = start_address; + pfmem->end = end_address + 0xfffff; + pfmem->len = pfmem->end - pfmem->start + 1; + pfmem->fromMem = 0; + + ibmphp_add_resource (pfmem); + } + } + break; + } /* end of switch */ + } /* end if vendor */ + } /* end for function */ + } /* end for device */ + + bus = &bus_cur; + return 0; +} diff --git a/drivers/pci/hotplug/pci_hotplug_core.c b/drivers/pci/hotplug/pci_hotplug_core.c new file mode 100644 index 0000000..535fce0 --- /dev/null +++ b/drivers/pci/hotplug/pci_hotplug_core.c @@ -0,0 +1,689 @@ +/* + * 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 <kristen.c.accardi@intel.com> + * + */ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/kernel.h> +#include <linux/types.h> +#include <linux/list.h> +#include <linux/kobject.h> +#include <linux/sysfs.h> +#include <linux/pagemap.h> +#include <linux/slab.h> +#include <linux/init.h> +#include <linux/mount.h> +#include <linux/namei.h> +#include <linux/mutex.h> +#include <linux/pci.h> +#include <linux/pci_hotplug.h> +#include <asm/uaccess.h> +#include "../pci.h" + +#define MY_NAME "pci_hotplug" + +#define dbg(fmt, arg...) do { if (debug) printk(KERN_DEBUG "%s: %s: " fmt , MY_NAME , __func__ , ## 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" + + +////////////////////////////////////////////////////////////////// + +static LIST_HEAD(pci_hotplug_slot_list); +static DEFINE_MUTEX(pci_hp_mutex); + +/* 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 */ + "25 GBps PCI-E", /* 0x14 */ +}; + +#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 + +/* Weee, fun with macros... */ +#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 (!try_module_get(ops->owner)) \ + return -ENODEV; \ + if (ops->get_##name) \ + retval = ops->get_##name(slot, value); \ + else \ + *value = slot->info->name; \ + module_put(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 pci_slot *slot, char *buf) +{ + int retval; + u8 value; + + retval = get_power_status(slot->hotplug, &value); + if (retval) + goto exit; + retval = sprintf (buf, "%d\n", value); +exit: + return retval; +} + +static ssize_t power_write_file(struct pci_slot *pci_slot, const char *buf, + size_t count) +{ + struct hotplug_slot *slot = pci_slot->hotplug; + unsigned long lpower; + u8 power; + int retval = 0; + + lpower = simple_strtoul (buf, NULL, 10); + power = (u8)(lpower & 0xff); + dbg ("power = %d\n", power); + + if (!try_module_get(slot->ops->owner)) { + retval = -ENODEV; + goto exit; + } + switch (power) { + case 0: + if (slot->ops->disable_slot) + retval = slot->ops->disable_slot(slot); + break; + + case 1: + if (slot->ops->enable_slot) + retval = slot->ops->enable_slot(slot); + break; + + default: + err ("Illegal value specified for power\n"); + retval = -EINVAL; + } + module_put(slot->ops->owner); + +exit: + if (retval) + return retval; + return count; +} + +static struct pci_slot_attribute hotplug_slot_attr_power = { + .attr = {.name = "power", .mode = S_IFREG | S_IRUGO | S_IWUSR}, + .show = power_read_file, + .store = power_write_file +}; + +static ssize_t attention_read_file(struct pci_slot *slot, char *buf) +{ + int retval; + u8 value; + + retval = get_attention_status(slot->hotplug, &value); + if (retval) + goto exit; + retval = sprintf(buf, "%d\n", value); + +exit: + return retval; +} + +static ssize_t attention_write_file(struct pci_slot *slot, const char *buf, + size_t count) +{ + struct hotplug_slot_ops *ops = slot->hotplug->ops; + unsigned long lattention; + u8 attention; + int retval = 0; + + lattention = simple_strtoul (buf, NULL, 10); + attention = (u8)(lattention & 0xff); + dbg (" - attention = %d\n", attention); + + if (!try_module_get(ops->owner)) { + retval = -ENODEV; + goto exit; + } + if (ops->set_attention_status) + retval = ops->set_attention_status(slot->hotplug, attention); + module_put(ops->owner); + +exit: + if (retval) + return retval; + return count; +} + +static struct pci_slot_attribute hotplug_slot_attr_attention = { + .attr = {.name = "attention", .mode = S_IFREG | S_IRUGO | S_IWUSR}, + .show = attention_read_file, + .store = attention_write_file +}; + +static ssize_t latch_read_file(struct pci_slot *slot, char *buf) +{ + int retval; + u8 value; + + retval = get_latch_status(slot->hotplug, &value); + if (retval) + goto exit; + retval = sprintf (buf, "%d\n", value); + +exit: + return retval; +} + +static struct pci_slot_attribute hotplug_slot_attr_latch = { + .attr = {.name = "latch", .mode = S_IFREG | S_IRUGO}, + .show = latch_read_file, +}; + +static ssize_t presence_read_file(struct pci_slot *slot, char *buf) +{ + int retval; + u8 value; + + retval = get_adapter_status(slot->hotplug, &value); + if (retval) + goto exit; + retval = sprintf (buf, "%d\n", value); + +exit: + return retval; +} + +static struct pci_slot_attribute hotplug_slot_attr_presence = { + .attr = {.name = "adapter", .mode = S_IFREG | S_IRUGO}, + .show = presence_read_file, +}; + +static char *unknown_speed = "Unknown bus speed"; + +static ssize_t max_bus_speed_read_file(struct pci_slot *slot, char *buf) +{ + char *speed_string; + int retval; + enum pci_bus_speed value; + + retval = get_max_bus_speed(slot->hotplug, &value); + if (retval) + goto exit; + + if (value == PCI_SPEED_UNKNOWN) + speed_string = unknown_speed; + else + speed_string = pci_bus_speed_strings[value]; + + retval = sprintf (buf, "%s\n", speed_string); + +exit: + return retval; +} + +static struct pci_slot_attribute hotplug_slot_attr_max_bus_speed = { + .attr = {.name = "max_bus_speed", .mode = S_IFREG | S_IRUGO}, + .show = max_bus_speed_read_file, +}; + +static ssize_t cur_bus_speed_read_file(struct pci_slot *slot, char *buf) +{ + char *speed_string; + int retval; + enum pci_bus_speed value; + + retval = get_cur_bus_speed(slot->hotplug, &value); + if (retval) + goto exit; + + if (value == PCI_SPEED_UNKNOWN) + speed_string = unknown_speed; + else + speed_string = pci_bus_speed_strings[value]; + + retval = sprintf (buf, "%s\n", speed_string); + +exit: + return retval; +} + +static struct pci_slot_attribute hotplug_slot_attr_cur_bus_speed = { + .attr = {.name = "cur_bus_speed", .mode = S_IFREG | S_IRUGO}, + .show = cur_bus_speed_read_file, +}; + +static ssize_t test_write_file(struct pci_slot *pci_slot, const char *buf, + size_t count) +{ + struct hotplug_slot *slot = pci_slot->hotplug; + unsigned long ltest; + u32 test; + int retval = 0; + + ltest = simple_strtoul (buf, NULL, 10); + test = (u32)(ltest & 0xffffffff); + dbg ("test = %d\n", test); + + if (!try_module_get(slot->ops->owner)) { + retval = -ENODEV; + goto exit; + } + if (slot->ops->hardware_test) + retval = slot->ops->hardware_test(slot, test); + module_put(slot->ops->owner); + +exit: + if (retval) + return retval; + return count; +} + +static struct pci_slot_attribute hotplug_slot_attr_test = { + .attr = {.name = "test", .mode = S_IFREG | S_IRUGO | S_IWUSR}, + .store = test_write_file +}; + +static int has_power_file(struct pci_slot *pci_slot) +{ + struct hotplug_slot *slot = pci_slot->hotplug; + if ((!slot) || (!slot->ops)) + return -ENODEV; + if ((slot->ops->enable_slot) || + (slot->ops->disable_slot) || + (slot->ops->get_power_status)) + return 0; + return -ENOENT; +} + +static int has_attention_file(struct pci_slot *pci_slot) +{ + struct hotplug_slot *slot = pci_slot->hotplug; + if ((!slot) || (!slot->ops)) + return -ENODEV; + if ((slot->ops->set_attention_status) || + (slot->ops->get_attention_status)) + return 0; + return -ENOENT; +} + +static int has_latch_file(struct pci_slot *pci_slot) +{ + struct hotplug_slot *slot = pci_slot->hotplug; + if ((!slot) || (!slot->ops)) + return -ENODEV; + if (slot->ops->get_latch_status) + return 0; + return -ENOENT; +} + +static int has_adapter_file(struct pci_slot *pci_slot) +{ + struct hotplug_slot *slot = pci_slot->hotplug; + if ((!slot) || (!slot->ops)) + return -ENODEV; + if (slot->ops->get_adapter_status) + return 0; + return -ENOENT; +} + +static int has_max_bus_speed_file(struct pci_slot *pci_slot) +{ + struct hotplug_slot *slot = pci_slot->hotplug; + if ((!slot) || (!slot->ops)) + return -ENODEV; + if (slot->ops->get_max_bus_speed) + return 0; + return -ENOENT; +} + +static int has_cur_bus_speed_file(struct pci_slot *pci_slot) +{ + struct hotplug_slot *slot = pci_slot->hotplug; + if ((!slot) || (!slot->ops)) + return -ENODEV; + if (slot->ops->get_cur_bus_speed) + return 0; + return -ENOENT; +} + +static int has_test_file(struct pci_slot *pci_slot) +{ + struct hotplug_slot *slot = pci_slot->hotplug; + if ((!slot) || (!slot->ops)) + return -ENODEV; + if (slot->ops->hardware_test) + return 0; + return -ENOENT; +} + +static int fs_add_slot(struct pci_slot *slot) +{ + int retval = 0; + + if (has_power_file(slot) == 0) { + retval = sysfs_create_file(&slot->kobj, &hotplug_slot_attr_power.attr); + if (retval) + goto exit_power; + } + + if (has_attention_file(slot) == 0) { + retval = sysfs_create_file(&slot->kobj, + &hotplug_slot_attr_attention.attr); + if (retval) + goto exit_attention; + } + + if (has_latch_file(slot) == 0) { + retval = sysfs_create_file(&slot->kobj, + &hotplug_slot_attr_latch.attr); + if (retval) + goto exit_latch; + } + + if (has_adapter_file(slot) == 0) { + retval = sysfs_create_file(&slot->kobj, + &hotplug_slot_attr_presence.attr); + if (retval) + goto exit_adapter; + } + + if (has_max_bus_speed_file(slot) == 0) { + retval = sysfs_create_file(&slot->kobj, + &hotplug_slot_attr_max_bus_speed.attr); + if (retval) + goto exit_max_speed; + } + + if (has_cur_bus_speed_file(slot) == 0) { + retval = sysfs_create_file(&slot->kobj, + &hotplug_slot_attr_cur_bus_speed.attr); + if (retval) + goto exit_cur_speed; + } + + if (has_test_file(slot) == 0) { + retval = sysfs_create_file(&slot->kobj, + &hotplug_slot_attr_test.attr); + if (retval) + goto exit_test; + } + + goto exit; + +exit_test: + if (has_cur_bus_speed_file(slot) == 0) + sysfs_remove_file(&slot->kobj, &hotplug_slot_attr_cur_bus_speed.attr); + +exit_cur_speed: + if (has_max_bus_speed_file(slot) == 0) + sysfs_remove_file(&slot->kobj, &hotplug_slot_attr_max_bus_speed.attr); + +exit_max_speed: + if (has_adapter_file(slot) == 0) + sysfs_remove_file(&slot->kobj, &hotplug_slot_attr_presence.attr); + +exit_adapter: + if (has_latch_file(slot) == 0) + sysfs_remove_file(&slot->kobj, &hotplug_slot_attr_latch.attr); + +exit_latch: + if (has_attention_file(slot) == 0) + sysfs_remove_file(&slot->kobj, &hotplug_slot_attr_attention.attr); + +exit_attention: + if (has_power_file(slot) == 0) + sysfs_remove_file(&slot->kobj, &hotplug_slot_attr_power.attr); +exit_power: +exit: + return retval; +} + +static void fs_remove_slot(struct pci_slot *slot) +{ + if (has_power_file(slot) == 0) + sysfs_remove_file(&slot->kobj, &hotplug_slot_attr_power.attr); + + if (has_attention_file(slot) == 0) + sysfs_remove_file(&slot->kobj, &hotplug_slot_attr_attention.attr); + + if (has_latch_file(slot) == 0) + sysfs_remove_file(&slot->kobj, &hotplug_slot_attr_latch.attr); + + if (has_adapter_file(slot) == 0) + sysfs_remove_file(&slot->kobj, &hotplug_slot_attr_presence.attr); + + if (has_max_bus_speed_file(slot) == 0) + sysfs_remove_file(&slot->kobj, &hotplug_slot_attr_max_bus_speed.attr); + + if (has_cur_bus_speed_file(slot) == 0) + sysfs_remove_file(&slot->kobj, &hotplug_slot_attr_cur_bus_speed.attr); + + if (has_test_file(slot) == 0) + sysfs_remove_file(&slot->kobj, &hotplug_slot_attr_test.attr); +} + +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(hotplug_slot_name(slot), name) == 0) + return slot; + } + return NULL; +} + +/** + * pci_hp_register - register a hotplug_slot with the PCI hotplug subsystem + * @bus: bus this slot is on + * @slot: pointer to the &struct hotplug_slot to register + * @slot_nr: slot number + * @name: name registered with kobject core + * + * 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 pci_bus *bus, int slot_nr, + const char *name) +{ + int result; + struct pci_slot *pci_slot; + + if (slot == NULL) + return -ENODEV; + if ((slot->info == NULL) || (slot->ops == NULL)) + return -EINVAL; + if (slot->release == NULL) { + dbg("Why are you trying to register a hotplug slot " + "without a proper release function?\n"); + return -EINVAL; + } + + mutex_lock(&pci_hp_mutex); + + /* + * No problems if we call this interface from both ACPI_PCI_SLOT + * driver and call it here again. If we've already created the + * pci_slot, the interface will simply bump the refcount. + */ + pci_slot = pci_create_slot(bus, slot_nr, name, slot); + if (IS_ERR(pci_slot)) { + result = PTR_ERR(pci_slot); + goto out; + } + + slot->pci_slot = pci_slot; + pci_slot->hotplug = slot; + + list_add(&slot->slot_list, &pci_hotplug_slot_list); + + result = fs_add_slot(pci_slot); + kobject_uevent(&pci_slot->kobj, KOBJ_ADD); + dbg("Added slot %s to the list\n", name); +out: + mutex_unlock(&pci_hp_mutex); + return result; +} + +/** + * pci_hp_deregister - deregister a hotplug_slot with the PCI hotplug subsystem + * @hotplug: 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 *hotplug) +{ + struct hotplug_slot *temp; + struct pci_slot *slot; + + if (!hotplug) + return -ENODEV; + + mutex_lock(&pci_hp_mutex); + temp = get_slot_from_name(hotplug_slot_name(hotplug)); + if (temp != hotplug) { + mutex_unlock(&pci_hp_mutex); + return -ENODEV; + } + + list_del(&hotplug->slot_list); + + slot = hotplug->pci_slot; + fs_remove_slot(slot); + dbg("Removed slot %s from the list\n", hotplug_slot_name(hotplug)); + + hotplug->release(hotplug); + slot->hotplug = NULL; + pci_destroy_slot(slot); + mutex_unlock(&pci_hp_mutex); + + return 0; +} + +/** + * pci_hp_change_slot_info - changes the slot's information structure in the core + * @hotplug: pointer to the slot whose info has changed + * @info: pointer to the info copy into the slot's info structure + * + * @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 __must_check pci_hp_change_slot_info(struct hotplug_slot *hotplug, + struct hotplug_slot_info *info) +{ + struct pci_slot *slot; + if (!hotplug || !info) + return -ENODEV; + slot = hotplug->pci_slot; + + memcpy(hotplug->info, info, sizeof(struct hotplug_slot_info)); + + return 0; +} + +static int __init pci_hotplug_init (void) +{ + int result; + + result = cpci_hotplug_init(debug); + if (result) { + err ("cpci_hotplug_init with error %d\n", result); + goto err_cpci; + } + + info (DRIVER_DESC " version: " DRIVER_VERSION "\n"); + +err_cpci: + return result; +} + +static void __exit pci_hotplug_exit (void) +{ + cpci_hotplug_exit(); +} + +module_init(pci_hotplug_init); +module_exit(pci_hotplug_exit); + +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); +module_param(debug, bool, 0644); +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); diff --git a/drivers/pci/hotplug/pciehp.h b/drivers/pci/hotplug/pciehp.h new file mode 100644 index 0000000..b2801a7 --- /dev/null +++ b/drivers/pci/hotplug/pciehp.h @@ -0,0 +1,241 @@ +/* + * PCI Express Hot Plug Controller Driver + * + * Copyright (C) 1995,2001 Compaq Computer Corporation + * Copyright (C) 2001 Greg Kroah-Hartman (greg@kroah.com) + * Copyright (C) 2001 IBM Corp. + * Copyright (C) 2003-2004 Intel Corporation + * + * 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>, <kristen.c.accardi@intel.com> + * + */ +#ifndef _PCIEHP_H +#define _PCIEHP_H + +#include <linux/types.h> +#include <linux/pci.h> +#include <linux/pci_hotplug.h> +#include <linux/delay.h> +#include <linux/sched.h> /* signal_pending() */ +#include <linux/pcieport_if.h> +#include <linux/mutex.h> + +#define MY_NAME "pciehp" + +extern int pciehp_poll_mode; +extern int pciehp_poll_time; +extern int pciehp_debug; +extern int pciehp_force; +extern struct workqueue_struct *pciehp_wq; + +#define dbg(format, arg...) \ + do { \ + if (pciehp_debug) \ + printk("%s: " format, 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) + +#define ctrl_dbg(ctrl, format, arg...) \ + do { \ + if (pciehp_debug) \ + dev_printk(, &ctrl->pcie->device, \ + format, ## arg); \ + } while (0) +#define ctrl_err(ctrl, format, arg...) \ + dev_err(&ctrl->pcie->device, format, ## arg) +#define ctrl_info(ctrl, format, arg...) \ + dev_info(&ctrl->pcie->device, format, ## arg) +#define ctrl_warn(ctrl, format, arg...) \ + dev_warn(&ctrl->pcie->device, format, ## arg) + +#define SLOT_NAME_SIZE 10 +struct slot { + u8 bus; + u8 device; + u8 state; + u8 hp_slot; + u32 number; + struct controller *ctrl; + struct hpc_ops *hpc_ops; + struct hotplug_slot *hotplug_slot; + struct list_head slot_list; + unsigned long last_emi_toggle; + struct delayed_work work; /* work for button event */ + struct mutex lock; +}; + +struct event_info { + u32 event_type; + struct slot *p_slot; + struct work_struct work; +}; + +struct controller { + struct mutex crit_sect; /* critical section mutex */ + struct mutex ctrl_lock; /* controller lock */ + int num_slots; /* Number of slots on ctlr */ + int slot_num_inc; /* 1 or -1 */ + struct pci_dev *pci_dev; + struct pcie_device *pcie; /* PCI Express port service */ + struct list_head slot_list; + struct hpc_ops *hpc_ops; + wait_queue_head_t queue; /* sleep & wake process */ + u8 slot_device_offset; + u32 first_slot; /* First physical slot number */ /* PCIE only has 1 slot */ + u8 slot_bus; /* Bus where the slots handled by this controller sit */ + u32 slot_cap; + u8 cap_base; + struct timer_list poll_timer; + int cmd_busy; + unsigned int no_cmd_complete:1; + unsigned int link_active_reporting:1; +}; + +#define INT_BUTTON_IGNORE 0 +#define INT_PRESENCE_ON 1 +#define INT_PRESENCE_OFF 2 +#define INT_SWITCH_CLOSE 3 +#define INT_SWITCH_OPEN 4 +#define INT_POWER_FAULT 5 +#define INT_POWER_FAULT_CLEAR 6 +#define INT_BUTTON_PRESS 7 +#define INT_BUTTON_RELEASE 8 +#define INT_BUTTON_CANCEL 9 + +#define STATIC_STATE 0 +#define BLINKINGON_STATE 1 +#define BLINKINGOFF_STATE 2 +#define POWERON_STATE 3 +#define POWEROFF_STATE 4 + +/* Error messages */ +#define INTERLOCK_OPEN 0x00000002 +#define ADD_NOT_SUPPORTED 0x00000003 +#define CARD_FUNCTIONING 0x00000005 +#define ADAPTER_NOT_SAME 0x00000006 +#define NO_ADAPTER_PRESENT 0x00000009 +#define NOT_ENOUGH_RESOURCES 0x0000000B +#define DEVICE_TYPE_NOT_SUPPORTED 0x0000000C +#define WRONG_BUS_FREQUENCY 0x0000000D +#define POWER_FAILURE 0x0000000E + +/* Field definitions in Slot Capabilities Register */ +#define ATTN_BUTTN_PRSN 0x00000001 +#define PWR_CTRL_PRSN 0x00000002 +#define MRL_SENS_PRSN 0x00000004 +#define ATTN_LED_PRSN 0x00000008 +#define PWR_LED_PRSN 0x00000010 +#define HP_SUPR_RM_SUP 0x00000020 +#define EMI_PRSN 0x00020000 +#define NO_CMD_CMPL_SUP 0x00040000 + +#define ATTN_BUTTN(ctrl) ((ctrl)->slot_cap & ATTN_BUTTN_PRSN) +#define POWER_CTRL(ctrl) ((ctrl)->slot_cap & PWR_CTRL_PRSN) +#define MRL_SENS(ctrl) ((ctrl)->slot_cap & MRL_SENS_PRSN) +#define ATTN_LED(ctrl) ((ctrl)->slot_cap & ATTN_LED_PRSN) +#define PWR_LED(ctrl) ((ctrl)->slot_cap & PWR_LED_PRSN) +#define HP_SUPR_RM(ctrl) ((ctrl)->slot_cap & HP_SUPR_RM_SUP) +#define EMI(ctrl) ((ctrl)->slot_cap & EMI_PRSN) +#define NO_CMD_CMPL(ctrl) ((ctrl)->slot_cap & NO_CMD_CMPL_SUP) + +extern int pciehp_sysfs_enable_slot(struct slot *slot); +extern int pciehp_sysfs_disable_slot(struct slot *slot); +extern u8 pciehp_handle_attention_button(struct slot *p_slot); + extern u8 pciehp_handle_switch_change(struct slot *p_slot); +extern u8 pciehp_handle_presence_change(struct slot *p_slot); +extern u8 pciehp_handle_power_fault(struct slot *p_slot); +extern int pciehp_configure_device(struct slot *p_slot); +extern int pciehp_unconfigure_device(struct slot *p_slot); +extern void pciehp_queue_pushbutton_work(struct work_struct *work); +struct controller *pcie_init(struct pcie_device *dev); +int pciehp_enable_slot(struct slot *p_slot); +int pciehp_disable_slot(struct slot *p_slot); +int pcie_enable_notification(struct controller *ctrl); + +static inline const char *slot_name(struct slot *slot) +{ + return hotplug_slot_name(slot->hotplug_slot); +} + +static inline struct slot *pciehp_find_slot(struct controller *ctrl, u8 device) +{ + struct slot *slot; + + list_for_each_entry(slot, &ctrl->slot_list, slot_list) { + if (slot->device == device) + return slot; + } + + ctrl_err(ctrl, "Slot (device=0x%02x) not found\n", device); + return NULL; +} + +struct hpc_ops { + int (*power_on_slot)(struct slot *slot); + int (*power_off_slot)(struct slot *slot); + int (*get_power_status)(struct slot *slot, u8 *status); + int (*get_attention_status)(struct slot *slot, u8 *status); + int (*set_attention_status)(struct slot *slot, u8 status); + int (*get_latch_status)(struct slot *slot, u8 *status); + int (*get_adapter_status)(struct slot *slot, u8 *status); + int (*get_emi_status)(struct slot *slot, u8 *status); + int (*toggle_emi)(struct slot *slot); + int (*get_max_bus_speed)(struct slot *slot, enum pci_bus_speed *speed); + int (*get_cur_bus_speed)(struct slot *slot, enum pci_bus_speed *speed); + int (*get_max_lnk_width)(struct slot *slot, enum pcie_link_width *val); + int (*get_cur_lnk_width)(struct slot *slot, enum pcie_link_width *val); + int (*query_power_fault)(struct slot *slot); + void (*green_led_on)(struct slot *slot); + void (*green_led_off)(struct slot *slot); + void (*green_led_blink)(struct slot *slot); + void (*release_ctlr)(struct controller *ctrl); + int (*check_lnk_status)(struct controller *ctrl); +}; + +#ifdef CONFIG_ACPI +#include <acpi/acpi.h> +#include <acpi/acpi_bus.h> +#include <acpi/actypes.h> +#include <linux/pci-acpi.h> + +static inline int pciehp_get_hp_hw_control_from_firmware(struct pci_dev *dev) +{ + u32 flags = (OSC_PCI_EXPRESS_NATIVE_HP_CONTROL | + OSC_PCI_EXPRESS_CAP_STRUCTURE_CONTROL); + return acpi_get_hp_hw_control_from_firmware(dev, flags); +} + +static inline int pciehp_get_hp_params_from_firmware(struct pci_dev *dev, + struct hotplug_params *hpp) +{ + if (ACPI_FAILURE(acpi_get_hp_params_from_firmware(dev->bus, hpp))) + return -ENODEV; + return 0; +} +#else +#define pciehp_get_hp_hw_control_from_firmware(dev) 0 +#define pciehp_get_hp_params_from_firmware(dev, hpp) (-ENODEV) +#endif /* CONFIG_ACPI */ +#endif /* _PCIEHP_H */ diff --git a/drivers/pci/hotplug/pciehp_core.c b/drivers/pci/hotplug/pciehp_core.c new file mode 100644 index 0000000..b2ecff4 --- /dev/null +++ b/drivers/pci/hotplug/pciehp_core.c @@ -0,0 +1,543 @@ +/* + * PCI Express Hot Plug Controller Driver + * + * Copyright (C) 1995,2001 Compaq Computer Corporation + * Copyright (C) 2001 Greg Kroah-Hartman (greg@kroah.com) + * Copyright (C) 2001 IBM Corp. + * Copyright (C) 2003-2004 Intel Corporation + * + * 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>, <kristen.c.accardi@intel.com> + * + */ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/kernel.h> +#include <linux/types.h> +#include <linux/pci.h> +#include "pciehp.h" +#include <linux/interrupt.h> +#include <linux/time.h> + +/* Global variables */ +int pciehp_debug; +int pciehp_poll_mode; +int pciehp_poll_time; +int pciehp_force; +struct workqueue_struct *pciehp_wq; + +#define DRIVER_VERSION "0.4" +#define DRIVER_AUTHOR "Dan Zink <dan.zink@compaq.com>, Greg Kroah-Hartman <greg@kroah.com>, Dely Sy <dely.l.sy@intel.com>" +#define DRIVER_DESC "PCI Express Hot Plug Controller Driver" + +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); + +module_param(pciehp_debug, bool, 0644); +module_param(pciehp_poll_mode, bool, 0644); +module_param(pciehp_poll_time, int, 0644); +module_param(pciehp_force, bool, 0644); +MODULE_PARM_DESC(pciehp_debug, "Debugging mode enabled or not"); +MODULE_PARM_DESC(pciehp_poll_mode, "Using polling mechanism for hot-plug events or not"); +MODULE_PARM_DESC(pciehp_poll_time, "Polling mechanism frequency, in seconds"); +MODULE_PARM_DESC(pciehp_force, "Force pciehp, even if _OSC and OSHP are missing"); + +#define PCIE_MODULE_NAME "pciehp" + +static int set_attention_status (struct hotplug_slot *slot, u8 value); +static int enable_slot (struct hotplug_slot *slot); +static int disable_slot (struct hotplug_slot *slot); +static int get_power_status (struct hotplug_slot *slot, u8 *value); +static int get_attention_status (struct hotplug_slot *slot, u8 *value); +static int get_latch_status (struct hotplug_slot *slot, u8 *value); +static int get_adapter_status (struct hotplug_slot *slot, u8 *value); +static int get_max_bus_speed (struct hotplug_slot *slot, enum pci_bus_speed *value); +static int get_cur_bus_speed (struct hotplug_slot *slot, enum pci_bus_speed *value); + +static struct hotplug_slot_ops pciehp_hotplug_slot_ops = { + .owner = THIS_MODULE, + .set_attention_status = set_attention_status, + .enable_slot = enable_slot, + .disable_slot = disable_slot, + .get_power_status = get_power_status, + .get_attention_status = get_attention_status, + .get_latch_status = get_latch_status, + .get_adapter_status = get_adapter_status, + .get_max_bus_speed = get_max_bus_speed, + .get_cur_bus_speed = get_cur_bus_speed, +}; + +/* + * Check the status of the Electro Mechanical Interlock (EMI) + */ +static int get_lock_status(struct hotplug_slot *hotplug_slot, u8 *value) +{ + struct slot *slot = hotplug_slot->private; + return (slot->hpc_ops->get_emi_status(slot, value)); +} + +/* + * sysfs interface for the Electro Mechanical Interlock (EMI) + * 1 == locked, 0 == unlocked + */ +static ssize_t lock_read_file(struct hotplug_slot *slot, char *buf) +{ + int retval; + u8 value; + + retval = get_lock_status(slot, &value); + if (retval) + goto lock_read_exit; + retval = sprintf (buf, "%d\n", value); + +lock_read_exit: + return retval; +} + +/* + * Change the status of the Electro Mechanical Interlock (EMI) + * This is a toggle - in addition there must be at least 1 second + * in between toggles. + */ +static int set_lock_status(struct hotplug_slot *hotplug_slot, u8 status) +{ + struct slot *slot = hotplug_slot->private; + int retval; + u8 value; + + mutex_lock(&slot->ctrl->crit_sect); + + /* has it been >1 sec since our last toggle? */ + if ((get_seconds() - slot->last_emi_toggle) < 1) { + mutex_unlock(&slot->ctrl->crit_sect); + return -EINVAL; + } + + /* see what our current state is */ + retval = get_lock_status(hotplug_slot, &value); + if (retval || (value == status)) + goto set_lock_exit; + + slot->hpc_ops->toggle_emi(slot); +set_lock_exit: + mutex_unlock(&slot->ctrl->crit_sect); + return 0; +} + +/* + * sysfs interface which allows the user to toggle the Electro Mechanical + * Interlock. Valid values are either 0 or 1. 0 == unlock, 1 == lock + */ +static ssize_t lock_write_file(struct hotplug_slot *hotplug_slot, + const char *buf, size_t count) +{ + struct slot *slot = hotplug_slot->private; + unsigned long llock; + u8 lock; + int retval = 0; + + llock = simple_strtoul(buf, NULL, 10); + lock = (u8)(llock & 0xff); + + switch (lock) { + case 0: + case 1: + retval = set_lock_status(hotplug_slot, lock); + break; + default: + ctrl_err(slot->ctrl, "%d is an invalid lock value\n", + lock); + retval = -EINVAL; + } + if (retval) + return retval; + return count; +} + +static struct hotplug_slot_attribute hotplug_slot_attr_lock = { + .attr = {.name = "lock", .mode = S_IFREG | S_IRUGO | S_IWUSR}, + .show = lock_read_file, + .store = lock_write_file +}; + +/** + * release_slot - free up the memory used by a slot + * @hotplug_slot: slot to free + */ +static void release_slot(struct hotplug_slot *hotplug_slot) +{ + struct slot *slot = hotplug_slot->private; + + ctrl_dbg(slot->ctrl, "%s: physical_slot = %s\n", + __func__, hotplug_slot_name(hotplug_slot)); + + kfree(hotplug_slot->info); + kfree(hotplug_slot); +} + +static int init_slots(struct controller *ctrl) +{ + struct slot *slot; + struct hotplug_slot *hotplug_slot; + struct hotplug_slot_info *info; + char name[SLOT_NAME_SIZE]; + int retval = -ENOMEM; + + list_for_each_entry(slot, &ctrl->slot_list, slot_list) { + hotplug_slot = kzalloc(sizeof(*hotplug_slot), GFP_KERNEL); + if (!hotplug_slot) + goto error; + + info = kzalloc(sizeof(*info), GFP_KERNEL); + if (!info) + goto error_hpslot; + + /* register this slot with the hotplug pci core */ + hotplug_slot->info = info; + hotplug_slot->private = slot; + hotplug_slot->release = &release_slot; + hotplug_slot->ops = &pciehp_hotplug_slot_ops; + slot->hotplug_slot = hotplug_slot; + snprintf(name, SLOT_NAME_SIZE, "%u", slot->number); + + ctrl_dbg(ctrl, "Registering domain:bus:dev=%04x:%02x:%02x " + "hp_slot=%x sun=%x slot_device_offset=%x\n", + pci_domain_nr(ctrl->pci_dev->subordinate), + slot->bus, slot->device, slot->hp_slot, slot->number, + ctrl->slot_device_offset); + retval = pci_hp_register(hotplug_slot, + ctrl->pci_dev->subordinate, + slot->device, + name); + if (retval) { + ctrl_err(ctrl, "pci_hp_register failed with error %d\n", + retval); + goto error_info; + } + get_power_status(hotplug_slot, &info->power_status); + get_attention_status(hotplug_slot, &info->attention_status); + get_latch_status(hotplug_slot, &info->latch_status); + get_adapter_status(hotplug_slot, &info->adapter_status); + /* create additional sysfs entries */ + if (EMI(ctrl)) { + retval = sysfs_create_file(&hotplug_slot->pci_slot->kobj, + &hotplug_slot_attr_lock.attr); + if (retval) { + pci_hp_deregister(hotplug_slot); + ctrl_err(ctrl, "Cannot create additional sysfs " + "entries\n"); + goto error_info; + } + } + } + + return 0; +error_info: + kfree(info); +error_hpslot: + kfree(hotplug_slot); +error: + return retval; +} + +static void cleanup_slots(struct controller *ctrl) +{ + struct slot *slot; + + list_for_each_entry(slot, &ctrl->slot_list, slot_list) { + if (EMI(ctrl)) + sysfs_remove_file(&slot->hotplug_slot->pci_slot->kobj, + &hotplug_slot_attr_lock.attr); + pci_hp_deregister(slot->hotplug_slot); + } +} + +/* + * set_attention_status - Turns the Amber LED for a slot on, off or blink + */ +static int set_attention_status(struct hotplug_slot *hotplug_slot, u8 status) +{ + struct slot *slot = hotplug_slot->private; + + ctrl_dbg(slot->ctrl, "%s: physical_slot = %s\n", + __func__, slot_name(slot)); + + hotplug_slot->info->attention_status = status; + + if (ATTN_LED(slot->ctrl)) + slot->hpc_ops->set_attention_status(slot, status); + + return 0; +} + + +static int enable_slot(struct hotplug_slot *hotplug_slot) +{ + struct slot *slot = hotplug_slot->private; + + ctrl_dbg(slot->ctrl, "%s: physical_slot = %s\n", + __func__, slot_name(slot)); + + return pciehp_sysfs_enable_slot(slot); +} + + +static int disable_slot(struct hotplug_slot *hotplug_slot) +{ + struct slot *slot = hotplug_slot->private; + + ctrl_dbg(slot->ctrl, "%s: physical_slot = %s\n", + __func__, slot_name(slot)); + + return pciehp_sysfs_disable_slot(slot); +} + +static int get_power_status(struct hotplug_slot *hotplug_slot, u8 *value) +{ + struct slot *slot = hotplug_slot->private; + int retval; + + ctrl_dbg(slot->ctrl, "%s: physical_slot = %s\n", + __func__, slot_name(slot)); + + retval = slot->hpc_ops->get_power_status(slot, value); + if (retval < 0) + *value = hotplug_slot->info->power_status; + + return 0; +} + +static int get_attention_status(struct hotplug_slot *hotplug_slot, u8 *value) +{ + struct slot *slot = hotplug_slot->private; + int retval; + + ctrl_dbg(slot->ctrl, "%s: physical_slot = %s\n", + __func__, slot_name(slot)); + + retval = slot->hpc_ops->get_attention_status(slot, value); + if (retval < 0) + *value = hotplug_slot->info->attention_status; + + return 0; +} + +static int get_latch_status(struct hotplug_slot *hotplug_slot, u8 *value) +{ + struct slot *slot = hotplug_slot->private; + int retval; + + ctrl_dbg(slot->ctrl, "%s: physical_slot = %s\n", + __func__, slot_name(slot)); + + retval = slot->hpc_ops->get_latch_status(slot, value); + if (retval < 0) + *value = hotplug_slot->info->latch_status; + + return 0; +} + +static int get_adapter_status(struct hotplug_slot *hotplug_slot, u8 *value) +{ + struct slot *slot = hotplug_slot->private; + int retval; + + ctrl_dbg(slot->ctrl, "%s: physical_slot = %s\n", + __func__, slot_name(slot)); + + retval = slot->hpc_ops->get_adapter_status(slot, value); + if (retval < 0) + *value = hotplug_slot->info->adapter_status; + + return 0; +} + +static int get_max_bus_speed(struct hotplug_slot *hotplug_slot, + enum pci_bus_speed *value) +{ + struct slot *slot = hotplug_slot->private; + int retval; + + ctrl_dbg(slot->ctrl, "%s: physical_slot = %s\n", + __func__, slot_name(slot)); + + retval = slot->hpc_ops->get_max_bus_speed(slot, value); + if (retval < 0) + *value = PCI_SPEED_UNKNOWN; + + return 0; +} + +static int get_cur_bus_speed(struct hotplug_slot *hotplug_slot, enum pci_bus_speed *value) +{ + struct slot *slot = hotplug_slot->private; + int retval; + + ctrl_dbg(slot->ctrl, "%s: physical_slot = %s\n", + __func__, slot_name(slot)); + + retval = slot->hpc_ops->get_cur_bus_speed(slot, value); + if (retval < 0) + *value = PCI_SPEED_UNKNOWN; + + return 0; +} + +static int pciehp_probe(struct pcie_device *dev, const struct pcie_port_service_id *id) +{ + int rc; + struct controller *ctrl; + struct slot *t_slot; + u8 value; + struct pci_dev *pdev = dev->port; + + if (pciehp_force) + dev_info(&dev->device, + "Bypassing BIOS check for pciehp use on %s\n", + pci_name(pdev)); + else if (pciehp_get_hp_hw_control_from_firmware(pdev)) + goto err_out_none; + + ctrl = pcie_init(dev); + if (!ctrl) { + dev_err(&dev->device, "Controller initialization failed\n"); + goto err_out_none; + } + set_service_data(dev, ctrl); + + /* Setup the slot information structures */ + rc = init_slots(ctrl); + if (rc) { + if (rc == -EBUSY) + ctrl_warn(ctrl, "Slot already registered by another " + "hotplug driver\n"); + else + ctrl_err(ctrl, "Slot initialization failed\n"); + goto err_out_release_ctlr; + } + + /* Check if slot is occupied */ + t_slot = pciehp_find_slot(ctrl, ctrl->slot_device_offset); + t_slot->hpc_ops->get_adapter_status(t_slot, &value); + if (value) { + if (pciehp_force) + pciehp_enable_slot(t_slot); + } else { + /* Power off slot if not occupied */ + if (POWER_CTRL(ctrl)) { + rc = t_slot->hpc_ops->power_off_slot(t_slot); + if (rc) + goto err_out_free_ctrl_slot; + } + } + + return 0; + +err_out_free_ctrl_slot: + cleanup_slots(ctrl); +err_out_release_ctlr: + ctrl->hpc_ops->release_ctlr(ctrl); +err_out_none: + return -ENODEV; +} + +static void pciehp_remove (struct pcie_device *dev) +{ + struct controller *ctrl = get_service_data(dev); + + cleanup_slots(ctrl); + ctrl->hpc_ops->release_ctlr(ctrl); +} + +#ifdef CONFIG_PM +static int pciehp_suspend (struct pcie_device *dev, pm_message_t state) +{ + dev_info(&dev->device, "%s ENTRY\n", __func__); + return 0; +} + +static int pciehp_resume (struct pcie_device *dev) +{ + dev_info(&dev->device, "%s ENTRY\n", __func__); + if (pciehp_force) { + struct controller *ctrl = get_service_data(dev); + struct slot *t_slot; + u8 status; + + /* reinitialize the chipset's event detection logic */ + pcie_enable_notification(ctrl); + + t_slot = pciehp_find_slot(ctrl, ctrl->slot_device_offset); + + /* Check if slot is occupied */ + t_slot->hpc_ops->get_adapter_status(t_slot, &status); + if (status) + pciehp_enable_slot(t_slot); + else + pciehp_disable_slot(t_slot); + } + return 0; +} +#endif + +static struct pcie_port_service_id port_pci_ids[] = { { + .vendor = PCI_ANY_ID, + .device = PCI_ANY_ID, + .port_type = PCIE_ANY_PORT, + .service_type = PCIE_PORT_SERVICE_HP, + .driver_data = 0, + }, { /* end: all zeroes */ } +}; + +static struct pcie_port_service_driver hpdriver_portdrv = { + .name = PCIE_MODULE_NAME, + .id_table = &port_pci_ids[0], + + .probe = pciehp_probe, + .remove = pciehp_remove, + +#ifdef CONFIG_PM + .suspend = pciehp_suspend, + .resume = pciehp_resume, +#endif /* PM */ +}; + +static int __init pcied_init(void) +{ + int retval = 0; + + retval = pcie_port_service_register(&hpdriver_portdrv); + dbg("pcie_port_service_register = %d\n", retval); + info(DRIVER_DESC " version: " DRIVER_VERSION "\n"); + if (retval) + dbg("Failure to register service\n"); + return retval; +} + +static void __exit pcied_cleanup(void) +{ + dbg("unload_pciehpd()\n"); + pcie_port_service_unregister(&hpdriver_portdrv); + info(DRIVER_DESC " version: " DRIVER_VERSION " unloaded\n"); +} + +module_init(pcied_init); +module_exit(pcied_cleanup); diff --git a/drivers/pci/hotplug/pciehp_ctrl.c b/drivers/pci/hotplug/pciehp_ctrl.c new file mode 100644 index 0000000..fead63c --- /dev/null +++ b/drivers/pci/hotplug/pciehp_ctrl.c @@ -0,0 +1,696 @@ +/* + * PCI Express Hot Plug Controller Driver + * + * Copyright (C) 1995,2001 Compaq Computer Corporation + * Copyright (C) 2001 Greg Kroah-Hartman (greg@kroah.com) + * Copyright (C) 2001 IBM Corp. + * Copyright (C) 2003-2004 Intel Corporation + * + * 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>, <kristen.c.accardi@intel.com> + * + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/types.h> +#include <linux/smp_lock.h> +#include <linux/pci.h> +#include <linux/workqueue.h> +#include "../pci.h" +#include "pciehp.h" + +static void interrupt_event_handler(struct work_struct *work); + +static int queue_interrupt_event(struct slot *p_slot, u32 event_type) +{ + struct event_info *info; + + info = kmalloc(sizeof(*info), GFP_ATOMIC); + if (!info) + return -ENOMEM; + + info->event_type = event_type; + info->p_slot = p_slot; + INIT_WORK(&info->work, interrupt_event_handler); + + schedule_work(&info->work); + + return 0; +} + +u8 pciehp_handle_attention_button(struct slot *p_slot) +{ + u32 event_type; + struct controller *ctrl = p_slot->ctrl; + + /* Attention Button Change */ + ctrl_dbg(ctrl, "Attention button interrupt received\n"); + + /* + * Button pressed - See if need to TAKE ACTION!!! + */ + ctrl_info(ctrl, "Button pressed on Slot(%s)\n", slot_name(p_slot)); + event_type = INT_BUTTON_PRESS; + + queue_interrupt_event(p_slot, event_type); + + return 0; +} + +u8 pciehp_handle_switch_change(struct slot *p_slot) +{ + u8 getstatus; + u32 event_type; + struct controller *ctrl = p_slot->ctrl; + + /* Switch Change */ + ctrl_dbg(ctrl, "Switch interrupt received\n"); + + p_slot->hpc_ops->get_latch_status(p_slot, &getstatus); + if (getstatus) { + /* + * Switch opened + */ + ctrl_info(ctrl, "Latch open on Slot(%s)\n", slot_name(p_slot)); + event_type = INT_SWITCH_OPEN; + } else { + /* + * Switch closed + */ + ctrl_info(ctrl, "Latch close on Slot(%s)\n", slot_name(p_slot)); + event_type = INT_SWITCH_CLOSE; + } + + queue_interrupt_event(p_slot, event_type); + + return 1; +} + +u8 pciehp_handle_presence_change(struct slot *p_slot) +{ + u32 event_type; + u8 presence_save; + struct controller *ctrl = p_slot->ctrl; + + /* Presence Change */ + ctrl_dbg(ctrl, "Presence/Notify input change\n"); + + /* Switch is open, assume a presence change + * Save the presence state + */ + p_slot->hpc_ops->get_adapter_status(p_slot, &presence_save); + if (presence_save) { + /* + * Card Present + */ + ctrl_info(ctrl, "Card present on Slot(%s)\n", slot_name(p_slot)); + event_type = INT_PRESENCE_ON; + } else { + /* + * Not Present + */ + ctrl_info(ctrl, "Card not present on Slot(%s)\n", + slot_name(p_slot)); + event_type = INT_PRESENCE_OFF; + } + + queue_interrupt_event(p_slot, event_type); + + return 1; +} + +u8 pciehp_handle_power_fault(struct slot *p_slot) +{ + u32 event_type; + struct controller *ctrl = p_slot->ctrl; + + /* power fault */ + ctrl_dbg(ctrl, "Power fault interrupt received\n"); + + if ( !(p_slot->hpc_ops->query_power_fault(p_slot))) { + /* + * power fault Cleared + */ + ctrl_info(ctrl, "Power fault cleared on Slot(%s)\n", + slot_name(p_slot)); + event_type = INT_POWER_FAULT_CLEAR; + } else { + /* + * power fault + */ + ctrl_info(ctrl, "Power fault on Slot(%s)\n", slot_name(p_slot)); + event_type = INT_POWER_FAULT; + ctrl_info(ctrl, "Power fault bit %x set\n", 0); + } + + queue_interrupt_event(p_slot, event_type); + + return 1; +} + +/* The following routines constitute the bulk of the + hotplug controller logic + */ + +static void set_slot_off(struct controller *ctrl, struct slot * pslot) +{ + /* turn off slot, turn on Amber LED, turn off Green LED if supported*/ + if (POWER_CTRL(ctrl)) { + if (pslot->hpc_ops->power_off_slot(pslot)) { + ctrl_err(ctrl, + "Issue of Slot Power Off command failed\n"); + return; + } + } + + /* + * After turning power off, we must wait for at least 1 second + * before taking any action that relies on power having been + * removed from the slot/adapter. + */ + msleep(1000); + + if (PWR_LED(ctrl)) + pslot->hpc_ops->green_led_off(pslot); + + if (ATTN_LED(ctrl)) { + if (pslot->hpc_ops->set_attention_status(pslot, 1)) { + ctrl_err(ctrl, + "Issue of Set Attention Led command failed\n"); + return; + } + } +} + +/** + * board_added - Called after a board has been added to the system. + * @p_slot: &slot where board is added + * + * Turns power on for the board. + * Configures board. + */ +static int board_added(struct slot *p_slot) +{ + int retval = 0; + struct controller *ctrl = p_slot->ctrl; + struct pci_bus *parent = ctrl->pci_dev->subordinate; + + ctrl_dbg(ctrl, "%s: slot device, slot offset, hp slot = %d, %d, %d\n", + __func__, p_slot->device, ctrl->slot_device_offset, + p_slot->hp_slot); + + if (POWER_CTRL(ctrl)) { + /* Power on slot */ + retval = p_slot->hpc_ops->power_on_slot(p_slot); + if (retval) + return retval; + } + + if (PWR_LED(ctrl)) + p_slot->hpc_ops->green_led_blink(p_slot); + + /* Check link training status */ + retval = p_slot->hpc_ops->check_lnk_status(ctrl); + if (retval) { + ctrl_err(ctrl, "Failed to check link status\n"); + set_slot_off(ctrl, p_slot); + return retval; + } + + /* Check for a power fault */ + if (p_slot->hpc_ops->query_power_fault(p_slot)) { + ctrl_dbg(ctrl, "Power fault detected\n"); + retval = POWER_FAILURE; + goto err_exit; + } + + retval = pciehp_configure_device(p_slot); + if (retval) { + ctrl_err(ctrl, "Cannot add device at %04x:%02x:%02x\n", + pci_domain_nr(parent), p_slot->bus, p_slot->device); + goto err_exit; + } + + /* + * Some PCI Express root ports require fixup after hot-plug operation. + */ + if (pcie_mch_quirk) + pci_fixup_device(pci_fixup_final, ctrl->pci_dev); + if (PWR_LED(ctrl)) + p_slot->hpc_ops->green_led_on(p_slot); + + return 0; + +err_exit: + set_slot_off(ctrl, p_slot); + return retval; +} + +/** + * remove_board - Turns off slot and LEDs + * @p_slot: slot where board is being removed + */ +static int remove_board(struct slot *p_slot) +{ + int retval = 0; + struct controller *ctrl = p_slot->ctrl; + + retval = pciehp_unconfigure_device(p_slot); + if (retval) + return retval; + + ctrl_dbg(ctrl, "%s: hp_slot = %d\n", __func__, p_slot->hp_slot); + + if (POWER_CTRL(ctrl)) { + /* power off slot */ + retval = p_slot->hpc_ops->power_off_slot(p_slot); + if (retval) { + ctrl_err(ctrl, + "Issue of Slot Disable command failed\n"); + return retval; + } + } + + /* + * After turning power off, we must wait for at least 1 second + * before taking any action that relies on power having been + * removed from the slot/adapter. + */ + msleep(1000); + + if (PWR_LED(ctrl)) + /* turn off Green LED */ + p_slot->hpc_ops->green_led_off(p_slot); + + return 0; +} + +struct power_work_info { + struct slot *p_slot; + struct work_struct work; +}; + +/** + * pciehp_power_thread - handle pushbutton events + * @work: &struct work_struct describing work to be done + * + * Scheduled procedure to handle blocking stuff for the pushbuttons. + * Handles all pending events and exits. + */ +static void pciehp_power_thread(struct work_struct *work) +{ + struct power_work_info *info = + container_of(work, struct power_work_info, work); + struct slot *p_slot = info->p_slot; + + mutex_lock(&p_slot->lock); + switch (p_slot->state) { + case POWEROFF_STATE: + mutex_unlock(&p_slot->lock); + ctrl_dbg(p_slot->ctrl, + "Disabling domain:bus:device=%04x:%02x:%02x\n", + pci_domain_nr(p_slot->ctrl->pci_dev->subordinate), + p_slot->bus, p_slot->device); + pciehp_disable_slot(p_slot); + mutex_lock(&p_slot->lock); + p_slot->state = STATIC_STATE; + break; + case POWERON_STATE: + mutex_unlock(&p_slot->lock); + if (pciehp_enable_slot(p_slot) && + PWR_LED(p_slot->ctrl)) + p_slot->hpc_ops->green_led_off(p_slot); + mutex_lock(&p_slot->lock); + p_slot->state = STATIC_STATE; + break; + default: + break; + } + mutex_unlock(&p_slot->lock); + + kfree(info); +} + +void pciehp_queue_pushbutton_work(struct work_struct *work) +{ + struct slot *p_slot = container_of(work, struct slot, work.work); + struct power_work_info *info; + + info = kmalloc(sizeof(*info), GFP_KERNEL); + if (!info) { + ctrl_err(p_slot->ctrl, "%s: Cannot allocate memory\n", + __func__); + return; + } + info->p_slot = p_slot; + INIT_WORK(&info->work, pciehp_power_thread); + + mutex_lock(&p_slot->lock); + switch (p_slot->state) { + case BLINKINGOFF_STATE: + p_slot->state = POWEROFF_STATE; + break; + case BLINKINGON_STATE: + p_slot->state = POWERON_STATE; + break; + default: + goto out; + } + queue_work(pciehp_wq, &info->work); + out: + mutex_unlock(&p_slot->lock); +} + +static int update_slot_info(struct slot *slot) +{ + struct hotplug_slot_info *info; + int result; + + info = kmalloc(sizeof(*info), GFP_KERNEL); + if (!info) + return -ENOMEM; + + slot->hpc_ops->get_power_status(slot, &(info->power_status)); + slot->hpc_ops->get_attention_status(slot, &(info->attention_status)); + slot->hpc_ops->get_latch_status(slot, &(info->latch_status)); + slot->hpc_ops->get_adapter_status(slot, &(info->adapter_status)); + + result = pci_hp_change_slot_info(slot->hotplug_slot, info); + kfree (info); + return result; +} + +/* + * Note: This function must be called with slot->lock held + */ +static void handle_button_press_event(struct slot *p_slot) +{ + struct controller *ctrl = p_slot->ctrl; + u8 getstatus; + + switch (p_slot->state) { + case STATIC_STATE: + p_slot->hpc_ops->get_power_status(p_slot, &getstatus); + if (getstatus) { + p_slot->state = BLINKINGOFF_STATE; + ctrl_info(ctrl, + "PCI slot #%s - powering off due to button " + "press.\n", slot_name(p_slot)); + } else { + p_slot->state = BLINKINGON_STATE; + ctrl_info(ctrl, + "PCI slot #%s - powering on due to button " + "press.\n", slot_name(p_slot)); + } + /* blink green LED and turn off amber */ + if (PWR_LED(ctrl)) + p_slot->hpc_ops->green_led_blink(p_slot); + if (ATTN_LED(ctrl)) + p_slot->hpc_ops->set_attention_status(p_slot, 0); + + schedule_delayed_work(&p_slot->work, 5*HZ); + break; + case BLINKINGOFF_STATE: + case BLINKINGON_STATE: + /* + * Cancel if we are still blinking; this means that we + * press the attention again before the 5 sec. limit + * expires to cancel hot-add or hot-remove + */ + ctrl_info(ctrl, "Button cancel on Slot(%s)\n", slot_name(p_slot)); + cancel_delayed_work(&p_slot->work); + if (p_slot->state == BLINKINGOFF_STATE) { + if (PWR_LED(ctrl)) + p_slot->hpc_ops->green_led_on(p_slot); + } else { + if (PWR_LED(ctrl)) + p_slot->hpc_ops->green_led_off(p_slot); + } + if (ATTN_LED(ctrl)) + p_slot->hpc_ops->set_attention_status(p_slot, 0); + ctrl_info(ctrl, "PCI slot #%s - action canceled " + "due to button press\n", slot_name(p_slot)); + p_slot->state = STATIC_STATE; + break; + case POWEROFF_STATE: + case POWERON_STATE: + /* + * Ignore if the slot is on power-on or power-off state; + * this means that the previous attention button action + * to hot-add or hot-remove is undergoing + */ + ctrl_info(ctrl, "Button ignore on Slot(%s)\n", slot_name(p_slot)); + update_slot_info(p_slot); + break; + default: + ctrl_warn(ctrl, "Not a valid state\n"); + break; + } +} + +/* + * Note: This function must be called with slot->lock held + */ +static void handle_surprise_event(struct slot *p_slot) +{ + u8 getstatus; + struct power_work_info *info; + + info = kmalloc(sizeof(*info), GFP_KERNEL); + if (!info) { + ctrl_err(p_slot->ctrl, "%s: Cannot allocate memory\n", + __func__); + return; + } + info->p_slot = p_slot; + INIT_WORK(&info->work, pciehp_power_thread); + + p_slot->hpc_ops->get_adapter_status(p_slot, &getstatus); + if (!getstatus) + p_slot->state = POWEROFF_STATE; + else + p_slot->state = POWERON_STATE; + + queue_work(pciehp_wq, &info->work); +} + +static void interrupt_event_handler(struct work_struct *work) +{ + struct event_info *info = container_of(work, struct event_info, work); + struct slot *p_slot = info->p_slot; + struct controller *ctrl = p_slot->ctrl; + + mutex_lock(&p_slot->lock); + switch (info->event_type) { + case INT_BUTTON_PRESS: + handle_button_press_event(p_slot); + break; + case INT_POWER_FAULT: + if (!POWER_CTRL(ctrl)) + break; + if (ATTN_LED(ctrl)) + p_slot->hpc_ops->set_attention_status(p_slot, 1); + if (PWR_LED(ctrl)) + p_slot->hpc_ops->green_led_off(p_slot); + break; + case INT_PRESENCE_ON: + case INT_PRESENCE_OFF: + if (!HP_SUPR_RM(ctrl)) + break; + ctrl_dbg(ctrl, "Surprise Removal\n"); + update_slot_info(p_slot); + handle_surprise_event(p_slot); + break; + default: + update_slot_info(p_slot); + break; + } + mutex_unlock(&p_slot->lock); + + kfree(info); +} + +int pciehp_enable_slot(struct slot *p_slot) +{ + u8 getstatus = 0; + int rc; + struct controller *ctrl = p_slot->ctrl; + + /* Check to see if (latch closed, card present, power off) */ + mutex_lock(&p_slot->ctrl->crit_sect); + + rc = p_slot->hpc_ops->get_adapter_status(p_slot, &getstatus); + if (rc || !getstatus) { + ctrl_info(ctrl, "No adapter on slot(%s)\n", slot_name(p_slot)); + mutex_unlock(&p_slot->ctrl->crit_sect); + return -ENODEV; + } + if (MRL_SENS(p_slot->ctrl)) { + rc = p_slot->hpc_ops->get_latch_status(p_slot, &getstatus); + if (rc || getstatus) { + ctrl_info(ctrl, "Latch open on slot(%s)\n", + slot_name(p_slot)); + mutex_unlock(&p_slot->ctrl->crit_sect); + return -ENODEV; + } + } + + if (POWER_CTRL(p_slot->ctrl)) { + rc = p_slot->hpc_ops->get_power_status(p_slot, &getstatus); + if (rc || getstatus) { + ctrl_info(ctrl, "Already enabled on slot(%s)\n", + slot_name(p_slot)); + mutex_unlock(&p_slot->ctrl->crit_sect); + return -EINVAL; + } + } + + p_slot->hpc_ops->get_latch_status(p_slot, &getstatus); + + rc = board_added(p_slot); + if (rc) { + p_slot->hpc_ops->get_latch_status(p_slot, &getstatus); + } + + update_slot_info(p_slot); + + mutex_unlock(&p_slot->ctrl->crit_sect); + return rc; +} + + +int pciehp_disable_slot(struct slot *p_slot) +{ + u8 getstatus = 0; + int ret = 0; + struct controller *ctrl = p_slot->ctrl; + + if (!p_slot->ctrl) + return 1; + + /* Check to see if (latch closed, card present, power on) */ + mutex_lock(&p_slot->ctrl->crit_sect); + + if (!HP_SUPR_RM(p_slot->ctrl)) { + ret = p_slot->hpc_ops->get_adapter_status(p_slot, &getstatus); + if (ret || !getstatus) { + ctrl_info(ctrl, "No adapter on slot(%s)\n", + slot_name(p_slot)); + mutex_unlock(&p_slot->ctrl->crit_sect); + return -ENODEV; + } + } + + if (MRL_SENS(p_slot->ctrl)) { + ret = p_slot->hpc_ops->get_latch_status(p_slot, &getstatus); + if (ret || getstatus) { + ctrl_info(ctrl, "Latch open on slot(%s)\n", + slot_name(p_slot)); + mutex_unlock(&p_slot->ctrl->crit_sect); + return -ENODEV; + } + } + + if (POWER_CTRL(p_slot->ctrl)) { + ret = p_slot->hpc_ops->get_power_status(p_slot, &getstatus); + if (ret || !getstatus) { + ctrl_info(ctrl, "Already disabled on slot(%s)\n", + slot_name(p_slot)); + mutex_unlock(&p_slot->ctrl->crit_sect); + return -EINVAL; + } + } + + ret = remove_board(p_slot); + update_slot_info(p_slot); + + mutex_unlock(&p_slot->ctrl->crit_sect); + return ret; +} + +int pciehp_sysfs_enable_slot(struct slot *p_slot) +{ + int retval = -ENODEV; + struct controller *ctrl = p_slot->ctrl; + + mutex_lock(&p_slot->lock); + switch (p_slot->state) { + case BLINKINGON_STATE: + cancel_delayed_work(&p_slot->work); + case STATIC_STATE: + p_slot->state = POWERON_STATE; + mutex_unlock(&p_slot->lock); + retval = pciehp_enable_slot(p_slot); + mutex_lock(&p_slot->lock); + p_slot->state = STATIC_STATE; + break; + case POWERON_STATE: + ctrl_info(ctrl, "Slot %s is already in powering on state\n", + slot_name(p_slot)); + break; + case BLINKINGOFF_STATE: + case POWEROFF_STATE: + ctrl_info(ctrl, "Already enabled on slot %s\n", + slot_name(p_slot)); + break; + default: + ctrl_err(ctrl, "Not a valid state on slot %s\n", + slot_name(p_slot)); + break; + } + mutex_unlock(&p_slot->lock); + + return retval; +} + +int pciehp_sysfs_disable_slot(struct slot *p_slot) +{ + int retval = -ENODEV; + struct controller *ctrl = p_slot->ctrl; + + mutex_lock(&p_slot->lock); + switch (p_slot->state) { + case BLINKINGOFF_STATE: + cancel_delayed_work(&p_slot->work); + case STATIC_STATE: + p_slot->state = POWEROFF_STATE; + mutex_unlock(&p_slot->lock); + retval = pciehp_disable_slot(p_slot); + mutex_lock(&p_slot->lock); + p_slot->state = STATIC_STATE; + break; + case POWEROFF_STATE: + ctrl_info(ctrl, "Slot %s is already in powering off state\n", + slot_name(p_slot)); + break; + case BLINKINGON_STATE: + case POWERON_STATE: + ctrl_info(ctrl, "Already disabled on slot %s\n", + slot_name(p_slot)); + break; + default: + ctrl_err(ctrl, "Not a valid state on slot %s\n", + slot_name(p_slot)); + break; + } + mutex_unlock(&p_slot->lock); + + return retval; +} diff --git a/drivers/pci/hotplug/pciehp_hpc.c b/drivers/pci/hotplug/pciehp_hpc.c new file mode 100644 index 0000000..b643ca1 --- /dev/null +++ b/drivers/pci/hotplug/pciehp_hpc.c @@ -0,0 +1,1268 @@ +/* + * PCI Express PCI Hot Plug Driver + * + * Copyright (C) 1995,2001 Compaq Computer Corporation + * Copyright (C) 2001 Greg Kroah-Hartman (greg@kroah.com) + * Copyright (C) 2001 IBM Corp. + * Copyright (C) 2003-2004 Intel Corporation + * + * 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>,<kristen.c.accardi@intel.com> + * + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/types.h> +#include <linux/signal.h> +#include <linux/jiffies.h> +#include <linux/timer.h> +#include <linux/pci.h> +#include <linux/interrupt.h> +#include <linux/time.h> + +#include "../pci.h" +#include "pciehp.h" + +static atomic_t pciehp_num_controllers = ATOMIC_INIT(0); + +struct ctrl_reg { + u8 cap_id; + u8 nxt_ptr; + u16 cap_reg; + u32 dev_cap; + u16 dev_ctrl; + u16 dev_status; + u32 lnk_cap; + u16 lnk_ctrl; + u16 lnk_status; + u32 slot_cap; + u16 slot_ctrl; + u16 slot_status; + u16 root_ctrl; + u16 rsvp; + u32 root_status; +} __attribute__ ((packed)); + +/* offsets to the controller registers based on the above structure layout */ +enum ctrl_offsets { + PCIECAPID = offsetof(struct ctrl_reg, cap_id), + NXTCAPPTR = offsetof(struct ctrl_reg, nxt_ptr), + CAPREG = offsetof(struct ctrl_reg, cap_reg), + DEVCAP = offsetof(struct ctrl_reg, dev_cap), + DEVCTRL = offsetof(struct ctrl_reg, dev_ctrl), + DEVSTATUS = offsetof(struct ctrl_reg, dev_status), + LNKCAP = offsetof(struct ctrl_reg, lnk_cap), + LNKCTRL = offsetof(struct ctrl_reg, lnk_ctrl), + LNKSTATUS = offsetof(struct ctrl_reg, lnk_status), + SLOTCAP = offsetof(struct ctrl_reg, slot_cap), + SLOTCTRL = offsetof(struct ctrl_reg, slot_ctrl), + SLOTSTATUS = offsetof(struct ctrl_reg, slot_status), + ROOTCTRL = offsetof(struct ctrl_reg, root_ctrl), + ROOTSTATUS = offsetof(struct ctrl_reg, root_status), +}; + +static inline int pciehp_readw(struct controller *ctrl, int reg, u16 *value) +{ + struct pci_dev *dev = ctrl->pci_dev; + return pci_read_config_word(dev, ctrl->cap_base + reg, value); +} + +static inline int pciehp_readl(struct controller *ctrl, int reg, u32 *value) +{ + struct pci_dev *dev = ctrl->pci_dev; + return pci_read_config_dword(dev, ctrl->cap_base + reg, value); +} + +static inline int pciehp_writew(struct controller *ctrl, int reg, u16 value) +{ + struct pci_dev *dev = ctrl->pci_dev; + return pci_write_config_word(dev, ctrl->cap_base + reg, value); +} + +static inline int pciehp_writel(struct controller *ctrl, int reg, u32 value) +{ + struct pci_dev *dev = ctrl->pci_dev; + return pci_write_config_dword(dev, ctrl->cap_base + reg, value); +} + +/* Field definitions in PCI Express Capabilities Register */ +#define CAP_VER 0x000F +#define DEV_PORT_TYPE 0x00F0 +#define SLOT_IMPL 0x0100 +#define MSG_NUM 0x3E00 + +/* Device or Port Type */ +#define NAT_ENDPT 0x00 +#define LEG_ENDPT 0x01 +#define ROOT_PORT 0x04 +#define UP_STREAM 0x05 +#define DN_STREAM 0x06 +#define PCIE_PCI_BRDG 0x07 +#define PCI_PCIE_BRDG 0x10 + +/* Field definitions in Device Capabilities Register */ +#define DATTN_BUTTN_PRSN 0x1000 +#define DATTN_LED_PRSN 0x2000 +#define DPWR_LED_PRSN 0x4000 + +/* Field definitions in Link Capabilities Register */ +#define MAX_LNK_SPEED 0x000F +#define MAX_LNK_WIDTH 0x03F0 +#define LINK_ACTIVE_REPORTING 0x00100000 + +/* Link Width Encoding */ +#define LNK_X1 0x01 +#define LNK_X2 0x02 +#define LNK_X4 0x04 +#define LNK_X8 0x08 +#define LNK_X12 0x0C +#define LNK_X16 0x10 +#define LNK_X32 0x20 + +/*Field definitions of Link Status Register */ +#define LNK_SPEED 0x000F +#define NEG_LINK_WD 0x03F0 +#define LNK_TRN_ERR 0x0400 +#define LNK_TRN 0x0800 +#define SLOT_CLK_CONF 0x1000 +#define LINK_ACTIVE 0x2000 + +/* Field definitions in Slot Capabilities Register */ +#define ATTN_BUTTN_PRSN 0x00000001 +#define PWR_CTRL_PRSN 0x00000002 +#define MRL_SENS_PRSN 0x00000004 +#define ATTN_LED_PRSN 0x00000008 +#define PWR_LED_PRSN 0x00000010 +#define HP_SUPR_RM_SUP 0x00000020 +#define HP_CAP 0x00000040 +#define SLOT_PWR_VALUE 0x000003F8 +#define SLOT_PWR_LIMIT 0x00000C00 +#define PSN 0xFFF80000 /* PSN: Physical Slot Number */ + +/* Field definitions in Slot Control Register */ +#define ATTN_BUTTN_ENABLE 0x0001 +#define PWR_FAULT_DETECT_ENABLE 0x0002 +#define MRL_DETECT_ENABLE 0x0004 +#define PRSN_DETECT_ENABLE 0x0008 +#define CMD_CMPL_INTR_ENABLE 0x0010 +#define HP_INTR_ENABLE 0x0020 +#define ATTN_LED_CTRL 0x00C0 +#define PWR_LED_CTRL 0x0300 +#define PWR_CTRL 0x0400 +#define EMI_CTRL 0x0800 + +/* Attention indicator and Power indicator states */ +#define LED_ON 0x01 +#define LED_BLINK 0x10 +#define LED_OFF 0x11 + +/* Power Control Command */ +#define POWER_ON 0 +#define POWER_OFF 0x0400 + +/* EMI Status defines */ +#define EMI_DISENGAGED 0 +#define EMI_ENGAGED 1 + +/* Field definitions in Slot Status Register */ +#define ATTN_BUTTN_PRESSED 0x0001 +#define PWR_FAULT_DETECTED 0x0002 +#define MRL_SENS_CHANGED 0x0004 +#define PRSN_DETECT_CHANGED 0x0008 +#define CMD_COMPLETED 0x0010 +#define MRL_STATE 0x0020 +#define PRSN_STATE 0x0040 +#define EMI_STATE 0x0080 +#define EMI_STATUS_BIT 7 + +static irqreturn_t pcie_isr(int irq, void *dev_id); +static void start_int_poll_timer(struct controller *ctrl, int sec); + +/* This is the interrupt polling timeout function. */ +static void int_poll_timeout(unsigned long data) +{ + struct controller *ctrl = (struct controller *)data; + + /* Poll for interrupt events. regs == NULL => polling */ + pcie_isr(0, ctrl); + + init_timer(&ctrl->poll_timer); + if (!pciehp_poll_time) + pciehp_poll_time = 2; /* default polling interval is 2 sec */ + + start_int_poll_timer(ctrl, pciehp_poll_time); +} + +/* This function starts the interrupt polling timer. */ +static void start_int_poll_timer(struct controller *ctrl, int sec) +{ + /* Clamp to sane value */ + if ((sec <= 0) || (sec > 60)) + sec = 2; + + ctrl->poll_timer.function = &int_poll_timeout; + ctrl->poll_timer.data = (unsigned long)ctrl; + ctrl->poll_timer.expires = jiffies + sec * HZ; + add_timer(&ctrl->poll_timer); +} + +static inline int pciehp_request_irq(struct controller *ctrl) +{ + int retval, irq = ctrl->pcie->irq; + + /* Install interrupt polling timer. Start with 10 sec delay */ + if (pciehp_poll_mode) { + init_timer(&ctrl->poll_timer); + start_int_poll_timer(ctrl, 10); + return 0; + } + + /* Installs the interrupt handler */ + retval = request_irq(irq, pcie_isr, IRQF_SHARED, MY_NAME, ctrl); + if (retval) + ctrl_err(ctrl, "Cannot get irq %d for the hotplug controller\n", + irq); + return retval; +} + +static inline void pciehp_free_irq(struct controller *ctrl) +{ + if (pciehp_poll_mode) + del_timer_sync(&ctrl->poll_timer); + else + free_irq(ctrl->pcie->irq, ctrl); +} + +static int pcie_poll_cmd(struct controller *ctrl) +{ + u16 slot_status; + int timeout = 1000; + + if (!pciehp_readw(ctrl, SLOTSTATUS, &slot_status)) { + if (slot_status & CMD_COMPLETED) { + pciehp_writew(ctrl, SLOTSTATUS, CMD_COMPLETED); + return 1; + } + } + while (timeout > 0) { + msleep(10); + timeout -= 10; + if (!pciehp_readw(ctrl, SLOTSTATUS, &slot_status)) { + if (slot_status & CMD_COMPLETED) { + pciehp_writew(ctrl, SLOTSTATUS, CMD_COMPLETED); + return 1; + } + } + } + return 0; /* timeout */ +} + +static void pcie_wait_cmd(struct controller *ctrl, int poll) +{ + unsigned int msecs = pciehp_poll_mode ? 2500 : 1000; + unsigned long timeout = msecs_to_jiffies(msecs); + int rc; + + if (poll) + rc = pcie_poll_cmd(ctrl); + else + rc = wait_event_timeout(ctrl->queue, !ctrl->cmd_busy, timeout); + if (!rc) + ctrl_dbg(ctrl, "Command not completed in 1000 msec\n"); +} + +/** + * pcie_write_cmd - Issue controller command + * @ctrl: controller to which the command is issued + * @cmd: command value written to slot control register + * @mask: bitmask of slot control register to be modified + */ +static int pcie_write_cmd(struct controller *ctrl, u16 cmd, u16 mask) +{ + int retval = 0; + u16 slot_status; + u16 slot_ctrl; + + mutex_lock(&ctrl->ctrl_lock); + + retval = pciehp_readw(ctrl, SLOTSTATUS, &slot_status); + if (retval) { + ctrl_err(ctrl, "%s: Cannot read SLOTSTATUS register\n", + __func__); + goto out; + } + + if (slot_status & CMD_COMPLETED) { + if (!ctrl->no_cmd_complete) { + /* + * After 1 sec and CMD_COMPLETED still not set, just + * proceed forward to issue the next command according + * to spec. Just print out the error message. + */ + ctrl_dbg(ctrl, "CMD_COMPLETED not clear after 1 sec\n"); + } else if (!NO_CMD_CMPL(ctrl)) { + /* + * This controller semms to notify of command completed + * event even though it supports none of power + * controller, attention led, power led and EMI. + */ + ctrl_dbg(ctrl, "Unexpected CMD_COMPLETED. Need to " + "wait for command completed event.\n"); + ctrl->no_cmd_complete = 0; + } else { + ctrl_dbg(ctrl, "Unexpected CMD_COMPLETED. Maybe " + "the controller is broken.\n"); + } + } + + retval = pciehp_readw(ctrl, SLOTCTRL, &slot_ctrl); + if (retval) { + ctrl_err(ctrl, "%s: Cannot read SLOTCTRL register\n", __func__); + goto out; + } + + slot_ctrl &= ~mask; + slot_ctrl |= (cmd & mask); + ctrl->cmd_busy = 1; + smp_mb(); + retval = pciehp_writew(ctrl, SLOTCTRL, slot_ctrl); + if (retval) + ctrl_err(ctrl, "Cannot write to SLOTCTRL register\n"); + + /* + * Wait for command completion. + */ + if (!retval && !ctrl->no_cmd_complete) { + int poll = 0; + /* + * if hotplug interrupt is not enabled or command + * completed interrupt is not enabled, we need to poll + * command completed event. + */ + if (!(slot_ctrl & HP_INTR_ENABLE) || + !(slot_ctrl & CMD_CMPL_INTR_ENABLE)) + poll = 1; + pcie_wait_cmd(ctrl, poll); + } + out: + mutex_unlock(&ctrl->ctrl_lock); + return retval; +} + +static inline int check_link_active(struct controller *ctrl) +{ + u16 link_status; + + if (pciehp_readw(ctrl, LNKSTATUS, &link_status)) + return 0; + return !!(link_status & LINK_ACTIVE); +} + +static void pcie_wait_link_active(struct controller *ctrl) +{ + int timeout = 1000; + + if (check_link_active(ctrl)) + return; + while (timeout > 0) { + msleep(10); + timeout -= 10; + if (check_link_active(ctrl)) + return; + } + ctrl_dbg(ctrl, "Data Link Layer Link Active not set in 1000 msec\n"); +} + +static int hpc_check_lnk_status(struct controller *ctrl) +{ + u16 lnk_status; + int retval = 0; + + /* + * Data Link Layer Link Active Reporting must be capable for + * hot-plug capable downstream port. But old controller might + * not implement it. In this case, we wait for 1000 ms. + */ + if (ctrl->link_active_reporting){ + /* Wait for Data Link Layer Link Active bit to be set */ + pcie_wait_link_active(ctrl); + /* + * We must wait for 100 ms after the Data Link Layer + * Link Active bit reads 1b before initiating a + * configuration access to the hot added device. + */ + msleep(100); + } else + msleep(1000); + + retval = pciehp_readw(ctrl, LNKSTATUS, &lnk_status); + if (retval) { + ctrl_err(ctrl, "Cannot read LNKSTATUS register\n"); + return retval; + } + + ctrl_dbg(ctrl, "%s: lnk_status = %x\n", __func__, lnk_status); + if ( (lnk_status & LNK_TRN) || (lnk_status & LNK_TRN_ERR) || + !(lnk_status & NEG_LINK_WD)) { + ctrl_err(ctrl, "Link Training Error occurs \n"); + retval = -1; + return retval; + } + + return retval; +} + +static int hpc_get_attention_status(struct slot *slot, u8 *status) +{ + struct controller *ctrl = slot->ctrl; + u16 slot_ctrl; + u8 atten_led_state; + int retval = 0; + + retval = pciehp_readw(ctrl, SLOTCTRL, &slot_ctrl); + if (retval) { + ctrl_err(ctrl, "%s: Cannot read SLOTCTRL register\n", __func__); + return retval; + } + + ctrl_dbg(ctrl, "%s: SLOTCTRL %x, value read %x\n", + __func__, ctrl->cap_base + SLOTCTRL, slot_ctrl); + + atten_led_state = (slot_ctrl & ATTN_LED_CTRL) >> 6; + + switch (atten_led_state) { + case 0: + *status = 0xFF; /* Reserved */ + break; + case 1: + *status = 1; /* On */ + break; + case 2: + *status = 2; /* Blink */ + break; + case 3: + *status = 0; /* Off */ + break; + default: + *status = 0xFF; + break; + } + + return 0; +} + +static int hpc_get_power_status(struct slot *slot, u8 *status) +{ + struct controller *ctrl = slot->ctrl; + u16 slot_ctrl; + u8 pwr_state; + int retval = 0; + + retval = pciehp_readw(ctrl, SLOTCTRL, &slot_ctrl); + if (retval) { + ctrl_err(ctrl, "%s: Cannot read SLOTCTRL register\n", __func__); + return retval; + } + ctrl_dbg(ctrl, "%s: SLOTCTRL %x value read %x\n", + __func__, ctrl->cap_base + SLOTCTRL, slot_ctrl); + + pwr_state = (slot_ctrl & PWR_CTRL) >> 10; + + switch (pwr_state) { + case 0: + *status = 1; + break; + case 1: + *status = 0; + break; + default: + *status = 0xFF; + break; + } + + return retval; +} + +static int hpc_get_latch_status(struct slot *slot, u8 *status) +{ + struct controller *ctrl = slot->ctrl; + u16 slot_status; + int retval = 0; + + retval = pciehp_readw(ctrl, SLOTSTATUS, &slot_status); + if (retval) { + ctrl_err(ctrl, "%s: Cannot read SLOTSTATUS register\n", + __func__); + return retval; + } + + *status = (((slot_status & MRL_STATE) >> 5) == 0) ? 0 : 1; + + return 0; +} + +static int hpc_get_adapter_status(struct slot *slot, u8 *status) +{ + struct controller *ctrl = slot->ctrl; + u16 slot_status; + u8 card_state; + int retval = 0; + + retval = pciehp_readw(ctrl, SLOTSTATUS, &slot_status); + if (retval) { + ctrl_err(ctrl, "%s: Cannot read SLOTSTATUS register\n", + __func__); + return retval; + } + card_state = (u8)((slot_status & PRSN_STATE) >> 6); + *status = (card_state == 1) ? 1 : 0; + + return 0; +} + +static int hpc_query_power_fault(struct slot *slot) +{ + struct controller *ctrl = slot->ctrl; + u16 slot_status; + u8 pwr_fault; + int retval = 0; + + retval = pciehp_readw(ctrl, SLOTSTATUS, &slot_status); + if (retval) { + ctrl_err(ctrl, "Cannot check for power fault\n"); + return retval; + } + pwr_fault = (u8)((slot_status & PWR_FAULT_DETECTED) >> 1); + + return pwr_fault; +} + +static int hpc_get_emi_status(struct slot *slot, u8 *status) +{ + struct controller *ctrl = slot->ctrl; + u16 slot_status; + int retval = 0; + + retval = pciehp_readw(ctrl, SLOTSTATUS, &slot_status); + if (retval) { + ctrl_err(ctrl, "Cannot check EMI status\n"); + return retval; + } + *status = (slot_status & EMI_STATE) >> EMI_STATUS_BIT; + + return retval; +} + +static int hpc_toggle_emi(struct slot *slot) +{ + u16 slot_cmd; + u16 cmd_mask; + int rc; + + slot_cmd = EMI_CTRL; + cmd_mask = EMI_CTRL; + rc = pcie_write_cmd(slot->ctrl, slot_cmd, cmd_mask); + slot->last_emi_toggle = get_seconds(); + + return rc; +} + +static int hpc_set_attention_status(struct slot *slot, u8 value) +{ + struct controller *ctrl = slot->ctrl; + u16 slot_cmd; + u16 cmd_mask; + int rc; + + cmd_mask = ATTN_LED_CTRL; + switch (value) { + case 0 : /* turn off */ + slot_cmd = 0x00C0; + break; + case 1: /* turn on */ + slot_cmd = 0x0040; + break; + case 2: /* turn blink */ + slot_cmd = 0x0080; + break; + default: + return -1; + } + rc = pcie_write_cmd(ctrl, slot_cmd, cmd_mask); + ctrl_dbg(ctrl, "%s: SLOTCTRL %x write cmd %x\n", + __func__, ctrl->cap_base + SLOTCTRL, slot_cmd); + + return rc; +} + +static void hpc_set_green_led_on(struct slot *slot) +{ + struct controller *ctrl = slot->ctrl; + u16 slot_cmd; + u16 cmd_mask; + + slot_cmd = 0x0100; + cmd_mask = PWR_LED_CTRL; + pcie_write_cmd(ctrl, slot_cmd, cmd_mask); + ctrl_dbg(ctrl, "%s: SLOTCTRL %x write cmd %x\n", + __func__, ctrl->cap_base + SLOTCTRL, slot_cmd); +} + +static void hpc_set_green_led_off(struct slot *slot) +{ + struct controller *ctrl = slot->ctrl; + u16 slot_cmd; + u16 cmd_mask; + + slot_cmd = 0x0300; + cmd_mask = PWR_LED_CTRL; + pcie_write_cmd(ctrl, slot_cmd, cmd_mask); + ctrl_dbg(ctrl, "%s: SLOTCTRL %x write cmd %x\n", + __func__, ctrl->cap_base + SLOTCTRL, slot_cmd); +} + +static void hpc_set_green_led_blink(struct slot *slot) +{ + struct controller *ctrl = slot->ctrl; + u16 slot_cmd; + u16 cmd_mask; + + slot_cmd = 0x0200; + cmd_mask = PWR_LED_CTRL; + pcie_write_cmd(ctrl, slot_cmd, cmd_mask); + ctrl_dbg(ctrl, "%s: SLOTCTRL %x write cmd %x\n", + __func__, ctrl->cap_base + SLOTCTRL, slot_cmd); +} + +static int hpc_power_on_slot(struct slot * slot) +{ + struct controller *ctrl = slot->ctrl; + u16 slot_cmd; + u16 cmd_mask; + u16 slot_status; + int retval = 0; + + ctrl_dbg(ctrl, "%s: slot->hp_slot %x\n", __func__, slot->hp_slot); + + /* Clear sticky power-fault bit from previous power failures */ + retval = pciehp_readw(ctrl, SLOTSTATUS, &slot_status); + if (retval) { + ctrl_err(ctrl, "%s: Cannot read SLOTSTATUS register\n", + __func__); + return retval; + } + slot_status &= PWR_FAULT_DETECTED; + if (slot_status) { + retval = pciehp_writew(ctrl, SLOTSTATUS, slot_status); + if (retval) { + ctrl_err(ctrl, + "%s: Cannot write to SLOTSTATUS register\n", + __func__); + return retval; + } + } + + slot_cmd = POWER_ON; + cmd_mask = PWR_CTRL; + /* Enable detection that we turned off at slot power-off time */ + if (!pciehp_poll_mode) { + slot_cmd |= (PWR_FAULT_DETECT_ENABLE | MRL_DETECT_ENABLE | + PRSN_DETECT_ENABLE); + cmd_mask |= (PWR_FAULT_DETECT_ENABLE | MRL_DETECT_ENABLE | + PRSN_DETECT_ENABLE); + } + + retval = pcie_write_cmd(ctrl, slot_cmd, cmd_mask); + + if (retval) { + ctrl_err(ctrl, "Write %x command failed!\n", slot_cmd); + return -1; + } + ctrl_dbg(ctrl, "%s: SLOTCTRL %x write cmd %x\n", + __func__, ctrl->cap_base + SLOTCTRL, slot_cmd); + + return retval; +} + +static inline int pcie_mask_bad_dllp(struct controller *ctrl) +{ + struct pci_dev *dev = ctrl->pci_dev; + int pos; + u32 reg; + + pos = pci_find_ext_capability(dev, PCI_EXT_CAP_ID_ERR); + if (!pos) + return 0; + pci_read_config_dword(dev, pos + PCI_ERR_COR_MASK, ®); + if (reg & PCI_ERR_COR_BAD_DLLP) + return 0; + reg |= PCI_ERR_COR_BAD_DLLP; + pci_write_config_dword(dev, pos + PCI_ERR_COR_MASK, reg); + return 1; +} + +static inline void pcie_unmask_bad_dllp(struct controller *ctrl) +{ + struct pci_dev *dev = ctrl->pci_dev; + u32 reg; + int pos; + + pos = pci_find_ext_capability(dev, PCI_EXT_CAP_ID_ERR); + if (!pos) + return; + pci_read_config_dword(dev, pos + PCI_ERR_COR_MASK, ®); + if (!(reg & PCI_ERR_COR_BAD_DLLP)) + return; + reg &= ~PCI_ERR_COR_BAD_DLLP; + pci_write_config_dword(dev, pos + PCI_ERR_COR_MASK, reg); +} + +static int hpc_power_off_slot(struct slot * slot) +{ + struct controller *ctrl = slot->ctrl; + u16 slot_cmd; + u16 cmd_mask; + int retval = 0; + int changed; + + ctrl_dbg(ctrl, "%s: slot->hp_slot %x\n", __func__, slot->hp_slot); + + /* + * Set Bad DLLP Mask bit in Correctable Error Mask + * Register. This is the workaround against Bad DLLP error + * that sometimes happens during turning power off the slot + * which conforms to PCI Express 1.0a spec. + */ + changed = pcie_mask_bad_dllp(ctrl); + + slot_cmd = POWER_OFF; + cmd_mask = PWR_CTRL; + /* + * If we get MRL or presence detect interrupts now, the isr + * will notice the sticky power-fault bit too and issue power + * indicator change commands. This will lead to an endless loop + * of command completions, since the power-fault bit remains on + * till the slot is powered on again. + */ + if (!pciehp_poll_mode) { + slot_cmd &= ~(PWR_FAULT_DETECT_ENABLE | MRL_DETECT_ENABLE | + PRSN_DETECT_ENABLE); + cmd_mask |= (PWR_FAULT_DETECT_ENABLE | MRL_DETECT_ENABLE | + PRSN_DETECT_ENABLE); + } + + retval = pcie_write_cmd(ctrl, slot_cmd, cmd_mask); + if (retval) { + ctrl_err(ctrl, "Write command failed!\n"); + retval = -1; + goto out; + } + ctrl_dbg(ctrl, "%s: SLOTCTRL %x write cmd %x\n", + __func__, ctrl->cap_base + SLOTCTRL, slot_cmd); + out: + if (changed) + pcie_unmask_bad_dllp(ctrl); + + return retval; +} + +static irqreturn_t pcie_isr(int irq, void *dev_id) +{ + struct controller *ctrl = (struct controller *)dev_id; + u16 detected, intr_loc; + struct slot *p_slot; + + /* + * In order to guarantee that all interrupt events are + * serviced, we need to re-inspect Slot Status register after + * clearing what is presumed to be the last pending interrupt. + */ + intr_loc = 0; + do { + if (pciehp_readw(ctrl, SLOTSTATUS, &detected)) { + ctrl_err(ctrl, "%s: Cannot read SLOTSTATUS\n", + __func__); + return IRQ_NONE; + } + + detected &= (ATTN_BUTTN_PRESSED | PWR_FAULT_DETECTED | + MRL_SENS_CHANGED | PRSN_DETECT_CHANGED | + CMD_COMPLETED); + intr_loc |= detected; + if (!intr_loc) + return IRQ_NONE; + if (detected && pciehp_writew(ctrl, SLOTSTATUS, detected)) { + ctrl_err(ctrl, "%s: Cannot write to SLOTSTATUS\n", + __func__); + return IRQ_NONE; + } + } while (detected); + + ctrl_dbg(ctrl, "%s: intr_loc %x\n", __func__, intr_loc); + + /* Check Command Complete Interrupt Pending */ + if (intr_loc & CMD_COMPLETED) { + ctrl->cmd_busy = 0; + smp_mb(); + wake_up(&ctrl->queue); + } + + if (!(intr_loc & ~CMD_COMPLETED)) + return IRQ_HANDLED; + + p_slot = pciehp_find_slot(ctrl, ctrl->slot_device_offset); + + /* Check MRL Sensor Changed */ + if (intr_loc & MRL_SENS_CHANGED) + pciehp_handle_switch_change(p_slot); + + /* Check Attention Button Pressed */ + if (intr_loc & ATTN_BUTTN_PRESSED) + pciehp_handle_attention_button(p_slot); + + /* Check Presence Detect Changed */ + if (intr_loc & PRSN_DETECT_CHANGED) + pciehp_handle_presence_change(p_slot); + + /* Check Power Fault Detected */ + if (intr_loc & PWR_FAULT_DETECTED) + pciehp_handle_power_fault(p_slot); + + return IRQ_HANDLED; +} + +static int hpc_get_max_lnk_speed(struct slot *slot, enum pci_bus_speed *value) +{ + struct controller *ctrl = slot->ctrl; + enum pcie_link_speed lnk_speed; + u32 lnk_cap; + int retval = 0; + + retval = pciehp_readl(ctrl, LNKCAP, &lnk_cap); + if (retval) { + ctrl_err(ctrl, "%s: Cannot read LNKCAP register\n", __func__); + return retval; + } + + switch (lnk_cap & 0x000F) { + case 1: + lnk_speed = PCIE_2PT5GB; + break; + default: + lnk_speed = PCIE_LNK_SPEED_UNKNOWN; + break; + } + + *value = lnk_speed; + ctrl_dbg(ctrl, "Max link speed = %d\n", lnk_speed); + + return retval; +} + +static int hpc_get_max_lnk_width(struct slot *slot, + enum pcie_link_width *value) +{ + struct controller *ctrl = slot->ctrl; + enum pcie_link_width lnk_wdth; + u32 lnk_cap; + int retval = 0; + + retval = pciehp_readl(ctrl, LNKCAP, &lnk_cap); + if (retval) { + ctrl_err(ctrl, "%s: Cannot read LNKCAP register\n", __func__); + return retval; + } + + switch ((lnk_cap & 0x03F0) >> 4){ + case 0: + lnk_wdth = PCIE_LNK_WIDTH_RESRV; + break; + case 1: + lnk_wdth = PCIE_LNK_X1; + break; + case 2: + lnk_wdth = PCIE_LNK_X2; + break; + case 4: + lnk_wdth = PCIE_LNK_X4; + break; + case 8: + lnk_wdth = PCIE_LNK_X8; + break; + case 12: + lnk_wdth = PCIE_LNK_X12; + break; + case 16: + lnk_wdth = PCIE_LNK_X16; + break; + case 32: + lnk_wdth = PCIE_LNK_X32; + break; + default: + lnk_wdth = PCIE_LNK_WIDTH_UNKNOWN; + break; + } + + *value = lnk_wdth; + ctrl_dbg(ctrl, "Max link width = %d\n", lnk_wdth); + + return retval; +} + +static int hpc_get_cur_lnk_speed(struct slot *slot, enum pci_bus_speed *value) +{ + struct controller *ctrl = slot->ctrl; + enum pcie_link_speed lnk_speed = PCI_SPEED_UNKNOWN; + int retval = 0; + u16 lnk_status; + + retval = pciehp_readw(ctrl, LNKSTATUS, &lnk_status); + if (retval) { + ctrl_err(ctrl, "%s: Cannot read LNKSTATUS register\n", + __func__); + return retval; + } + + switch (lnk_status & 0x0F) { + case 1: + lnk_speed = PCIE_2PT5GB; + break; + default: + lnk_speed = PCIE_LNK_SPEED_UNKNOWN; + break; + } + + *value = lnk_speed; + ctrl_dbg(ctrl, "Current link speed = %d\n", lnk_speed); + + return retval; +} + +static int hpc_get_cur_lnk_width(struct slot *slot, + enum pcie_link_width *value) +{ + struct controller *ctrl = slot->ctrl; + enum pcie_link_width lnk_wdth = PCIE_LNK_WIDTH_UNKNOWN; + int retval = 0; + u16 lnk_status; + + retval = pciehp_readw(ctrl, LNKSTATUS, &lnk_status); + if (retval) { + ctrl_err(ctrl, "%s: Cannot read LNKSTATUS register\n", + __func__); + return retval; + } + + switch ((lnk_status & 0x03F0) >> 4){ + case 0: + lnk_wdth = PCIE_LNK_WIDTH_RESRV; + break; + case 1: + lnk_wdth = PCIE_LNK_X1; + break; + case 2: + lnk_wdth = PCIE_LNK_X2; + break; + case 4: + lnk_wdth = PCIE_LNK_X4; + break; + case 8: + lnk_wdth = PCIE_LNK_X8; + break; + case 12: + lnk_wdth = PCIE_LNK_X12; + break; + case 16: + lnk_wdth = PCIE_LNK_X16; + break; + case 32: + lnk_wdth = PCIE_LNK_X32; + break; + default: + lnk_wdth = PCIE_LNK_WIDTH_UNKNOWN; + break; + } + + *value = lnk_wdth; + ctrl_dbg(ctrl, "Current link width = %d\n", lnk_wdth); + + return retval; +} + +static void pcie_release_ctrl(struct controller *ctrl); +static struct hpc_ops pciehp_hpc_ops = { + .power_on_slot = hpc_power_on_slot, + .power_off_slot = hpc_power_off_slot, + .set_attention_status = hpc_set_attention_status, + .get_power_status = hpc_get_power_status, + .get_attention_status = hpc_get_attention_status, + .get_latch_status = hpc_get_latch_status, + .get_adapter_status = hpc_get_adapter_status, + .get_emi_status = hpc_get_emi_status, + .toggle_emi = hpc_toggle_emi, + + .get_max_bus_speed = hpc_get_max_lnk_speed, + .get_cur_bus_speed = hpc_get_cur_lnk_speed, + .get_max_lnk_width = hpc_get_max_lnk_width, + .get_cur_lnk_width = hpc_get_cur_lnk_width, + + .query_power_fault = hpc_query_power_fault, + .green_led_on = hpc_set_green_led_on, + .green_led_off = hpc_set_green_led_off, + .green_led_blink = hpc_set_green_led_blink, + + .release_ctlr = pcie_release_ctrl, + .check_lnk_status = hpc_check_lnk_status, +}; + +int pcie_enable_notification(struct controller *ctrl) +{ + u16 cmd, mask; + + cmd = PRSN_DETECT_ENABLE; + if (ATTN_BUTTN(ctrl)) + cmd |= ATTN_BUTTN_ENABLE; + if (POWER_CTRL(ctrl)) + cmd |= PWR_FAULT_DETECT_ENABLE; + if (MRL_SENS(ctrl)) + cmd |= MRL_DETECT_ENABLE; + if (!pciehp_poll_mode) + cmd |= HP_INTR_ENABLE | CMD_CMPL_INTR_ENABLE; + + mask = PRSN_DETECT_ENABLE | ATTN_BUTTN_ENABLE | MRL_DETECT_ENABLE | + PWR_FAULT_DETECT_ENABLE | HP_INTR_ENABLE | CMD_CMPL_INTR_ENABLE; + + if (pcie_write_cmd(ctrl, cmd, mask)) { + ctrl_err(ctrl, "Cannot enable software notification\n"); + return -1; + } + return 0; +} + +static void pcie_disable_notification(struct controller *ctrl) +{ + u16 mask; + mask = PRSN_DETECT_ENABLE | ATTN_BUTTN_ENABLE | MRL_DETECT_ENABLE | + PWR_FAULT_DETECT_ENABLE | HP_INTR_ENABLE | CMD_CMPL_INTR_ENABLE; + if (pcie_write_cmd(ctrl, 0, mask)) + ctrl_warn(ctrl, "Cannot disable software notification\n"); +} + +static int pcie_init_notification(struct controller *ctrl) +{ + if (pciehp_request_irq(ctrl)) + return -1; + if (pcie_enable_notification(ctrl)) { + pciehp_free_irq(ctrl); + return -1; + } + return 0; +} + +static void pcie_shutdown_notification(struct controller *ctrl) +{ + pcie_disable_notification(ctrl); + pciehp_free_irq(ctrl); +} + +static int pcie_init_slot(struct controller *ctrl) +{ + struct slot *slot; + + slot = kzalloc(sizeof(*slot), GFP_KERNEL); + if (!slot) + return -ENOMEM; + + slot->hp_slot = 0; + slot->ctrl = ctrl; + slot->bus = ctrl->pci_dev->subordinate->number; + slot->device = ctrl->slot_device_offset + slot->hp_slot; + slot->hpc_ops = ctrl->hpc_ops; + slot->number = ctrl->first_slot; + mutex_init(&slot->lock); + INIT_DELAYED_WORK(&slot->work, pciehp_queue_pushbutton_work); + list_add(&slot->slot_list, &ctrl->slot_list); + return 0; +} + +static void pcie_cleanup_slot(struct controller *ctrl) +{ + struct slot *slot; + slot = list_first_entry(&ctrl->slot_list, struct slot, slot_list); + list_del(&slot->slot_list); + cancel_delayed_work(&slot->work); + flush_scheduled_work(); + flush_workqueue(pciehp_wq); + kfree(slot); +} + +static inline void dbg_ctrl(struct controller *ctrl) +{ + int i; + u16 reg16; + struct pci_dev *pdev = ctrl->pci_dev; + + if (!pciehp_debug) + return; + + ctrl_info(ctrl, "Hotplug Controller:\n"); + ctrl_info(ctrl, " Seg/Bus/Dev/Func/IRQ : %s IRQ %d\n", + pci_name(pdev), pdev->irq); + ctrl_info(ctrl, " Vendor ID : 0x%04x\n", pdev->vendor); + ctrl_info(ctrl, " Device ID : 0x%04x\n", pdev->device); + ctrl_info(ctrl, " Subsystem ID : 0x%04x\n", + pdev->subsystem_device); + ctrl_info(ctrl, " Subsystem Vendor ID : 0x%04x\n", + pdev->subsystem_vendor); + ctrl_info(ctrl, " PCIe Cap offset : 0x%02x\n", ctrl->cap_base); + for (i = 0; i < DEVICE_COUNT_RESOURCE; i++) { + if (!pci_resource_len(pdev, i)) + continue; + ctrl_info(ctrl, " PCI resource [%d] : 0x%llx@0x%llx\n", + i, (unsigned long long)pci_resource_len(pdev, i), + (unsigned long long)pci_resource_start(pdev, i)); + } + ctrl_info(ctrl, "Slot Capabilities : 0x%08x\n", ctrl->slot_cap); + ctrl_info(ctrl, " Physical Slot Number : %d\n", ctrl->first_slot); + ctrl_info(ctrl, " Attention Button : %3s\n", + ATTN_BUTTN(ctrl) ? "yes" : "no"); + ctrl_info(ctrl, " Power Controller : %3s\n", + POWER_CTRL(ctrl) ? "yes" : "no"); + ctrl_info(ctrl, " MRL Sensor : %3s\n", + MRL_SENS(ctrl) ? "yes" : "no"); + ctrl_info(ctrl, " Attention Indicator : %3s\n", + ATTN_LED(ctrl) ? "yes" : "no"); + ctrl_info(ctrl, " Power Indicator : %3s\n", + PWR_LED(ctrl) ? "yes" : "no"); + ctrl_info(ctrl, " Hot-Plug Surprise : %3s\n", + HP_SUPR_RM(ctrl) ? "yes" : "no"); + ctrl_info(ctrl, " EMI Present : %3s\n", + EMI(ctrl) ? "yes" : "no"); + ctrl_info(ctrl, " Command Completed : %3s\n", + NO_CMD_CMPL(ctrl) ? "no" : "yes"); + pciehp_readw(ctrl, SLOTSTATUS, ®16); + ctrl_info(ctrl, "Slot Status : 0x%04x\n", reg16); + pciehp_readw(ctrl, SLOTCTRL, ®16); + ctrl_info(ctrl, "Slot Control : 0x%04x\n", reg16); +} + +struct controller *pcie_init(struct pcie_device *dev) +{ + struct controller *ctrl; + u32 slot_cap, link_cap; + struct pci_dev *pdev = dev->port; + + ctrl = kzalloc(sizeof(*ctrl), GFP_KERNEL); + if (!ctrl) { + dev_err(&dev->device, "%s: Out of memory\n", __func__); + goto abort; + } + INIT_LIST_HEAD(&ctrl->slot_list); + + ctrl->pcie = dev; + ctrl->pci_dev = pdev; + ctrl->cap_base = pci_find_capability(pdev, PCI_CAP_ID_EXP); + if (!ctrl->cap_base) { + ctrl_err(ctrl, "Cannot find PCI Express capability\n"); + goto abort_ctrl; + } + if (pciehp_readl(ctrl, SLOTCAP, &slot_cap)) { + ctrl_err(ctrl, "Cannot read SLOTCAP register\n"); + goto abort_ctrl; + } + + ctrl->slot_cap = slot_cap; + ctrl->first_slot = slot_cap >> 19; + ctrl->slot_device_offset = 0; + ctrl->num_slots = 1; + ctrl->hpc_ops = &pciehp_hpc_ops; + mutex_init(&ctrl->crit_sect); + mutex_init(&ctrl->ctrl_lock); + init_waitqueue_head(&ctrl->queue); + dbg_ctrl(ctrl); + /* + * Controller doesn't notify of command completion if the "No + * Command Completed Support" bit is set in Slot Capability + * register or the controller supports none of power + * controller, attention led, power led and EMI. + */ + if (NO_CMD_CMPL(ctrl) || + !(POWER_CTRL(ctrl) | ATTN_LED(ctrl) | PWR_LED(ctrl) | EMI(ctrl))) + ctrl->no_cmd_complete = 1; + + /* Check if Data Link Layer Link Active Reporting is implemented */ + if (pciehp_readl(ctrl, LNKCAP, &link_cap)) { + ctrl_err(ctrl, "%s: Cannot read LNKCAP register\n", __func__); + goto abort_ctrl; + } + if (link_cap & LINK_ACTIVE_REPORTING) { + ctrl_dbg(ctrl, "Link Active Reporting supported\n"); + ctrl->link_active_reporting = 1; + } + + /* Clear all remaining event bits in Slot Status register */ + if (pciehp_writew(ctrl, SLOTSTATUS, 0x1f)) + goto abort_ctrl; + + /* Disable sotfware notification */ + pcie_disable_notification(ctrl); + + /* + * If this is the first controller to be initialized, + * initialize the pciehp work queue + */ + if (atomic_add_return(1, &pciehp_num_controllers) == 1) { + pciehp_wq = create_singlethread_workqueue("pciehpd"); + if (!pciehp_wq) + goto abort_ctrl; + } + + ctrl_info(ctrl, "HPC vendor_id %x device_id %x ss_vid %x ss_did %x\n", + pdev->vendor, pdev->device, pdev->subsystem_vendor, + pdev->subsystem_device); + + if (pcie_init_slot(ctrl)) + goto abort_ctrl; + + if (pcie_init_notification(ctrl)) + goto abort_slot; + + return ctrl; + +abort_slot: + pcie_cleanup_slot(ctrl); +abort_ctrl: + kfree(ctrl); +abort: + return NULL; +} + +void pcie_release_ctrl(struct controller *ctrl) +{ + pcie_shutdown_notification(ctrl); + pcie_cleanup_slot(ctrl); + /* + * If this is the last controller to be released, destroy the + * pciehp work queue + */ + if (atomic_dec_and_test(&pciehp_num_controllers)) + destroy_workqueue(pciehp_wq); + kfree(ctrl); +} diff --git a/drivers/pci/hotplug/pciehp_pci.c b/drivers/pci/hotplug/pciehp_pci.c new file mode 100644 index 0000000..10f9566 --- /dev/null +++ b/drivers/pci/hotplug/pciehp_pci.c @@ -0,0 +1,295 @@ +/* + * PCI Express Hot Plug Controller Driver + * + * Copyright (C) 1995,2001 Compaq Computer Corporation + * Copyright (C) 2001 Greg Kroah-Hartman (greg@kroah.com) + * Copyright (C) 2001 IBM Corp. + * Copyright (C) 2003-2004 Intel Corporation + * + * 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>, <kristen.c.accardi@intel.com> + * + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/types.h> +#include <linux/pci.h> +#include "../pci.h" +#include "pciehp.h" + +static void program_hpp_type0(struct pci_dev *dev, struct hpp_type0 *hpp) +{ + u16 pci_cmd, pci_bctl; + + if (hpp->revision > 1) { + warn("Rev.%d type0 record not supported\n", hpp->revision); + return; + } + + pci_write_config_byte(dev, PCI_CACHE_LINE_SIZE, hpp->cache_line_size); + pci_write_config_byte(dev, PCI_LATENCY_TIMER, hpp->latency_timer); + pci_read_config_word(dev, PCI_COMMAND, &pci_cmd); + if (hpp->enable_serr) + pci_cmd |= PCI_COMMAND_SERR; + else + pci_cmd &= ~PCI_COMMAND_SERR; + if (hpp->enable_perr) + pci_cmd |= PCI_COMMAND_PARITY; + else + pci_cmd &= ~PCI_COMMAND_PARITY; + pci_write_config_word(dev, PCI_COMMAND, pci_cmd); + + /* Program bridge control value */ + if ((dev->class >> 8) == PCI_CLASS_BRIDGE_PCI) { + pci_write_config_byte(dev, PCI_SEC_LATENCY_TIMER, + hpp->latency_timer); + pci_read_config_word(dev, PCI_BRIDGE_CONTROL, &pci_bctl); + if (hpp->enable_serr) + pci_bctl |= PCI_BRIDGE_CTL_SERR; + else + pci_bctl &= ~PCI_BRIDGE_CTL_SERR; + if (hpp->enable_perr) + pci_bctl |= PCI_BRIDGE_CTL_PARITY; + else + pci_bctl &= ~PCI_BRIDGE_CTL_PARITY; + pci_write_config_word(dev, PCI_BRIDGE_CONTROL, pci_bctl); + } +} + +static void program_hpp_type2(struct pci_dev *dev, struct hpp_type2 *hpp) +{ + int pos; + u16 reg16; + u32 reg32; + + if (hpp->revision > 1) { + warn("Rev.%d type2 record not supported\n", hpp->revision); + return; + } + + /* Find PCI Express capability */ + pos = pci_find_capability(dev, PCI_CAP_ID_EXP); + if (!pos) + return; + + /* Initialize Device Control Register */ + pci_read_config_word(dev, pos + PCI_EXP_DEVCTL, ®16); + reg16 = (reg16 & hpp->pci_exp_devctl_and) | hpp->pci_exp_devctl_or; + pci_write_config_word(dev, pos + PCI_EXP_DEVCTL, reg16); + + /* Initialize Link Control Register */ + if (dev->subordinate) { + pci_read_config_word(dev, pos + PCI_EXP_LNKCTL, ®16); + reg16 = (reg16 & hpp->pci_exp_lnkctl_and) + | hpp->pci_exp_lnkctl_or; + pci_write_config_word(dev, pos + PCI_EXP_LNKCTL, reg16); + } + + /* Find Advanced Error Reporting Enhanced Capability */ + pos = pci_find_ext_capability(dev, PCI_EXT_CAP_ID_ERR); + if (!pos) + return; + + /* Initialize Uncorrectable Error Mask Register */ + pci_read_config_dword(dev, pos + PCI_ERR_UNCOR_MASK, ®32); + reg32 = (reg32 & hpp->unc_err_mask_and) | hpp->unc_err_mask_or; + pci_write_config_dword(dev, pos + PCI_ERR_UNCOR_MASK, reg32); + + /* Initialize Uncorrectable Error Severity Register */ + pci_read_config_dword(dev, pos + PCI_ERR_UNCOR_SEVER, ®32); + reg32 = (reg32 & hpp->unc_err_sever_and) | hpp->unc_err_sever_or; + pci_write_config_dword(dev, pos + PCI_ERR_UNCOR_SEVER, reg32); + + /* Initialize Correctable Error Mask Register */ + pci_read_config_dword(dev, pos + PCI_ERR_COR_MASK, ®32); + reg32 = (reg32 & hpp->cor_err_mask_and) | hpp->cor_err_mask_or; + pci_write_config_dword(dev, pos + PCI_ERR_COR_MASK, reg32); + + /* Initialize Advanced Error Capabilities and Control Register */ + pci_read_config_dword(dev, pos + PCI_ERR_CAP, ®32); + reg32 = (reg32 & hpp->adv_err_cap_and) | hpp->adv_err_cap_or; + pci_write_config_dword(dev, pos + PCI_ERR_CAP, reg32); + + /* + * FIXME: The following two registers are not supported yet. + * + * o Secondary Uncorrectable Error Severity Register + * o Secondary Uncorrectable Error Mask Register + */ +} + +static void program_fw_provided_values(struct pci_dev *dev) +{ + struct pci_dev *cdev; + struct hotplug_params hpp; + + /* Program hpp values for this device */ + if (!(dev->hdr_type == PCI_HEADER_TYPE_NORMAL || + (dev->hdr_type == PCI_HEADER_TYPE_BRIDGE && + (dev->class >> 8) == PCI_CLASS_BRIDGE_PCI))) + return; + + if (pciehp_get_hp_params_from_firmware(dev, &hpp)) { + warn("Could not get hotplug parameters\n"); + return; + } + + if (hpp.t2) + program_hpp_type2(dev, hpp.t2); + if (hpp.t0) + program_hpp_type0(dev, hpp.t0); + + /* Program child devices */ + if (dev->subordinate) { + list_for_each_entry(cdev, &dev->subordinate->devices, + bus_list) + program_fw_provided_values(cdev); + } +} + +static int __ref pciehp_add_bridge(struct pci_dev *dev) +{ + struct pci_bus *parent = dev->bus; + int pass, busnr, start = parent->secondary; + int end = parent->subordinate; + + for (busnr = start; busnr <= end; busnr++) { + if (!pci_find_bus(pci_domain_nr(parent), busnr)) + break; + } + if (busnr-- > end) { + err("No bus number available for hot-added bridge %s\n", + pci_name(dev)); + return -1; + } + for (pass = 0; pass < 2; pass++) + busnr = pci_scan_bridge(parent, dev, busnr, pass); + if (!dev->subordinate) + return -1; + pci_bus_size_bridges(dev->subordinate); + pci_bus_assign_resources(parent); + pci_enable_bridges(parent); + pci_bus_add_devices(parent); + return 0; +} + +int pciehp_configure_device(struct slot *p_slot) +{ + struct pci_dev *dev; + struct pci_bus *parent = p_slot->ctrl->pci_dev->subordinate; + int num, fn; + struct controller *ctrl = p_slot->ctrl; + + dev = pci_get_slot(parent, PCI_DEVFN(p_slot->device, 0)); + if (dev) { + ctrl_err(ctrl, "Device %s already exists " + "at %04x:%02x:%02x, cannot hot-add\n", pci_name(dev), + pci_domain_nr(parent), p_slot->bus, p_slot->device); + pci_dev_put(dev); + return -EINVAL; + } + + num = pci_scan_slot(parent, PCI_DEVFN(p_slot->device, 0)); + if (num == 0) { + ctrl_err(ctrl, "No new device found\n"); + return -ENODEV; + } + + for (fn = 0; fn < 8; fn++) { + dev = pci_get_slot(parent, PCI_DEVFN(p_slot->device, fn)); + if (!dev) + continue; + if ((dev->class >> 16) == PCI_BASE_CLASS_DISPLAY) { + ctrl_err(ctrl, "Cannot hot-add display device %s\n", + pci_name(dev)); + pci_dev_put(dev); + continue; + } + if ((dev->hdr_type == PCI_HEADER_TYPE_BRIDGE) || + (dev->hdr_type == PCI_HEADER_TYPE_CARDBUS)) { + pciehp_add_bridge(dev); + } + program_fw_provided_values(dev); + pci_dev_put(dev); + } + + pci_bus_assign_resources(parent); + pci_bus_add_devices(parent); + return 0; +} + +int pciehp_unconfigure_device(struct slot *p_slot) +{ + int ret, rc = 0; + int j; + u8 bctl = 0; + u8 presence = 0; + struct pci_bus *parent = p_slot->ctrl->pci_dev->subordinate; + u16 command; + struct controller *ctrl = p_slot->ctrl; + + ctrl_dbg(ctrl, "%s: domain:bus:dev = %04x:%02x:%02x\n", + __func__, pci_domain_nr(parent), p_slot->bus, p_slot->device); + ret = p_slot->hpc_ops->get_adapter_status(p_slot, &presence); + if (ret) + presence = 0; + + for (j = 0; j < 8; j++) { + struct pci_dev* temp = pci_get_slot(parent, + (p_slot->device << 3) | j); + if (!temp) + continue; + if ((temp->class >> 16) == PCI_BASE_CLASS_DISPLAY) { + ctrl_err(ctrl, "Cannot remove display device %s\n", + pci_name(temp)); + pci_dev_put(temp); + continue; + } + if (temp->hdr_type == PCI_HEADER_TYPE_BRIDGE && presence) { + pci_read_config_byte(temp, PCI_BRIDGE_CONTROL, &bctl); + if (bctl & PCI_BRIDGE_CTL_VGA) { + ctrl_err(ctrl, + "Cannot remove display device %s\n", + pci_name(temp)); + pci_dev_put(temp); + continue; + } + } + pci_remove_bus_device(temp); + /* + * Ensure that no new Requests will be generated from + * the device. + */ + if (presence) { + pci_read_config_word(temp, PCI_COMMAND, &command); + command &= ~(PCI_COMMAND_MASTER | PCI_COMMAND_SERR); + command |= PCI_COMMAND_INTX_DISABLE; + pci_write_config_word(temp, PCI_COMMAND, command); + } + pci_dev_put(temp); + } + /* + * Some PCI Express root ports require fixup after hot-plug operation. + */ + if (pcie_mch_quirk) + pci_fixup_device(pci_fixup_final, p_slot->ctrl->pci_dev); + + return rc; +} diff --git a/drivers/pci/hotplug/pcihp_skeleton.c b/drivers/pci/hotplug/pcihp_skeleton.c new file mode 100644 index 0000000..e3dd6cf --- /dev/null +++ b/drivers/pci/hotplug/pcihp_skeleton.c @@ -0,0 +1,360 @@ +/* + * PCI Hot Plug Controller Skeleton Driver - 0.3 + * + * Copyright (C) 2001,2003 Greg Kroah-Hartman (greg@kroah.com) + * Copyright (C) 2001,2003 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. + * + * This driver is to be used as a skeleton driver to show how to interface + * with the pci hotplug core easily. + * + * Send feedback to <greg@kroah.com> + * + */ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/pci.h> +#include <linux/pci_hotplug.h> +#include <linux/init.h> + +#define SLOT_NAME_SIZE 10 +struct slot { + u8 number; + struct hotplug_slot *hotplug_slot; + struct list_head slot_list; + char name[SLOT_NAME_SIZE]; +}; + +static LIST_HEAD(slot_list); + +#define MY_NAME "pcihp_skeleton" + +#define dbg(format, arg...) \ + do { \ + if (debug) \ + printk (KERN_DEBUG "%s: " format "\n", \ + MY_NAME , ## arg); \ + } while (0) +#define err(format, arg...) printk(KERN_ERR "%s: " format "\n", MY_NAME , ## arg) +#define info(format, arg...) printk(KERN_INFO "%s: " format "\n", MY_NAME , ## arg) +#define warn(format, arg...) printk(KERN_WARNING "%s: " format "\n", MY_NAME , ## arg) + +/* local variables */ +static int debug; +static int num_slots; + +#define DRIVER_VERSION "0.3" +#define DRIVER_AUTHOR "Greg Kroah-Hartman <greg@kroah.com>" +#define DRIVER_DESC "Hot Plug PCI Controller Skeleton Driver" + +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); +module_param(debug, bool, 0644); +MODULE_PARM_DESC(debug, "Debugging mode enabled or not"); + +static int enable_slot (struct hotplug_slot *slot); +static int disable_slot (struct hotplug_slot *slot); +static int set_attention_status (struct hotplug_slot *slot, u8 value); +static int hardware_test (struct hotplug_slot *slot, u32 value); +static int get_power_status (struct hotplug_slot *slot, u8 *value); +static int get_attention_status (struct hotplug_slot *slot, u8 *value); +static int get_latch_status (struct hotplug_slot *slot, u8 *value); +static int get_adapter_status (struct hotplug_slot *slot, u8 *value); + +static struct hotplug_slot_ops skel_hotplug_slot_ops = { + .owner = THIS_MODULE, + .enable_slot = enable_slot, + .disable_slot = disable_slot, + .set_attention_status = set_attention_status, + .hardware_test = hardware_test, + .get_power_status = get_power_status, + .get_attention_status = get_attention_status, + .get_latch_status = get_latch_status, + .get_adapter_status = get_adapter_status, +}; + +static int enable_slot(struct hotplug_slot *hotplug_slot) +{ + struct slot *slot = hotplug_slot->private; + int retval = 0; + + dbg("%s - physical_slot = %s\n", __func__, hotplug_slot->name); + + /* + * Fill in code here to enable the specified slot + */ + + return retval; +} + +static int disable_slot(struct hotplug_slot *hotplug_slot) +{ + struct slot *slot = hotplug_slot->private; + int retval = 0; + + dbg("%s - physical_slot = %s\n", __func__, hotplug_slot->name); + + /* + * Fill in code here to disable the specified slot + */ + + return retval; +} + +static int set_attention_status(struct hotplug_slot *hotplug_slot, u8 status) +{ + struct slot *slot = hotplug_slot->private; + int retval = 0; + + dbg("%s - physical_slot = %s\n", __func__, hotplug_slot->name); + + switch (status) { + case 0: + /* + * Fill in code here to turn light off + */ + break; + + case 1: + default: + /* + * Fill in code here to turn light on + */ + break; + } + + return retval; +} + +static int hardware_test(struct hotplug_slot *hotplug_slot, u32 value) +{ + struct slot *slot = hotplug_slot->private; + int retval = 0; + + dbg("%s - physical_slot = %s\n", __func__, hotplug_slot->name); + + switch (value) { + case 0: + /* Specify a test here */ + break; + case 1: + /* Specify another test here */ + break; + } + + return retval; +} + +static int get_power_status(struct hotplug_slot *hotplug_slot, u8 *value) +{ + struct slot *slot = hotplug_slot->private; + int retval = 0; + + dbg("%s - physical_slot = %s\n", __func__, hotplug_slot->name); + + /* + * Fill in logic to get the current power status of the specific + * slot and store it in the *value location. + */ + + return retval; +} + +static int get_attention_status(struct hotplug_slot *hotplug_slot, u8 *value) +{ + struct slot *slot = hotplug_slot->private; + int retval = 0; + + dbg("%s - physical_slot = %s\n", __func__, hotplug_slot->name); + + /* + * Fill in logic to get the current attention status of the specific + * slot and store it in the *value location. + */ + + return retval; +} + +static int get_latch_status(struct hotplug_slot *hotplug_slot, u8 *value) +{ + struct slot *slot = hotplug_slot->private; + int retval = 0; + + dbg("%s - physical_slot = %s\n", __func__, hotplug_slot->name); + + /* + * Fill in logic to get the current latch status of the specific + * slot and store it in the *value location. + */ + + return retval; +} + +static int get_adapter_status(struct hotplug_slot *hotplug_slot, u8 *value) +{ + struct slot *slot = hotplug_slot->private; + int retval = 0; + + dbg("%s - physical_slot = %s\n", __func__, hotplug_slot->name); + + /* + * Fill in logic to get the current adapter status of the specific + * slot and store it in the *value location. + */ + + return retval; +} + +static void release_slot(struct hotplug_slot *hotplug_slot) +{ + struct slot *slot = hotplug_slot->private; + + dbg("%s - physical_slot = %s\n", __func__, hotplug_slot->name); + kfree(slot->hotplug_slot->info); + kfree(slot->hotplug_slot); + kfree(slot); +} + +static void make_slot_name(struct slot *slot) +{ + /* + * Stupid way to make a filename out of the slot name. + * replace this if your hardware provides a better way to name slots. + */ + snprintf(slot->hotplug_slot->name, SLOT_NAME_SIZE, "%d", slot->number); +} + +/** + * init_slots - initialize 'struct slot' structures for each slot + * + */ +static int __init init_slots(void) +{ + struct slot *slot; + struct hotplug_slot *hotplug_slot; + struct hotplug_slot_info *info; + int retval = -ENOMEM; + int i; + + /* + * Create a structure for each slot, and register that slot + * with the pci_hotplug subsystem. + */ + for (i = 0; i < num_slots; ++i) { + slot = kzalloc(sizeof(*slot), GFP_KERNEL); + if (!slot) + goto error; + + hotplug_slot = kzalloc(sizeof(*hotplug_slot), GFP_KERNEL); + if (!hotplug_slot) + goto error_slot; + slot->hotplug_slot = hotplug_slot; + + info = kzalloc(sizeof(*info), GFP_KERNEL); + if (!info) + goto error_hpslot; + hotplug_slot->info = info; + + slot->number = i; + + hotplug_slot->name = slot->name; + hotplug_slot->private = slot; + hotplug_slot->release = &release_slot; + make_slot_name(slot); + hotplug_slot->ops = &skel_hotplug_slot_ops; + + /* + * Initialize the slot info structure with some known + * good values. + */ + get_power_status(hotplug_slot, &info->power_status); + get_attention_status(hotplug_slot, &info->attention_status); + get_latch_status(hotplug_slot, &info->latch_status); + get_adapter_status(hotplug_slot, &info->adapter_status); + + dbg("registering slot %d\n", i); + retval = pci_hp_register(slot->hotplug_slot); + if (retval) { + err("pci_hp_register failed with error %d\n", retval); + goto error_info; + } + + /* add slot to our internal list */ + list_add(&slot->slot_list, &slot_list); + } + + return 0; +error_info: + kfree(info); +error_hpslot: + kfree(hotplug_slot); +error_slot: + kfree(slot); +error: + return retval; +} + +static void __exit cleanup_slots(void) +{ + struct list_head *tmp; + struct list_head *next; + struct slot *slot; + + /* + * Unregister all of our slots with the pci_hotplug subsystem. + * Memory will be freed in release_slot() callback after slot's + * lifespan is finished. + */ + list_for_each_safe(tmp, next, &slot_list) { + slot = list_entry(tmp, struct slot, slot_list); + list_del(&slot->slot_list); + pci_hp_deregister(slot->hotplug_slot); + } +} + +static int __init pcihp_skel_init(void) +{ + int retval; + + info(DRIVER_DESC " version: " DRIVER_VERSION "\n"); + /* + * Do specific initialization stuff for your driver here + * like initializing your controller hardware (if any) and + * determining the number of slots you have in the system + * right now. + */ + num_slots = 5; + + return init_slots(); +} + +static void __exit pcihp_skel_exit(void) +{ + /* + * Clean everything up. + */ + cleanup_slots(); +} + +module_init(pcihp_skel_init); +module_exit(pcihp_skel_exit); diff --git a/drivers/pci/hotplug/rpadlpar.h b/drivers/pci/hotplug/rpadlpar.h new file mode 100644 index 0000000..4a0a59b --- /dev/null +++ b/drivers/pci/hotplug/rpadlpar.h @@ -0,0 +1,24 @@ +/* + * Interface for Dynamic Logical Partitioning of I/O Slots on + * RPA-compliant PPC64 platform. + * + * John Rose <johnrose@austin.ibm.com> + * October 2003 + * + * Copyright (C) 2003 IBM. + * + * 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. + */ +#ifndef _RPADLPAR_IO_H_ +#define _RPADLPAR_IO_H_ + +extern int dlpar_sysfs_init(void); +extern void dlpar_sysfs_exit(void); + +extern int dlpar_add_slot(char *drc_name); +extern int dlpar_remove_slot(char *drc_name); + +#endif diff --git a/drivers/pci/hotplug/rpadlpar_core.c b/drivers/pci/hotplug/rpadlpar_core.c new file mode 100644 index 0000000..9c2a22f --- /dev/null +++ b/drivers/pci/hotplug/rpadlpar_core.c @@ -0,0 +1,477 @@ +/* + * Interface for Dynamic Logical Partitioning of I/O Slots on + * RPA-compliant PPC64 platform. + * + * John Rose <johnrose@austin.ibm.com> + * Linda Xie <lxie@us.ibm.com> + * + * October 2003 + * + * Copyright (C) 2003 IBM. + * + * 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. + */ +#include <linux/init.h> +#include <linux/pci.h> +#include <linux/string.h> + +#include <asm/pci-bridge.h> +#include <linux/mutex.h> +#include <asm/rtas.h> +#include <asm/vio.h> + +#include "../pci.h" +#include "rpaphp.h" +#include "rpadlpar.h" + +static DEFINE_MUTEX(rpadlpar_mutex); + +#define DLPAR_MODULE_NAME "rpadlpar_io" + +#define NODE_TYPE_VIO 1 +#define NODE_TYPE_SLOT 2 +#define NODE_TYPE_PHB 3 + +static struct device_node *find_vio_slot_node(char *drc_name) +{ + struct device_node *parent = of_find_node_by_name(NULL, "vdevice"); + struct device_node *dn = NULL; + char *name; + int rc; + + if (!parent) + return NULL; + + while ((dn = of_get_next_child(parent, dn))) { + rc = rpaphp_get_drc_props(dn, NULL, &name, NULL, NULL); + if ((rc == 0) && (!strcmp(drc_name, name))) + break; + } + + return dn; +} + +/* Find dlpar-capable pci node that contains the specified name and type */ +static struct device_node *find_php_slot_pci_node(char *drc_name, + char *drc_type) +{ + struct device_node *np = NULL; + char *name; + char *type; + int rc; + + while ((np = of_find_node_by_name(np, "pci"))) { + rc = rpaphp_get_drc_props(np, NULL, &name, &type, NULL); + if (rc == 0) + if (!strcmp(drc_name, name) && !strcmp(drc_type, type)) + break; + } + + return np; +} + +static struct device_node *find_dlpar_node(char *drc_name, int *node_type) +{ + struct device_node *dn; + + dn = find_php_slot_pci_node(drc_name, "SLOT"); + if (dn) { + *node_type = NODE_TYPE_SLOT; + return dn; + } + + dn = find_php_slot_pci_node(drc_name, "PHB"); + if (dn) { + *node_type = NODE_TYPE_PHB; + return dn; + } + + dn = find_vio_slot_node(drc_name); + if (dn) { + *node_type = NODE_TYPE_VIO; + return dn; + } + + return NULL; +} + +/** + * find_php_slot - return hotplug slot structure for device node + * @dn: target &device_node + * + * This routine will return the hotplug slot structure + * for a given device node. Note that built-in PCI slots + * may be dlpar-able, but not hot-pluggable, so this routine + * will return NULL for built-in PCI slots. + */ +static struct slot *find_php_slot(struct device_node *dn) +{ + struct list_head *tmp, *n; + struct slot *slot; + + list_for_each_safe(tmp, n, &rpaphp_slot_head) { + slot = list_entry(tmp, struct slot, rpaphp_slot_list); + if (slot->dn == dn) + return slot; + } + + return NULL; +} + +static struct pci_dev *dlpar_find_new_dev(struct pci_bus *parent, + struct device_node *dev_dn) +{ + struct pci_dev *tmp = NULL; + struct device_node *child_dn; + + list_for_each_entry(tmp, &parent->devices, bus_list) { + child_dn = pci_device_to_OF_node(tmp); + if (child_dn == dev_dn) + return tmp; + } + return NULL; +} + +static void dlpar_pci_add_bus(struct device_node *dn) +{ + struct pci_dn *pdn = PCI_DN(dn); + struct pci_controller *phb = pdn->phb; + struct pci_dev *dev = NULL; + + eeh_add_device_tree_early(dn); + + /* Add EADS device to PHB bus, adding new entry to bus->devices */ + dev = of_create_pci_dev(dn, phb->bus, pdn->devfn); + if (!dev) { + printk(KERN_ERR "%s: failed to create pci dev for %s\n", + __func__, dn->full_name); + return; + } + + if (dev->hdr_type == PCI_HEADER_TYPE_BRIDGE || + dev->hdr_type == PCI_HEADER_TYPE_CARDBUS) + of_scan_pci_bridge(dn, dev); + + pcibios_fixup_new_pci_devices(dev->subordinate); + + /* Claim new bus resources */ + pcibios_claim_one_bus(dev->bus); + + /* Map IO space for child bus, which may or may not succeed */ + pcibios_map_io_space(dev->subordinate); + + /* Add new devices to global lists. Register in proc, sysfs. */ + pci_bus_add_devices(phb->bus); +} + +static int dlpar_add_pci_slot(char *drc_name, struct device_node *dn) +{ + struct pci_dev *dev; + struct pci_controller *phb; + + if (pcibios_find_pci_bus(dn)) + return -EINVAL; + + /* Add pci bus */ + dlpar_pci_add_bus(dn); + + /* Confirm new bridge dev was created */ + phb = PCI_DN(dn)->phb; + dev = dlpar_find_new_dev(phb->bus, dn); + + if (!dev) { + printk(KERN_ERR "%s: unable to add bus %s\n", __func__, + drc_name); + return -EIO; + } + + if (dev->hdr_type != PCI_HEADER_TYPE_BRIDGE) { + printk(KERN_ERR "%s: unexpected header type %d, unable to add bus %s\n", + __func__, dev->hdr_type, drc_name); + return -EIO; + } + + /* Add hotplug slot */ + if (rpaphp_add_slot(dn)) { + printk(KERN_ERR "%s: unable to add hotplug slot %s\n", + __func__, drc_name); + return -EIO; + } + return 0; +} + +static int dlpar_remove_root_bus(struct pci_controller *phb) +{ + struct pci_bus *phb_bus; + int rc; + + phb_bus = phb->bus; + if (!(list_empty(&phb_bus->children) && + list_empty(&phb_bus->devices))) { + return -EBUSY; + } + + rc = pcibios_remove_root_bus(phb); + if (rc) + return -EIO; + + device_unregister(phb_bus->bridge); + pci_remove_bus(phb_bus); + + return 0; +} + +static int dlpar_remove_phb(char *drc_name, struct device_node *dn) +{ + struct slot *slot; + struct pci_dn *pdn; + int rc = 0; + + if (!pcibios_find_pci_bus(dn)) + return -EINVAL; + + /* If pci slot is hotplugable, use hotplug to remove it */ + slot = find_php_slot(dn); + if (slot) { + if (rpaphp_deregister_slot(slot)) { + printk(KERN_ERR + "%s: unable to remove hotplug slot %s\n", + __func__, drc_name); + return -EIO; + } + } + + pdn = dn->data; + BUG_ON(!pdn || !pdn->phb); + rc = dlpar_remove_root_bus(pdn->phb); + if (rc < 0) + return rc; + + pdn->phb = NULL; + + return 0; +} + +static int dlpar_add_phb(char *drc_name, struct device_node *dn) +{ + struct pci_controller *phb; + + if (PCI_DN(dn) && PCI_DN(dn)->phb) { + /* PHB already exists */ + return -EINVAL; + } + + phb = init_phb_dynamic(dn); + if (!phb) + return -EIO; + + if (rpaphp_add_slot(dn)) { + printk(KERN_ERR "%s: unable to add hotplug slot %s\n", + __func__, drc_name); + return -EIO; + } + return 0; +} + +static int dlpar_add_vio_slot(char *drc_name, struct device_node *dn) +{ + if (vio_find_node(dn)) + return -EINVAL; + + if (!vio_register_device_node(dn)) { + printk(KERN_ERR + "%s: failed to register vio node %s\n", + __func__, drc_name); + return -EIO; + } + return 0; +} + +/** + * dlpar_add_slot - DLPAR add an I/O Slot + * @drc_name: drc-name of newly added slot + * + * Make the hotplug module and the kernel aware of a newly added I/O Slot. + * Return Codes: + * 0 Success + * -ENODEV Not a valid drc_name + * -EINVAL Slot already added + * -ERESTARTSYS Signalled before obtaining lock + * -EIO Internal PCI Error + */ +int dlpar_add_slot(char *drc_name) +{ + struct device_node *dn = NULL; + int node_type; + int rc = -EIO; + + if (mutex_lock_interruptible(&rpadlpar_mutex)) + return -ERESTARTSYS; + + /* Find newly added node */ + dn = find_dlpar_node(drc_name, &node_type); + if (!dn) { + rc = -ENODEV; + goto exit; + } + + switch (node_type) { + case NODE_TYPE_VIO: + rc = dlpar_add_vio_slot(drc_name, dn); + break; + case NODE_TYPE_SLOT: + rc = dlpar_add_pci_slot(drc_name, dn); + break; + case NODE_TYPE_PHB: + rc = dlpar_add_phb(drc_name, dn); + break; + } + + printk(KERN_INFO "%s: slot %s added\n", DLPAR_MODULE_NAME, drc_name); +exit: + mutex_unlock(&rpadlpar_mutex); + return rc; +} + +/** + * dlpar_remove_vio_slot - DLPAR remove a virtual I/O Slot + * @drc_name: drc-name of newly added slot + * @dn: &device_node + * + * Remove the kernel and hotplug representations of an I/O Slot. + * Return Codes: + * 0 Success + * -EINVAL Vio dev doesn't exist + */ +static int dlpar_remove_vio_slot(char *drc_name, struct device_node *dn) +{ + struct vio_dev *vio_dev; + + vio_dev = vio_find_node(dn); + if (!vio_dev) + return -EINVAL; + + vio_unregister_device(vio_dev); + return 0; +} + +/** + * dlpar_remove_pci_slot - DLPAR remove a PCI I/O Slot + * @drc_name: drc-name of newly added slot + * @dn: &device_node + * + * Remove the kernel and hotplug representations of a PCI I/O Slot. + * Return Codes: + * 0 Success + * -ENODEV Not a valid drc_name + * -EIO Internal PCI Error + */ +int dlpar_remove_pci_slot(char *drc_name, struct device_node *dn) +{ + struct pci_bus *bus; + struct slot *slot; + + bus = pcibios_find_pci_bus(dn); + if (!bus) + return -EINVAL; + + /* If pci slot is hotplugable, use hotplug to remove it */ + slot = find_php_slot(dn); + if (slot) { + if (rpaphp_deregister_slot(slot)) { + printk(KERN_ERR + "%s: unable to remove hotplug slot %s\n", + __func__, drc_name); + return -EIO; + } + } else + pcibios_remove_pci_devices(bus); + + if (pcibios_unmap_io_space(bus)) { + printk(KERN_ERR "%s: failed to unmap bus range\n", + __func__); + return -ERANGE; + } + + BUG_ON(!bus->self); + pci_remove_bus_device(bus->self); + return 0; +} + +/** + * dlpar_remove_slot - DLPAR remove an I/O Slot + * @drc_name: drc-name of newly added slot + * + * Remove the kernel and hotplug representations of an I/O Slot. + * Return Codes: + * 0 Success + * -ENODEV Not a valid drc_name + * -EINVAL Slot already removed + * -ERESTARTSYS Signalled before obtaining lock + * -EIO Internal Error + */ +int dlpar_remove_slot(char *drc_name) +{ + struct device_node *dn; + int node_type; + int rc = 0; + + if (mutex_lock_interruptible(&rpadlpar_mutex)) + return -ERESTARTSYS; + + dn = find_dlpar_node(drc_name, &node_type); + if (!dn) { + rc = -ENODEV; + goto exit; + } + + switch (node_type) { + case NODE_TYPE_VIO: + rc = dlpar_remove_vio_slot(drc_name, dn); + break; + case NODE_TYPE_PHB: + rc = dlpar_remove_phb(drc_name, dn); + break; + case NODE_TYPE_SLOT: + rc = dlpar_remove_pci_slot(drc_name, dn); + break; + } + printk(KERN_INFO "%s: slot %s removed\n", DLPAR_MODULE_NAME, drc_name); +exit: + mutex_unlock(&rpadlpar_mutex); + return rc; +} + +static inline int is_dlpar_capable(void) +{ + int rc = rtas_token("ibm,configure-connector"); + + return (int) (rc != RTAS_UNKNOWN_SERVICE); +} + +int __init rpadlpar_io_init(void) +{ + int rc = 0; + + if (!is_dlpar_capable()) { + printk(KERN_WARNING "%s: partition not DLPAR capable\n", + __func__); + return -EPERM; + } + + rc = dlpar_sysfs_init(); + return rc; +} + +void rpadlpar_io_exit(void) +{ + dlpar_sysfs_exit(); + return; +} + +module_init(rpadlpar_io_init); +module_exit(rpadlpar_io_exit); +MODULE_LICENSE("GPL"); diff --git a/drivers/pci/hotplug/rpadlpar_sysfs.c b/drivers/pci/hotplug/rpadlpar_sysfs.c new file mode 100644 index 0000000..a796301 --- /dev/null +++ b/drivers/pci/hotplug/rpadlpar_sysfs.c @@ -0,0 +1,130 @@ +/* + * Interface for Dynamic Logical Partitioning of I/O Slots on + * RPA-compliant PPC64 platform. + * + * John Rose <johnrose@austin.ibm.com> + * October 2003 + * + * Copyright (C) 2003 IBM. + * + * 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. + */ +#include <linux/kobject.h> +#include <linux/string.h> +#include <linux/pci.h> +#include <linux/pci_hotplug.h> +#include "rpadlpar.h" +#include "../pci.h" + +#define DLPAR_KOBJ_NAME "control" + +/* Those two have no quotes because they are passed to __ATTR() which + * stringifies the argument (yuck !) + */ +#define ADD_SLOT_ATTR_NAME add_slot +#define REMOVE_SLOT_ATTR_NAME remove_slot + +#define MAX_DRC_NAME_LEN 64 + +static ssize_t add_slot_store(struct kobject *kobj, struct kobj_attribute *attr, + const char *buf, size_t nbytes) +{ + char drc_name[MAX_DRC_NAME_LEN]; + char *end; + int rc; + + if (nbytes >= MAX_DRC_NAME_LEN) + return 0; + + memcpy(drc_name, buf, nbytes); + + end = strchr(drc_name, '\n'); + if (!end) + end = &drc_name[nbytes]; + *end = '\0'; + + rc = dlpar_add_slot(drc_name); + if (rc) + return rc; + + return nbytes; +} + +static ssize_t add_slot_show(struct kobject *kobj, + struct kobj_attribute *attr, char *buf) +{ + return sprintf(buf, "0\n"); +} + +static ssize_t remove_slot_store(struct kobject *kobj, + struct kobj_attribute *attr, + const char *buf, size_t nbytes) +{ + char drc_name[MAX_DRC_NAME_LEN]; + int rc; + char *end; + + if (nbytes >= MAX_DRC_NAME_LEN) + return 0; + + memcpy(drc_name, buf, nbytes); + + end = strchr(drc_name, '\n'); + if (!end) + end = &drc_name[nbytes]; + *end = '\0'; + + rc = dlpar_remove_slot(drc_name); + if (rc) + return rc; + + return nbytes; +} + +static ssize_t remove_slot_show(struct kobject *kobj, + struct kobj_attribute *attr, char *buf) +{ + return sprintf(buf, "0\n"); +} + +static struct kobj_attribute add_slot_attr = + __ATTR(ADD_SLOT_ATTR_NAME, 0644, add_slot_show, add_slot_store); + +static struct kobj_attribute remove_slot_attr = + __ATTR(REMOVE_SLOT_ATTR_NAME, 0644, remove_slot_show, remove_slot_store); + +static struct attribute *default_attrs[] = { + &add_slot_attr.attr, + &remove_slot_attr.attr, + NULL, +}; + +static struct attribute_group dlpar_attr_group = { + .attrs = default_attrs, +}; + +static struct kobject *dlpar_kobj; + +int dlpar_sysfs_init(void) +{ + int error; + + dlpar_kobj = kobject_create_and_add(DLPAR_KOBJ_NAME, + &pci_slots_kset->kobj); + if (!dlpar_kobj) + return -EINVAL; + + error = sysfs_create_group(dlpar_kobj, &dlpar_attr_group); + if (error) + kobject_put(dlpar_kobj); + return error; +} + +void dlpar_sysfs_exit(void) +{ + sysfs_remove_group(dlpar_kobj, &dlpar_attr_group); + kobject_put(dlpar_kobj); +} diff --git a/drivers/pci/hotplug/rpaphp.h b/drivers/pci/hotplug/rpaphp.h new file mode 100644 index 0000000..419919a --- /dev/null +++ b/drivers/pci/hotplug/rpaphp.h @@ -0,0 +1,103 @@ +/* + * PCI Hot Plug Controller Driver for RPA-compliant PPC64 platform. + * + * Copyright (C) 2003 Linda Xie <lxie@us.ibm.com> + * + * 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 <lxie@us.ibm.com>, + * + */ + +#ifndef _PPC64PHP_H +#define _PPC64PHP_H + +#include <linux/pci.h> +#include <linux/pci_hotplug.h> + +#define DR_INDICATOR 9002 +#define DR_ENTITY_SENSE 9003 + +#define POWER_ON 100 +#define POWER_OFF 0 + +#define LED_OFF 0 +#define LED_ON 1 /* continuous on */ +#define LED_ID 2 /* slow blinking */ +#define LED_ACTION 3 /* fast blinking */ + +/* Sensor values from rtas_get-sensor */ +#define EMPTY 0 /* No card in slot */ +#define PRESENT 1 /* Card in slot */ + +#define MY_NAME "rpaphp" +extern int rpaphp_debug; +#define dbg(format, arg...) \ + do { \ + if (rpaphp_debug) \ + printk(KERN_DEBUG "%s: " format, \ + 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) + +/* slot states */ + +#define NOT_VALID 3 +#define NOT_CONFIGURED 2 +#define CONFIGURED 1 +#define EMPTY 0 + +/* + * struct slot - slot information for each *physical* slot + */ +struct slot { + struct list_head rpaphp_slot_list; + int state; + u32 index; + u32 type; + u32 power_domain; + char *name; + struct device_node *dn; + struct pci_bus *bus; + struct list_head *pci_devs; + struct hotplug_slot *hotplug_slot; +}; + +extern struct hotplug_slot_ops rpaphp_hotplug_slot_ops; +extern struct list_head rpaphp_slot_head; + +/* function prototypes */ + +/* rpaphp_pci.c */ +extern int rpaphp_enable_slot(struct slot *slot); +extern int rpaphp_get_sensor_state(struct slot *slot, int *state); + +/* rpaphp_core.c */ +extern int rpaphp_add_slot(struct device_node *dn); +extern int rpaphp_get_drc_props(struct device_node *dn, int *drc_index, + char **drc_name, char **drc_type, int *drc_power_domain); + +/* rpaphp_slot.c */ +extern void dealloc_slot_struct(struct slot *slot); +extern struct slot *alloc_slot_struct(struct device_node *dn, int drc_index, char *drc_name, int power_domain); +extern int rpaphp_register_slot(struct slot *slot); +extern int rpaphp_deregister_slot(struct slot *slot); + +#endif /* _PPC64PHP_H */ diff --git a/drivers/pci/hotplug/rpaphp_core.c b/drivers/pci/hotplug/rpaphp_core.c new file mode 100644 index 0000000..95d02a0 --- /dev/null +++ b/drivers/pci/hotplug/rpaphp_core.c @@ -0,0 +1,441 @@ +/* + * PCI Hot Plug Controller Driver for RPA-compliant PPC64 platform. + * Copyright (C) 2003 Linda Xie <lxie@us.ibm.com> + * + * 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 <lxie@us.ibm.com> + * + */ +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/pci.h> +#include <linux/pci_hotplug.h> +#include <linux/slab.h> +#include <linux/smp.h> +#include <linux/init.h> +#include <asm/eeh.h> /* for eeh_add_device() */ +#include <asm/rtas.h> /* rtas_call */ +#include <asm/pci-bridge.h> /* for pci_controller */ +#include "../pci.h" /* for pci_add_new_bus */ + /* and pci_do_scan_bus */ +#include "rpaphp.h" + +int rpaphp_debug; +LIST_HEAD(rpaphp_slot_head); + +#define DRIVER_VERSION "0.1" +#define DRIVER_AUTHOR "Linda Xie <lxie@us.ibm.com>" +#define DRIVER_DESC "RPA HOT Plug PCI Controller Driver" + +#define MAX_LOC_CODE 128 + +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); + +module_param_named(debug, rpaphp_debug, bool, 0644); + +/** + * set_attention_status - set attention LED + * @hotplug_slot: target &hotplug_slot + * @value: LED control value + * + * echo 0 > attention -- set LED OFF + * echo 1 > attention -- set LED ON + * echo 2 > attention -- set LED ID(identify, light is blinking) + */ +static int set_attention_status(struct hotplug_slot *hotplug_slot, u8 value) +{ + int rc; + struct slot *slot = (struct slot *)hotplug_slot->private; + + switch (value) { + case 0: + case 1: + case 2: + break; + default: + value = 1; + break; + } + + rc = rtas_set_indicator(DR_INDICATOR, slot->index, value); + if (!rc) + hotplug_slot->info->attention_status = value; + + return rc; +} + +/** + * get_power_status - get power status of a slot + * @hotplug_slot: slot to get status + * @value: pointer to store status + */ +static int get_power_status(struct hotplug_slot *hotplug_slot, u8 * value) +{ + int retval, level; + struct slot *slot = (struct slot *)hotplug_slot->private; + + retval = rtas_get_power_level (slot->power_domain, &level); + if (!retval) + *value = level; + return retval; +} + +/** + * get_attention_status - get attention LED status + * @hotplug_slot: slot to get status + * @value: pointer to store status + */ +static int get_attention_status(struct hotplug_slot *hotplug_slot, u8 * value) +{ + struct slot *slot = (struct slot *)hotplug_slot->private; + *value = slot->hotplug_slot->info->attention_status; + return 0; +} + +static int get_adapter_status(struct hotplug_slot *hotplug_slot, u8 * value) +{ + struct slot *slot = (struct slot *)hotplug_slot->private; + int rc, state; + + rc = rpaphp_get_sensor_state(slot, &state); + + *value = NOT_VALID; + if (rc) + return rc; + + if (state == EMPTY) + *value = EMPTY; + else if (state == PRESENT) + *value = slot->state; + + return 0; +} + +static int get_max_bus_speed(struct hotplug_slot *hotplug_slot, enum pci_bus_speed *value) +{ + struct slot *slot = (struct slot *)hotplug_slot->private; + + switch (slot->type) { + case 1: + case 2: + case 3: + case 4: + case 5: + case 6: + *value = PCI_SPEED_33MHz; /* speed for case 1-6 */ + break; + case 7: + case 8: + *value = PCI_SPEED_66MHz; + break; + case 11: + case 14: + *value = PCI_SPEED_66MHz_PCIX; + break; + case 12: + case 15: + *value = PCI_SPEED_100MHz_PCIX; + break; + case 13: + case 16: + *value = PCI_SPEED_133MHz_PCIX; + break; + default: + *value = PCI_SPEED_UNKNOWN; + break; + + } + return 0; +} + +static int get_children_props(struct device_node *dn, const int **drc_indexes, + const int **drc_names, const int **drc_types, + const int **drc_power_domains) +{ + const int *indexes, *names, *types, *domains; + + indexes = of_get_property(dn, "ibm,drc-indexes", NULL); + names = of_get_property(dn, "ibm,drc-names", NULL); + types = of_get_property(dn, "ibm,drc-types", NULL); + domains = of_get_property(dn, "ibm,drc-power-domains", NULL); + + if (!indexes || !names || !types || !domains) { + /* Slot does not have dynamically-removable children */ + return -EINVAL; + } + if (drc_indexes) + *drc_indexes = indexes; + if (drc_names) + /* &drc_names[1] contains NULL terminated slot names */ + *drc_names = names; + if (drc_types) + /* &drc_types[1] contains NULL terminated slot types */ + *drc_types = types; + if (drc_power_domains) + *drc_power_domains = domains; + + return 0; +} + +/* To get the DRC props describing the current node, first obtain it's + * my-drc-index property. Next obtain the DRC list from it's parent. Use + * the my-drc-index for correlation, and obtain the requested properties. + */ +int rpaphp_get_drc_props(struct device_node *dn, int *drc_index, + char **drc_name, char **drc_type, int *drc_power_domain) +{ + const int *indexes, *names; + const int *types, *domains; + const unsigned int *my_index; + char *name_tmp, *type_tmp; + int i, rc; + + my_index = of_get_property(dn, "ibm,my-drc-index", NULL); + if (!my_index) { + /* Node isn't DLPAR/hotplug capable */ + return -EINVAL; + } + + rc = get_children_props(dn->parent, &indexes, &names, &types, &domains); + if (rc < 0) { + return -EINVAL; + } + + name_tmp = (char *) &names[1]; + type_tmp = (char *) &types[1]; + + /* Iterate through parent properties, looking for my-drc-index */ + for (i = 0; i < indexes[0]; i++) { + if ((unsigned int) indexes[i + 1] == *my_index) { + if (drc_name) + *drc_name = name_tmp; + if (drc_type) + *drc_type = type_tmp; + if (drc_index) + *drc_index = *my_index; + if (drc_power_domain) + *drc_power_domain = domains[i+1]; + return 0; + } + name_tmp += (strlen(name_tmp) + 1); + type_tmp += (strlen(type_tmp) + 1); + } + + return -EINVAL; +} + +static int is_php_type(char *drc_type) +{ + unsigned long value; + char *endptr; + + /* PCI Hotplug nodes have an integer for drc_type */ + value = simple_strtoul(drc_type, &endptr, 10); + if (endptr == drc_type) + return 0; + + return 1; +} + +/** + * is_php_dn() - return 1 if this is a hotpluggable pci slot, else 0 + * @dn: target &device_node + * @indexes: passed to get_children_props() + * @names: passed to get_children_props() + * @types: returned from get_children_props() + * @power_domains: + * + * This routine will return true only if the device node is + * a hotpluggable slot. This routine will return false + * for built-in pci slots (even when the built-in slots are + * dlparable.) + */ +static int is_php_dn(struct device_node *dn, const int **indexes, + const int **names, const int **types, const int **power_domains) +{ + const int *drc_types; + int rc; + + rc = get_children_props(dn, indexes, names, &drc_types, power_domains); + if (rc < 0) + return 0; + + if (!is_php_type((char *) &drc_types[1])) + return 0; + + *types = drc_types; + return 1; +} + +/** + * rpaphp_add_slot -- declare a hotplug slot to the hotplug subsystem. + * @dn: device node of slot + * + * This subroutine will register a hotplugable slot with the + * PCI hotplug infrastructure. This routine is typicaly called + * during boot time, if the hotplug slots are present at boot time, + * or is called later, by the dlpar add code, if the slot is + * being dynamically added during runtime. + * + * If the device node points at an embedded (built-in) slot, this + * routine will just return without doing anything, since embedded + * slots cannot be hotplugged. + * + * To remove a slot, it suffices to call rpaphp_deregister_slot(). + */ +int rpaphp_add_slot(struct device_node *dn) +{ + struct slot *slot; + int retval = 0; + int i; + const int *indexes, *names, *types, *power_domains; + char *name, *type; + + if (!dn->name || strcmp(dn->name, "pci")) + return 0; + + /* If this is not a hotplug slot, return without doing anything. */ + if (!is_php_dn(dn, &indexes, &names, &types, &power_domains)) + return 0; + + dbg("Entry %s: dn->full_name=%s\n", __func__, dn->full_name); + + /* register PCI devices */ + name = (char *) &names[1]; + type = (char *) &types[1]; + for (i = 0; i < indexes[0]; i++) { + + slot = alloc_slot_struct(dn, indexes[i + 1], name, power_domains[i + 1]); + if (!slot) + return -ENOMEM; + + slot->type = simple_strtoul(type, NULL, 10); + + dbg("Found drc-index:0x%x drc-name:%s drc-type:%s\n", + indexes[i + 1], name, type); + + retval = rpaphp_enable_slot(slot); + if (!retval) + retval = rpaphp_register_slot(slot); + + if (retval) + dealloc_slot_struct(slot); + + name += strlen(name) + 1; + type += strlen(type) + 1; + } + dbg("%s - Exit: rc[%d]\n", __func__, retval); + + /* XXX FIXME: reports a failure only if last entry in loop failed */ + return retval; +} + +static void __exit cleanup_slots(void) +{ + struct list_head *tmp, *n; + struct slot *slot; + + /* + * Unregister all of our slots with the pci_hotplug subsystem, + * and free up all memory that we had allocated. + * memory will be freed in release_slot callback. + */ + + list_for_each_safe(tmp, n, &rpaphp_slot_head) { + slot = list_entry(tmp, struct slot, rpaphp_slot_list); + list_del(&slot->rpaphp_slot_list); + pci_hp_deregister(slot->hotplug_slot); + } + return; +} + +static int __init rpaphp_init(void) +{ + struct device_node *dn = NULL; + + info(DRIVER_DESC " version: " DRIVER_VERSION "\n"); + + while ((dn = of_find_node_by_name(dn, "pci"))) + rpaphp_add_slot(dn); + + return 0; +} + +static void __exit rpaphp_exit(void) +{ + cleanup_slots(); +} + +static int enable_slot(struct hotplug_slot *hotplug_slot) +{ + struct slot *slot = (struct slot *)hotplug_slot->private; + int state; + int retval; + + if (slot->state == CONFIGURED) + return 0; + + retval = rpaphp_get_sensor_state(slot, &state); + if (retval) + return retval; + + if (state == PRESENT) { + pcibios_add_pci_devices(slot->bus); + slot->state = CONFIGURED; + } else if (state == EMPTY) { + slot->state = EMPTY; + } else { + err("%s: slot[%s] is in invalid state\n", __func__, slot->name); + slot->state = NOT_VALID; + return -EINVAL; + } + return 0; +} + +static int disable_slot(struct hotplug_slot *hotplug_slot) +{ + struct slot *slot = (struct slot *)hotplug_slot->private; + if (slot->state == NOT_CONFIGURED) + return -EINVAL; + + pcibios_remove_pci_devices(slot->bus); + slot->state = NOT_CONFIGURED; + return 0; +} + +struct hotplug_slot_ops rpaphp_hotplug_slot_ops = { + .owner = THIS_MODULE, + .enable_slot = enable_slot, + .disable_slot = disable_slot, + .set_attention_status = set_attention_status, + .get_power_status = get_power_status, + .get_attention_status = get_attention_status, + .get_adapter_status = get_adapter_status, + .get_max_bus_speed = get_max_bus_speed, +}; + +module_init(rpaphp_init); +module_exit(rpaphp_exit); + +EXPORT_SYMBOL_GPL(rpaphp_add_slot); +EXPORT_SYMBOL_GPL(rpaphp_slot_head); +EXPORT_SYMBOL_GPL(rpaphp_get_drc_props); diff --git a/drivers/pci/hotplug/rpaphp_pci.c b/drivers/pci/hotplug/rpaphp_pci.c new file mode 100644 index 0000000..513e1e2 --- /dev/null +++ b/drivers/pci/hotplug/rpaphp_pci.c @@ -0,0 +1,136 @@ +/* + * PCI Hot Plug Controller Driver for RPA-compliant PPC64 platform. + * Copyright (C) 2003 Linda Xie <lxie@us.ibm.com> + * + * 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 <lxie@us.ibm.com> + * + */ +#include <linux/pci.h> +#include <linux/string.h> + +#include <asm/pci-bridge.h> +#include <asm/rtas.h> +#include <asm/machdep.h> + +#include "../pci.h" /* for pci_add_new_bus */ +#include "rpaphp.h" + +int rpaphp_get_sensor_state(struct slot *slot, int *state) +{ + int rc; + int setlevel; + + rc = rtas_get_sensor(DR_ENTITY_SENSE, slot->index, state); + + if (rc < 0) { + if (rc == -EFAULT || rc == -EEXIST) { + dbg("%s: slot must be power up to get sensor-state\n", + __func__); + + /* some slots have to be powered up + * before get-sensor will succeed. + */ + rc = rtas_set_power_level(slot->power_domain, POWER_ON, + &setlevel); + if (rc < 0) { + dbg("%s: power on slot[%s] failed rc=%d.\n", + __func__, slot->name, rc); + } else { + rc = rtas_get_sensor(DR_ENTITY_SENSE, + slot->index, state); + } + } else if (rc == -ENODEV) + info("%s: slot is unusable\n", __func__); + else + err("%s failed to get sensor state\n", __func__); + } + return rc; +} + +/** + * rpaphp_enable_slot - record slot state, config pci device + * @slot: target &slot + * + * Initialize values in the slot, and the hotplug_slot info + * structures to indicate if there is a pci card plugged into + * the slot. If the slot is not empty, run the pcibios routine + * to get pcibios stuff correctly set up. + */ +int rpaphp_enable_slot(struct slot *slot) +{ + int rc, level, state; + struct pci_bus *bus; + struct hotplug_slot_info *info = slot->hotplug_slot->info; + + info->adapter_status = NOT_VALID; + slot->state = EMPTY; + + /* Find out if the power is turned on for the slot */ + rc = rtas_get_power_level(slot->power_domain, &level); + if (rc) + return rc; + info->power_status = level; + + /* Figure out if there is an adapter in the slot */ + rc = rpaphp_get_sensor_state(slot, &state); + if (rc) + return rc; + + bus = pcibios_find_pci_bus(slot->dn); + if (!bus) { + err("%s: no pci_bus for dn %s\n", __func__, slot->dn->full_name); + return -EINVAL; + } + + info->adapter_status = EMPTY; + slot->bus = bus; + slot->pci_devs = &bus->devices; + + /* if there's an adapter in the slot, go add the pci devices */ + if (state == PRESENT) { + info->adapter_status = NOT_CONFIGURED; + slot->state = NOT_CONFIGURED; + + /* non-empty slot has to have child */ + if (!slot->dn->child) { + err("%s: slot[%s]'s device_node doesn't have child for adapter\n", + __func__, slot->name); + return -EINVAL; + } + + if (list_empty(&bus->devices)) + pcibios_add_pci_devices(bus); + + if (!list_empty(&bus->devices)) { + info->adapter_status = CONFIGURED; + slot->state = CONFIGURED; + } + + if (rpaphp_debug) { + struct pci_dev *dev; + dbg("%s: pci_devs of slot[%s]\n", __func__, slot->dn->full_name); + list_for_each_entry (dev, &bus->devices, bus_list) + dbg("\t%s\n", pci_name(dev)); + } + } + + return 0; +} + diff --git a/drivers/pci/hotplug/rpaphp_slot.c b/drivers/pci/hotplug/rpaphp_slot.c new file mode 100644 index 0000000..2ea9cf1 --- /dev/null +++ b/drivers/pci/hotplug/rpaphp_slot.c @@ -0,0 +1,149 @@ +/* + * RPA Virtual I/O device functions + * Copyright (C) 2004 Linda Xie <lxie@us.ibm.com> + * + * 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 <lxie@us.ibm.com> + * + */ +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/kobject.h> +#include <linux/sysfs.h> +#include <linux/pci.h> +#include <linux/string.h> +#include <linux/slab.h> + +#include <asm/rtas.h> +#include "rpaphp.h" + +/* free up the memory used by a slot */ +static void rpaphp_release_slot(struct hotplug_slot *hotplug_slot) +{ + struct slot *slot = (struct slot *) hotplug_slot->private; + dealloc_slot_struct(slot); +} + +void dealloc_slot_struct(struct slot *slot) +{ + kfree(slot->hotplug_slot->info); + kfree(slot->name); + kfree(slot->hotplug_slot); + kfree(slot); +} + +struct slot *alloc_slot_struct(struct device_node *dn, + int drc_index, char *drc_name, int power_domain) +{ + struct slot *slot; + + slot = kzalloc(sizeof(struct slot), GFP_KERNEL); + if (!slot) + goto error_nomem; + slot->hotplug_slot = kzalloc(sizeof(struct hotplug_slot), GFP_KERNEL); + if (!slot->hotplug_slot) + goto error_slot; + slot->hotplug_slot->info = kzalloc(sizeof(struct hotplug_slot_info), + GFP_KERNEL); + if (!slot->hotplug_slot->info) + goto error_hpslot; + slot->name = kstrdup(drc_name, GFP_KERNEL); + if (!slot->name) + goto error_info; + slot->dn = dn; + slot->index = drc_index; + slot->power_domain = power_domain; + slot->hotplug_slot->private = slot; + slot->hotplug_slot->ops = &rpaphp_hotplug_slot_ops; + slot->hotplug_slot->release = &rpaphp_release_slot; + + return (slot); + +error_info: + kfree(slot->hotplug_slot->info); +error_hpslot: + kfree(slot->hotplug_slot); +error_slot: + kfree(slot); +error_nomem: + return NULL; +} + +static int is_registered(struct slot *slot) +{ + struct slot *tmp_slot; + + list_for_each_entry(tmp_slot, &rpaphp_slot_head, rpaphp_slot_list) { + if (!strcmp(tmp_slot->name, slot->name)) + return 1; + } + return 0; +} + +int rpaphp_deregister_slot(struct slot *slot) +{ + int retval = 0; + struct hotplug_slot *php_slot = slot->hotplug_slot; + + dbg("%s - Entry: deregistering slot=%s\n", + __func__, slot->name); + + list_del(&slot->rpaphp_slot_list); + + retval = pci_hp_deregister(php_slot); + if (retval) + err("Problem unregistering a slot %s\n", slot->name); + + dbg("%s - Exit: rc[%d]\n", __func__, retval); + return retval; +} +EXPORT_SYMBOL_GPL(rpaphp_deregister_slot); + +int rpaphp_register_slot(struct slot *slot) +{ + struct hotplug_slot *php_slot = slot->hotplug_slot; + int retval; + int slotno; + + dbg("%s registering slot:path[%s] index[%x], name[%s] pdomain[%x] type[%d]\n", + __func__, slot->dn->full_name, slot->index, slot->name, + slot->power_domain, slot->type); + + /* should not try to register the same slot twice */ + if (is_registered(slot)) { + err("rpaphp_register_slot: slot[%s] is already registered\n", slot->name); + return -EAGAIN; + } + + if (slot->dn->child) + slotno = PCI_SLOT(PCI_DN(slot->dn->child)->devfn); + else + slotno = -1; + retval = pci_hp_register(php_slot, slot->bus, slotno, slot->name); + if (retval) { + err("pci_hp_register failed with error %d\n", retval); + return retval; + } + + /* add slot to our internal list */ + list_add(&slot->rpaphp_slot_list, &rpaphp_slot_head); + info("Slot [%s] registered\n", slot->name); + return 0; +} + diff --git a/drivers/pci/hotplug/sgi_hotplug.c b/drivers/pci/hotplug/sgi_hotplug.c new file mode 100644 index 0000000..3eee709 --- /dev/null +++ b/drivers/pci/hotplug/sgi_hotplug.c @@ -0,0 +1,731 @@ +/* + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file "COPYING" in the main directory of this archive + * for more details. + * + * Copyright (C) 2005-2006 Silicon Graphics, Inc. All rights reserved. + * + * This work was based on the 2.4/2.6 kernel development by Dick Reigner. + * Work to add BIOS PROM support was completed by Mike Habeck. + */ + +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/pci.h> +#include <linux/pci_hotplug.h> +#include <linux/proc_fs.h> +#include <linux/types.h> +#include <linux/mutex.h> + +#include <asm/sn/addrs.h> +#include <asm/sn/geo.h> +#include <asm/sn/l1.h> +#include <asm/sn/module.h> +#include <asm/sn/pcibr_provider.h> +#include <asm/sn/pcibus_provider_defs.h> +#include <asm/sn/pcidev.h> +#include <asm/sn/sn_feature_sets.h> +#include <asm/sn/sn_sal.h> +#include <asm/sn/types.h> +#include <linux/acpi.h> +#include <asm/sn/acpi.h> + +#include "../pci.h" + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("SGI (prarit@sgi.com, dickie@sgi.com, habeck@sgi.com)"); +MODULE_DESCRIPTION("SGI Altix Hot Plug PCI Controller Driver"); + + +/* SAL call error codes. Keep in sync with prom header io/include/pcibr.h */ +#define PCI_SLOT_ALREADY_UP 2 /* slot already up */ +#define PCI_SLOT_ALREADY_DOWN 3 /* slot already down */ +#define PCI_L1_ERR 7 /* L1 console command error */ +#define PCI_EMPTY_33MHZ 15 /* empty 33 MHz bus */ + + +#define PCIIO_ASIC_TYPE_TIOCA 4 +#define PCI_L1_QSIZE 128 /* our L1 message buffer size */ +#define SN_MAX_HP_SLOTS 32 /* max hotplug slots */ +#define SN_SLOT_NAME_SIZE 33 /* size of name string */ + +/* internal list head */ +static struct list_head sn_hp_list; + +/* hotplug_slot struct's private pointer */ +struct slot { + int device_num; + struct pci_bus *pci_bus; + /* this struct for glue internal only */ + struct hotplug_slot *hotplug_slot; + struct list_head hp_list; + char physical_path[SN_SLOT_NAME_SIZE]; +}; + +struct pcibr_slot_enable_resp { + int resp_sub_errno; + char resp_l1_msg[PCI_L1_QSIZE + 1]; +}; + +struct pcibr_slot_disable_resp { + int resp_sub_errno; + char resp_l1_msg[PCI_L1_QSIZE + 1]; +}; + +enum sn_pci_req_e { + PCI_REQ_SLOT_ELIGIBLE, + PCI_REQ_SLOT_DISABLE +}; + +static int enable_slot(struct hotplug_slot *slot); +static int disable_slot(struct hotplug_slot *slot); +static inline int get_power_status(struct hotplug_slot *slot, u8 *value); + +static struct hotplug_slot_ops sn_hotplug_slot_ops = { + .owner = THIS_MODULE, + .enable_slot = enable_slot, + .disable_slot = disable_slot, + .get_power_status = get_power_status, +}; + +static DEFINE_MUTEX(sn_hotplug_mutex); + +static ssize_t path_show (struct hotplug_slot *bss_hotplug_slot, + char *buf) +{ + int retval = -ENOENT; + struct slot *slot = bss_hotplug_slot->private; + + if (!slot) + return retval; + + retval = sprintf (buf, "%s\n", slot->physical_path); + return retval; +} + +static struct hotplug_slot_attribute sn_slot_path_attr = __ATTR_RO(path); + +static int sn_pci_slot_valid(struct pci_bus *pci_bus, int device) +{ + struct pcibus_info *pcibus_info; + u16 busnum, segment, ioboard_type; + + pcibus_info = SN_PCIBUS_BUSSOFT_INFO(pci_bus); + + /* Check to see if this is a valid slot on 'pci_bus' */ + if (!(pcibus_info->pbi_valid_devices & (1 << device))) + return -EPERM; + + ioboard_type = sn_ioboard_to_pci_bus(pci_bus); + busnum = pcibus_info->pbi_buscommon.bs_persist_busnum; + segment = pci_domain_nr(pci_bus) & 0xf; + + /* Do not allow hotplug operations on base I/O cards */ + if ((ioboard_type == L1_BRICKTYPE_IX || + ioboard_type == L1_BRICKTYPE_IA) && + (segment == 1 && busnum == 0 && device != 1)) + return -EPERM; + + return 1; +} + +static int sn_pci_bus_valid(struct pci_bus *pci_bus) +{ + struct pcibus_info *pcibus_info; + u32 asic_type; + u16 ioboard_type; + + /* Don't register slots hanging off the TIOCA bus */ + pcibus_info = SN_PCIBUS_BUSSOFT_INFO(pci_bus); + asic_type = pcibus_info->pbi_buscommon.bs_asic_type; + if (asic_type == PCIIO_ASIC_TYPE_TIOCA) + return -EPERM; + + /* Only register slots in I/O Bricks that support hotplug */ + ioboard_type = sn_ioboard_to_pci_bus(pci_bus); + switch (ioboard_type) { + case L1_BRICKTYPE_IX: + case L1_BRICKTYPE_PX: + case L1_BRICKTYPE_IA: + case L1_BRICKTYPE_PA: + case L1_BOARDTYPE_PCIX3SLOT: + return 1; + break; + default: + return -EPERM; + break; + } + + return -EIO; +} + +static int sn_hp_slot_private_alloc(struct hotplug_slot *bss_hotplug_slot, + struct pci_bus *pci_bus, int device, + char *name) +{ + struct pcibus_info *pcibus_info; + struct slot *slot; + + pcibus_info = SN_PCIBUS_BUSSOFT_INFO(pci_bus); + + slot = kzalloc(sizeof(*slot), GFP_KERNEL); + if (!slot) + return -ENOMEM; + bss_hotplug_slot->private = slot; + + slot->device_num = device; + slot->pci_bus = pci_bus; + sprintf(name, "%04x:%02x:%02x", + pci_domain_nr(pci_bus), + ((u16)pcibus_info->pbi_buscommon.bs_persist_busnum), + device + 1); + + sn_generate_path(pci_bus, slot->physical_path); + + slot->hotplug_slot = bss_hotplug_slot; + list_add(&slot->hp_list, &sn_hp_list); + + return 0; +} + +static struct hotplug_slot * sn_hp_destroy(void) +{ + struct slot *slot; + struct pci_slot *pci_slot; + struct hotplug_slot *bss_hotplug_slot = NULL; + + list_for_each_entry(slot, &sn_hp_list, hp_list) { + bss_hotplug_slot = slot->hotplug_slot; + pci_slot = bss_hotplug_slot->pci_slot; + list_del(&((struct slot *)bss_hotplug_slot->private)-> + hp_list); + sysfs_remove_file(&pci_slot->kobj, + &sn_slot_path_attr.attr); + break; + } + return bss_hotplug_slot; +} + +static void sn_bus_free_data(struct pci_dev *dev) +{ + struct pci_bus *subordinate_bus; + struct pci_dev *child; + + /* Recursively clean up sn_irq_info structs */ + if (dev->subordinate) { + subordinate_bus = dev->subordinate; + list_for_each_entry(child, &subordinate_bus->devices, bus_list) + sn_bus_free_data(child); + } + /* + * Some drivers may use dma accesses during the + * driver remove function. We release the sysdata + * areas after the driver remove functions have + * been called. + */ + sn_bus_store_sysdata(dev); + sn_pci_unfixup_slot(dev); +} + +static int sn_slot_enable(struct hotplug_slot *bss_hotplug_slot, + int device_num, char **ssdt) +{ + struct slot *slot = bss_hotplug_slot->private; + struct pcibus_info *pcibus_info; + struct pcibr_slot_enable_resp resp; + int rc; + + pcibus_info = SN_PCIBUS_BUSSOFT_INFO(slot->pci_bus); + + /* + * Power-on and initialize the slot in the SN + * PCI infrastructure. + */ + rc = sal_pcibr_slot_enable(pcibus_info, device_num, &resp, ssdt); + + + if (rc == PCI_SLOT_ALREADY_UP) { + dev_dbg(&slot->pci_bus->self->dev, "is already active\n"); + return 1; /* return 1 to user */ + } + + if (rc == PCI_L1_ERR) { + dev_dbg(&slot->pci_bus->self->dev, + "L1 failure %d with message: %s", + resp.resp_sub_errno, resp.resp_l1_msg); + return -EPERM; + } + + if (rc) { + dev_dbg(&slot->pci_bus->self->dev, + "insert failed with error %d sub-error %d\n", + rc, resp.resp_sub_errno); + return -EIO; + } + + pcibus_info = SN_PCIBUS_BUSSOFT_INFO(slot->pci_bus); + pcibus_info->pbi_enabled_devices |= (1 << device_num); + + return 0; +} + +static int sn_slot_disable(struct hotplug_slot *bss_hotplug_slot, + int device_num, int action) +{ + struct slot *slot = bss_hotplug_slot->private; + struct pcibus_info *pcibus_info; + struct pcibr_slot_disable_resp resp; + int rc; + + pcibus_info = SN_PCIBUS_BUSSOFT_INFO(slot->pci_bus); + + rc = sal_pcibr_slot_disable(pcibus_info, device_num, action, &resp); + + if ((action == PCI_REQ_SLOT_ELIGIBLE) && + (rc == PCI_SLOT_ALREADY_DOWN)) { + dev_dbg(&slot->pci_bus->self->dev, "Slot %s already inactive\n", slot->physical_path); + return 1; /* return 1 to user */ + } + + if ((action == PCI_REQ_SLOT_ELIGIBLE) && (rc == PCI_EMPTY_33MHZ)) { + dev_dbg(&slot->pci_bus->self->dev, + "Cannot remove last 33MHz card\n"); + return -EPERM; + } + + if ((action == PCI_REQ_SLOT_ELIGIBLE) && (rc == PCI_L1_ERR)) { + dev_dbg(&slot->pci_bus->self->dev, + "L1 failure %d with message \n%s\n", + resp.resp_sub_errno, resp.resp_l1_msg); + return -EPERM; + } + + if ((action == PCI_REQ_SLOT_ELIGIBLE) && rc) { + dev_dbg(&slot->pci_bus->self->dev, + "remove failed with error %d sub-error %d\n", + rc, resp.resp_sub_errno); + return -EIO; + } + + if ((action == PCI_REQ_SLOT_ELIGIBLE) && !rc) + return 0; + + if ((action == PCI_REQ_SLOT_DISABLE) && !rc) { + pcibus_info = SN_PCIBUS_BUSSOFT_INFO(slot->pci_bus); + pcibus_info->pbi_enabled_devices &= ~(1 << device_num); + dev_dbg(&slot->pci_bus->self->dev, "remove successful\n"); + return 0; + } + + if ((action == PCI_REQ_SLOT_DISABLE) && rc) { + dev_dbg(&slot->pci_bus->self->dev,"remove failed rc = %d\n", rc); + } + + return rc; +} + +/* + * Power up and configure the slot via a SAL call to PROM. + * Scan slot (and any children), do any platform specific fixup, + * and find device driver. + */ +static int enable_slot(struct hotplug_slot *bss_hotplug_slot) +{ + struct slot *slot = bss_hotplug_slot->private; + struct pci_bus *new_bus = NULL; + struct pci_dev *dev; + int func, num_funcs; + int new_ppb = 0; + int rc; + char *ssdt = NULL; + void pcibios_fixup_device_resources(struct pci_dev *); + + /* Serialize the Linux PCI infrastructure */ + mutex_lock(&sn_hotplug_mutex); + + /* + * Power-on and initialize the slot in the SN + * PCI infrastructure. Also, retrieve the ACPI SSDT + * table for the slot (if ACPI capable PROM). + */ + rc = sn_slot_enable(bss_hotplug_slot, slot->device_num, &ssdt); + if (rc) { + mutex_unlock(&sn_hotplug_mutex); + return rc; + } + + if (ssdt) + ssdt = __va(ssdt); + /* Add the new SSDT for the slot to the ACPI namespace */ + if (SN_ACPI_BASE_SUPPORT() && ssdt) { + acpi_status ret; + + ret = acpi_load_table((struct acpi_table_header *)ssdt); + if (ACPI_FAILURE(ret)) { + printk(KERN_ERR "%s: acpi_load_table failed (0x%x)\n", + __func__, ret); + /* try to continue on */ + } + } + + num_funcs = pci_scan_slot(slot->pci_bus, + PCI_DEVFN(slot->device_num + 1, 0)); + if (!num_funcs) { + dev_dbg(&slot->pci_bus->self->dev, "no device in slot\n"); + mutex_unlock(&sn_hotplug_mutex); + return -ENODEV; + } + + /* + * Map SN resources for all functions on the card + * to the Linux PCI interface and tell the drivers + * about them. + */ + for (func = 0; func < num_funcs; func++) { + dev = pci_get_slot(slot->pci_bus, + PCI_DEVFN(slot->device_num + 1, + PCI_FUNC(func))); + if (dev) { + /* Need to do slot fixup on PPB before fixup of children + * (PPB's pcidev_info needs to be in pcidev_info list + * before child's SN_PCIDEV_INFO() call to setup + * pdi_host_pcidev_info). + */ + pcibios_fixup_device_resources(dev); + if (SN_ACPI_BASE_SUPPORT()) + sn_acpi_slot_fixup(dev); + else + sn_io_slot_fixup(dev); + if (dev->hdr_type == PCI_HEADER_TYPE_BRIDGE) { + unsigned char sec_bus; + pci_read_config_byte(dev, PCI_SECONDARY_BUS, + &sec_bus); + new_bus = pci_add_new_bus(dev->bus, dev, + sec_bus); + pci_scan_child_bus(new_bus); + new_ppb = 1; + } + pci_dev_put(dev); + } + } + + /* + * Add the slot's devices to the ACPI infrastructure */ + if (SN_ACPI_BASE_SUPPORT() && ssdt) { + unsigned long long adr; + struct acpi_device *pdevice; + struct acpi_device *device; + acpi_handle phandle; + acpi_handle chandle = NULL; + acpi_handle rethandle; + acpi_status ret; + + phandle = PCI_CONTROLLER(slot->pci_bus)->acpi_handle; + + if (acpi_bus_get_device(phandle, &pdevice)) { + dev_dbg(&slot->pci_bus->self->dev, + "no parent device, assuming NULL\n"); + pdevice = NULL; + } + + /* + * Walk the rootbus node's immediate children looking for + * the slot's device node(s). There can be more than + * one for multifunction devices. + */ + for (;;) { + rethandle = NULL; + ret = acpi_get_next_object(ACPI_TYPE_DEVICE, + phandle, chandle, + &rethandle); + + if (ret == AE_NOT_FOUND || rethandle == NULL) + break; + + chandle = rethandle; + + ret = acpi_evaluate_integer(chandle, METHOD_NAME__ADR, + NULL, &adr); + + if (ACPI_SUCCESS(ret) && + (adr>>16) == (slot->device_num + 1)) { + + ret = acpi_bus_add(&device, pdevice, chandle, + ACPI_BUS_TYPE_DEVICE); + if (ACPI_FAILURE(ret)) { + printk(KERN_ERR "%s: acpi_bus_add " + "failed (0x%x) for slot %d " + "func %d\n", __func__, + ret, (int)(adr>>16), + (int)(adr&0xffff)); + /* try to continue on */ + } else { + acpi_bus_start(device); + } + } + } + } + + /* Call the driver for the new device */ + pci_bus_add_devices(slot->pci_bus); + /* Call the drivers for the new devices subordinate to PPB */ + if (new_ppb) + pci_bus_add_devices(new_bus); + + mutex_unlock(&sn_hotplug_mutex); + + if (rc == 0) + dev_dbg(&slot->pci_bus->self->dev, + "insert operation successful\n"); + else + dev_dbg(&slot->pci_bus->self->dev, + "insert operation failed rc = %d\n", rc); + + return rc; +} + +static int disable_slot(struct hotplug_slot *bss_hotplug_slot) +{ + struct slot *slot = bss_hotplug_slot->private; + struct pci_dev *dev; + int func; + int rc; + acpi_owner_id ssdt_id = 0; + + /* Acquire update access to the bus */ + mutex_lock(&sn_hotplug_mutex); + + /* is it okay to bring this slot down? */ + rc = sn_slot_disable(bss_hotplug_slot, slot->device_num, + PCI_REQ_SLOT_ELIGIBLE); + if (rc) + goto leaving; + + /* free the ACPI resources for the slot */ + if (SN_ACPI_BASE_SUPPORT() && + PCI_CONTROLLER(slot->pci_bus)->acpi_handle) { + unsigned long long adr; + struct acpi_device *device; + acpi_handle phandle; + acpi_handle chandle = NULL; + acpi_handle rethandle; + acpi_status ret; + + /* Get the rootbus node pointer */ + phandle = PCI_CONTROLLER(slot->pci_bus)->acpi_handle; + + /* + * Walk the rootbus node's immediate children looking for + * the slot's device node(s). There can be more than + * one for multifunction devices. + */ + for (;;) { + rethandle = NULL; + ret = acpi_get_next_object(ACPI_TYPE_DEVICE, + phandle, chandle, + &rethandle); + + if (ret == AE_NOT_FOUND || rethandle == NULL) + break; + + chandle = rethandle; + + ret = acpi_evaluate_integer(chandle, + METHOD_NAME__ADR, + NULL, &adr); + if (ACPI_SUCCESS(ret) && + (adr>>16) == (slot->device_num + 1)) { + /* retain the owner id */ + acpi_get_id(chandle, &ssdt_id); + + ret = acpi_bus_get_device(chandle, + &device); + if (ACPI_SUCCESS(ret)) + acpi_bus_trim(device, 1); + } + } + + } + + /* Free the SN resources assigned to the Linux device.*/ + for (func = 0; func < 8; func++) { + dev = pci_get_slot(slot->pci_bus, + PCI_DEVFN(slot->device_num + 1, + PCI_FUNC(func))); + if (dev) { + sn_bus_free_data(dev); + pci_remove_bus_device(dev); + pci_dev_put(dev); + } + } + + /* Remove the SSDT for the slot from the ACPI namespace */ + if (SN_ACPI_BASE_SUPPORT() && ssdt_id) { + acpi_status ret; + ret = acpi_unload_table_id(ssdt_id); + if (ACPI_FAILURE(ret)) { + printk(KERN_ERR "%s: acpi_unload_table_id " + "failed (0x%x) for id %d\n", + __func__, ret, ssdt_id); + /* try to continue on */ + } + } + + /* free the collected sysdata pointers */ + sn_bus_free_sysdata(); + + /* Deactivate slot */ + rc = sn_slot_disable(bss_hotplug_slot, slot->device_num, + PCI_REQ_SLOT_DISABLE); + leaving: + /* Release the bus lock */ + mutex_unlock(&sn_hotplug_mutex); + + return rc; +} + +static inline int get_power_status(struct hotplug_slot *bss_hotplug_slot, + u8 *value) +{ + struct slot *slot = bss_hotplug_slot->private; + struct pcibus_info *pcibus_info; + u32 power; + + pcibus_info = SN_PCIBUS_BUSSOFT_INFO(slot->pci_bus); + mutex_lock(&sn_hotplug_mutex); + power = pcibus_info->pbi_enabled_devices & (1 << slot->device_num); + *value = power ? 1 : 0; + mutex_unlock(&sn_hotplug_mutex); + return 0; +} + +static void sn_release_slot(struct hotplug_slot *bss_hotplug_slot) +{ + kfree(bss_hotplug_slot->info); + kfree(bss_hotplug_slot->private); + kfree(bss_hotplug_slot); +} + +static int sn_hotplug_slot_register(struct pci_bus *pci_bus) +{ + int device; + struct pci_slot *pci_slot; + struct hotplug_slot *bss_hotplug_slot; + char name[SN_SLOT_NAME_SIZE]; + int rc = 0; + + /* + * Currently only four devices are supported, + * in the future there maybe more -- up to 32. + */ + + for (device = 0; device < SN_MAX_HP_SLOTS ; device++) { + if (sn_pci_slot_valid(pci_bus, device) != 1) + continue; + + bss_hotplug_slot = kzalloc(sizeof(*bss_hotplug_slot), + GFP_KERNEL); + if (!bss_hotplug_slot) { + rc = -ENOMEM; + goto alloc_err; + } + + bss_hotplug_slot->info = + kzalloc(sizeof(struct hotplug_slot_info), + GFP_KERNEL); + if (!bss_hotplug_slot->info) { + rc = -ENOMEM; + goto alloc_err; + } + + if (sn_hp_slot_private_alloc(bss_hotplug_slot, + pci_bus, device, name)) { + rc = -ENOMEM; + goto alloc_err; + } + bss_hotplug_slot->ops = &sn_hotplug_slot_ops; + bss_hotplug_slot->release = &sn_release_slot; + + rc = pci_hp_register(bss_hotplug_slot, pci_bus, device, name); + if (rc) + goto register_err; + + pci_slot = bss_hotplug_slot->pci_slot; + rc = sysfs_create_file(&pci_slot->kobj, + &sn_slot_path_attr.attr); + if (rc) + goto register_err; + } + dev_dbg(&pci_bus->self->dev, "Registered bus with hotplug\n"); + return rc; + +register_err: + dev_dbg(&pci_bus->self->dev, "bus failed to register with err = %d\n", + rc); + +alloc_err: + if (rc == -ENOMEM) + dev_dbg(&pci_bus->self->dev, "Memory allocation error\n"); + + /* destroy THIS element */ + if (bss_hotplug_slot) + sn_release_slot(bss_hotplug_slot); + + /* destroy anything else on the list */ + while ((bss_hotplug_slot = sn_hp_destroy())) + pci_hp_deregister(bss_hotplug_slot); + + return rc; +} + +static int sn_pci_hotplug_init(void) +{ + struct pci_bus *pci_bus = NULL; + int rc; + int registered = 0; + + if (!sn_prom_feature_available(PRF_HOTPLUG_SUPPORT)) { + printk(KERN_ERR "%s: PROM version does not support hotplug.\n", + __func__); + return -EPERM; + } + + INIT_LIST_HEAD(&sn_hp_list); + + while ((pci_bus = pci_find_next_bus(pci_bus))) { + if (!pci_bus->sysdata) + continue; + + rc = sn_pci_bus_valid(pci_bus); + if (rc != 1) { + dev_dbg(&pci_bus->self->dev, "not a valid hotplug bus\n"); + continue; + } + dev_dbg(&pci_bus->self->dev, "valid hotplug bus\n"); + + rc = sn_hotplug_slot_register(pci_bus); + if (!rc) { + registered = 1; + } else { + registered = 0; + break; + } + } + + return registered == 1 ? 0 : -ENODEV; +} + +static void sn_pci_hotplug_exit(void) +{ + struct hotplug_slot *bss_hotplug_slot; + + while ((bss_hotplug_slot = sn_hp_destroy())) + pci_hp_deregister(bss_hotplug_slot); + + if (!list_empty(&sn_hp_list)) + printk(KERN_ERR "%s: internal list is not empty\n", __FILE__); +} + +module_init(sn_pci_hotplug_init); +module_exit(sn_pci_hotplug_exit); diff --git a/drivers/pci/hotplug/shpchp.h b/drivers/pci/hotplug/shpchp.h new file mode 100644 index 0000000..6aba0b6 --- /dev/null +++ b/drivers/pci/hotplug/shpchp.h @@ -0,0 +1,358 @@ +/* + * Standard Hot Plug Controller Driver + * + * Copyright (C) 1995,2001 Compaq Computer Corporation + * Copyright (C) 2001 Greg Kroah-Hartman (greg@kroah.com) + * Copyright (C) 2001 IBM + * Copyright (C) 2003-2004 Intel Corporation + * + * 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>,<kristen.c.accardi@intel.com> + * + */ +#ifndef _SHPCHP_H +#define _SHPCHP_H + +#include <linux/types.h> +#include <linux/pci.h> +#include <linux/pci_hotplug.h> +#include <linux/delay.h> +#include <linux/sched.h> /* signal_pending(), struct timer_list */ +#include <linux/mutex.h> + +#if !defined(MODULE) + #define MY_NAME "shpchp" +#else + #define MY_NAME THIS_MODULE->name +#endif + +extern int shpchp_poll_mode; +extern int shpchp_poll_time; +extern int shpchp_debug; +extern struct workqueue_struct *shpchp_wq; + +#define dbg(format, arg...) \ + do { \ + if (shpchp_debug) \ + printk("%s: " format, 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) + +#define ctrl_dbg(ctrl, format, arg...) \ + do { \ + if (shpchp_debug) \ + dev_printk(, &ctrl->pci_dev->dev, \ + format, ## arg); \ + } while (0) +#define ctrl_err(ctrl, format, arg...) \ + dev_err(&ctrl->pci_dev->dev, format, ## arg) +#define ctrl_info(ctrl, format, arg...) \ + dev_info(&ctrl->pci_dev->dev, format, ## arg) +#define ctrl_warn(ctrl, format, arg...) \ + dev_warn(&ctrl->pci_dev->dev, format, ## arg) + + +#define SLOT_NAME_SIZE 10 +struct slot { + u8 bus; + u8 device; + u16 status; + u32 number; + u8 is_a_board; + u8 state; + u8 presence_save; + u8 pwr_save; + struct controller *ctrl; + struct hpc_ops *hpc_ops; + struct hotplug_slot *hotplug_slot; + struct list_head slot_list; + struct delayed_work work; /* work for button event */ + struct mutex lock; + u8 hp_slot; +}; + +struct event_info { + u32 event_type; + struct slot *p_slot; + struct work_struct work; +}; + +struct controller { + struct mutex crit_sect; /* critical section mutex */ + struct mutex cmd_lock; /* command lock */ + int num_slots; /* Number of slots on ctlr */ + int slot_num_inc; /* 1 or -1 */ + struct pci_dev *pci_dev; + struct list_head slot_list; + struct hpc_ops *hpc_ops; + wait_queue_head_t queue; /* sleep & wake process */ + u8 slot_device_offset; + u32 pcix_misc2_reg; /* for amd pogo errata */ + u32 first_slot; /* First physical slot number */ + u32 cap_offset; + unsigned long mmio_base; + unsigned long mmio_size; + void __iomem *creg; + struct timer_list poll_timer; +}; + +/* Define AMD SHPC ID */ +#define PCI_DEVICE_ID_AMD_GOLAM_7450 0x7450 +#define PCI_DEVICE_ID_AMD_POGO_7458 0x7458 + +/* AMD PCIX bridge registers */ +#define PCIX_MEM_BASE_LIMIT_OFFSET 0x1C +#define PCIX_MISCII_OFFSET 0x48 +#define PCIX_MISC_BRIDGE_ERRORS_OFFSET 0x80 + +/* AMD PCIX_MISCII masks and offsets */ +#define PERRNONFATALENABLE_MASK 0x00040000 +#define PERRFATALENABLE_MASK 0x00080000 +#define PERRFLOODENABLE_MASK 0x00100000 +#define SERRNONFATALENABLE_MASK 0x00200000 +#define SERRFATALENABLE_MASK 0x00400000 + +/* AMD PCIX_MISC_BRIDGE_ERRORS masks and offsets */ +#define PERR_OBSERVED_MASK 0x00000001 + +/* AMD PCIX_MEM_BASE_LIMIT masks */ +#define RSE_MASK 0x40000000 + +#define INT_BUTTON_IGNORE 0 +#define INT_PRESENCE_ON 1 +#define INT_PRESENCE_OFF 2 +#define INT_SWITCH_CLOSE 3 +#define INT_SWITCH_OPEN 4 +#define INT_POWER_FAULT 5 +#define INT_POWER_FAULT_CLEAR 6 +#define INT_BUTTON_PRESS 7 +#define INT_BUTTON_RELEASE 8 +#define INT_BUTTON_CANCEL 9 + +#define STATIC_STATE 0 +#define BLINKINGON_STATE 1 +#define BLINKINGOFF_STATE 2 +#define POWERON_STATE 3 +#define POWEROFF_STATE 4 + +/* Error messages */ +#define INTERLOCK_OPEN 0x00000002 +#define ADD_NOT_SUPPORTED 0x00000003 +#define CARD_FUNCTIONING 0x00000005 +#define ADAPTER_NOT_SAME 0x00000006 +#define NO_ADAPTER_PRESENT 0x00000009 +#define NOT_ENOUGH_RESOURCES 0x0000000B +#define DEVICE_TYPE_NOT_SUPPORTED 0x0000000C +#define WRONG_BUS_FREQUENCY 0x0000000D +#define POWER_FAILURE 0x0000000E + +extern int __must_check shpchp_create_ctrl_files(struct controller *ctrl); +extern void shpchp_remove_ctrl_files(struct controller *ctrl); +extern int shpchp_sysfs_enable_slot(struct slot *slot); +extern int shpchp_sysfs_disable_slot(struct slot *slot); +extern u8 shpchp_handle_attention_button(u8 hp_slot, struct controller *ctrl); +extern u8 shpchp_handle_switch_change(u8 hp_slot, struct controller *ctrl); +extern u8 shpchp_handle_presence_change(u8 hp_slot, struct controller *ctrl); +extern u8 shpchp_handle_power_fault(u8 hp_slot, struct controller *ctrl); +extern int shpchp_configure_device(struct slot *p_slot); +extern int shpchp_unconfigure_device(struct slot *p_slot); +extern void cleanup_slots(struct controller *ctrl); +extern void shpchp_queue_pushbutton_work(struct work_struct *work); +extern int shpc_init( struct controller *ctrl, struct pci_dev *pdev); + +static inline const char *slot_name(struct slot *slot) +{ + return hotplug_slot_name(slot->hotplug_slot); +} + +#ifdef CONFIG_ACPI +#include <linux/pci-acpi.h> +static inline int get_hp_params_from_firmware(struct pci_dev *dev, + struct hotplug_params *hpp) +{ + if (ACPI_FAILURE(acpi_get_hp_params_from_firmware(dev->bus, hpp))) + return -ENODEV; + return 0; +} + +static inline int get_hp_hw_control_from_firmware(struct pci_dev *dev) +{ + u32 flags = OSC_SHPC_NATIVE_HP_CONTROL; + return acpi_get_hp_hw_control_from_firmware(dev, flags); +} +#else +#define get_hp_params_from_firmware(dev, hpp) (-ENODEV) +#define get_hp_hw_control_from_firmware(dev) (0) +#endif + +struct ctrl_reg { + volatile u32 base_offset; + volatile u32 slot_avail1; + volatile u32 slot_avail2; + volatile u32 slot_config; + volatile u16 sec_bus_config; + volatile u8 msi_ctrl; + volatile u8 prog_interface; + volatile u16 cmd; + volatile u16 cmd_status; + volatile u32 intr_loc; + volatile u32 serr_loc; + volatile u32 serr_intr_enable; + volatile u32 slot1; +} __attribute__ ((packed)); + +/* offsets to the controller registers based on the above structure layout */ +enum ctrl_offsets { + BASE_OFFSET = offsetof(struct ctrl_reg, base_offset), + SLOT_AVAIL1 = offsetof(struct ctrl_reg, slot_avail1), + SLOT_AVAIL2 = offsetof(struct ctrl_reg, slot_avail2), + SLOT_CONFIG = offsetof(struct ctrl_reg, slot_config), + SEC_BUS_CONFIG = offsetof(struct ctrl_reg, sec_bus_config), + MSI_CTRL = offsetof(struct ctrl_reg, msi_ctrl), + PROG_INTERFACE = offsetof(struct ctrl_reg, prog_interface), + CMD = offsetof(struct ctrl_reg, cmd), + CMD_STATUS = offsetof(struct ctrl_reg, cmd_status), + INTR_LOC = offsetof(struct ctrl_reg, intr_loc), + SERR_LOC = offsetof(struct ctrl_reg, serr_loc), + SERR_INTR_ENABLE = offsetof(struct ctrl_reg, serr_intr_enable), + SLOT1 = offsetof(struct ctrl_reg, slot1), +}; + +static inline struct slot *get_slot(struct hotplug_slot *hotplug_slot) +{ + return hotplug_slot->private; +} + +static inline struct slot *shpchp_find_slot(struct controller *ctrl, u8 device) +{ + struct slot *slot; + + list_for_each_entry(slot, &ctrl->slot_list, slot_list) { + if (slot->device == device) + return slot; + } + + ctrl_err(ctrl, "Slot (device=0x%02x) not found\n", device); + return NULL; +} + +static inline void amd_pogo_errata_save_misc_reg(struct slot *p_slot) +{ + u32 pcix_misc2_temp; + + /* save MiscII register */ + pci_read_config_dword(p_slot->ctrl->pci_dev, PCIX_MISCII_OFFSET, &pcix_misc2_temp); + + p_slot->ctrl->pcix_misc2_reg = pcix_misc2_temp; + + /* clear SERR/PERR enable bits */ + pcix_misc2_temp &= ~SERRFATALENABLE_MASK; + pcix_misc2_temp &= ~SERRNONFATALENABLE_MASK; + pcix_misc2_temp &= ~PERRFLOODENABLE_MASK; + pcix_misc2_temp &= ~PERRFATALENABLE_MASK; + pcix_misc2_temp &= ~PERRNONFATALENABLE_MASK; + pci_write_config_dword(p_slot->ctrl->pci_dev, PCIX_MISCII_OFFSET, pcix_misc2_temp); +} + +static inline void amd_pogo_errata_restore_misc_reg(struct slot *p_slot) +{ + u32 pcix_misc2_temp; + u32 pcix_bridge_errors_reg; + u32 pcix_mem_base_reg; + u8 perr_set; + u8 rse_set; + + /* write-one-to-clear Bridge_Errors[ PERR_OBSERVED ] */ + pci_read_config_dword(p_slot->ctrl->pci_dev, PCIX_MISC_BRIDGE_ERRORS_OFFSET, &pcix_bridge_errors_reg); + perr_set = pcix_bridge_errors_reg & PERR_OBSERVED_MASK; + if (perr_set) { + ctrl_dbg(p_slot->ctrl, + "Bridge_Errors[ PERR_OBSERVED = %08X] (W1C)\n", + perr_set); + + pci_write_config_dword(p_slot->ctrl->pci_dev, PCIX_MISC_BRIDGE_ERRORS_OFFSET, perr_set); + } + + /* write-one-to-clear Memory_Base_Limit[ RSE ] */ + pci_read_config_dword(p_slot->ctrl->pci_dev, PCIX_MEM_BASE_LIMIT_OFFSET, &pcix_mem_base_reg); + rse_set = pcix_mem_base_reg & RSE_MASK; + if (rse_set) { + ctrl_dbg(p_slot->ctrl, "Memory_Base_Limit[ RSE ] (W1C)\n"); + + pci_write_config_dword(p_slot->ctrl->pci_dev, PCIX_MEM_BASE_LIMIT_OFFSET, rse_set); + } + /* restore MiscII register */ + pci_read_config_dword( p_slot->ctrl->pci_dev, PCIX_MISCII_OFFSET, &pcix_misc2_temp ); + + if (p_slot->ctrl->pcix_misc2_reg & SERRFATALENABLE_MASK) + pcix_misc2_temp |= SERRFATALENABLE_MASK; + else + pcix_misc2_temp &= ~SERRFATALENABLE_MASK; + + if (p_slot->ctrl->pcix_misc2_reg & SERRNONFATALENABLE_MASK) + pcix_misc2_temp |= SERRNONFATALENABLE_MASK; + else + pcix_misc2_temp &= ~SERRNONFATALENABLE_MASK; + + if (p_slot->ctrl->pcix_misc2_reg & PERRFLOODENABLE_MASK) + pcix_misc2_temp |= PERRFLOODENABLE_MASK; + else + pcix_misc2_temp &= ~PERRFLOODENABLE_MASK; + + if (p_slot->ctrl->pcix_misc2_reg & PERRFATALENABLE_MASK) + pcix_misc2_temp |= PERRFATALENABLE_MASK; + else + pcix_misc2_temp &= ~PERRFATALENABLE_MASK; + + if (p_slot->ctrl->pcix_misc2_reg & PERRNONFATALENABLE_MASK) + pcix_misc2_temp |= PERRNONFATALENABLE_MASK; + else + pcix_misc2_temp &= ~PERRNONFATALENABLE_MASK; + pci_write_config_dword(p_slot->ctrl->pci_dev, PCIX_MISCII_OFFSET, pcix_misc2_temp); +} + +struct hpc_ops { + int (*power_on_slot)(struct slot *slot); + int (*slot_enable)(struct slot *slot); + int (*slot_disable)(struct slot *slot); + int (*set_bus_speed_mode)(struct slot *slot, enum pci_bus_speed speed); + int (*get_power_status)(struct slot *slot, u8 *status); + int (*get_attention_status)(struct slot *slot, u8 *status); + int (*set_attention_status)(struct slot *slot, u8 status); + int (*get_latch_status)(struct slot *slot, u8 *status); + int (*get_adapter_status)(struct slot *slot, u8 *status); + int (*get_max_bus_speed)(struct slot *slot, enum pci_bus_speed *speed); + int (*get_cur_bus_speed)(struct slot *slot, enum pci_bus_speed *speed); + int (*get_adapter_speed)(struct slot *slot, enum pci_bus_speed *speed); + int (*get_mode1_ECC_cap)(struct slot *slot, u8 *mode); + int (*get_prog_int)(struct slot *slot, u8 *prog_int); + int (*query_power_fault)(struct slot *slot); + void (*green_led_on)(struct slot *slot); + void (*green_led_off)(struct slot *slot); + void (*green_led_blink)(struct slot *slot); + void (*release_ctlr)(struct controller *ctrl); + int (*check_cmd_status)(struct controller *ctrl); +}; + +#endif /* _SHPCHP_H */ diff --git a/drivers/pci/hotplug/shpchp_core.c b/drivers/pci/hotplug/shpchp_core.c new file mode 100644 index 0000000..fe8d149 --- /dev/null +++ b/drivers/pci/hotplug/shpchp_core.c @@ -0,0 +1,412 @@ +/* + * Standard Hot Plug Controller Driver + * + * Copyright (C) 1995,2001 Compaq Computer Corporation + * Copyright (C) 2001 Greg Kroah-Hartman (greg@kroah.com) + * Copyright (C) 2001 IBM Corp. + * Copyright (C) 2003-2004 Intel Corporation + * + * 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>, <kristen.c.accardi@intel.com> + * + */ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/kernel.h> +#include <linux/types.h> +#include <linux/pci.h> +#include <linux/workqueue.h> +#include "shpchp.h" + +/* Global variables */ +int shpchp_debug; +int shpchp_poll_mode; +int shpchp_poll_time; +struct workqueue_struct *shpchp_wq; + +#define DRIVER_VERSION "0.4" +#define DRIVER_AUTHOR "Dan Zink <dan.zink@compaq.com>, Greg Kroah-Hartman <greg@kroah.com>, Dely Sy <dely.l.sy@intel.com>" +#define DRIVER_DESC "Standard Hot Plug PCI Controller Driver" + +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); + +module_param(shpchp_debug, bool, 0644); +module_param(shpchp_poll_mode, bool, 0644); +module_param(shpchp_poll_time, int, 0644); +MODULE_PARM_DESC(shpchp_debug, "Debugging mode enabled or not"); +MODULE_PARM_DESC(shpchp_poll_mode, "Using polling mechanism for hot-plug events or not"); +MODULE_PARM_DESC(shpchp_poll_time, "Polling mechanism frequency, in seconds"); + +#define SHPC_MODULE_NAME "shpchp" + +static int set_attention_status (struct hotplug_slot *slot, u8 value); +static int enable_slot (struct hotplug_slot *slot); +static int disable_slot (struct hotplug_slot *slot); +static int get_power_status (struct hotplug_slot *slot, u8 *value); +static int get_attention_status (struct hotplug_slot *slot, u8 *value); +static int get_latch_status (struct hotplug_slot *slot, u8 *value); +static int get_adapter_status (struct hotplug_slot *slot, u8 *value); +static int get_max_bus_speed (struct hotplug_slot *slot, enum pci_bus_speed *value); +static int get_cur_bus_speed (struct hotplug_slot *slot, enum pci_bus_speed *value); + +static struct hotplug_slot_ops shpchp_hotplug_slot_ops = { + .owner = THIS_MODULE, + .set_attention_status = set_attention_status, + .enable_slot = enable_slot, + .disable_slot = disable_slot, + .get_power_status = get_power_status, + .get_attention_status = get_attention_status, + .get_latch_status = get_latch_status, + .get_adapter_status = get_adapter_status, + .get_max_bus_speed = get_max_bus_speed, + .get_cur_bus_speed = get_cur_bus_speed, +}; + +/** + * release_slot - free up the memory used by a slot + * @hotplug_slot: slot to free + */ +static void release_slot(struct hotplug_slot *hotplug_slot) +{ + struct slot *slot = hotplug_slot->private; + + ctrl_dbg(slot->ctrl, "%s: physical_slot = %s\n", + __func__, slot_name(slot)); + + kfree(slot->hotplug_slot->info); + kfree(slot->hotplug_slot); + kfree(slot); +} + +static int init_slots(struct controller *ctrl) +{ + struct slot *slot; + struct hotplug_slot *hotplug_slot; + struct hotplug_slot_info *info; + char name[SLOT_NAME_SIZE]; + int retval = -ENOMEM; + int i; + + for (i = 0; i < ctrl->num_slots; i++) { + slot = kzalloc(sizeof(*slot), GFP_KERNEL); + if (!slot) + goto error; + + hotplug_slot = kzalloc(sizeof(*hotplug_slot), GFP_KERNEL); + if (!hotplug_slot) + goto error_slot; + slot->hotplug_slot = hotplug_slot; + + info = kzalloc(sizeof(*info), GFP_KERNEL); + if (!info) + goto error_hpslot; + hotplug_slot->info = info; + + slot->hp_slot = i; + slot->ctrl = ctrl; + slot->bus = ctrl->pci_dev->subordinate->number; + slot->device = ctrl->slot_device_offset + i; + slot->hpc_ops = ctrl->hpc_ops; + slot->number = ctrl->first_slot + (ctrl->slot_num_inc * i); + mutex_init(&slot->lock); + INIT_DELAYED_WORK(&slot->work, shpchp_queue_pushbutton_work); + + /* register this slot with the hotplug pci core */ + hotplug_slot->private = slot; + hotplug_slot->release = &release_slot; + snprintf(name, SLOT_NAME_SIZE, "%d", slot->number); + hotplug_slot->ops = &shpchp_hotplug_slot_ops; + + ctrl_dbg(ctrl, "Registering domain:bus:dev=%04x:%02x:%02x " + "hp_slot=%x sun=%x slot_device_offset=%x\n", + pci_domain_nr(ctrl->pci_dev->subordinate), + slot->bus, slot->device, slot->hp_slot, slot->number, + ctrl->slot_device_offset); + retval = pci_hp_register(slot->hotplug_slot, + ctrl->pci_dev->subordinate, slot->device, name); + if (retval) { + ctrl_err(ctrl, "pci_hp_register failed with error %d\n", + retval); + goto error_info; + } + + get_power_status(hotplug_slot, &info->power_status); + get_attention_status(hotplug_slot, &info->attention_status); + get_latch_status(hotplug_slot, &info->latch_status); + get_adapter_status(hotplug_slot, &info->adapter_status); + + list_add(&slot->slot_list, &ctrl->slot_list); + } + + return 0; +error_info: + kfree(info); +error_hpslot: + kfree(hotplug_slot); +error_slot: + kfree(slot); +error: + return retval; +} + +void cleanup_slots(struct controller *ctrl) +{ + struct list_head *tmp; + struct list_head *next; + struct slot *slot; + + list_for_each_safe(tmp, next, &ctrl->slot_list) { + slot = list_entry(tmp, struct slot, slot_list); + list_del(&slot->slot_list); + cancel_delayed_work(&slot->work); + flush_scheduled_work(); + flush_workqueue(shpchp_wq); + pci_hp_deregister(slot->hotplug_slot); + } +} + +/* + * set_attention_status - Turns the Amber LED for a slot on, off or blink + */ +static int set_attention_status (struct hotplug_slot *hotplug_slot, u8 status) +{ + struct slot *slot = get_slot(hotplug_slot); + + ctrl_dbg(slot->ctrl, "%s: physical_slot = %s\n", + __func__, slot_name(slot)); + + hotplug_slot->info->attention_status = status; + slot->hpc_ops->set_attention_status(slot, status); + + return 0; +} + +static int enable_slot (struct hotplug_slot *hotplug_slot) +{ + struct slot *slot = get_slot(hotplug_slot); + + ctrl_dbg(slot->ctrl, "%s: physical_slot = %s\n", + __func__, slot_name(slot)); + + return shpchp_sysfs_enable_slot(slot); +} + +static int disable_slot (struct hotplug_slot *hotplug_slot) +{ + struct slot *slot = get_slot(hotplug_slot); + + ctrl_dbg(slot->ctrl, "%s: physical_slot = %s\n", + __func__, slot_name(slot)); + + return shpchp_sysfs_disable_slot(slot); +} + +static int get_power_status (struct hotplug_slot *hotplug_slot, u8 *value) +{ + struct slot *slot = get_slot(hotplug_slot); + int retval; + + ctrl_dbg(slot->ctrl, "%s: physical_slot = %s\n", + __func__, slot_name(slot)); + + retval = slot->hpc_ops->get_power_status(slot, value); + if (retval < 0) + *value = hotplug_slot->info->power_status; + + return 0; +} + +static int get_attention_status (struct hotplug_slot *hotplug_slot, u8 *value) +{ + struct slot *slot = get_slot(hotplug_slot); + int retval; + + ctrl_dbg(slot->ctrl, "%s: physical_slot = %s\n", + __func__, slot_name(slot)); + + retval = slot->hpc_ops->get_attention_status(slot, value); + if (retval < 0) + *value = hotplug_slot->info->attention_status; + + return 0; +} + +static int get_latch_status (struct hotplug_slot *hotplug_slot, u8 *value) +{ + struct slot *slot = get_slot(hotplug_slot); + int retval; + + ctrl_dbg(slot->ctrl, "%s: physical_slot = %s\n", + __func__, slot_name(slot)); + + retval = slot->hpc_ops->get_latch_status(slot, value); + if (retval < 0) + *value = hotplug_slot->info->latch_status; + + return 0; +} + +static int get_adapter_status (struct hotplug_slot *hotplug_slot, u8 *value) +{ + struct slot *slot = get_slot(hotplug_slot); + int retval; + + ctrl_dbg(slot->ctrl, "%s: physical_slot = %s\n", + __func__, slot_name(slot)); + + retval = slot->hpc_ops->get_adapter_status(slot, value); + if (retval < 0) + *value = hotplug_slot->info->adapter_status; + + return 0; +} + +static int get_max_bus_speed(struct hotplug_slot *hotplug_slot, + enum pci_bus_speed *value) +{ + struct slot *slot = get_slot(hotplug_slot); + int retval; + + ctrl_dbg(slot->ctrl, "%s: physical_slot = %s\n", + __func__, slot_name(slot)); + + retval = slot->hpc_ops->get_max_bus_speed(slot, value); + if (retval < 0) + *value = PCI_SPEED_UNKNOWN; + + return 0; +} + +static int get_cur_bus_speed (struct hotplug_slot *hotplug_slot, enum pci_bus_speed *value) +{ + struct slot *slot = get_slot(hotplug_slot); + int retval; + + ctrl_dbg(slot->ctrl, "%s: physical_slot = %s\n", + __func__, slot_name(slot)); + + retval = slot->hpc_ops->get_cur_bus_speed(slot, value); + if (retval < 0) + *value = PCI_SPEED_UNKNOWN; + + return 0; +} + +static int is_shpc_capable(struct pci_dev *dev) +{ + if ((dev->vendor == PCI_VENDOR_ID_AMD) || (dev->device == + PCI_DEVICE_ID_AMD_GOLAM_7450)) + return 1; + if (!pci_find_capability(dev, PCI_CAP_ID_SHPC)) + return 0; + if (get_hp_hw_control_from_firmware(dev)) + return 0; + return 1; +} + +static int shpc_probe(struct pci_dev *pdev, const struct pci_device_id *ent) +{ + int rc; + struct controller *ctrl; + + if (!is_shpc_capable(pdev)) + return -ENODEV; + + ctrl = kzalloc(sizeof(*ctrl), GFP_KERNEL); + if (!ctrl) { + dev_err(&pdev->dev, "%s: Out of memory\n", __func__); + goto err_out_none; + } + INIT_LIST_HEAD(&ctrl->slot_list); + + rc = shpc_init(ctrl, pdev); + if (rc) { + ctrl_dbg(ctrl, "Controller initialization failed\n"); + goto err_out_free_ctrl; + } + + pci_set_drvdata(pdev, ctrl); + + /* Setup the slot information structures */ + rc = init_slots(ctrl); + if (rc) { + ctrl_err(ctrl, "Slot initialization failed\n"); + goto err_out_release_ctlr; + } + + rc = shpchp_create_ctrl_files(ctrl); + if (rc) + goto err_cleanup_slots; + + return 0; + +err_cleanup_slots: + cleanup_slots(ctrl); +err_out_release_ctlr: + ctrl->hpc_ops->release_ctlr(ctrl); +err_out_free_ctrl: + kfree(ctrl); +err_out_none: + return -ENODEV; +} + +static void shpc_remove(struct pci_dev *dev) +{ + struct controller *ctrl = pci_get_drvdata(dev); + + shpchp_remove_ctrl_files(ctrl); + ctrl->hpc_ops->release_ctlr(ctrl); + kfree(ctrl); +} + +static struct pci_device_id shpcd_pci_tbl[] = { + {PCI_DEVICE_CLASS(((PCI_CLASS_BRIDGE_PCI << 8) | 0x00), ~0)}, + { /* end: all zeroes */ } +}; +MODULE_DEVICE_TABLE(pci, shpcd_pci_tbl); + +static struct pci_driver shpc_driver = { + .name = SHPC_MODULE_NAME, + .id_table = shpcd_pci_tbl, + .probe = shpc_probe, + .remove = shpc_remove, +}; + +static int __init shpcd_init(void) +{ + int retval = 0; + + retval = pci_register_driver(&shpc_driver); + dbg("%s: pci_register_driver = %d\n", __func__, retval); + info(DRIVER_DESC " version: " DRIVER_VERSION "\n"); + return retval; +} + +static void __exit shpcd_cleanup(void) +{ + dbg("unload_shpchpd()\n"); + pci_unregister_driver(&shpc_driver); + info(DRIVER_DESC " version: " DRIVER_VERSION " unloaded\n"); +} + +module_init(shpcd_init); +module_exit(shpcd_cleanup); diff --git a/drivers/pci/hotplug/shpchp_ctrl.c b/drivers/pci/hotplug/shpchp_ctrl.c new file mode 100644 index 0000000..b8ab279 --- /dev/null +++ b/drivers/pci/hotplug/shpchp_ctrl.c @@ -0,0 +1,739 @@ +/* + * Standard Hot Plug Controller Driver + * + * Copyright (C) 1995,2001 Compaq Computer Corporation + * Copyright (C) 2001 Greg Kroah-Hartman (greg@kroah.com) + * Copyright (C) 2001 IBM Corp. + * Copyright (C) 2003-2004 Intel Corporation + * + * 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>, <kristen.c.accardi@intel.com> + * + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/types.h> +#include <linux/pci.h> +#include <linux/workqueue.h> +#include "../pci.h" +#include "shpchp.h" + +static void interrupt_event_handler(struct work_struct *work); +static int shpchp_enable_slot(struct slot *p_slot); +static int shpchp_disable_slot(struct slot *p_slot); + +static int queue_interrupt_event(struct slot *p_slot, u32 event_type) +{ + struct event_info *info; + + info = kmalloc(sizeof(*info), GFP_ATOMIC); + if (!info) + return -ENOMEM; + + info->event_type = event_type; + info->p_slot = p_slot; + INIT_WORK(&info->work, interrupt_event_handler); + + schedule_work(&info->work); + + return 0; +} + +u8 shpchp_handle_attention_button(u8 hp_slot, struct controller *ctrl) +{ + struct slot *p_slot; + u32 event_type; + + /* Attention Button Change */ + ctrl_dbg(ctrl, "Attention button interrupt received\n"); + + p_slot = shpchp_find_slot(ctrl, hp_slot + ctrl->slot_device_offset); + p_slot->hpc_ops->get_adapter_status(p_slot, &(p_slot->presence_save)); + + /* + * Button pressed - See if need to TAKE ACTION!!! + */ + ctrl_info(ctrl, "Button pressed on Slot(%s)\n", slot_name(p_slot)); + event_type = INT_BUTTON_PRESS; + + queue_interrupt_event(p_slot, event_type); + + return 0; + +} + +u8 shpchp_handle_switch_change(u8 hp_slot, struct controller *ctrl) +{ + struct slot *p_slot; + u8 getstatus; + u32 event_type; + + /* Switch Change */ + ctrl_dbg(ctrl, "Switch interrupt received\n"); + + p_slot = shpchp_find_slot(ctrl, hp_slot + ctrl->slot_device_offset); + p_slot->hpc_ops->get_adapter_status(p_slot, &(p_slot->presence_save)); + p_slot->hpc_ops->get_latch_status(p_slot, &getstatus); + ctrl_dbg(ctrl, "Card present %x Power status %x\n", + p_slot->presence_save, p_slot->pwr_save); + + if (getstatus) { + /* + * Switch opened + */ + ctrl_info(ctrl, "Latch open on Slot(%s)\n", slot_name(p_slot)); + event_type = INT_SWITCH_OPEN; + if (p_slot->pwr_save && p_slot->presence_save) { + event_type = INT_POWER_FAULT; + ctrl_err(ctrl, "Surprise Removal of card\n"); + } + } else { + /* + * Switch closed + */ + ctrl_info(ctrl, "Latch close on Slot(%s)\n", slot_name(p_slot)); + event_type = INT_SWITCH_CLOSE; + } + + queue_interrupt_event(p_slot, event_type); + + return 1; +} + +u8 shpchp_handle_presence_change(u8 hp_slot, struct controller *ctrl) +{ + struct slot *p_slot; + u32 event_type; + + /* Presence Change */ + ctrl_dbg(ctrl, "Presence/Notify input change\n"); + + p_slot = shpchp_find_slot(ctrl, hp_slot + ctrl->slot_device_offset); + + /* + * Save the presence state + */ + p_slot->hpc_ops->get_adapter_status(p_slot, &(p_slot->presence_save)); + if (p_slot->presence_save) { + /* + * Card Present + */ + ctrl_info(ctrl, "Card present on Slot(%s)\n", + slot_name(p_slot)); + event_type = INT_PRESENCE_ON; + } else { + /* + * Not Present + */ + ctrl_info(ctrl, "Card not present on Slot(%s)\n", + slot_name(p_slot)); + event_type = INT_PRESENCE_OFF; + } + + queue_interrupt_event(p_slot, event_type); + + return 1; +} + +u8 shpchp_handle_power_fault(u8 hp_slot, struct controller *ctrl) +{ + struct slot *p_slot; + u32 event_type; + + /* Power fault */ + ctrl_dbg(ctrl, "Power fault interrupt received\n"); + + p_slot = shpchp_find_slot(ctrl, hp_slot + ctrl->slot_device_offset); + + if ( !(p_slot->hpc_ops->query_power_fault(p_slot))) { + /* + * Power fault Cleared + */ + ctrl_info(ctrl, "Power fault cleared on Slot(%s)\n", + slot_name(p_slot)); + p_slot->status = 0x00; + event_type = INT_POWER_FAULT_CLEAR; + } else { + /* + * Power fault + */ + ctrl_info(ctrl, "Power fault on Slot(%s)\n", slot_name(p_slot)); + event_type = INT_POWER_FAULT; + /* set power fault status for this board */ + p_slot->status = 0xFF; + ctrl_info(ctrl, "Power fault bit %x set\n", hp_slot); + } + + queue_interrupt_event(p_slot, event_type); + + return 1; +} + +/* The following routines constitute the bulk of the + hotplug controller logic + */ +static int change_bus_speed(struct controller *ctrl, struct slot *p_slot, + enum pci_bus_speed speed) +{ + int rc = 0; + + ctrl_dbg(ctrl, "Change speed to %d\n", speed); + if ((rc = p_slot->hpc_ops->set_bus_speed_mode(p_slot, speed))) { + ctrl_err(ctrl, "%s: Issue of set bus speed mode command " + "failed\n", __func__); + return WRONG_BUS_FREQUENCY; + } + return rc; +} + +static int fix_bus_speed(struct controller *ctrl, struct slot *pslot, + u8 flag, enum pci_bus_speed asp, enum pci_bus_speed bsp, + enum pci_bus_speed msp) +{ + int rc = 0; + + /* + * If other slots on the same bus are occupied, we cannot + * change the bus speed. + */ + if (flag) { + if (asp < bsp) { + ctrl_err(ctrl, "Speed of bus %x and adapter %x " + "mismatch\n", bsp, asp); + rc = WRONG_BUS_FREQUENCY; + } + return rc; + } + + if (asp < msp) { + if (bsp != asp) + rc = change_bus_speed(ctrl, pslot, asp); + } else { + if (bsp != msp) + rc = change_bus_speed(ctrl, pslot, msp); + } + return rc; +} + +/** + * board_added - Called after a board has been added to the system. + * @p_slot: target &slot + * + * Turns power on for the board. + * Configures board. + */ +static int board_added(struct slot *p_slot) +{ + u8 hp_slot; + u8 slots_not_empty = 0; + int rc = 0; + enum pci_bus_speed asp, bsp, msp; + struct controller *ctrl = p_slot->ctrl; + struct pci_bus *parent = ctrl->pci_dev->subordinate; + + hp_slot = p_slot->device - ctrl->slot_device_offset; + + ctrl_dbg(ctrl, + "%s: p_slot->device, slot_offset, hp_slot = %d, %d ,%d\n", + __func__, p_slot->device, ctrl->slot_device_offset, hp_slot); + + /* Power on slot without connecting to bus */ + rc = p_slot->hpc_ops->power_on_slot(p_slot); + if (rc) { + ctrl_err(ctrl, "Failed to power on slot\n"); + return -1; + } + + if ((ctrl->pci_dev->vendor == 0x8086) && (ctrl->pci_dev->device == 0x0332)) { + if (slots_not_empty) + return WRONG_BUS_FREQUENCY; + + if ((rc = p_slot->hpc_ops->set_bus_speed_mode(p_slot, PCI_SPEED_33MHz))) { + ctrl_err(ctrl, "%s: Issue of set bus speed mode command" + " failed\n", __func__); + return WRONG_BUS_FREQUENCY; + } + + /* turn on board, blink green LED, turn off Amber LED */ + if ((rc = p_slot->hpc_ops->slot_enable(p_slot))) { + ctrl_err(ctrl, "Issue of Slot Enable command failed\n"); + return rc; + } + } + + rc = p_slot->hpc_ops->get_adapter_speed(p_slot, &asp); + if (rc) { + ctrl_err(ctrl, "Can't get adapter speed or " + "bus mode mismatch\n"); + return WRONG_BUS_FREQUENCY; + } + + rc = p_slot->hpc_ops->get_cur_bus_speed(p_slot, &bsp); + if (rc) { + ctrl_err(ctrl, "Can't get bus operation speed\n"); + return WRONG_BUS_FREQUENCY; + } + + rc = p_slot->hpc_ops->get_max_bus_speed(p_slot, &msp); + if (rc) { + ctrl_err(ctrl, "Can't get max bus operation speed\n"); + msp = bsp; + } + + /* Check if there are other slots or devices on the same bus */ + if (!list_empty(&ctrl->pci_dev->subordinate->devices)) + slots_not_empty = 1; + + ctrl_dbg(ctrl, "%s: slots_not_empty %d, adapter_speed %d, bus_speed %d," + " max_bus_speed %d\n", __func__, slots_not_empty, asp, + bsp, msp); + + rc = fix_bus_speed(ctrl, p_slot, slots_not_empty, asp, bsp, msp); + if (rc) + return rc; + + /* turn on board, blink green LED, turn off Amber LED */ + if ((rc = p_slot->hpc_ops->slot_enable(p_slot))) { + ctrl_err(ctrl, "Issue of Slot Enable command failed\n"); + return rc; + } + + /* Wait for ~1 second */ + msleep(1000); + + ctrl_dbg(ctrl, "%s: slot status = %x\n", __func__, p_slot->status); + /* Check for a power fault */ + if (p_slot->status == 0xFF) { + /* power fault occurred, but it was benign */ + ctrl_dbg(ctrl, "%s: Power fault\n", __func__); + rc = POWER_FAILURE; + p_slot->status = 0; + goto err_exit; + } + + if (shpchp_configure_device(p_slot)) { + ctrl_err(ctrl, "Cannot add device at %04x:%02x:%02x\n", + pci_domain_nr(parent), p_slot->bus, p_slot->device); + goto err_exit; + } + + p_slot->status = 0; + p_slot->is_a_board = 0x01; + p_slot->pwr_save = 1; + + p_slot->hpc_ops->green_led_on(p_slot); + + return 0; + +err_exit: + /* turn off slot, turn on Amber LED, turn off Green LED */ + rc = p_slot->hpc_ops->slot_disable(p_slot); + if (rc) { + ctrl_err(ctrl, "%s: Issue of Slot Disable command failed\n", + __func__); + return rc; + } + + return(rc); +} + + +/** + * remove_board - Turns off slot and LEDs + * @p_slot: target &slot + */ +static int remove_board(struct slot *p_slot) +{ + struct controller *ctrl = p_slot->ctrl; + u8 hp_slot; + int rc; + + if (shpchp_unconfigure_device(p_slot)) + return(1); + + hp_slot = p_slot->device - ctrl->slot_device_offset; + p_slot = shpchp_find_slot(ctrl, hp_slot + ctrl->slot_device_offset); + + ctrl_dbg(ctrl, "%s: hp_slot = %d\n", __func__, hp_slot); + + /* Change status to shutdown */ + if (p_slot->is_a_board) + p_slot->status = 0x01; + + /* turn off slot, turn on Amber LED, turn off Green LED */ + rc = p_slot->hpc_ops->slot_disable(p_slot); + if (rc) { + ctrl_err(ctrl, "%s: Issue of Slot Disable command failed\n", + __func__); + return rc; + } + + rc = p_slot->hpc_ops->set_attention_status(p_slot, 0); + if (rc) { + ctrl_err(ctrl, "Issue of Set Attention command failed\n"); + return rc; + } + + p_slot->pwr_save = 0; + p_slot->is_a_board = 0; + + return 0; +} + + +struct pushbutton_work_info { + struct slot *p_slot; + struct work_struct work; +}; + +/** + * shpchp_pushbutton_thread - handle pushbutton events + * @work: &struct work_struct to be handled + * + * Scheduled procedure to handle blocking stuff for the pushbuttons. + * Handles all pending events and exits. + */ +static void shpchp_pushbutton_thread(struct work_struct *work) +{ + struct pushbutton_work_info *info = + container_of(work, struct pushbutton_work_info, work); + struct slot *p_slot = info->p_slot; + + mutex_lock(&p_slot->lock); + switch (p_slot->state) { + case POWEROFF_STATE: + mutex_unlock(&p_slot->lock); + shpchp_disable_slot(p_slot); + mutex_lock(&p_slot->lock); + p_slot->state = STATIC_STATE; + break; + case POWERON_STATE: + mutex_unlock(&p_slot->lock); + if (shpchp_enable_slot(p_slot)) + p_slot->hpc_ops->green_led_off(p_slot); + mutex_lock(&p_slot->lock); + p_slot->state = STATIC_STATE; + break; + default: + break; + } + mutex_unlock(&p_slot->lock); + + kfree(info); +} + +void shpchp_queue_pushbutton_work(struct work_struct *work) +{ + struct slot *p_slot = container_of(work, struct slot, work.work); + struct pushbutton_work_info *info; + + info = kmalloc(sizeof(*info), GFP_KERNEL); + if (!info) { + ctrl_err(p_slot->ctrl, "%s: Cannot allocate memory\n", + __func__); + return; + } + info->p_slot = p_slot; + INIT_WORK(&info->work, shpchp_pushbutton_thread); + + mutex_lock(&p_slot->lock); + switch (p_slot->state) { + case BLINKINGOFF_STATE: + p_slot->state = POWEROFF_STATE; + break; + case BLINKINGON_STATE: + p_slot->state = POWERON_STATE; + break; + default: + goto out; + } + queue_work(shpchp_wq, &info->work); + out: + mutex_unlock(&p_slot->lock); +} + +static int update_slot_info (struct slot *slot) +{ + struct hotplug_slot_info *info; + int result; + + info = kmalloc(sizeof(*info), GFP_KERNEL); + if (!info) + return -ENOMEM; + + slot->hpc_ops->get_power_status(slot, &(info->power_status)); + slot->hpc_ops->get_attention_status(slot, &(info->attention_status)); + slot->hpc_ops->get_latch_status(slot, &(info->latch_status)); + slot->hpc_ops->get_adapter_status(slot, &(info->adapter_status)); + + result = pci_hp_change_slot_info(slot->hotplug_slot, info); + kfree (info); + return result; +} + +/* + * Note: This function must be called with slot->lock held + */ +static void handle_button_press_event(struct slot *p_slot) +{ + u8 getstatus; + struct controller *ctrl = p_slot->ctrl; + + switch (p_slot->state) { + case STATIC_STATE: + p_slot->hpc_ops->get_power_status(p_slot, &getstatus); + if (getstatus) { + p_slot->state = BLINKINGOFF_STATE; + ctrl_info(ctrl, "PCI slot #%s - powering off due to " + "button press.\n", slot_name(p_slot)); + } else { + p_slot->state = BLINKINGON_STATE; + ctrl_info(ctrl, "PCI slot #%s - powering on due to " + "button press.\n", slot_name(p_slot)); + } + /* blink green LED and turn off amber */ + p_slot->hpc_ops->green_led_blink(p_slot); + p_slot->hpc_ops->set_attention_status(p_slot, 0); + + schedule_delayed_work(&p_slot->work, 5*HZ); + break; + case BLINKINGOFF_STATE: + case BLINKINGON_STATE: + /* + * Cancel if we are still blinking; this means that we + * press the attention again before the 5 sec. limit + * expires to cancel hot-add or hot-remove + */ + ctrl_info(ctrl, "Button cancel on Slot(%s)\n", + slot_name(p_slot)); + cancel_delayed_work(&p_slot->work); + if (p_slot->state == BLINKINGOFF_STATE) + p_slot->hpc_ops->green_led_on(p_slot); + else + p_slot->hpc_ops->green_led_off(p_slot); + p_slot->hpc_ops->set_attention_status(p_slot, 0); + ctrl_info(ctrl, "PCI slot #%s - action canceled due to " + "button press\n", slot_name(p_slot)); + p_slot->state = STATIC_STATE; + break; + case POWEROFF_STATE: + case POWERON_STATE: + /* + * Ignore if the slot is on power-on or power-off state; + * this means that the previous attention button action + * to hot-add or hot-remove is undergoing + */ + ctrl_info(ctrl, "Button ignore on Slot(%s)\n", + slot_name(p_slot)); + update_slot_info(p_slot); + break; + default: + ctrl_warn(ctrl, "Not a valid state\n"); + break; + } +} + +static void interrupt_event_handler(struct work_struct *work) +{ + struct event_info *info = container_of(work, struct event_info, work); + struct slot *p_slot = info->p_slot; + + mutex_lock(&p_slot->lock); + switch (info->event_type) { + case INT_BUTTON_PRESS: + handle_button_press_event(p_slot); + break; + case INT_POWER_FAULT: + ctrl_dbg(p_slot->ctrl, "%s: Power fault\n", __func__); + p_slot->hpc_ops->set_attention_status(p_slot, 1); + p_slot->hpc_ops->green_led_off(p_slot); + break; + default: + update_slot_info(p_slot); + break; + } + mutex_unlock(&p_slot->lock); + + kfree(info); +} + + +static int shpchp_enable_slot (struct slot *p_slot) +{ + u8 getstatus = 0; + int rc, retval = -ENODEV; + struct controller *ctrl = p_slot->ctrl; + + /* Check to see if (latch closed, card present, power off) */ + mutex_lock(&p_slot->ctrl->crit_sect); + rc = p_slot->hpc_ops->get_adapter_status(p_slot, &getstatus); + if (rc || !getstatus) { + ctrl_info(ctrl, "No adapter on slot(%s)\n", slot_name(p_slot)); + goto out; + } + rc = p_slot->hpc_ops->get_latch_status(p_slot, &getstatus); + if (rc || getstatus) { + ctrl_info(ctrl, "Latch open on slot(%s)\n", slot_name(p_slot)); + goto out; + } + rc = p_slot->hpc_ops->get_power_status(p_slot, &getstatus); + if (rc || getstatus) { + ctrl_info(ctrl, "Already enabled on slot(%s)\n", + slot_name(p_slot)); + goto out; + } + + p_slot->is_a_board = 1; + + /* We have to save the presence info for these slots */ + p_slot->hpc_ops->get_adapter_status(p_slot, &(p_slot->presence_save)); + p_slot->hpc_ops->get_power_status(p_slot, &(p_slot->pwr_save)); + ctrl_dbg(ctrl, "%s: p_slot->pwr_save %x\n", __func__, p_slot->pwr_save); + p_slot->hpc_ops->get_latch_status(p_slot, &getstatus); + + if(((p_slot->ctrl->pci_dev->vendor == PCI_VENDOR_ID_AMD) || + (p_slot->ctrl->pci_dev->device == PCI_DEVICE_ID_AMD_POGO_7458)) + && p_slot->ctrl->num_slots == 1) { + /* handle amd pogo errata; this must be done before enable */ + amd_pogo_errata_save_misc_reg(p_slot); + retval = board_added(p_slot); + /* handle amd pogo errata; this must be done after enable */ + amd_pogo_errata_restore_misc_reg(p_slot); + } else + retval = board_added(p_slot); + + if (retval) { + p_slot->hpc_ops->get_adapter_status(p_slot, + &(p_slot->presence_save)); + p_slot->hpc_ops->get_latch_status(p_slot, &getstatus); + } + + update_slot_info(p_slot); + out: + mutex_unlock(&p_slot->ctrl->crit_sect); + return retval; +} + + +static int shpchp_disable_slot (struct slot *p_slot) +{ + u8 getstatus = 0; + int rc, retval = -ENODEV; + struct controller *ctrl = p_slot->ctrl; + + if (!p_slot->ctrl) + return -ENODEV; + + /* Check to see if (latch closed, card present, power on) */ + mutex_lock(&p_slot->ctrl->crit_sect); + + rc = p_slot->hpc_ops->get_adapter_status(p_slot, &getstatus); + if (rc || !getstatus) { + ctrl_info(ctrl, "No adapter on slot(%s)\n", slot_name(p_slot)); + goto out; + } + rc = p_slot->hpc_ops->get_latch_status(p_slot, &getstatus); + if (rc || getstatus) { + ctrl_info(ctrl, "Latch open on slot(%s)\n", slot_name(p_slot)); + goto out; + } + rc = p_slot->hpc_ops->get_power_status(p_slot, &getstatus); + if (rc || !getstatus) { + ctrl_info(ctrl, "Already disabled on slot(%s)\n", + slot_name(p_slot)); + goto out; + } + + retval = remove_board(p_slot); + update_slot_info(p_slot); + out: + mutex_unlock(&p_slot->ctrl->crit_sect); + return retval; +} + +int shpchp_sysfs_enable_slot(struct slot *p_slot) +{ + int retval = -ENODEV; + struct controller *ctrl = p_slot->ctrl; + + mutex_lock(&p_slot->lock); + switch (p_slot->state) { + case BLINKINGON_STATE: + cancel_delayed_work(&p_slot->work); + case STATIC_STATE: + p_slot->state = POWERON_STATE; + mutex_unlock(&p_slot->lock); + retval = shpchp_enable_slot(p_slot); + mutex_lock(&p_slot->lock); + p_slot->state = STATIC_STATE; + break; + case POWERON_STATE: + ctrl_info(ctrl, "Slot %s is already in powering on state\n", + slot_name(p_slot)); + break; + case BLINKINGOFF_STATE: + case POWEROFF_STATE: + ctrl_info(ctrl, "Already enabled on slot %s\n", + slot_name(p_slot)); + break; + default: + ctrl_err(ctrl, "Not a valid state on slot %s\n", + slot_name(p_slot)); + break; + } + mutex_unlock(&p_slot->lock); + + return retval; +} + +int shpchp_sysfs_disable_slot(struct slot *p_slot) +{ + int retval = -ENODEV; + struct controller *ctrl = p_slot->ctrl; + + mutex_lock(&p_slot->lock); + switch (p_slot->state) { + case BLINKINGOFF_STATE: + cancel_delayed_work(&p_slot->work); + case STATIC_STATE: + p_slot->state = POWEROFF_STATE; + mutex_unlock(&p_slot->lock); + retval = shpchp_disable_slot(p_slot); + mutex_lock(&p_slot->lock); + p_slot->state = STATIC_STATE; + break; + case POWEROFF_STATE: + ctrl_info(ctrl, "Slot %s is already in powering off state\n", + slot_name(p_slot)); + break; + case BLINKINGON_STATE: + case POWERON_STATE: + ctrl_info(ctrl, "Already disabled on slot %s\n", + slot_name(p_slot)); + break; + default: + ctrl_err(ctrl, "Not a valid state on slot %s\n", + slot_name(p_slot)); + break; + } + mutex_unlock(&p_slot->lock); + + return retval; +} diff --git a/drivers/pci/hotplug/shpchp_hpc.c b/drivers/pci/hotplug/shpchp_hpc.c new file mode 100644 index 0000000..86dc398 --- /dev/null +++ b/drivers/pci/hotplug/shpchp_hpc.c @@ -0,0 +1,1130 @@ +/* + * Standard PCI Hot Plug Driver + * + * Copyright (C) 1995,2001 Compaq Computer Corporation + * Copyright (C) 2001 Greg Kroah-Hartman (greg@kroah.com) + * Copyright (C) 2001 IBM Corp. + * Copyright (C) 2003-2004 Intel Corporation + * + * 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>,<kristen.c.accardi@intel.com> + * + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/types.h> +#include <linux/pci.h> +#include <linux/interrupt.h> + +#include "shpchp.h" + +/* Slot Available Register I field definition */ +#define SLOT_33MHZ 0x0000001f +#define SLOT_66MHZ_PCIX 0x00001f00 +#define SLOT_100MHZ_PCIX 0x001f0000 +#define SLOT_133MHZ_PCIX 0x1f000000 + +/* Slot Available Register II field definition */ +#define SLOT_66MHZ 0x0000001f +#define SLOT_66MHZ_PCIX_266 0x00000f00 +#define SLOT_100MHZ_PCIX_266 0x0000f000 +#define SLOT_133MHZ_PCIX_266 0x000f0000 +#define SLOT_66MHZ_PCIX_533 0x00f00000 +#define SLOT_100MHZ_PCIX_533 0x0f000000 +#define SLOT_133MHZ_PCIX_533 0xf0000000 + +/* Slot Configuration */ +#define SLOT_NUM 0x0000001F +#define FIRST_DEV_NUM 0x00001F00 +#define PSN 0x07FF0000 +#define UPDOWN 0x20000000 +#define MRLSENSOR 0x40000000 +#define ATTN_BUTTON 0x80000000 + +/* + * Interrupt Locator Register definitions + */ +#define CMD_INTR_PENDING (1 << 0) +#define SLOT_INTR_PENDING(i) (1 << (i + 1)) + +/* + * Controller SERR-INT Register + */ +#define GLOBAL_INTR_MASK (1 << 0) +#define GLOBAL_SERR_MASK (1 << 1) +#define COMMAND_INTR_MASK (1 << 2) +#define ARBITER_SERR_MASK (1 << 3) +#define COMMAND_DETECTED (1 << 16) +#define ARBITER_DETECTED (1 << 17) +#define SERR_INTR_RSVDZ_MASK 0xfffc0000 + +/* + * Logical Slot Register definitions + */ +#define SLOT_REG(i) (SLOT1 + (4 * i)) + +#define SLOT_STATE_SHIFT (0) +#define SLOT_STATE_MASK (3 << 0) +#define SLOT_STATE_PWRONLY (1) +#define SLOT_STATE_ENABLED (2) +#define SLOT_STATE_DISABLED (3) +#define PWR_LED_STATE_SHIFT (2) +#define PWR_LED_STATE_MASK (3 << 2) +#define ATN_LED_STATE_SHIFT (4) +#define ATN_LED_STATE_MASK (3 << 4) +#define ATN_LED_STATE_ON (1) +#define ATN_LED_STATE_BLINK (2) +#define ATN_LED_STATE_OFF (3) +#define POWER_FAULT (1 << 6) +#define ATN_BUTTON (1 << 7) +#define MRL_SENSOR (1 << 8) +#define MHZ66_CAP (1 << 9) +#define PRSNT_SHIFT (10) +#define PRSNT_MASK (3 << 10) +#define PCIX_CAP_SHIFT (12) +#define PCIX_CAP_MASK_PI1 (3 << 12) +#define PCIX_CAP_MASK_PI2 (7 << 12) +#define PRSNT_CHANGE_DETECTED (1 << 16) +#define ISO_PFAULT_DETECTED (1 << 17) +#define BUTTON_PRESS_DETECTED (1 << 18) +#define MRL_CHANGE_DETECTED (1 << 19) +#define CON_PFAULT_DETECTED (1 << 20) +#define PRSNT_CHANGE_INTR_MASK (1 << 24) +#define ISO_PFAULT_INTR_MASK (1 << 25) +#define BUTTON_PRESS_INTR_MASK (1 << 26) +#define MRL_CHANGE_INTR_MASK (1 << 27) +#define CON_PFAULT_INTR_MASK (1 << 28) +#define MRL_CHANGE_SERR_MASK (1 << 29) +#define CON_PFAULT_SERR_MASK (1 << 30) +#define SLOT_REG_RSVDZ_MASK (1 << 15) | (7 << 21) + +/* + * SHPC Command Code definitnions + * + * Slot Operation 00h - 3Fh + * Set Bus Segment Speed/Mode A 40h - 47h + * Power-Only All Slots 48h + * Enable All Slots 49h + * Set Bus Segment Speed/Mode B (PI=2) 50h - 5Fh + * Reserved Command Codes 60h - BFh + * Vendor Specific Commands C0h - FFh + */ +#define SET_SLOT_PWR 0x01 /* Slot Operation */ +#define SET_SLOT_ENABLE 0x02 +#define SET_SLOT_DISABLE 0x03 +#define SET_PWR_ON 0x04 +#define SET_PWR_BLINK 0x08 +#define SET_PWR_OFF 0x0c +#define SET_ATTN_ON 0x10 +#define SET_ATTN_BLINK 0x20 +#define SET_ATTN_OFF 0x30 +#define SETA_PCI_33MHZ 0x40 /* Set Bus Segment Speed/Mode A */ +#define SETA_PCI_66MHZ 0x41 +#define SETA_PCIX_66MHZ 0x42 +#define SETA_PCIX_100MHZ 0x43 +#define SETA_PCIX_133MHZ 0x44 +#define SETA_RESERVED1 0x45 +#define SETA_RESERVED2 0x46 +#define SETA_RESERVED3 0x47 +#define SET_PWR_ONLY_ALL 0x48 /* Power-Only All Slots */ +#define SET_ENABLE_ALL 0x49 /* Enable All Slots */ +#define SETB_PCI_33MHZ 0x50 /* Set Bus Segment Speed/Mode B */ +#define SETB_PCI_66MHZ 0x51 +#define SETB_PCIX_66MHZ_PM 0x52 +#define SETB_PCIX_100MHZ_PM 0x53 +#define SETB_PCIX_133MHZ_PM 0x54 +#define SETB_PCIX_66MHZ_EM 0x55 +#define SETB_PCIX_100MHZ_EM 0x56 +#define SETB_PCIX_133MHZ_EM 0x57 +#define SETB_PCIX_66MHZ_266 0x58 +#define SETB_PCIX_100MHZ_266 0x59 +#define SETB_PCIX_133MHZ_266 0x5a +#define SETB_PCIX_66MHZ_533 0x5b +#define SETB_PCIX_100MHZ_533 0x5c +#define SETB_PCIX_133MHZ_533 0x5d +#define SETB_RESERVED1 0x5e +#define SETB_RESERVED2 0x5f + +/* + * SHPC controller command error code + */ +#define SWITCH_OPEN 0x1 +#define INVALID_CMD 0x2 +#define INVALID_SPEED_MODE 0x4 + +/* + * For accessing SHPC Working Register Set via PCI Configuration Space + */ +#define DWORD_SELECT 0x2 +#define DWORD_DATA 0x4 + +/* Field Offset in Logical Slot Register - byte boundary */ +#define SLOT_EVENT_LATCH 0x2 +#define SLOT_SERR_INT_MASK 0x3 + +static atomic_t shpchp_num_controllers = ATOMIC_INIT(0); + +static irqreturn_t shpc_isr(int irq, void *dev_id); +static void start_int_poll_timer(struct controller *ctrl, int sec); +static int hpc_check_cmd_status(struct controller *ctrl); + +static inline u8 shpc_readb(struct controller *ctrl, int reg) +{ + return readb(ctrl->creg + reg); +} + +static inline void shpc_writeb(struct controller *ctrl, int reg, u8 val) +{ + writeb(val, ctrl->creg + reg); +} + +static inline u16 shpc_readw(struct controller *ctrl, int reg) +{ + return readw(ctrl->creg + reg); +} + +static inline void shpc_writew(struct controller *ctrl, int reg, u16 val) +{ + writew(val, ctrl->creg + reg); +} + +static inline u32 shpc_readl(struct controller *ctrl, int reg) +{ + return readl(ctrl->creg + reg); +} + +static inline void shpc_writel(struct controller *ctrl, int reg, u32 val) +{ + writel(val, ctrl->creg + reg); +} + +static inline int shpc_indirect_read(struct controller *ctrl, int index, + u32 *value) +{ + int rc; + u32 cap_offset = ctrl->cap_offset; + struct pci_dev *pdev = ctrl->pci_dev; + + rc = pci_write_config_byte(pdev, cap_offset + DWORD_SELECT, index); + if (rc) + return rc; + return pci_read_config_dword(pdev, cap_offset + DWORD_DATA, value); +} + +/* + * This is the interrupt polling timeout function. + */ +static void int_poll_timeout(unsigned long data) +{ + struct controller *ctrl = (struct controller *)data; + + /* Poll for interrupt events. regs == NULL => polling */ + shpc_isr(0, ctrl); + + init_timer(&ctrl->poll_timer); + if (!shpchp_poll_time) + shpchp_poll_time = 2; /* default polling interval is 2 sec */ + + start_int_poll_timer(ctrl, shpchp_poll_time); +} + +/* + * This function starts the interrupt polling timer. + */ +static void start_int_poll_timer(struct controller *ctrl, int sec) +{ + /* Clamp to sane value */ + if ((sec <= 0) || (sec > 60)) + sec = 2; + + ctrl->poll_timer.function = &int_poll_timeout; + ctrl->poll_timer.data = (unsigned long)ctrl; + ctrl->poll_timer.expires = jiffies + sec * HZ; + add_timer(&ctrl->poll_timer); +} + +static inline int is_ctrl_busy(struct controller *ctrl) +{ + u16 cmd_status = shpc_readw(ctrl, CMD_STATUS); + return cmd_status & 0x1; +} + +/* + * Returns 1 if SHPC finishes executing a command within 1 sec, + * otherwise returns 0. + */ +static inline int shpc_poll_ctrl_busy(struct controller *ctrl) +{ + int i; + + if (!is_ctrl_busy(ctrl)) + return 1; + + /* Check every 0.1 sec for a total of 1 sec */ + for (i = 0; i < 10; i++) { + msleep(100); + if (!is_ctrl_busy(ctrl)) + return 1; + } + + return 0; +} + +static inline int shpc_wait_cmd(struct controller *ctrl) +{ + int retval = 0; + unsigned long timeout = msecs_to_jiffies(1000); + int rc; + + if (shpchp_poll_mode) + rc = shpc_poll_ctrl_busy(ctrl); + else + rc = wait_event_interruptible_timeout(ctrl->queue, + !is_ctrl_busy(ctrl), timeout); + if (!rc && is_ctrl_busy(ctrl)) { + retval = -EIO; + ctrl_err(ctrl, "Command not completed in 1000 msec\n"); + } else if (rc < 0) { + retval = -EINTR; + ctrl_info(ctrl, "Command was interrupted by a signal\n"); + } + + return retval; +} + +static int shpc_write_cmd(struct slot *slot, u8 t_slot, u8 cmd) +{ + struct controller *ctrl = slot->ctrl; + u16 cmd_status; + int retval = 0; + u16 temp_word; + + mutex_lock(&slot->ctrl->cmd_lock); + + if (!shpc_poll_ctrl_busy(ctrl)) { + /* After 1 sec and and the controller is still busy */ + ctrl_err(ctrl, "Controller is still busy after 1 sec\n"); + retval = -EBUSY; + goto out; + } + + ++t_slot; + temp_word = (t_slot << 8) | (cmd & 0xFF); + ctrl_dbg(ctrl, "%s: t_slot %x cmd %x\n", __func__, t_slot, cmd); + + /* To make sure the Controller Busy bit is 0 before we send out the + * command. + */ + shpc_writew(ctrl, CMD, temp_word); + + /* + * Wait for command completion. + */ + retval = shpc_wait_cmd(slot->ctrl); + if (retval) + goto out; + + cmd_status = hpc_check_cmd_status(slot->ctrl); + if (cmd_status) { + ctrl_err(ctrl, + "Failed to issued command 0x%x (error code = %d)\n", + cmd, cmd_status); + retval = -EIO; + } + out: + mutex_unlock(&slot->ctrl->cmd_lock); + return retval; +} + +static int hpc_check_cmd_status(struct controller *ctrl) +{ + int retval = 0; + u16 cmd_status = shpc_readw(ctrl, CMD_STATUS) & 0x000F; + + switch (cmd_status >> 1) { + case 0: + retval = 0; + break; + case 1: + retval = SWITCH_OPEN; + ctrl_err(ctrl, "Switch opened!\n"); + break; + case 2: + retval = INVALID_CMD; + ctrl_err(ctrl, "Invalid HPC command!\n"); + break; + case 4: + retval = INVALID_SPEED_MODE; + ctrl_err(ctrl, "Invalid bus speed/mode!\n"); + break; + default: + retval = cmd_status; + } + + return retval; +} + + +static int hpc_get_attention_status(struct slot *slot, u8 *status) +{ + struct controller *ctrl = slot->ctrl; + u32 slot_reg = shpc_readl(ctrl, SLOT_REG(slot->hp_slot)); + u8 state = (slot_reg & ATN_LED_STATE_MASK) >> ATN_LED_STATE_SHIFT; + + switch (state) { + case ATN_LED_STATE_ON: + *status = 1; /* On */ + break; + case ATN_LED_STATE_BLINK: + *status = 2; /* Blink */ + break; + case ATN_LED_STATE_OFF: + *status = 0; /* Off */ + break; + default: + *status = 0xFF; /* Reserved */ + break; + } + + return 0; +} + +static int hpc_get_power_status(struct slot * slot, u8 *status) +{ + struct controller *ctrl = slot->ctrl; + u32 slot_reg = shpc_readl(ctrl, SLOT_REG(slot->hp_slot)); + u8 state = (slot_reg & SLOT_STATE_MASK) >> SLOT_STATE_SHIFT; + + switch (state) { + case SLOT_STATE_PWRONLY: + *status = 2; /* Powered only */ + break; + case SLOT_STATE_ENABLED: + *status = 1; /* Enabled */ + break; + case SLOT_STATE_DISABLED: + *status = 0; /* Disabled */ + break; + default: + *status = 0xFF; /* Reserved */ + break; + } + + return 0; +} + + +static int hpc_get_latch_status(struct slot *slot, u8 *status) +{ + struct controller *ctrl = slot->ctrl; + u32 slot_reg = shpc_readl(ctrl, SLOT_REG(slot->hp_slot)); + + *status = !!(slot_reg & MRL_SENSOR); /* 0 -> close; 1 -> open */ + + return 0; +} + +static int hpc_get_adapter_status(struct slot *slot, u8 *status) +{ + struct controller *ctrl = slot->ctrl; + u32 slot_reg = shpc_readl(ctrl, SLOT_REG(slot->hp_slot)); + u8 state = (slot_reg & PRSNT_MASK) >> PRSNT_SHIFT; + + *status = (state != 0x3) ? 1 : 0; + + return 0; +} + +static int hpc_get_prog_int(struct slot *slot, u8 *prog_int) +{ + struct controller *ctrl = slot->ctrl; + + *prog_int = shpc_readb(ctrl, PROG_INTERFACE); + + return 0; +} + +static int hpc_get_adapter_speed(struct slot *slot, enum pci_bus_speed *value) +{ + int retval = 0; + struct controller *ctrl = slot->ctrl; + u32 slot_reg = shpc_readl(ctrl, SLOT_REG(slot->hp_slot)); + u8 m66_cap = !!(slot_reg & MHZ66_CAP); + u8 pi, pcix_cap; + + if ((retval = hpc_get_prog_int(slot, &pi))) + return retval; + + switch (pi) { + case 1: + pcix_cap = (slot_reg & PCIX_CAP_MASK_PI1) >> PCIX_CAP_SHIFT; + break; + case 2: + pcix_cap = (slot_reg & PCIX_CAP_MASK_PI2) >> PCIX_CAP_SHIFT; + break; + default: + return -ENODEV; + } + + ctrl_dbg(ctrl, "%s: slot_reg = %x, pcix_cap = %x, m66_cap = %x\n", + __func__, slot_reg, pcix_cap, m66_cap); + + switch (pcix_cap) { + case 0x0: + *value = m66_cap ? PCI_SPEED_66MHz : PCI_SPEED_33MHz; + break; + case 0x1: + *value = PCI_SPEED_66MHz_PCIX; + break; + case 0x3: + *value = PCI_SPEED_133MHz_PCIX; + break; + case 0x4: + *value = PCI_SPEED_133MHz_PCIX_266; + break; + case 0x5: + *value = PCI_SPEED_133MHz_PCIX_533; + break; + case 0x2: + default: + *value = PCI_SPEED_UNKNOWN; + retval = -ENODEV; + break; + } + + ctrl_dbg(ctrl, "Adapter speed = %d\n", *value); + return retval; +} + +static int hpc_get_mode1_ECC_cap(struct slot *slot, u8 *mode) +{ + int retval = 0; + struct controller *ctrl = slot->ctrl; + u16 sec_bus_status = shpc_readw(ctrl, SEC_BUS_CONFIG); + u8 pi = shpc_readb(ctrl, PROG_INTERFACE); + + if (pi == 2) { + *mode = (sec_bus_status & 0x0100) >> 8; + } else { + retval = -1; + } + + ctrl_dbg(ctrl, "Mode 1 ECC cap = %d\n", *mode); + return retval; +} + +static int hpc_query_power_fault(struct slot * slot) +{ + struct controller *ctrl = slot->ctrl; + u32 slot_reg = shpc_readl(ctrl, SLOT_REG(slot->hp_slot)); + + /* Note: Logic 0 => fault */ + return !(slot_reg & POWER_FAULT); +} + +static int hpc_set_attention_status(struct slot *slot, u8 value) +{ + u8 slot_cmd = 0; + + switch (value) { + case 0 : + slot_cmd = SET_ATTN_OFF; /* OFF */ + break; + case 1: + slot_cmd = SET_ATTN_ON; /* ON */ + break; + case 2: + slot_cmd = SET_ATTN_BLINK; /* BLINK */ + break; + default: + return -1; + } + + return shpc_write_cmd(slot, slot->hp_slot, slot_cmd); +} + + +static void hpc_set_green_led_on(struct slot *slot) +{ + shpc_write_cmd(slot, slot->hp_slot, SET_PWR_ON); +} + +static void hpc_set_green_led_off(struct slot *slot) +{ + shpc_write_cmd(slot, slot->hp_slot, SET_PWR_OFF); +} + +static void hpc_set_green_led_blink(struct slot *slot) +{ + shpc_write_cmd(slot, slot->hp_slot, SET_PWR_BLINK); +} + +static void hpc_release_ctlr(struct controller *ctrl) +{ + int i; + u32 slot_reg, serr_int; + + /* + * Mask event interrupts and SERRs of all slots + */ + for (i = 0; i < ctrl->num_slots; i++) { + slot_reg = shpc_readl(ctrl, SLOT_REG(i)); + slot_reg |= (PRSNT_CHANGE_INTR_MASK | ISO_PFAULT_INTR_MASK | + BUTTON_PRESS_INTR_MASK | MRL_CHANGE_INTR_MASK | + CON_PFAULT_INTR_MASK | MRL_CHANGE_SERR_MASK | + CON_PFAULT_SERR_MASK); + slot_reg &= ~SLOT_REG_RSVDZ_MASK; + shpc_writel(ctrl, SLOT_REG(i), slot_reg); + } + + cleanup_slots(ctrl); + + /* + * Mask SERR and System Interrupt generation + */ + serr_int = shpc_readl(ctrl, SERR_INTR_ENABLE); + serr_int |= (GLOBAL_INTR_MASK | GLOBAL_SERR_MASK | + COMMAND_INTR_MASK | ARBITER_SERR_MASK); + serr_int &= ~SERR_INTR_RSVDZ_MASK; + shpc_writel(ctrl, SERR_INTR_ENABLE, serr_int); + + if (shpchp_poll_mode) + del_timer(&ctrl->poll_timer); + else { + free_irq(ctrl->pci_dev->irq, ctrl); + pci_disable_msi(ctrl->pci_dev); + } + + iounmap(ctrl->creg); + release_mem_region(ctrl->mmio_base, ctrl->mmio_size); + + /* + * If this is the last controller to be released, destroy the + * shpchpd work queue + */ + if (atomic_dec_and_test(&shpchp_num_controllers)) + destroy_workqueue(shpchp_wq); +} + +static int hpc_power_on_slot(struct slot * slot) +{ + int retval; + + retval = shpc_write_cmd(slot, slot->hp_slot, SET_SLOT_PWR); + if (retval) + ctrl_err(slot->ctrl, "%s: Write command failed!\n", __func__); + + return retval; +} + +static int hpc_slot_enable(struct slot * slot) +{ + int retval; + + /* Slot - Enable, Power Indicator - Blink, Attention Indicator - Off */ + retval = shpc_write_cmd(slot, slot->hp_slot, + SET_SLOT_ENABLE | SET_PWR_BLINK | SET_ATTN_OFF); + if (retval) + ctrl_err(slot->ctrl, "%s: Write command failed!\n", __func__); + + return retval; +} + +static int hpc_slot_disable(struct slot * slot) +{ + int retval; + + /* Slot - Disable, Power Indicator - Off, Attention Indicator - On */ + retval = shpc_write_cmd(slot, slot->hp_slot, + SET_SLOT_DISABLE | SET_PWR_OFF | SET_ATTN_ON); + if (retval) + ctrl_err(slot->ctrl, "%s: Write command failed!\n", __func__); + + return retval; +} + +static int hpc_set_bus_speed_mode(struct slot * slot, enum pci_bus_speed value) +{ + int retval; + struct controller *ctrl = slot->ctrl; + u8 pi, cmd; + + pi = shpc_readb(ctrl, PROG_INTERFACE); + if ((pi == 1) && (value > PCI_SPEED_133MHz_PCIX)) + return -EINVAL; + + switch (value) { + case PCI_SPEED_33MHz: + cmd = SETA_PCI_33MHZ; + break; + case PCI_SPEED_66MHz: + cmd = SETA_PCI_66MHZ; + break; + case PCI_SPEED_66MHz_PCIX: + cmd = SETA_PCIX_66MHZ; + break; + case PCI_SPEED_100MHz_PCIX: + cmd = SETA_PCIX_100MHZ; + break; + case PCI_SPEED_133MHz_PCIX: + cmd = SETA_PCIX_133MHZ; + break; + case PCI_SPEED_66MHz_PCIX_ECC: + cmd = SETB_PCIX_66MHZ_EM; + break; + case PCI_SPEED_100MHz_PCIX_ECC: + cmd = SETB_PCIX_100MHZ_EM; + break; + case PCI_SPEED_133MHz_PCIX_ECC: + cmd = SETB_PCIX_133MHZ_EM; + break; + case PCI_SPEED_66MHz_PCIX_266: + cmd = SETB_PCIX_66MHZ_266; + break; + case PCI_SPEED_100MHz_PCIX_266: + cmd = SETB_PCIX_100MHZ_266; + break; + case PCI_SPEED_133MHz_PCIX_266: + cmd = SETB_PCIX_133MHZ_266; + break; + case PCI_SPEED_66MHz_PCIX_533: + cmd = SETB_PCIX_66MHZ_533; + break; + case PCI_SPEED_100MHz_PCIX_533: + cmd = SETB_PCIX_100MHZ_533; + break; + case PCI_SPEED_133MHz_PCIX_533: + cmd = SETB_PCIX_133MHZ_533; + break; + default: + return -EINVAL; + } + + retval = shpc_write_cmd(slot, 0, cmd); + if (retval) + ctrl_err(ctrl, "%s: Write command failed!\n", __func__); + + return retval; +} + +static irqreturn_t shpc_isr(int irq, void *dev_id) +{ + struct controller *ctrl = (struct controller *)dev_id; + u32 serr_int, slot_reg, intr_loc, intr_loc2; + int hp_slot; + + /* Check to see if it was our interrupt */ + intr_loc = shpc_readl(ctrl, INTR_LOC); + if (!intr_loc) + return IRQ_NONE; + + ctrl_dbg(ctrl, "%s: intr_loc = %x\n", __func__, intr_loc); + + if(!shpchp_poll_mode) { + /* + * Mask Global Interrupt Mask - see implementation + * note on p. 139 of SHPC spec rev 1.0 + */ + serr_int = shpc_readl(ctrl, SERR_INTR_ENABLE); + serr_int |= GLOBAL_INTR_MASK; + serr_int &= ~SERR_INTR_RSVDZ_MASK; + shpc_writel(ctrl, SERR_INTR_ENABLE, serr_int); + + intr_loc2 = shpc_readl(ctrl, INTR_LOC); + ctrl_dbg(ctrl, "%s: intr_loc2 = %x\n", __func__, intr_loc2); + } + + if (intr_loc & CMD_INTR_PENDING) { + /* + * Command Complete Interrupt Pending + * RO only - clear by writing 1 to the Command Completion + * Detect bit in Controller SERR-INT register + */ + serr_int = shpc_readl(ctrl, SERR_INTR_ENABLE); + serr_int &= ~SERR_INTR_RSVDZ_MASK; + shpc_writel(ctrl, SERR_INTR_ENABLE, serr_int); + + wake_up_interruptible(&ctrl->queue); + } + + if (!(intr_loc & ~CMD_INTR_PENDING)) + goto out; + + for (hp_slot = 0; hp_slot < ctrl->num_slots; hp_slot++) { + /* To find out which slot has interrupt pending */ + if (!(intr_loc & SLOT_INTR_PENDING(hp_slot))) + continue; + + slot_reg = shpc_readl(ctrl, SLOT_REG(hp_slot)); + ctrl_dbg(ctrl, "Slot %x with intr, slot register = %x\n", + hp_slot, slot_reg); + + if (slot_reg & MRL_CHANGE_DETECTED) + shpchp_handle_switch_change(hp_slot, ctrl); + + if (slot_reg & BUTTON_PRESS_DETECTED) + shpchp_handle_attention_button(hp_slot, ctrl); + + if (slot_reg & PRSNT_CHANGE_DETECTED) + shpchp_handle_presence_change(hp_slot, ctrl); + + if (slot_reg & (ISO_PFAULT_DETECTED | CON_PFAULT_DETECTED)) + shpchp_handle_power_fault(hp_slot, ctrl); + + /* Clear all slot events */ + slot_reg &= ~SLOT_REG_RSVDZ_MASK; + shpc_writel(ctrl, SLOT_REG(hp_slot), slot_reg); + } + out: + if (!shpchp_poll_mode) { + /* Unmask Global Interrupt Mask */ + serr_int = shpc_readl(ctrl, SERR_INTR_ENABLE); + serr_int &= ~(GLOBAL_INTR_MASK | SERR_INTR_RSVDZ_MASK); + shpc_writel(ctrl, SERR_INTR_ENABLE, serr_int); + } + + return IRQ_HANDLED; +} + +static int hpc_get_max_bus_speed (struct slot *slot, enum pci_bus_speed *value) +{ + int retval = 0; + struct controller *ctrl = slot->ctrl; + enum pci_bus_speed bus_speed = PCI_SPEED_UNKNOWN; + u8 pi = shpc_readb(ctrl, PROG_INTERFACE); + u32 slot_avail1 = shpc_readl(ctrl, SLOT_AVAIL1); + u32 slot_avail2 = shpc_readl(ctrl, SLOT_AVAIL2); + + if (pi == 2) { + if (slot_avail2 & SLOT_133MHZ_PCIX_533) + bus_speed = PCI_SPEED_133MHz_PCIX_533; + else if (slot_avail2 & SLOT_100MHZ_PCIX_533) + bus_speed = PCI_SPEED_100MHz_PCIX_533; + else if (slot_avail2 & SLOT_66MHZ_PCIX_533) + bus_speed = PCI_SPEED_66MHz_PCIX_533; + else if (slot_avail2 & SLOT_133MHZ_PCIX_266) + bus_speed = PCI_SPEED_133MHz_PCIX_266; + else if (slot_avail2 & SLOT_100MHZ_PCIX_266) + bus_speed = PCI_SPEED_100MHz_PCIX_266; + else if (slot_avail2 & SLOT_66MHZ_PCIX_266) + bus_speed = PCI_SPEED_66MHz_PCIX_266; + } + + if (bus_speed == PCI_SPEED_UNKNOWN) { + if (slot_avail1 & SLOT_133MHZ_PCIX) + bus_speed = PCI_SPEED_133MHz_PCIX; + else if (slot_avail1 & SLOT_100MHZ_PCIX) + bus_speed = PCI_SPEED_100MHz_PCIX; + else if (slot_avail1 & SLOT_66MHZ_PCIX) + bus_speed = PCI_SPEED_66MHz_PCIX; + else if (slot_avail2 & SLOT_66MHZ) + bus_speed = PCI_SPEED_66MHz; + else if (slot_avail1 & SLOT_33MHZ) + bus_speed = PCI_SPEED_33MHz; + else + retval = -ENODEV; + } + + *value = bus_speed; + ctrl_dbg(ctrl, "Max bus speed = %d\n", bus_speed); + + return retval; +} + +static int hpc_get_cur_bus_speed (struct slot *slot, enum pci_bus_speed *value) +{ + int retval = 0; + struct controller *ctrl = slot->ctrl; + enum pci_bus_speed bus_speed = PCI_SPEED_UNKNOWN; + u16 sec_bus_reg = shpc_readw(ctrl, SEC_BUS_CONFIG); + u8 pi = shpc_readb(ctrl, PROG_INTERFACE); + u8 speed_mode = (pi == 2) ? (sec_bus_reg & 0xF) : (sec_bus_reg & 0x7); + + if ((pi == 1) && (speed_mode > 4)) { + *value = PCI_SPEED_UNKNOWN; + return -ENODEV; + } + + switch (speed_mode) { + case 0x0: + *value = PCI_SPEED_33MHz; + break; + case 0x1: + *value = PCI_SPEED_66MHz; + break; + case 0x2: + *value = PCI_SPEED_66MHz_PCIX; + break; + case 0x3: + *value = PCI_SPEED_100MHz_PCIX; + break; + case 0x4: + *value = PCI_SPEED_133MHz_PCIX; + break; + case 0x5: + *value = PCI_SPEED_66MHz_PCIX_ECC; + break; + case 0x6: + *value = PCI_SPEED_100MHz_PCIX_ECC; + break; + case 0x7: + *value = PCI_SPEED_133MHz_PCIX_ECC; + break; + case 0x8: + *value = PCI_SPEED_66MHz_PCIX_266; + break; + case 0x9: + *value = PCI_SPEED_100MHz_PCIX_266; + break; + case 0xa: + *value = PCI_SPEED_133MHz_PCIX_266; + break; + case 0xb: + *value = PCI_SPEED_66MHz_PCIX_533; + break; + case 0xc: + *value = PCI_SPEED_100MHz_PCIX_533; + break; + case 0xd: + *value = PCI_SPEED_133MHz_PCIX_533; + break; + default: + *value = PCI_SPEED_UNKNOWN; + retval = -ENODEV; + break; + } + + ctrl_dbg(ctrl, "Current bus speed = %d\n", bus_speed); + return retval; +} + +static struct hpc_ops shpchp_hpc_ops = { + .power_on_slot = hpc_power_on_slot, + .slot_enable = hpc_slot_enable, + .slot_disable = hpc_slot_disable, + .set_bus_speed_mode = hpc_set_bus_speed_mode, + .set_attention_status = hpc_set_attention_status, + .get_power_status = hpc_get_power_status, + .get_attention_status = hpc_get_attention_status, + .get_latch_status = hpc_get_latch_status, + .get_adapter_status = hpc_get_adapter_status, + + .get_max_bus_speed = hpc_get_max_bus_speed, + .get_cur_bus_speed = hpc_get_cur_bus_speed, + .get_adapter_speed = hpc_get_adapter_speed, + .get_mode1_ECC_cap = hpc_get_mode1_ECC_cap, + .get_prog_int = hpc_get_prog_int, + + .query_power_fault = hpc_query_power_fault, + .green_led_on = hpc_set_green_led_on, + .green_led_off = hpc_set_green_led_off, + .green_led_blink = hpc_set_green_led_blink, + + .release_ctlr = hpc_release_ctlr, +}; + +int shpc_init(struct controller *ctrl, struct pci_dev *pdev) +{ + int rc = -1, num_slots = 0; + u8 hp_slot; + u32 shpc_base_offset; + u32 tempdword, slot_reg, slot_config; + u8 i; + + ctrl->pci_dev = pdev; /* pci_dev of the P2P bridge */ + ctrl_dbg(ctrl, "Hotplug Controller:\n"); + + if ((pdev->vendor == PCI_VENDOR_ID_AMD) || (pdev->device == + PCI_DEVICE_ID_AMD_GOLAM_7450)) { + /* amd shpc driver doesn't use Base Offset; assume 0 */ + ctrl->mmio_base = pci_resource_start(pdev, 0); + ctrl->mmio_size = pci_resource_len(pdev, 0); + } else { + ctrl->cap_offset = pci_find_capability(pdev, PCI_CAP_ID_SHPC); + if (!ctrl->cap_offset) { + ctrl_err(ctrl, "Cannot find PCI capability\n"); + goto abort; + } + ctrl_dbg(ctrl, " cap_offset = %x\n", ctrl->cap_offset); + + rc = shpc_indirect_read(ctrl, 0, &shpc_base_offset); + if (rc) { + ctrl_err(ctrl, "Cannot read base_offset\n"); + goto abort; + } + + rc = shpc_indirect_read(ctrl, 3, &tempdword); + if (rc) { + ctrl_err(ctrl, "Cannot read slot config\n"); + goto abort; + } + num_slots = tempdword & SLOT_NUM; + ctrl_dbg(ctrl, " num_slots (indirect) %x\n", num_slots); + + for (i = 0; i < 9 + num_slots; i++) { + rc = shpc_indirect_read(ctrl, i, &tempdword); + if (rc) { + ctrl_err(ctrl, + "Cannot read creg (index = %d)\n", i); + goto abort; + } + ctrl_dbg(ctrl, " offset %d: value %x\n", i, tempdword); + } + + ctrl->mmio_base = + pci_resource_start(pdev, 0) + shpc_base_offset; + ctrl->mmio_size = 0x24 + 0x4 * num_slots; + } + + ctrl_info(ctrl, "HPC vendor_id %x device_id %x ss_vid %x ss_did %x\n", + pdev->vendor, pdev->device, pdev->subsystem_vendor, + pdev->subsystem_device); + + rc = pci_enable_device(pdev); + if (rc) { + ctrl_err(ctrl, "pci_enable_device failed\n"); + goto abort; + } + + if (!request_mem_region(ctrl->mmio_base, ctrl->mmio_size, MY_NAME)) { + ctrl_err(ctrl, "Cannot reserve MMIO region\n"); + rc = -1; + goto abort; + } + + ctrl->creg = ioremap(ctrl->mmio_base, ctrl->mmio_size); + if (!ctrl->creg) { + ctrl_err(ctrl, "Cannot remap MMIO region %lx @ %lx\n", + ctrl->mmio_size, ctrl->mmio_base); + release_mem_region(ctrl->mmio_base, ctrl->mmio_size); + rc = -1; + goto abort; + } + ctrl_dbg(ctrl, "ctrl->creg %p\n", ctrl->creg); + + mutex_init(&ctrl->crit_sect); + mutex_init(&ctrl->cmd_lock); + + /* Setup wait queue */ + init_waitqueue_head(&ctrl->queue); + + ctrl->hpc_ops = &shpchp_hpc_ops; + + /* Return PCI Controller Info */ + slot_config = shpc_readl(ctrl, SLOT_CONFIG); + ctrl->slot_device_offset = (slot_config & FIRST_DEV_NUM) >> 8; + ctrl->num_slots = slot_config & SLOT_NUM; + ctrl->first_slot = (slot_config & PSN) >> 16; + ctrl->slot_num_inc = ((slot_config & UPDOWN) >> 29) ? 1 : -1; + + /* Mask Global Interrupt Mask & Command Complete Interrupt Mask */ + tempdword = shpc_readl(ctrl, SERR_INTR_ENABLE); + ctrl_dbg(ctrl, "SERR_INTR_ENABLE = %x\n", tempdword); + tempdword |= (GLOBAL_INTR_MASK | GLOBAL_SERR_MASK | + COMMAND_INTR_MASK | ARBITER_SERR_MASK); + tempdword &= ~SERR_INTR_RSVDZ_MASK; + shpc_writel(ctrl, SERR_INTR_ENABLE, tempdword); + tempdword = shpc_readl(ctrl, SERR_INTR_ENABLE); + ctrl_dbg(ctrl, "SERR_INTR_ENABLE = %x\n", tempdword); + + /* Mask the MRL sensor SERR Mask of individual slot in + * Slot SERR-INT Mask & clear all the existing event if any + */ + for (hp_slot = 0; hp_slot < ctrl->num_slots; hp_slot++) { + slot_reg = shpc_readl(ctrl, SLOT_REG(hp_slot)); + ctrl_dbg(ctrl, "Default Logical Slot Register %d value %x\n", + hp_slot, slot_reg); + slot_reg |= (PRSNT_CHANGE_INTR_MASK | ISO_PFAULT_INTR_MASK | + BUTTON_PRESS_INTR_MASK | MRL_CHANGE_INTR_MASK | + CON_PFAULT_INTR_MASK | MRL_CHANGE_SERR_MASK | + CON_PFAULT_SERR_MASK); + slot_reg &= ~SLOT_REG_RSVDZ_MASK; + shpc_writel(ctrl, SLOT_REG(hp_slot), slot_reg); + } + + if (shpchp_poll_mode) { + /* Install interrupt polling timer. Start with 10 sec delay */ + init_timer(&ctrl->poll_timer); + start_int_poll_timer(ctrl, 10); + } else { + /* Installs the interrupt handler */ + rc = pci_enable_msi(pdev); + if (rc) { + ctrl_info(ctrl, + "Can't get msi for the hotplug controller\n"); + ctrl_info(ctrl, + "Use INTx for the hotplug controller\n"); + } + + rc = request_irq(ctrl->pci_dev->irq, shpc_isr, IRQF_SHARED, + MY_NAME, (void *)ctrl); + ctrl_dbg(ctrl, "request_irq %d for hpc%d (returns %d)\n", + ctrl->pci_dev->irq, + atomic_read(&shpchp_num_controllers), rc); + if (rc) { + ctrl_err(ctrl, "Can't get irq %d for the hotplug " + "controller\n", ctrl->pci_dev->irq); + goto abort_iounmap; + } + } + ctrl_dbg(ctrl, "HPC at %s irq=%x\n", pci_name(pdev), pdev->irq); + + /* + * If this is the first controller to be initialized, + * initialize the shpchpd work queue + */ + if (atomic_add_return(1, &shpchp_num_controllers) == 1) { + shpchp_wq = create_singlethread_workqueue("shpchpd"); + if (!shpchp_wq) { + rc = -ENOMEM; + goto abort_iounmap; + } + } + + /* + * Unmask all event interrupts of all slots + */ + for (hp_slot = 0; hp_slot < ctrl->num_slots; hp_slot++) { + slot_reg = shpc_readl(ctrl, SLOT_REG(hp_slot)); + ctrl_dbg(ctrl, "Default Logical Slot Register %d value %x\n", + hp_slot, slot_reg); + slot_reg &= ~(PRSNT_CHANGE_INTR_MASK | ISO_PFAULT_INTR_MASK | + BUTTON_PRESS_INTR_MASK | MRL_CHANGE_INTR_MASK | + CON_PFAULT_INTR_MASK | SLOT_REG_RSVDZ_MASK); + shpc_writel(ctrl, SLOT_REG(hp_slot), slot_reg); + } + if (!shpchp_poll_mode) { + /* Unmask all general input interrupts and SERR */ + tempdword = shpc_readl(ctrl, SERR_INTR_ENABLE); + tempdword &= ~(GLOBAL_INTR_MASK | COMMAND_INTR_MASK | + SERR_INTR_RSVDZ_MASK); + shpc_writel(ctrl, SERR_INTR_ENABLE, tempdword); + tempdword = shpc_readl(ctrl, SERR_INTR_ENABLE); + ctrl_dbg(ctrl, "SERR_INTR_ENABLE = %x\n", tempdword); + } + + return 0; + + /* We end up here for the many possible ways to fail this API. */ +abort_iounmap: + iounmap(ctrl->creg); +abort: + return rc; +} diff --git a/drivers/pci/hotplug/shpchp_pci.c b/drivers/pci/hotplug/shpchp_pci.c new file mode 100644 index 0000000..138f161 --- /dev/null +++ b/drivers/pci/hotplug/shpchp_pci.c @@ -0,0 +1,203 @@ +/* + * Standard Hot Plug Controller Driver + * + * Copyright (C) 1995,2001 Compaq Computer Corporation + * Copyright (C) 2001 Greg Kroah-Hartman (greg@kroah.com) + * Copyright (C) 2001 IBM Corp. + * Copyright (C) 2003-2004 Intel Corporation + * + * 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>, <kristen.c.accardi@intel.com> + * + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/types.h> +#include <linux/pci.h> +#include "../pci.h" +#include "shpchp.h" + +static void program_fw_provided_values(struct pci_dev *dev) +{ + u16 pci_cmd, pci_bctl; + struct pci_dev *cdev; + struct hotplug_params hpp; + + /* Program hpp values for this device */ + if (!(dev->hdr_type == PCI_HEADER_TYPE_NORMAL || + (dev->hdr_type == PCI_HEADER_TYPE_BRIDGE && + (dev->class >> 8) == PCI_CLASS_BRIDGE_PCI))) + return; + + /* use default values if we can't get them from firmware */ + if (get_hp_params_from_firmware(dev, &hpp) || + !hpp.t0 || (hpp.t0->revision > 1)) { + warn("Could not get hotplug parameters. Use defaults\n"); + hpp.t0 = &hpp.type0_data; + hpp.t0->revision = 0; + hpp.t0->cache_line_size = 8; + hpp.t0->latency_timer = 0x40; + hpp.t0->enable_serr = 0; + hpp.t0->enable_perr = 0; + } + + pci_write_config_byte(dev, + PCI_CACHE_LINE_SIZE, hpp.t0->cache_line_size); + pci_write_config_byte(dev, PCI_LATENCY_TIMER, hpp.t0->latency_timer); + pci_read_config_word(dev, PCI_COMMAND, &pci_cmd); + if (hpp.t0->enable_serr) + pci_cmd |= PCI_COMMAND_SERR; + else + pci_cmd &= ~PCI_COMMAND_SERR; + if (hpp.t0->enable_perr) + pci_cmd |= PCI_COMMAND_PARITY; + else + pci_cmd &= ~PCI_COMMAND_PARITY; + pci_write_config_word(dev, PCI_COMMAND, pci_cmd); + + /* Program bridge control value and child devices */ + if ((dev->class >> 8) == PCI_CLASS_BRIDGE_PCI) { + pci_write_config_byte(dev, PCI_SEC_LATENCY_TIMER, + hpp.t0->latency_timer); + pci_read_config_word(dev, PCI_BRIDGE_CONTROL, &pci_bctl); + if (hpp.t0->enable_serr) + pci_bctl |= PCI_BRIDGE_CTL_SERR; + else + pci_bctl &= ~PCI_BRIDGE_CTL_SERR; + if (hpp.t0->enable_perr) + pci_bctl |= PCI_BRIDGE_CTL_PARITY; + else + pci_bctl &= ~PCI_BRIDGE_CTL_PARITY; + pci_write_config_word(dev, PCI_BRIDGE_CONTROL, pci_bctl); + if (dev->subordinate) { + list_for_each_entry(cdev, &dev->subordinate->devices, + bus_list) + program_fw_provided_values(cdev); + } + } +} + +int __ref shpchp_configure_device(struct slot *p_slot) +{ + struct pci_dev *dev; + struct pci_bus *parent = p_slot->ctrl->pci_dev->subordinate; + int num, fn; + struct controller *ctrl = p_slot->ctrl; + + dev = pci_get_slot(parent, PCI_DEVFN(p_slot->device, 0)); + if (dev) { + ctrl_err(ctrl, "Device %s already exists " + "at %04x:%02x:%02x, cannot hot-add\n", pci_name(dev), + pci_domain_nr(parent), p_slot->bus, p_slot->device); + pci_dev_put(dev); + return -EINVAL; + } + + num = pci_scan_slot(parent, PCI_DEVFN(p_slot->device, 0)); + if (num == 0) { + ctrl_err(ctrl, "No new device found\n"); + return -ENODEV; + } + + for (fn = 0; fn < 8; fn++) { + dev = pci_get_slot(parent, PCI_DEVFN(p_slot->device, fn)); + if (!dev) + continue; + if ((dev->class >> 16) == PCI_BASE_CLASS_DISPLAY) { + ctrl_err(ctrl, "Cannot hot-add display device %s\n", + pci_name(dev)); + pci_dev_put(dev); + continue; + } + if ((dev->hdr_type == PCI_HEADER_TYPE_BRIDGE) || + (dev->hdr_type == PCI_HEADER_TYPE_CARDBUS)) { + /* Find an unused bus number for the new bridge */ + struct pci_bus *child; + unsigned char busnr, start = parent->secondary; + unsigned char end = parent->subordinate; + for (busnr = start; busnr <= end; busnr++) { + if (!pci_find_bus(pci_domain_nr(parent), + busnr)) + break; + } + if (busnr >= end) { + ctrl_err(ctrl, + "No free bus for hot-added bridge\n"); + pci_dev_put(dev); + continue; + } + child = pci_add_new_bus(parent, dev, busnr); + if (!child) { + ctrl_err(ctrl, "Cannot add new bus for %s\n", + pci_name(dev)); + pci_dev_put(dev); + continue; + } + child->subordinate = pci_do_scan_bus(child); + pci_bus_size_bridges(child); + } + program_fw_provided_values(dev); + pci_dev_put(dev); + } + + pci_bus_assign_resources(parent); + pci_bus_add_devices(parent); + pci_enable_bridges(parent); + return 0; +} + +int shpchp_unconfigure_device(struct slot *p_slot) +{ + int rc = 0; + int j; + u8 bctl = 0; + struct pci_bus *parent = p_slot->ctrl->pci_dev->subordinate; + struct controller *ctrl = p_slot->ctrl; + + ctrl_dbg(ctrl, "%s: domain:bus:dev = %04x:%02x:%02x\n", + __func__, pci_domain_nr(parent), p_slot->bus, p_slot->device); + + for (j=0; j<8 ; j++) { + struct pci_dev* temp = pci_get_slot(parent, + (p_slot->device << 3) | j); + if (!temp) + continue; + if ((temp->class >> 16) == PCI_BASE_CLASS_DISPLAY) { + ctrl_err(ctrl, "Cannot remove display device %s\n", + pci_name(temp)); + pci_dev_put(temp); + continue; + } + if (temp->hdr_type == PCI_HEADER_TYPE_BRIDGE) { + pci_read_config_byte(temp, PCI_BRIDGE_CONTROL, &bctl); + if (bctl & PCI_BRIDGE_CTL_VGA) { + ctrl_err(ctrl, + "Cannot remove display device %s\n", + pci_name(temp)); + pci_dev_put(temp); + continue; + } + } + pci_remove_bus_device(temp); + pci_dev_put(temp); + } + return rc; +} + diff --git a/drivers/pci/hotplug/shpchp_sysfs.c b/drivers/pci/hotplug/shpchp_sysfs.c new file mode 100644 index 0000000..29fa9d2 --- /dev/null +++ b/drivers/pci/hotplug/shpchp_sysfs.c @@ -0,0 +1,102 @@ +/* + * Compaq Hot Plug Controller Driver + * + * Copyright (c) 1995,2001 Compaq Computer Corporation + * Copyright (c) 2001,2003 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/module.h> +#include <linux/kernel.h> +#include <linux/types.h> +#include <linux/pci.h> +#include "shpchp.h" + + +/* A few routines that create sysfs entries for the hot plug controller */ + +static ssize_t show_ctrl (struct device *dev, struct device_attribute *attr, char *buf) +{ + struct pci_dev *pdev; + char * out = buf; + int index, busnr; + struct resource *res; + struct pci_bus *bus; + + pdev = container_of (dev, struct pci_dev, dev); + bus = pdev->subordinate; + + out += sprintf(buf, "Free resources: memory\n"); + for (index = 0; index < PCI_BUS_NUM_RESOURCES; index++) { + res = bus->resource[index]; + if (res && (res->flags & IORESOURCE_MEM) && + !(res->flags & IORESOURCE_PREFETCH)) { + out += sprintf(out, "start = %8.8llx, " + "length = %8.8llx\n", + (unsigned long long)res->start, + (unsigned long long)(res->end - res->start)); + } + } + out += sprintf(out, "Free resources: prefetchable memory\n"); + for (index = 0; index < PCI_BUS_NUM_RESOURCES; index++) { + res = bus->resource[index]; + if (res && (res->flags & IORESOURCE_MEM) && + (res->flags & IORESOURCE_PREFETCH)) { + out += sprintf(out, "start = %8.8llx, " + "length = %8.8llx\n", + (unsigned long long)res->start, + (unsigned long long)(res->end - res->start)); + } + } + out += sprintf(out, "Free resources: IO\n"); + for (index = 0; index < PCI_BUS_NUM_RESOURCES; index++) { + res = bus->resource[index]; + if (res && (res->flags & IORESOURCE_IO)) { + out += sprintf(out, "start = %8.8llx, " + "length = %8.8llx\n", + (unsigned long long)res->start, + (unsigned long long)(res->end - res->start)); + } + } + out += sprintf(out, "Free resources: bus numbers\n"); + for (busnr = bus->secondary; busnr <= bus->subordinate; busnr++) { + if (!pci_find_bus(pci_domain_nr(bus), busnr)) + break; + } + if (busnr < bus->subordinate) + out += sprintf(out, "start = %8.8x, length = %8.8x\n", + busnr, (bus->subordinate - busnr)); + + return out - buf; +} +static DEVICE_ATTR (ctrl, S_IRUGO, show_ctrl, NULL); + +int __must_check shpchp_create_ctrl_files (struct controller *ctrl) +{ + return device_create_file (&ctrl->pci_dev->dev, &dev_attr_ctrl); +} + +void shpchp_remove_ctrl_files(struct controller *ctrl) +{ + device_remove_file(&ctrl->pci_dev->dev, &dev_attr_ctrl); +} diff --git a/drivers/pci/htirq.c b/drivers/pci/htirq.c new file mode 100644 index 0000000..bf7d6ce --- /dev/null +++ b/drivers/pci/htirq.c @@ -0,0 +1,179 @@ +/* + * File: htirq.c + * Purpose: Hypertransport Interrupt Capability + * + * Copyright (C) 2006 Linux Networx + * Copyright (C) Eric Biederman <ebiederman@lnxi.com> + */ + +#include <linux/irq.h> +#include <linux/pci.h> +#include <linux/spinlock.h> +#include <linux/slab.h> +#include <linux/gfp.h> +#include <linux/htirq.h> + +/* Global ht irq lock. + * + * This is needed to serialize access to the data port in hypertransport + * irq capability. + * + * With multiple simultaneous hypertransport irq devices it might pay + * to make this more fine grained. But start with simple, stupid, and correct. + */ +static DEFINE_SPINLOCK(ht_irq_lock); + +struct ht_irq_cfg { + struct pci_dev *dev; + /* Update callback used to cope with buggy hardware */ + ht_irq_update_t *update; + unsigned pos; + unsigned idx; + struct ht_irq_msg msg; +}; + + +void write_ht_irq_msg(unsigned int irq, struct ht_irq_msg *msg) +{ + struct ht_irq_cfg *cfg = get_irq_data(irq); + unsigned long flags; + spin_lock_irqsave(&ht_irq_lock, flags); + if (cfg->msg.address_lo != msg->address_lo) { + pci_write_config_byte(cfg->dev, cfg->pos + 2, cfg->idx); + pci_write_config_dword(cfg->dev, cfg->pos + 4, msg->address_lo); + } + if (cfg->msg.address_hi != msg->address_hi) { + pci_write_config_byte(cfg->dev, cfg->pos + 2, cfg->idx + 1); + pci_write_config_dword(cfg->dev, cfg->pos + 4, msg->address_hi); + } + if (cfg->update) + cfg->update(cfg->dev, irq, msg); + spin_unlock_irqrestore(&ht_irq_lock, flags); + cfg->msg = *msg; +} + +void fetch_ht_irq_msg(unsigned int irq, struct ht_irq_msg *msg) +{ + struct ht_irq_cfg *cfg = get_irq_data(irq); + *msg = cfg->msg; +} + +void mask_ht_irq(unsigned int irq) +{ + struct ht_irq_cfg *cfg; + struct ht_irq_msg msg; + + cfg = get_irq_data(irq); + + msg = cfg->msg; + msg.address_lo |= 1; + write_ht_irq_msg(irq, &msg); +} + +void unmask_ht_irq(unsigned int irq) +{ + struct ht_irq_cfg *cfg; + struct ht_irq_msg msg; + + cfg = get_irq_data(irq); + + msg = cfg->msg; + msg.address_lo &= ~1; + write_ht_irq_msg(irq, &msg); +} + +/** + * __ht_create_irq - create an irq and attach it to a device. + * @dev: The hypertransport device to find the irq capability on. + * @idx: Which of the possible irqs to attach to. + * @update: Function to be called when changing the htirq message + * + * The irq number of the new irq or a negative error value is returned. + */ +int __ht_create_irq(struct pci_dev *dev, int idx, ht_irq_update_t *update) +{ + struct ht_irq_cfg *cfg; + unsigned long flags; + u32 data; + int max_irq; + int pos; + int irq; + + pos = pci_find_ht_capability(dev, HT_CAPTYPE_IRQ); + if (!pos) + return -EINVAL; + + /* Verify the idx I want to use is in range */ + spin_lock_irqsave(&ht_irq_lock, flags); + pci_write_config_byte(dev, pos + 2, 1); + pci_read_config_dword(dev, pos + 4, &data); + spin_unlock_irqrestore(&ht_irq_lock, flags); + + max_irq = (data >> 16) & 0xff; + if ( idx > max_irq) + return -EINVAL; + + cfg = kmalloc(sizeof(*cfg), GFP_KERNEL); + if (!cfg) + return -ENOMEM; + + cfg->dev = dev; + cfg->update = update; + cfg->pos = pos; + cfg->idx = 0x10 + (idx * 2); + /* Initialize msg to a value that will never match the first write. */ + cfg->msg.address_lo = 0xffffffff; + cfg->msg.address_hi = 0xffffffff; + + irq = create_irq(); + + if (irq <= 0) { + kfree(cfg); + return -EBUSY; + } + set_irq_data(irq, cfg); + + if (arch_setup_ht_irq(irq, dev) < 0) { + ht_destroy_irq(irq); + return -EBUSY; + } + + return irq; +} + +/** + * ht_create_irq - create an irq and attach it to a device. + * @dev: The hypertransport device to find the irq capability on. + * @idx: Which of the possible irqs to attach to. + * + * ht_create_irq needs to be called for all hypertransport devices + * that generate irqs. + * + * The irq number of the new irq or a negative error value is returned. + */ +int ht_create_irq(struct pci_dev *dev, int idx) +{ + return __ht_create_irq(dev, idx, NULL); +} + +/** + * ht_destroy_irq - destroy an irq created with ht_create_irq + * + * This reverses ht_create_irq removing the specified irq from + * existence. The irq should be free before this happens. + */ +void ht_destroy_irq(unsigned int irq) +{ + struct ht_irq_cfg *cfg; + + cfg = get_irq_data(irq); + set_irq_chip(irq, NULL); + set_irq_data(irq, NULL); + destroy_irq(irq); + + kfree(cfg); +} + +EXPORT_SYMBOL(__ht_create_irq); +EXPORT_SYMBOL(ht_create_irq); +EXPORT_SYMBOL(ht_destroy_irq); diff --git a/drivers/pci/intel-iommu.c b/drivers/pci/intel-iommu.c new file mode 100644 index 0000000..3a4fea1 --- /dev/null +++ b/drivers/pci/intel-iommu.c @@ -0,0 +1,2450 @@ +/* + * Copyright (c) 2006, Intel Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. 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., 59 Temple + * Place - Suite 330, Boston, MA 02111-1307 USA. + * + * Copyright (C) 2006-2008 Intel Corporation + * Author: Ashok Raj <ashok.raj@intel.com> + * Author: Shaohua Li <shaohua.li@intel.com> + * Author: Anil S Keshavamurthy <anil.s.keshavamurthy@intel.com> + * Author: Fenghua Yu <fenghua.yu@intel.com> + */ + +#include <linux/init.h> +#include <linux/bitmap.h> +#include <linux/debugfs.h> +#include <linux/slab.h> +#include <linux/irq.h> +#include <linux/interrupt.h> +#include <linux/sysdev.h> +#include <linux/spinlock.h> +#include <linux/pci.h> +#include <linux/dmar.h> +#include <linux/dma-mapping.h> +#include <linux/mempool.h> +#include <linux/timer.h> +#include <linux/iova.h> +#include <linux/intel-iommu.h> +#include <asm/cacheflush.h> +#include <asm/iommu.h> +#include "pci.h" + +#define ROOT_SIZE VTD_PAGE_SIZE +#define CONTEXT_SIZE VTD_PAGE_SIZE + +#define IS_GFX_DEVICE(pdev) ((pdev->class >> 16) == PCI_BASE_CLASS_DISPLAY) +#define IS_ISA_DEVICE(pdev) ((pdev->class >> 8) == PCI_CLASS_BRIDGE_ISA) + +#define IOAPIC_RANGE_START (0xfee00000) +#define IOAPIC_RANGE_END (0xfeefffff) +#define IOVA_START_ADDR (0x1000) + +#define DEFAULT_DOMAIN_ADDRESS_WIDTH 48 + +#define DOMAIN_MAX_ADDR(gaw) ((((u64)1) << gaw) - 1) + + +static void flush_unmaps_timeout(unsigned long data); + +DEFINE_TIMER(unmap_timer, flush_unmaps_timeout, 0, 0); + +#define HIGH_WATER_MARK 250 +struct deferred_flush_tables { + int next; + struct iova *iova[HIGH_WATER_MARK]; + struct dmar_domain *domain[HIGH_WATER_MARK]; +}; + +static struct deferred_flush_tables *deferred_flush; + +/* bitmap for indexing intel_iommus */ +static int g_num_of_iommus; + +static int rwbf_quirk = 0; + +static DEFINE_SPINLOCK(async_umap_flush_lock); +static LIST_HEAD(unmaps_to_do); + +static int timer_on; +static long list_size; + +static void domain_remove_dev_info(struct dmar_domain *domain); + +int dmar_disabled; +static int __initdata dmar_map_gfx = 1; +static int dmar_forcedac; +static int intel_iommu_strict; + +#define DUMMY_DEVICE_DOMAIN_INFO ((struct device_domain_info *)(-1)) +static DEFINE_SPINLOCK(device_domain_lock); +static LIST_HEAD(device_domain_list); + +static int __init intel_iommu_setup(char *str) +{ + if (!str) + return -EINVAL; + while (*str) { + if (!strncmp(str, "off", 3)) { + dmar_disabled = 1; + printk(KERN_INFO"Intel-IOMMU: disabled\n"); + } else if (!strncmp(str, "igfx_off", 8)) { + dmar_map_gfx = 0; + printk(KERN_INFO + "Intel-IOMMU: disable GFX device mapping\n"); + } else if (!strncmp(str, "forcedac", 8)) { + printk(KERN_INFO + "Intel-IOMMU: Forcing DAC for PCI devices\n"); + dmar_forcedac = 1; + } else if (!strncmp(str, "strict", 6)) { + printk(KERN_INFO + "Intel-IOMMU: disable batched IOTLB flush\n"); + intel_iommu_strict = 1; + } + + str += strcspn(str, ","); + while (*str == ',') + str++; + } + return 0; +} +__setup("intel_iommu=", intel_iommu_setup); + +static struct kmem_cache *iommu_domain_cache; +static struct kmem_cache *iommu_devinfo_cache; +static struct kmem_cache *iommu_iova_cache; + +static inline void *iommu_kmem_cache_alloc(struct kmem_cache *cachep) +{ + unsigned int flags; + void *vaddr; + + /* trying to avoid low memory issues */ + flags = current->flags & PF_MEMALLOC; + current->flags |= PF_MEMALLOC; + vaddr = kmem_cache_alloc(cachep, GFP_ATOMIC); + current->flags &= (~PF_MEMALLOC | flags); + return vaddr; +} + + +static inline void *alloc_pgtable_page(void) +{ + unsigned int flags; + void *vaddr; + + /* trying to avoid low memory issues */ + flags = current->flags & PF_MEMALLOC; + current->flags |= PF_MEMALLOC; + vaddr = (void *)get_zeroed_page(GFP_ATOMIC); + current->flags &= (~PF_MEMALLOC | flags); + return vaddr; +} + +static inline void free_pgtable_page(void *vaddr) +{ + free_page((unsigned long)vaddr); +} + +static inline void *alloc_domain_mem(void) +{ + return iommu_kmem_cache_alloc(iommu_domain_cache); +} + +static void free_domain_mem(void *vaddr) +{ + kmem_cache_free(iommu_domain_cache, vaddr); +} + +static inline void * alloc_devinfo_mem(void) +{ + return iommu_kmem_cache_alloc(iommu_devinfo_cache); +} + +static inline void free_devinfo_mem(void *vaddr) +{ + kmem_cache_free(iommu_devinfo_cache, vaddr); +} + +struct iova *alloc_iova_mem(void) +{ + return iommu_kmem_cache_alloc(iommu_iova_cache); +} + +void free_iova_mem(struct iova *iova) +{ + kmem_cache_free(iommu_iova_cache, iova); +} + +/* Gets context entry for a given bus and devfn */ +static struct context_entry * device_to_context_entry(struct intel_iommu *iommu, + u8 bus, u8 devfn) +{ + struct root_entry *root; + struct context_entry *context; + unsigned long phy_addr; + unsigned long flags; + + spin_lock_irqsave(&iommu->lock, flags); + root = &iommu->root_entry[bus]; + context = get_context_addr_from_root(root); + if (!context) { + context = (struct context_entry *)alloc_pgtable_page(); + if (!context) { + spin_unlock_irqrestore(&iommu->lock, flags); + return NULL; + } + __iommu_flush_cache(iommu, (void *)context, CONTEXT_SIZE); + phy_addr = virt_to_phys((void *)context); + set_root_value(root, phy_addr); + set_root_present(root); + __iommu_flush_cache(iommu, root, sizeof(*root)); + } + spin_unlock_irqrestore(&iommu->lock, flags); + return &context[devfn]; +} + +static int device_context_mapped(struct intel_iommu *iommu, u8 bus, u8 devfn) +{ + struct root_entry *root; + struct context_entry *context; + int ret; + unsigned long flags; + + spin_lock_irqsave(&iommu->lock, flags); + root = &iommu->root_entry[bus]; + context = get_context_addr_from_root(root); + if (!context) { + ret = 0; + goto out; + } + ret = context_present(context[devfn]); +out: + spin_unlock_irqrestore(&iommu->lock, flags); + return ret; +} + +static void clear_context_table(struct intel_iommu *iommu, u8 bus, u8 devfn) +{ + struct root_entry *root; + struct context_entry *context; + unsigned long flags; + + spin_lock_irqsave(&iommu->lock, flags); + root = &iommu->root_entry[bus]; + context = get_context_addr_from_root(root); + if (context) { + context_clear_entry(context[devfn]); + __iommu_flush_cache(iommu, &context[devfn], \ + sizeof(*context)); + } + spin_unlock_irqrestore(&iommu->lock, flags); +} + +static void free_context_table(struct intel_iommu *iommu) +{ + struct root_entry *root; + int i; + unsigned long flags; + struct context_entry *context; + + spin_lock_irqsave(&iommu->lock, flags); + if (!iommu->root_entry) { + goto out; + } + for (i = 0; i < ROOT_ENTRY_NR; i++) { + root = &iommu->root_entry[i]; + context = get_context_addr_from_root(root); + if (context) + free_pgtable_page(context); + } + free_pgtable_page(iommu->root_entry); + iommu->root_entry = NULL; +out: + spin_unlock_irqrestore(&iommu->lock, flags); +} + +/* page table handling */ +#define LEVEL_STRIDE (9) +#define LEVEL_MASK (((u64)1 << LEVEL_STRIDE) - 1) + +static inline int agaw_to_level(int agaw) +{ + return agaw + 2; +} + +static inline int agaw_to_width(int agaw) +{ + return 30 + agaw * LEVEL_STRIDE; + +} + +static inline int width_to_agaw(int width) +{ + return (width - 30) / LEVEL_STRIDE; +} + +static inline unsigned int level_to_offset_bits(int level) +{ + return (12 + (level - 1) * LEVEL_STRIDE); +} + +static inline int address_level_offset(u64 addr, int level) +{ + return ((addr >> level_to_offset_bits(level)) & LEVEL_MASK); +} + +static inline u64 level_mask(int level) +{ + return ((u64)-1 << level_to_offset_bits(level)); +} + +static inline u64 level_size(int level) +{ + return ((u64)1 << level_to_offset_bits(level)); +} + +static inline u64 align_to_level(u64 addr, int level) +{ + return ((addr + level_size(level) - 1) & level_mask(level)); +} + +static struct dma_pte * addr_to_dma_pte(struct dmar_domain *domain, u64 addr) +{ + int addr_width = agaw_to_width(domain->agaw); + struct dma_pte *parent, *pte = NULL; + int level = agaw_to_level(domain->agaw); + int offset; + unsigned long flags; + + BUG_ON(!domain->pgd); + + addr &= (((u64)1) << addr_width) - 1; + parent = domain->pgd; + + spin_lock_irqsave(&domain->mapping_lock, flags); + while (level > 0) { + void *tmp_page; + + offset = address_level_offset(addr, level); + pte = &parent[offset]; + if (level == 1) + break; + + if (!dma_pte_present(*pte)) { + tmp_page = alloc_pgtable_page(); + + if (!tmp_page) { + spin_unlock_irqrestore(&domain->mapping_lock, + flags); + return NULL; + } + __iommu_flush_cache(domain->iommu, tmp_page, + PAGE_SIZE); + dma_set_pte_addr(*pte, virt_to_phys(tmp_page)); + /* + * high level table always sets r/w, last level page + * table control read/write + */ + dma_set_pte_readable(*pte); + dma_set_pte_writable(*pte); + __iommu_flush_cache(domain->iommu, pte, sizeof(*pte)); + } + parent = phys_to_virt(dma_pte_addr(*pte)); + level--; + } + + spin_unlock_irqrestore(&domain->mapping_lock, flags); + return pte; +} + +/* return address's pte at specific level */ +static struct dma_pte *dma_addr_level_pte(struct dmar_domain *domain, u64 addr, + int level) +{ + struct dma_pte *parent, *pte = NULL; + int total = agaw_to_level(domain->agaw); + int offset; + + parent = domain->pgd; + while (level <= total) { + offset = address_level_offset(addr, total); + pte = &parent[offset]; + if (level == total) + return pte; + + if (!dma_pte_present(*pte)) + break; + parent = phys_to_virt(dma_pte_addr(*pte)); + total--; + } + return NULL; +} + +/* clear one page's page table */ +static void dma_pte_clear_one(struct dmar_domain *domain, u64 addr) +{ + struct dma_pte *pte = NULL; + + /* get last level pte */ + pte = dma_addr_level_pte(domain, addr, 1); + + if (pte) { + dma_clear_pte(*pte); + __iommu_flush_cache(domain->iommu, pte, sizeof(*pte)); + } +} + +/* clear last level pte, a tlb flush should be followed */ +static void dma_pte_clear_range(struct dmar_domain *domain, u64 start, u64 end) +{ + int addr_width = agaw_to_width(domain->agaw); + + start &= (((u64)1) << addr_width) - 1; + end &= (((u64)1) << addr_width) - 1; + /* in case it's partial page */ + start = PAGE_ALIGN(start); + end &= PAGE_MASK; + + /* we don't need lock here, nobody else touches the iova range */ + while (start < end) { + dma_pte_clear_one(domain, start); + start += VTD_PAGE_SIZE; + } +} + +/* free page table pages. last level pte should already be cleared */ +static void dma_pte_free_pagetable(struct dmar_domain *domain, + u64 start, u64 end) +{ + int addr_width = agaw_to_width(domain->agaw); + struct dma_pte *pte; + int total = agaw_to_level(domain->agaw); + int level; + u64 tmp; + + start &= (((u64)1) << addr_width) - 1; + end &= (((u64)1) << addr_width) - 1; + + /* we don't need lock here, nobody else touches the iova range */ + level = 2; + while (level <= total) { + tmp = align_to_level(start, level); + if (tmp >= end || (tmp + level_size(level) > end)) + return; + + while (tmp < end) { + pte = dma_addr_level_pte(domain, tmp, level); + if (pte) { + free_pgtable_page( + phys_to_virt(dma_pte_addr(*pte))); + dma_clear_pte(*pte); + __iommu_flush_cache(domain->iommu, + pte, sizeof(*pte)); + } + tmp += level_size(level); + } + level++; + } + /* free pgd */ + if (start == 0 && end >= ((((u64)1) << addr_width) - 1)) { + free_pgtable_page(domain->pgd); + domain->pgd = NULL; + } +} + +/* iommu handling */ +static int iommu_alloc_root_entry(struct intel_iommu *iommu) +{ + struct root_entry *root; + unsigned long flags; + + root = (struct root_entry *)alloc_pgtable_page(); + if (!root) + return -ENOMEM; + + __iommu_flush_cache(iommu, root, ROOT_SIZE); + + spin_lock_irqsave(&iommu->lock, flags); + iommu->root_entry = root; + spin_unlock_irqrestore(&iommu->lock, flags); + + return 0; +} + +static void iommu_set_root_entry(struct intel_iommu *iommu) +{ + void *addr; + u32 cmd, sts; + unsigned long flag; + + addr = iommu->root_entry; + + spin_lock_irqsave(&iommu->register_lock, flag); + dmar_writeq(iommu->reg + DMAR_RTADDR_REG, virt_to_phys(addr)); + + cmd = iommu->gcmd | DMA_GCMD_SRTP; + writel(cmd, iommu->reg + DMAR_GCMD_REG); + + /* Make sure hardware complete it */ + IOMMU_WAIT_OP(iommu, DMAR_GSTS_REG, + readl, (sts & DMA_GSTS_RTPS), sts); + + spin_unlock_irqrestore(&iommu->register_lock, flag); +} + +static void iommu_flush_write_buffer(struct intel_iommu *iommu) +{ + u32 val; + unsigned long flag; + + if (!rwbf_quirk && !cap_rwbf(iommu->cap)) + return; + val = iommu->gcmd | DMA_GCMD_WBF; + + spin_lock_irqsave(&iommu->register_lock, flag); + writel(val, iommu->reg + DMAR_GCMD_REG); + + /* Make sure hardware complete it */ + IOMMU_WAIT_OP(iommu, DMAR_GSTS_REG, + readl, (!(val & DMA_GSTS_WBFS)), val); + + spin_unlock_irqrestore(&iommu->register_lock, flag); +} + +/* return value determine if we need a write buffer flush */ +static int __iommu_flush_context(struct intel_iommu *iommu, + u16 did, u16 source_id, u8 function_mask, u64 type, + int non_present_entry_flush) +{ + u64 val = 0; + unsigned long flag; + + /* + * In the non-present entry flush case, if hardware doesn't cache + * non-present entry we do nothing and if hardware cache non-present + * entry, we flush entries of domain 0 (the domain id is used to cache + * any non-present entries) + */ + if (non_present_entry_flush) { + if (!cap_caching_mode(iommu->cap)) + return 1; + else + did = 0; + } + + switch (type) { + case DMA_CCMD_GLOBAL_INVL: + val = DMA_CCMD_GLOBAL_INVL; + break; + case DMA_CCMD_DOMAIN_INVL: + val = DMA_CCMD_DOMAIN_INVL|DMA_CCMD_DID(did); + break; + case DMA_CCMD_DEVICE_INVL: + val = DMA_CCMD_DEVICE_INVL|DMA_CCMD_DID(did) + | DMA_CCMD_SID(source_id) | DMA_CCMD_FM(function_mask); + break; + default: + BUG(); + } + val |= DMA_CCMD_ICC; + + spin_lock_irqsave(&iommu->register_lock, flag); + dmar_writeq(iommu->reg + DMAR_CCMD_REG, val); + + /* Make sure hardware complete it */ + IOMMU_WAIT_OP(iommu, DMAR_CCMD_REG, + dmar_readq, (!(val & DMA_CCMD_ICC)), val); + + spin_unlock_irqrestore(&iommu->register_lock, flag); + + /* flush context entry will implicitly flush write buffer */ + return 0; +} + +/* return value determine if we need a write buffer flush */ +static int __iommu_flush_iotlb(struct intel_iommu *iommu, u16 did, + u64 addr, unsigned int size_order, u64 type, + int non_present_entry_flush) +{ + int tlb_offset = ecap_iotlb_offset(iommu->ecap); + u64 val = 0, val_iva = 0; + unsigned long flag; + + /* + * In the non-present entry flush case, if hardware doesn't cache + * non-present entry we do nothing and if hardware cache non-present + * entry, we flush entries of domain 0 (the domain id is used to cache + * any non-present entries) + */ + if (non_present_entry_flush) { + if (!cap_caching_mode(iommu->cap)) + return 1; + else + did = 0; + } + + switch (type) { + case DMA_TLB_GLOBAL_FLUSH: + /* global flush doesn't need set IVA_REG */ + val = DMA_TLB_GLOBAL_FLUSH|DMA_TLB_IVT; + break; + case DMA_TLB_DSI_FLUSH: + val = DMA_TLB_DSI_FLUSH|DMA_TLB_IVT|DMA_TLB_DID(did); + break; + case DMA_TLB_PSI_FLUSH: + val = DMA_TLB_PSI_FLUSH|DMA_TLB_IVT|DMA_TLB_DID(did); + /* Note: always flush non-leaf currently */ + val_iva = size_order | addr; + break; + default: + BUG(); + } + /* Note: set drain read/write */ +#if 0 + /* + * This is probably to be super secure.. Looks like we can + * ignore it without any impact. + */ + if (cap_read_drain(iommu->cap)) + val |= DMA_TLB_READ_DRAIN; +#endif + if (cap_write_drain(iommu->cap)) + val |= DMA_TLB_WRITE_DRAIN; + + spin_lock_irqsave(&iommu->register_lock, flag); + /* Note: Only uses first TLB reg currently */ + if (val_iva) + dmar_writeq(iommu->reg + tlb_offset, val_iva); + dmar_writeq(iommu->reg + tlb_offset + 8, val); + + /* Make sure hardware complete it */ + IOMMU_WAIT_OP(iommu, tlb_offset + 8, + dmar_readq, (!(val & DMA_TLB_IVT)), val); + + spin_unlock_irqrestore(&iommu->register_lock, flag); + + /* check IOTLB invalidation granularity */ + if (DMA_TLB_IAIG(val) == 0) + printk(KERN_ERR"IOMMU: flush IOTLB failed\n"); + if (DMA_TLB_IAIG(val) != DMA_TLB_IIRG(type)) + pr_debug("IOMMU: tlb flush request %Lx, actual %Lx\n", + (unsigned long long)DMA_TLB_IIRG(type), + (unsigned long long)DMA_TLB_IAIG(val)); + /* flush iotlb entry will implicitly flush write buffer */ + return 0; +} + +static int iommu_flush_iotlb_psi(struct intel_iommu *iommu, u16 did, + u64 addr, unsigned int pages, int non_present_entry_flush) +{ + unsigned int mask; + + BUG_ON(addr & (~VTD_PAGE_MASK)); + BUG_ON(pages == 0); + + /* Fallback to domain selective flush if no PSI support */ + if (!cap_pgsel_inv(iommu->cap)) + return iommu->flush.flush_iotlb(iommu, did, 0, 0, + DMA_TLB_DSI_FLUSH, + non_present_entry_flush); + + /* + * PSI requires page size to be 2 ^ x, and the base address is naturally + * aligned to the size + */ + mask = ilog2(__roundup_pow_of_two(pages)); + /* Fallback to domain selective flush if size is too big */ + if (mask > cap_max_amask_val(iommu->cap)) + return iommu->flush.flush_iotlb(iommu, did, 0, 0, + DMA_TLB_DSI_FLUSH, non_present_entry_flush); + + return iommu->flush.flush_iotlb(iommu, did, addr, mask, + DMA_TLB_PSI_FLUSH, + non_present_entry_flush); +} + +static void iommu_disable_protect_mem_regions(struct intel_iommu *iommu) +{ + u32 pmen; + unsigned long flags; + + spin_lock_irqsave(&iommu->register_lock, flags); + pmen = readl(iommu->reg + DMAR_PMEN_REG); + pmen &= ~DMA_PMEN_EPM; + writel(pmen, iommu->reg + DMAR_PMEN_REG); + + /* wait for the protected region status bit to clear */ + IOMMU_WAIT_OP(iommu, DMAR_PMEN_REG, + readl, !(pmen & DMA_PMEN_PRS), pmen); + + spin_unlock_irqrestore(&iommu->register_lock, flags); +} + +static int iommu_enable_translation(struct intel_iommu *iommu) +{ + u32 sts; + unsigned long flags; + + spin_lock_irqsave(&iommu->register_lock, flags); + writel(iommu->gcmd|DMA_GCMD_TE, iommu->reg + DMAR_GCMD_REG); + + /* Make sure hardware complete it */ + IOMMU_WAIT_OP(iommu, DMAR_GSTS_REG, + readl, (sts & DMA_GSTS_TES), sts); + + iommu->gcmd |= DMA_GCMD_TE; + spin_unlock_irqrestore(&iommu->register_lock, flags); + return 0; +} + +static int iommu_disable_translation(struct intel_iommu *iommu) +{ + u32 sts; + unsigned long flag; + + spin_lock_irqsave(&iommu->register_lock, flag); + iommu->gcmd &= ~DMA_GCMD_TE; + writel(iommu->gcmd, iommu->reg + DMAR_GCMD_REG); + + /* Make sure hardware complete it */ + IOMMU_WAIT_OP(iommu, DMAR_GSTS_REG, + readl, (!(sts & DMA_GSTS_TES)), sts); + + spin_unlock_irqrestore(&iommu->register_lock, flag); + return 0; +} + +/* iommu interrupt handling. Most stuff are MSI-like. */ + +static const char *fault_reason_strings[] = +{ + "Software", + "Present bit in root entry is clear", + "Present bit in context entry is clear", + "Invalid context entry", + "Access beyond MGAW", + "PTE Write access is not set", + "PTE Read access is not set", + "Next page table ptr is invalid", + "Root table address invalid", + "Context table ptr is invalid", + "non-zero reserved fields in RTP", + "non-zero reserved fields in CTP", + "non-zero reserved fields in PTE", +}; +#define MAX_FAULT_REASON_IDX (ARRAY_SIZE(fault_reason_strings) - 1) + +const char *dmar_get_fault_reason(u8 fault_reason) +{ + if (fault_reason > MAX_FAULT_REASON_IDX) + return "Unknown"; + else + return fault_reason_strings[fault_reason]; +} + +void dmar_msi_unmask(unsigned int irq) +{ + struct intel_iommu *iommu = get_irq_data(irq); + unsigned long flag; + + /* unmask it */ + spin_lock_irqsave(&iommu->register_lock, flag); + writel(0, iommu->reg + DMAR_FECTL_REG); + /* Read a reg to force flush the post write */ + readl(iommu->reg + DMAR_FECTL_REG); + spin_unlock_irqrestore(&iommu->register_lock, flag); +} + +void dmar_msi_mask(unsigned int irq) +{ + unsigned long flag; + struct intel_iommu *iommu = get_irq_data(irq); + + /* mask it */ + spin_lock_irqsave(&iommu->register_lock, flag); + writel(DMA_FECTL_IM, iommu->reg + DMAR_FECTL_REG); + /* Read a reg to force flush the post write */ + readl(iommu->reg + DMAR_FECTL_REG); + spin_unlock_irqrestore(&iommu->register_lock, flag); +} + +void dmar_msi_write(int irq, struct msi_msg *msg) +{ + struct intel_iommu *iommu = get_irq_data(irq); + unsigned long flag; + + spin_lock_irqsave(&iommu->register_lock, flag); + writel(msg->data, iommu->reg + DMAR_FEDATA_REG); + writel(msg->address_lo, iommu->reg + DMAR_FEADDR_REG); + writel(msg->address_hi, iommu->reg + DMAR_FEUADDR_REG); + spin_unlock_irqrestore(&iommu->register_lock, flag); +} + +void dmar_msi_read(int irq, struct msi_msg *msg) +{ + struct intel_iommu *iommu = get_irq_data(irq); + unsigned long flag; + + spin_lock_irqsave(&iommu->register_lock, flag); + msg->data = readl(iommu->reg + DMAR_FEDATA_REG); + msg->address_lo = readl(iommu->reg + DMAR_FEADDR_REG); + msg->address_hi = readl(iommu->reg + DMAR_FEUADDR_REG); + spin_unlock_irqrestore(&iommu->register_lock, flag); +} + +static int iommu_page_fault_do_one(struct intel_iommu *iommu, int type, + u8 fault_reason, u16 source_id, unsigned long long addr) +{ + const char *reason; + + reason = dmar_get_fault_reason(fault_reason); + + printk(KERN_ERR + "DMAR:[%s] Request device [%02x:%02x.%d] " + "fault addr %llx \n" + "DMAR:[fault reason %02d] %s\n", + (type ? "DMA Read" : "DMA Write"), + (source_id >> 8), PCI_SLOT(source_id & 0xFF), + PCI_FUNC(source_id & 0xFF), addr, fault_reason, reason); + return 0; +} + +#define PRIMARY_FAULT_REG_LEN (16) +static irqreturn_t iommu_page_fault(int irq, void *dev_id) +{ + struct intel_iommu *iommu = dev_id; + int reg, fault_index; + u32 fault_status; + unsigned long flag; + + spin_lock_irqsave(&iommu->register_lock, flag); + fault_status = readl(iommu->reg + DMAR_FSTS_REG); + + /* TBD: ignore advanced fault log currently */ + if (!(fault_status & DMA_FSTS_PPF)) + goto clear_overflow; + + fault_index = dma_fsts_fault_record_index(fault_status); + reg = cap_fault_reg_offset(iommu->cap); + while (1) { + u8 fault_reason; + u16 source_id; + u64 guest_addr; + int type; + u32 data; + + /* highest 32 bits */ + data = readl(iommu->reg + reg + + fault_index * PRIMARY_FAULT_REG_LEN + 12); + if (!(data & DMA_FRCD_F)) + break; + + fault_reason = dma_frcd_fault_reason(data); + type = dma_frcd_type(data); + + data = readl(iommu->reg + reg + + fault_index * PRIMARY_FAULT_REG_LEN + 8); + source_id = dma_frcd_source_id(data); + + guest_addr = dmar_readq(iommu->reg + reg + + fault_index * PRIMARY_FAULT_REG_LEN); + guest_addr = dma_frcd_page_addr(guest_addr); + /* clear the fault */ + writel(DMA_FRCD_F, iommu->reg + reg + + fault_index * PRIMARY_FAULT_REG_LEN + 12); + + spin_unlock_irqrestore(&iommu->register_lock, flag); + + iommu_page_fault_do_one(iommu, type, fault_reason, + source_id, guest_addr); + + fault_index++; + if (fault_index > cap_num_fault_regs(iommu->cap)) + fault_index = 0; + spin_lock_irqsave(&iommu->register_lock, flag); + } +clear_overflow: + /* clear primary fault overflow */ + fault_status = readl(iommu->reg + DMAR_FSTS_REG); + if (fault_status & DMA_FSTS_PFO) + writel(DMA_FSTS_PFO, iommu->reg + DMAR_FSTS_REG); + + spin_unlock_irqrestore(&iommu->register_lock, flag); + return IRQ_HANDLED; +} + +int dmar_set_interrupt(struct intel_iommu *iommu) +{ + int irq, ret; + + irq = create_irq(); + if (!irq) { + printk(KERN_ERR "IOMMU: no free vectors\n"); + return -EINVAL; + } + + set_irq_data(irq, iommu); + iommu->irq = irq; + + ret = arch_setup_dmar_msi(irq); + if (ret) { + set_irq_data(irq, NULL); + iommu->irq = 0; + destroy_irq(irq); + return 0; + } + + /* Force fault register is cleared */ + iommu_page_fault(irq, iommu); + + ret = request_irq(irq, iommu_page_fault, 0, iommu->name, iommu); + if (ret) + printk(KERN_ERR "IOMMU: can't request irq\n"); + return ret; +} + +static int iommu_init_domains(struct intel_iommu *iommu) +{ + unsigned long ndomains; + unsigned long nlongs; + + ndomains = cap_ndoms(iommu->cap); + pr_debug("Number of Domains supportd <%ld>\n", ndomains); + nlongs = BITS_TO_LONGS(ndomains); + + /* TBD: there might be 64K domains, + * consider other allocation for future chip + */ + iommu->domain_ids = kcalloc(nlongs, sizeof(unsigned long), GFP_KERNEL); + if (!iommu->domain_ids) { + printk(KERN_ERR "Allocating domain id array failed\n"); + return -ENOMEM; + } + iommu->domains = kcalloc(ndomains, sizeof(struct dmar_domain *), + GFP_KERNEL); + if (!iommu->domains) { + printk(KERN_ERR "Allocating domain array failed\n"); + kfree(iommu->domain_ids); + return -ENOMEM; + } + + spin_lock_init(&iommu->lock); + + /* + * if Caching mode is set, then invalid translations are tagged + * with domainid 0. Hence we need to pre-allocate it. + */ + if (cap_caching_mode(iommu->cap)) + set_bit(0, iommu->domain_ids); + return 0; +} + + +static void domain_exit(struct dmar_domain *domain); + +void free_dmar_iommu(struct intel_iommu *iommu) +{ + struct dmar_domain *domain; + int i; + + i = find_first_bit(iommu->domain_ids, cap_ndoms(iommu->cap)); + for (; i < cap_ndoms(iommu->cap); ) { + domain = iommu->domains[i]; + clear_bit(i, iommu->domain_ids); + domain_exit(domain); + i = find_next_bit(iommu->domain_ids, + cap_ndoms(iommu->cap), i+1); + } + + if (iommu->gcmd & DMA_GCMD_TE) + iommu_disable_translation(iommu); + + if (iommu->irq) { + set_irq_data(iommu->irq, NULL); + /* This will mask the irq */ + free_irq(iommu->irq, iommu); + destroy_irq(iommu->irq); + } + + kfree(iommu->domains); + kfree(iommu->domain_ids); + + /* free context mapping */ + free_context_table(iommu); +} + +static struct dmar_domain * iommu_alloc_domain(struct intel_iommu *iommu) +{ + unsigned long num; + unsigned long ndomains; + struct dmar_domain *domain; + unsigned long flags; + + domain = alloc_domain_mem(); + if (!domain) + return NULL; + + ndomains = cap_ndoms(iommu->cap); + + spin_lock_irqsave(&iommu->lock, flags); + num = find_first_zero_bit(iommu->domain_ids, ndomains); + if (num >= ndomains) { + spin_unlock_irqrestore(&iommu->lock, flags); + free_domain_mem(domain); + printk(KERN_ERR "IOMMU: no free domain ids\n"); + return NULL; + } + + set_bit(num, iommu->domain_ids); + domain->id = num; + domain->iommu = iommu; + iommu->domains[num] = domain; + spin_unlock_irqrestore(&iommu->lock, flags); + + return domain; +} + +static void iommu_free_domain(struct dmar_domain *domain) +{ + unsigned long flags; + + spin_lock_irqsave(&domain->iommu->lock, flags); + clear_bit(domain->id, domain->iommu->domain_ids); + spin_unlock_irqrestore(&domain->iommu->lock, flags); +} + +static struct iova_domain reserved_iova_list; +static struct lock_class_key reserved_alloc_key; +static struct lock_class_key reserved_rbtree_key; + +static void dmar_init_reserved_ranges(void) +{ + struct pci_dev *pdev = NULL; + struct iova *iova; + int i; + u64 addr, size; + + init_iova_domain(&reserved_iova_list, DMA_32BIT_PFN); + + lockdep_set_class(&reserved_iova_list.iova_alloc_lock, + &reserved_alloc_key); + lockdep_set_class(&reserved_iova_list.iova_rbtree_lock, + &reserved_rbtree_key); + + /* IOAPIC ranges shouldn't be accessed by DMA */ + iova = reserve_iova(&reserved_iova_list, IOVA_PFN(IOAPIC_RANGE_START), + IOVA_PFN(IOAPIC_RANGE_END)); + if (!iova) + printk(KERN_ERR "Reserve IOAPIC range failed\n"); + + /* Reserve all PCI MMIO to avoid peer-to-peer access */ + for_each_pci_dev(pdev) { + struct resource *r; + + for (i = 0; i < PCI_NUM_RESOURCES; i++) { + r = &pdev->resource[i]; + if (!r->flags || !(r->flags & IORESOURCE_MEM)) + continue; + addr = r->start; + addr &= PAGE_MASK; + size = r->end - addr; + size = PAGE_ALIGN(size); + iova = reserve_iova(&reserved_iova_list, IOVA_PFN(addr), + IOVA_PFN(size + addr) - 1); + if (!iova) + printk(KERN_ERR "Reserve iova failed\n"); + } + } + +} + +static void domain_reserve_special_ranges(struct dmar_domain *domain) +{ + copy_reserved_iova(&reserved_iova_list, &domain->iovad); +} + +static inline int guestwidth_to_adjustwidth(int gaw) +{ + int agaw; + int r = (gaw - 12) % 9; + + if (r == 0) + agaw = gaw; + else + agaw = gaw + 9 - r; + if (agaw > 64) + agaw = 64; + return agaw; +} + +static int domain_init(struct dmar_domain *domain, int guest_width) +{ + struct intel_iommu *iommu; + int adjust_width, agaw; + unsigned long sagaw; + + init_iova_domain(&domain->iovad, DMA_32BIT_PFN); + spin_lock_init(&domain->mapping_lock); + + domain_reserve_special_ranges(domain); + + /* calculate AGAW */ + iommu = domain->iommu; + if (guest_width > cap_mgaw(iommu->cap)) + guest_width = cap_mgaw(iommu->cap); + domain->gaw = guest_width; + adjust_width = guestwidth_to_adjustwidth(guest_width); + agaw = width_to_agaw(adjust_width); + sagaw = cap_sagaw(iommu->cap); + if (!test_bit(agaw, &sagaw)) { + /* hardware doesn't support it, choose a bigger one */ + pr_debug("IOMMU: hardware doesn't support agaw %d\n", agaw); + agaw = find_next_bit(&sagaw, 5, agaw); + if (agaw >= 5) + return -ENODEV; + } + domain->agaw = agaw; + INIT_LIST_HEAD(&domain->devices); + + /* always allocate the top pgd */ + domain->pgd = (struct dma_pte *)alloc_pgtable_page(); + if (!domain->pgd) + return -ENOMEM; + __iommu_flush_cache(iommu, domain->pgd, PAGE_SIZE); + return 0; +} + +static void domain_exit(struct dmar_domain *domain) +{ + u64 end; + + /* Domain 0 is reserved, so dont process it */ + if (!domain) + return; + + domain_remove_dev_info(domain); + /* destroy iovas */ + put_iova_domain(&domain->iovad); + end = DOMAIN_MAX_ADDR(domain->gaw); + end = end & (~PAGE_MASK); + + /* clear ptes */ + dma_pte_clear_range(domain, 0, end); + + /* free page tables */ + dma_pte_free_pagetable(domain, 0, end); + + iommu_free_domain(domain); + free_domain_mem(domain); +} + +static int domain_context_mapping_one(struct dmar_domain *domain, + u8 bus, u8 devfn) +{ + struct context_entry *context; + struct intel_iommu *iommu = domain->iommu; + unsigned long flags; + + pr_debug("Set context mapping for %02x:%02x.%d\n", + bus, PCI_SLOT(devfn), PCI_FUNC(devfn)); + BUG_ON(!domain->pgd); + context = device_to_context_entry(iommu, bus, devfn); + if (!context) + return -ENOMEM; + spin_lock_irqsave(&iommu->lock, flags); + if (context_present(*context)) { + spin_unlock_irqrestore(&iommu->lock, flags); + return 0; + } + + context_set_domain_id(*context, domain->id); + context_set_address_width(*context, domain->agaw); + context_set_address_root(*context, virt_to_phys(domain->pgd)); + context_set_translation_type(*context, CONTEXT_TT_MULTI_LEVEL); + context_set_fault_enable(*context); + context_set_present(*context); + __iommu_flush_cache(iommu, context, sizeof(*context)); + + /* it's a non-present to present mapping */ + if (iommu->flush.flush_context(iommu, domain->id, + (((u16)bus) << 8) | devfn, DMA_CCMD_MASK_NOBIT, + DMA_CCMD_DEVICE_INVL, 1)) + iommu_flush_write_buffer(iommu); + else + iommu->flush.flush_iotlb(iommu, 0, 0, 0, DMA_TLB_DSI_FLUSH, 0); + + spin_unlock_irqrestore(&iommu->lock, flags); + return 0; +} + +static int +domain_context_mapping(struct dmar_domain *domain, struct pci_dev *pdev) +{ + int ret; + struct pci_dev *tmp, *parent; + + ret = domain_context_mapping_one(domain, pdev->bus->number, + pdev->devfn); + if (ret) + return ret; + + /* dependent device mapping */ + tmp = pci_find_upstream_pcie_bridge(pdev); + if (!tmp) + return 0; + /* Secondary interface's bus number and devfn 0 */ + parent = pdev->bus->self; + while (parent != tmp) { + ret = domain_context_mapping_one(domain, parent->bus->number, + parent->devfn); + if (ret) + return ret; + parent = parent->bus->self; + } + if (tmp->is_pcie) /* this is a PCIE-to-PCI bridge */ + return domain_context_mapping_one(domain, + tmp->subordinate->number, 0); + else /* this is a legacy PCI bridge */ + return domain_context_mapping_one(domain, + tmp->bus->number, tmp->devfn); +} + +static int domain_context_mapped(struct dmar_domain *domain, + struct pci_dev *pdev) +{ + int ret; + struct pci_dev *tmp, *parent; + + ret = device_context_mapped(domain->iommu, + pdev->bus->number, pdev->devfn); + if (!ret) + return ret; + /* dependent device mapping */ + tmp = pci_find_upstream_pcie_bridge(pdev); + if (!tmp) + return ret; + /* Secondary interface's bus number and devfn 0 */ + parent = pdev->bus->self; + while (parent != tmp) { + ret = device_context_mapped(domain->iommu, parent->bus->number, + parent->devfn); + if (!ret) + return ret; + parent = parent->bus->self; + } + if (tmp->is_pcie) + return device_context_mapped(domain->iommu, + tmp->subordinate->number, 0); + else + return device_context_mapped(domain->iommu, + tmp->bus->number, tmp->devfn); +} + +static int +domain_page_mapping(struct dmar_domain *domain, dma_addr_t iova, + u64 hpa, size_t size, int prot) +{ + u64 start_pfn, end_pfn; + struct dma_pte *pte; + int index; + int addr_width = agaw_to_width(domain->agaw); + + hpa &= (((u64)1) << addr_width) - 1; + + if ((prot & (DMA_PTE_READ|DMA_PTE_WRITE)) == 0) + return -EINVAL; + iova &= PAGE_MASK; + start_pfn = ((u64)hpa) >> VTD_PAGE_SHIFT; + end_pfn = (VTD_PAGE_ALIGN(((u64)hpa) + size)) >> VTD_PAGE_SHIFT; + index = 0; + while (start_pfn < end_pfn) { + pte = addr_to_dma_pte(domain, iova + VTD_PAGE_SIZE * index); + if (!pte) + return -ENOMEM; + /* We don't need lock here, nobody else + * touches the iova range + */ + BUG_ON(dma_pte_addr(*pte)); + dma_set_pte_addr(*pte, start_pfn << VTD_PAGE_SHIFT); + dma_set_pte_prot(*pte, prot); + __iommu_flush_cache(domain->iommu, pte, sizeof(*pte)); + start_pfn++; + index++; + } + return 0; +} + +static void detach_domain_for_dev(struct dmar_domain *domain, u8 bus, u8 devfn) +{ + clear_context_table(domain->iommu, bus, devfn); + domain->iommu->flush.flush_context(domain->iommu, 0, 0, 0, + DMA_CCMD_GLOBAL_INVL, 0); + domain->iommu->flush.flush_iotlb(domain->iommu, 0, 0, 0, + DMA_TLB_GLOBAL_FLUSH, 0); +} + +static void domain_remove_dev_info(struct dmar_domain *domain) +{ + struct device_domain_info *info; + unsigned long flags; + + spin_lock_irqsave(&device_domain_lock, flags); + while (!list_empty(&domain->devices)) { + info = list_entry(domain->devices.next, + struct device_domain_info, link); + list_del(&info->link); + list_del(&info->global); + if (info->dev) + info->dev->dev.archdata.iommu = NULL; + spin_unlock_irqrestore(&device_domain_lock, flags); + + detach_domain_for_dev(info->domain, info->bus, info->devfn); + free_devinfo_mem(info); + + spin_lock_irqsave(&device_domain_lock, flags); + } + spin_unlock_irqrestore(&device_domain_lock, flags); +} + +/* + * find_domain + * Note: we use struct pci_dev->dev.archdata.iommu stores the info + */ +static struct dmar_domain * +find_domain(struct pci_dev *pdev) +{ + struct device_domain_info *info; + + /* No lock here, assumes no domain exit in normal case */ + info = pdev->dev.archdata.iommu; + if (info) + return info->domain; + return NULL; +} + +/* domain is initialized */ +static struct dmar_domain *get_domain_for_dev(struct pci_dev *pdev, int gaw) +{ + struct dmar_domain *domain, *found = NULL; + struct intel_iommu *iommu; + struct dmar_drhd_unit *drhd; + struct device_domain_info *info, *tmp; + struct pci_dev *dev_tmp; + unsigned long flags; + int bus = 0, devfn = 0; + + domain = find_domain(pdev); + if (domain) + return domain; + + dev_tmp = pci_find_upstream_pcie_bridge(pdev); + if (dev_tmp) { + if (dev_tmp->is_pcie) { + bus = dev_tmp->subordinate->number; + devfn = 0; + } else { + bus = dev_tmp->bus->number; + devfn = dev_tmp->devfn; + } + spin_lock_irqsave(&device_domain_lock, flags); + list_for_each_entry(info, &device_domain_list, global) { + if (info->bus == bus && info->devfn == devfn) { + found = info->domain; + break; + } + } + spin_unlock_irqrestore(&device_domain_lock, flags); + /* pcie-pci bridge already has a domain, uses it */ + if (found) { + domain = found; + goto found_domain; + } + } + + /* Allocate new domain for the device */ + drhd = dmar_find_matched_drhd_unit(pdev); + if (!drhd) { + printk(KERN_ERR "IOMMU: can't find DMAR for device %s\n", + pci_name(pdev)); + return NULL; + } + iommu = drhd->iommu; + + domain = iommu_alloc_domain(iommu); + if (!domain) + goto error; + + if (domain_init(domain, gaw)) { + domain_exit(domain); + goto error; + } + + /* register pcie-to-pci device */ + if (dev_tmp) { + info = alloc_devinfo_mem(); + if (!info) { + domain_exit(domain); + goto error; + } + info->bus = bus; + info->devfn = devfn; + info->dev = NULL; + info->domain = domain; + /* This domain is shared by devices under p2p bridge */ + domain->flags |= DOMAIN_FLAG_MULTIPLE_DEVICES; + + /* pcie-to-pci bridge already has a domain, uses it */ + found = NULL; + spin_lock_irqsave(&device_domain_lock, flags); + list_for_each_entry(tmp, &device_domain_list, global) { + if (tmp->bus == bus && tmp->devfn == devfn) { + found = tmp->domain; + break; + } + } + if (found) { + free_devinfo_mem(info); + domain_exit(domain); + domain = found; + } else { + list_add(&info->link, &domain->devices); + list_add(&info->global, &device_domain_list); + } + spin_unlock_irqrestore(&device_domain_lock, flags); + } + +found_domain: + info = alloc_devinfo_mem(); + if (!info) + goto error; + info->bus = pdev->bus->number; + info->devfn = pdev->devfn; + info->dev = pdev; + info->domain = domain; + spin_lock_irqsave(&device_domain_lock, flags); + /* somebody is fast */ + found = find_domain(pdev); + if (found != NULL) { + spin_unlock_irqrestore(&device_domain_lock, flags); + if (found != domain) { + domain_exit(domain); + domain = found; + } + free_devinfo_mem(info); + return domain; + } + list_add(&info->link, &domain->devices); + list_add(&info->global, &device_domain_list); + pdev->dev.archdata.iommu = info; + spin_unlock_irqrestore(&device_domain_lock, flags); + return domain; +error: + /* recheck it here, maybe others set it */ + return find_domain(pdev); +} + +static int iommu_prepare_identity_map(struct pci_dev *pdev, + unsigned long long start, + unsigned long long end) +{ + struct dmar_domain *domain; + unsigned long size; + unsigned long long base; + int ret; + + printk(KERN_INFO + "IOMMU: Setting identity map for device %s [0x%Lx - 0x%Lx]\n", + pci_name(pdev), start, end); + /* page table init */ + domain = get_domain_for_dev(pdev, DEFAULT_DOMAIN_ADDRESS_WIDTH); + if (!domain) + return -ENOMEM; + + /* The address might not be aligned */ + base = start & PAGE_MASK; + size = end - base; + size = PAGE_ALIGN(size); + if (!reserve_iova(&domain->iovad, IOVA_PFN(base), + IOVA_PFN(base + size) - 1)) { + printk(KERN_ERR "IOMMU: reserve iova failed\n"); + ret = -ENOMEM; + goto error; + } + + pr_debug("Mapping reserved region %lx@%llx for %s\n", + size, base, pci_name(pdev)); + /* + * RMRR range might have overlap with physical memory range, + * clear it first + */ + dma_pte_clear_range(domain, base, base + size); + + ret = domain_page_mapping(domain, base, base, size, + DMA_PTE_READ|DMA_PTE_WRITE); + if (ret) + goto error; + + /* context entry init */ + ret = domain_context_mapping(domain, pdev); + if (!ret) + return 0; +error: + domain_exit(domain); + return ret; + +} + +static inline int iommu_prepare_rmrr_dev(struct dmar_rmrr_unit *rmrr, + struct pci_dev *pdev) +{ + if (pdev->dev.archdata.iommu == DUMMY_DEVICE_DOMAIN_INFO) + return 0; + return iommu_prepare_identity_map(pdev, rmrr->base_address, + rmrr->end_address + 1); +} + +#ifdef CONFIG_DMAR_GFX_WA +struct iommu_prepare_data { + struct pci_dev *pdev; + int ret; +}; + +static int __init iommu_prepare_work_fn(unsigned long start_pfn, + unsigned long end_pfn, void *datax) +{ + struct iommu_prepare_data *data; + + data = (struct iommu_prepare_data *)datax; + + data->ret = iommu_prepare_identity_map(data->pdev, + start_pfn<<PAGE_SHIFT, end_pfn<<PAGE_SHIFT); + return data->ret; + +} + +static int __init iommu_prepare_with_active_regions(struct pci_dev *pdev) +{ + int nid; + struct iommu_prepare_data data; + + data.pdev = pdev; + data.ret = 0; + + for_each_online_node(nid) { + work_with_active_regions(nid, iommu_prepare_work_fn, &data); + if (data.ret) + return data.ret; + } + return data.ret; +} + +static void __init iommu_prepare_gfx_mapping(void) +{ + struct pci_dev *pdev = NULL; + int ret; + + for_each_pci_dev(pdev) { + if (pdev->dev.archdata.iommu == DUMMY_DEVICE_DOMAIN_INFO || + !IS_GFX_DEVICE(pdev)) + continue; + printk(KERN_INFO "IOMMU: gfx device %s 1-1 mapping\n", + pci_name(pdev)); + ret = iommu_prepare_with_active_regions(pdev); + if (ret) + printk(KERN_ERR "IOMMU: mapping reserved region failed\n"); + } +} +#endif + +#ifdef CONFIG_DMAR_FLOPPY_WA +static inline void iommu_prepare_isa(void) +{ + struct pci_dev *pdev; + int ret; + + pdev = pci_get_class(PCI_CLASS_BRIDGE_ISA << 8, NULL); + if (!pdev) + return; + + printk(KERN_INFO "IOMMU: Prepare 0-16M unity mapping for LPC\n"); + ret = iommu_prepare_identity_map(pdev, 0, 16*1024*1024); + + if (ret) + printk("IOMMU: Failed to create 0-64M identity map, " + "floppy might not work\n"); + +} +#else +static inline void iommu_prepare_isa(void) +{ + return; +} +#endif /* !CONFIG_DMAR_FLPY_WA */ + +int __init init_dmars(void) +{ + struct dmar_drhd_unit *drhd; + struct dmar_rmrr_unit *rmrr; + struct pci_dev *pdev; + struct intel_iommu *iommu; + int i, ret, unit = 0; + + /* + * for each drhd + * allocate root + * initialize and program root entry to not present + * endfor + */ + for_each_drhd_unit(drhd) { + g_num_of_iommus++; + /* + * lock not needed as this is only incremented in the single + * threaded kernel __init code path all other access are read + * only + */ + } + + deferred_flush = kzalloc(g_num_of_iommus * + sizeof(struct deferred_flush_tables), GFP_KERNEL); + if (!deferred_flush) { + ret = -ENOMEM; + goto error; + } + + for_each_drhd_unit(drhd) { + if (drhd->ignored) + continue; + + iommu = drhd->iommu; + + ret = iommu_init_domains(iommu); + if (ret) + goto error; + + /* + * TBD: + * we could share the same root & context tables + * amoung all IOMMU's. Need to Split it later. + */ + ret = iommu_alloc_root_entry(iommu); + if (ret) { + printk(KERN_ERR "IOMMU: allocate root entry failed\n"); + goto error; + } + } + + for_each_drhd_unit(drhd) { + if (drhd->ignored) + continue; + + iommu = drhd->iommu; + if (dmar_enable_qi(iommu)) { + /* + * Queued Invalidate not enabled, use Register Based + * Invalidate + */ + iommu->flush.flush_context = __iommu_flush_context; + iommu->flush.flush_iotlb = __iommu_flush_iotlb; + printk(KERN_INFO "IOMMU 0x%Lx: using Register based " + "invalidation\n", + (unsigned long long)drhd->reg_base_addr); + } else { + iommu->flush.flush_context = qi_flush_context; + iommu->flush.flush_iotlb = qi_flush_iotlb; + printk(KERN_INFO "IOMMU 0x%Lx: using Queued " + "invalidation\n", + (unsigned long long)drhd->reg_base_addr); + } + } + + /* + * For each rmrr + * for each dev attached to rmrr + * do + * locate drhd for dev, alloc domain for dev + * allocate free domain + * allocate page table entries for rmrr + * if context not allocated for bus + * allocate and init context + * set present in root table for this bus + * init context with domain, translation etc + * endfor + * endfor + */ + for_each_rmrr_units(rmrr) { + for (i = 0; i < rmrr->devices_cnt; i++) { + pdev = rmrr->devices[i]; + /* some BIOS lists non-exist devices in DMAR table */ + if (!pdev) + continue; + ret = iommu_prepare_rmrr_dev(rmrr, pdev); + if (ret) + printk(KERN_ERR + "IOMMU: mapping reserved region failed\n"); + } + } + + iommu_prepare_gfx_mapping(); + + iommu_prepare_isa(); + + /* + * for each drhd + * enable fault log + * global invalidate context cache + * global invalidate iotlb + * enable translation + */ + for_each_drhd_unit(drhd) { + if (drhd->ignored) + continue; + iommu = drhd->iommu; + sprintf (iommu->name, "dmar%d", unit++); + + iommu_flush_write_buffer(iommu); + + ret = dmar_set_interrupt(iommu); + if (ret) + goto error; + + iommu_set_root_entry(iommu); + + iommu->flush.flush_context(iommu, 0, 0, 0, DMA_CCMD_GLOBAL_INVL, + 0); + iommu->flush.flush_iotlb(iommu, 0, 0, 0, DMA_TLB_GLOBAL_FLUSH, + 0); + iommu_disable_protect_mem_regions(iommu); + + ret = iommu_enable_translation(iommu); + if (ret) + goto error; + } + + return 0; +error: + for_each_drhd_unit(drhd) { + if (drhd->ignored) + continue; + iommu = drhd->iommu; + free_iommu(iommu); + } + return ret; +} + +static inline u64 aligned_size(u64 host_addr, size_t size) +{ + u64 addr; + addr = (host_addr & (~PAGE_MASK)) + size; + return PAGE_ALIGN(addr); +} + +struct iova * +iommu_alloc_iova(struct dmar_domain *domain, size_t size, u64 end) +{ + struct iova *piova; + + /* Make sure it's in range */ + end = min_t(u64, DOMAIN_MAX_ADDR(domain->gaw), end); + if (!size || (IOVA_START_ADDR + size > end)) + return NULL; + + piova = alloc_iova(&domain->iovad, + size >> PAGE_SHIFT, IOVA_PFN(end), 1); + return piova; +} + +static struct iova * +__intel_alloc_iova(struct device *dev, struct dmar_domain *domain, + size_t size, u64 dma_mask) +{ + struct pci_dev *pdev = to_pci_dev(dev); + struct iova *iova = NULL; + + if (dma_mask <= DMA_32BIT_MASK || dmar_forcedac) + iova = iommu_alloc_iova(domain, size, dma_mask); + else { + /* + * First try to allocate an io virtual address in + * DMA_32BIT_MASK and if that fails then try allocating + * from higher range + */ + iova = iommu_alloc_iova(domain, size, DMA_32BIT_MASK); + if (!iova) + iova = iommu_alloc_iova(domain, size, dma_mask); + } + + if (!iova) { + printk(KERN_ERR"Allocating iova for %s failed", pci_name(pdev)); + return NULL; + } + + return iova; +} + +static struct dmar_domain * +get_valid_domain_for_dev(struct pci_dev *pdev) +{ + struct dmar_domain *domain; + int ret; + + domain = get_domain_for_dev(pdev, + DEFAULT_DOMAIN_ADDRESS_WIDTH); + if (!domain) { + printk(KERN_ERR + "Allocating domain for %s failed", pci_name(pdev)); + return NULL; + } + + /* make sure context mapping is ok */ + if (unlikely(!domain_context_mapped(domain, pdev))) { + ret = domain_context_mapping(domain, pdev); + if (ret) { + printk(KERN_ERR + "Domain context map for %s failed", + pci_name(pdev)); + return NULL; + } + } + + return domain; +} + +static dma_addr_t __intel_map_single(struct device *hwdev, phys_addr_t paddr, + size_t size, int dir, u64 dma_mask) +{ + struct pci_dev *pdev = to_pci_dev(hwdev); + struct dmar_domain *domain; + phys_addr_t start_paddr; + struct iova *iova; + int prot = 0; + int ret; + + BUG_ON(dir == DMA_NONE); + if (pdev->dev.archdata.iommu == DUMMY_DEVICE_DOMAIN_INFO) + return paddr; + + domain = get_valid_domain_for_dev(pdev); + if (!domain) + return 0; + + size = aligned_size((u64)paddr, size); + + iova = __intel_alloc_iova(hwdev, domain, size, pdev->dma_mask); + if (!iova) + goto error; + + start_paddr = (phys_addr_t)iova->pfn_lo << PAGE_SHIFT; + + /* + * Check if DMAR supports zero-length reads on write only + * mappings.. + */ + if (dir == DMA_TO_DEVICE || dir == DMA_BIDIRECTIONAL || \ + !cap_zlr(domain->iommu->cap)) + prot |= DMA_PTE_READ; + if (dir == DMA_FROM_DEVICE || dir == DMA_BIDIRECTIONAL) + prot |= DMA_PTE_WRITE; + /* + * paddr - (paddr + size) might be partial page, we should map the whole + * page. Note: if two part of one page are separately mapped, we + * might have two guest_addr mapping to the same host paddr, but this + * is not a big problem + */ + ret = domain_page_mapping(domain, start_paddr, + ((u64)paddr) & PAGE_MASK, size, prot); + if (ret) + goto error; + + /* it's a non-present to present mapping */ + ret = iommu_flush_iotlb_psi(domain->iommu, domain->id, + start_paddr, size >> VTD_PAGE_SHIFT, 1); + if (ret) + iommu_flush_write_buffer(domain->iommu); + + return start_paddr + ((u64)paddr & (~PAGE_MASK)); + +error: + if (iova) + __free_iova(&domain->iovad, iova); + printk(KERN_ERR"Device %s request: %lx@%llx dir %d --- failed\n", + pci_name(pdev), size, (unsigned long long)paddr, dir); + return 0; +} + +dma_addr_t intel_map_single(struct device *hwdev, phys_addr_t paddr, + size_t size, int dir) +{ + return __intel_map_single(hwdev, paddr, size, dir, + to_pci_dev(hwdev)->dma_mask); +} + +static void flush_unmaps(void) +{ + int i, j; + + timer_on = 0; + + /* just flush them all */ + for (i = 0; i < g_num_of_iommus; i++) { + if (deferred_flush[i].next) { + struct intel_iommu *iommu = + deferred_flush[i].domain[0]->iommu; + + iommu->flush.flush_iotlb(iommu, 0, 0, 0, + DMA_TLB_GLOBAL_FLUSH, 0); + for (j = 0; j < deferred_flush[i].next; j++) { + __free_iova(&deferred_flush[i].domain[j]->iovad, + deferred_flush[i].iova[j]); + } + deferred_flush[i].next = 0; + } + } + + list_size = 0; +} + +static void flush_unmaps_timeout(unsigned long data) +{ + unsigned long flags; + + spin_lock_irqsave(&async_umap_flush_lock, flags); + flush_unmaps(); + spin_unlock_irqrestore(&async_umap_flush_lock, flags); +} + +static void add_unmap(struct dmar_domain *dom, struct iova *iova) +{ + unsigned long flags; + int next, iommu_id; + + spin_lock_irqsave(&async_umap_flush_lock, flags); + if (list_size == HIGH_WATER_MARK) + flush_unmaps(); + + iommu_id = dom->iommu->seq_id; + + next = deferred_flush[iommu_id].next; + deferred_flush[iommu_id].domain[next] = dom; + deferred_flush[iommu_id].iova[next] = iova; + deferred_flush[iommu_id].next++; + + if (!timer_on) { + mod_timer(&unmap_timer, jiffies + msecs_to_jiffies(10)); + timer_on = 1; + } + list_size++; + spin_unlock_irqrestore(&async_umap_flush_lock, flags); +} + +void intel_unmap_single(struct device *dev, dma_addr_t dev_addr, size_t size, + int dir) +{ + struct pci_dev *pdev = to_pci_dev(dev); + struct dmar_domain *domain; + unsigned long start_addr; + struct iova *iova; + + if (pdev->dev.archdata.iommu == DUMMY_DEVICE_DOMAIN_INFO) + return; + domain = find_domain(pdev); + BUG_ON(!domain); + + iova = find_iova(&domain->iovad, IOVA_PFN(dev_addr)); + if (!iova) + return; + + start_addr = iova->pfn_lo << PAGE_SHIFT; + size = aligned_size((u64)dev_addr, size); + + pr_debug("Device %s unmapping: %lx@%llx\n", + pci_name(pdev), size, (unsigned long long)start_addr); + + /* clear the whole page */ + dma_pte_clear_range(domain, start_addr, start_addr + size); + /* free page tables */ + dma_pte_free_pagetable(domain, start_addr, start_addr + size); + if (intel_iommu_strict) { + if (iommu_flush_iotlb_psi(domain->iommu, + domain->id, start_addr, size >> VTD_PAGE_SHIFT, 0)) + iommu_flush_write_buffer(domain->iommu); + /* free iova */ + __free_iova(&domain->iovad, iova); + } else { + add_unmap(domain, iova); + /* + * queue up the release of the unmap to save the 1/6th of the + * cpu used up by the iotlb flush operation... + */ + } +} + +void *intel_alloc_coherent(struct device *hwdev, size_t size, + dma_addr_t *dma_handle, gfp_t flags) +{ + void *vaddr; + int order; + + size = PAGE_ALIGN(size); + order = get_order(size); + flags &= ~(GFP_DMA | GFP_DMA32); + + vaddr = (void *)__get_free_pages(flags, order); + if (!vaddr) + return NULL; + memset(vaddr, 0, size); + + *dma_handle = __intel_map_single(hwdev, virt_to_bus(vaddr), size, + DMA_BIDIRECTIONAL, + hwdev->coherent_dma_mask); + if (*dma_handle) + return vaddr; + free_pages((unsigned long)vaddr, order); + return NULL; +} + +void intel_free_coherent(struct device *hwdev, size_t size, void *vaddr, + dma_addr_t dma_handle) +{ + int order; + + size = PAGE_ALIGN(size); + order = get_order(size); + + intel_unmap_single(hwdev, dma_handle, size, DMA_BIDIRECTIONAL); + free_pages((unsigned long)vaddr, order); +} + +#define SG_ENT_VIRT_ADDRESS(sg) (sg_virt((sg))) + +void intel_unmap_sg(struct device *hwdev, struct scatterlist *sglist, + int nelems, int dir) +{ + int i; + struct pci_dev *pdev = to_pci_dev(hwdev); + struct dmar_domain *domain; + unsigned long start_addr; + struct iova *iova; + size_t size = 0; + void *addr; + struct scatterlist *sg; + + if (pdev->dev.archdata.iommu == DUMMY_DEVICE_DOMAIN_INFO) + return; + + domain = find_domain(pdev); + + iova = find_iova(&domain->iovad, IOVA_PFN(sglist[0].dma_address)); + if (!iova) + return; + for_each_sg(sglist, sg, nelems, i) { + addr = SG_ENT_VIRT_ADDRESS(sg); + size += aligned_size((u64)addr, sg->length); + } + + start_addr = iova->pfn_lo << PAGE_SHIFT; + + /* clear the whole page */ + dma_pte_clear_range(domain, start_addr, start_addr + size); + /* free page tables */ + dma_pte_free_pagetable(domain, start_addr, start_addr + size); + + if (iommu_flush_iotlb_psi(domain->iommu, domain->id, start_addr, + size >> VTD_PAGE_SHIFT, 0)) + iommu_flush_write_buffer(domain->iommu); + + /* free iova */ + __free_iova(&domain->iovad, iova); +} + +static int intel_nontranslate_map_sg(struct device *hddev, + struct scatterlist *sglist, int nelems, int dir) +{ + int i; + struct scatterlist *sg; + + for_each_sg(sglist, sg, nelems, i) { + BUG_ON(!sg_page(sg)); + sg->dma_address = virt_to_bus(SG_ENT_VIRT_ADDRESS(sg)); + sg->dma_length = sg->length; + } + return nelems; +} + +int intel_map_sg(struct device *hwdev, struct scatterlist *sglist, int nelems, + int dir) +{ + void *addr; + int i; + struct pci_dev *pdev = to_pci_dev(hwdev); + struct dmar_domain *domain; + size_t size = 0; + int prot = 0; + size_t offset = 0; + struct iova *iova = NULL; + int ret; + struct scatterlist *sg; + unsigned long start_addr; + + BUG_ON(dir == DMA_NONE); + if (pdev->dev.archdata.iommu == DUMMY_DEVICE_DOMAIN_INFO) + return intel_nontranslate_map_sg(hwdev, sglist, nelems, dir); + + domain = get_valid_domain_for_dev(pdev); + if (!domain) + return 0; + + for_each_sg(sglist, sg, nelems, i) { + addr = SG_ENT_VIRT_ADDRESS(sg); + addr = (void *)virt_to_phys(addr); + size += aligned_size((u64)addr, sg->length); + } + + iova = __intel_alloc_iova(hwdev, domain, size, pdev->dma_mask); + if (!iova) { + sglist->dma_length = 0; + return 0; + } + + /* + * Check if DMAR supports zero-length reads on write only + * mappings.. + */ + if (dir == DMA_TO_DEVICE || dir == DMA_BIDIRECTIONAL || \ + !cap_zlr(domain->iommu->cap)) + prot |= DMA_PTE_READ; + if (dir == DMA_FROM_DEVICE || dir == DMA_BIDIRECTIONAL) + prot |= DMA_PTE_WRITE; + + start_addr = iova->pfn_lo << PAGE_SHIFT; + offset = 0; + for_each_sg(sglist, sg, nelems, i) { + addr = SG_ENT_VIRT_ADDRESS(sg); + addr = (void *)virt_to_phys(addr); + size = aligned_size((u64)addr, sg->length); + ret = domain_page_mapping(domain, start_addr + offset, + ((u64)addr) & PAGE_MASK, + size, prot); + if (ret) { + /* clear the page */ + dma_pte_clear_range(domain, start_addr, + start_addr + offset); + /* free page tables */ + dma_pte_free_pagetable(domain, start_addr, + start_addr + offset); + /* free iova */ + __free_iova(&domain->iovad, iova); + return 0; + } + sg->dma_address = start_addr + offset + + ((u64)addr & (~PAGE_MASK)); + sg->dma_length = sg->length; + offset += size; + } + + /* it's a non-present to present mapping */ + if (iommu_flush_iotlb_psi(domain->iommu, domain->id, + start_addr, offset >> VTD_PAGE_SHIFT, 1)) + iommu_flush_write_buffer(domain->iommu); + return nelems; +} + +static struct dma_mapping_ops intel_dma_ops = { + .alloc_coherent = intel_alloc_coherent, + .free_coherent = intel_free_coherent, + .map_single = intel_map_single, + .unmap_single = intel_unmap_single, + .map_sg = intel_map_sg, + .unmap_sg = intel_unmap_sg, +}; + +static inline int iommu_domain_cache_init(void) +{ + int ret = 0; + + iommu_domain_cache = kmem_cache_create("iommu_domain", + sizeof(struct dmar_domain), + 0, + SLAB_HWCACHE_ALIGN, + + NULL); + if (!iommu_domain_cache) { + printk(KERN_ERR "Couldn't create iommu_domain cache\n"); + ret = -ENOMEM; + } + + return ret; +} + +static inline int iommu_devinfo_cache_init(void) +{ + int ret = 0; + + iommu_devinfo_cache = kmem_cache_create("iommu_devinfo", + sizeof(struct device_domain_info), + 0, + SLAB_HWCACHE_ALIGN, + NULL); + if (!iommu_devinfo_cache) { + printk(KERN_ERR "Couldn't create devinfo cache\n"); + ret = -ENOMEM; + } + + return ret; +} + +static inline int iommu_iova_cache_init(void) +{ + int ret = 0; + + iommu_iova_cache = kmem_cache_create("iommu_iova", + sizeof(struct iova), + 0, + SLAB_HWCACHE_ALIGN, + NULL); + if (!iommu_iova_cache) { + printk(KERN_ERR "Couldn't create iova cache\n"); + ret = -ENOMEM; + } + + return ret; +} + +static int __init iommu_init_mempool(void) +{ + int ret; + ret = iommu_iova_cache_init(); + if (ret) + return ret; + + ret = iommu_domain_cache_init(); + if (ret) + goto domain_error; + + ret = iommu_devinfo_cache_init(); + if (!ret) + return ret; + + kmem_cache_destroy(iommu_domain_cache); +domain_error: + kmem_cache_destroy(iommu_iova_cache); + + return -ENOMEM; +} + +static void __init iommu_exit_mempool(void) +{ + kmem_cache_destroy(iommu_devinfo_cache); + kmem_cache_destroy(iommu_domain_cache); + kmem_cache_destroy(iommu_iova_cache); + +} + +static void __init init_no_remapping_devices(void) +{ + struct dmar_drhd_unit *drhd; + + for_each_drhd_unit(drhd) { + if (!drhd->include_all) { + int i; + for (i = 0; i < drhd->devices_cnt; i++) + if (drhd->devices[i] != NULL) + break; + /* ignore DMAR unit if no pci devices exist */ + if (i == drhd->devices_cnt) + drhd->ignored = 1; + } + } + + if (dmar_map_gfx) + return; + + for_each_drhd_unit(drhd) { + int i; + if (drhd->ignored || drhd->include_all) + continue; + + for (i = 0; i < drhd->devices_cnt; i++) + if (drhd->devices[i] && + !IS_GFX_DEVICE(drhd->devices[i])) + break; + + if (i < drhd->devices_cnt) + continue; + + /* bypass IOMMU if it is just for gfx devices */ + drhd->ignored = 1; + for (i = 0; i < drhd->devices_cnt; i++) { + if (!drhd->devices[i]) + continue; + drhd->devices[i]->dev.archdata.iommu = DUMMY_DEVICE_DOMAIN_INFO; + } + } +} + +int __init intel_iommu_init(void) +{ + int ret = 0; + + if (dmar_table_init()) + return -ENODEV; + + if (dmar_dev_scope_init()) + return -ENODEV; + + /* + * Check the need for DMA-remapping initialization now. + * Above initialization will also be used by Interrupt-remapping. + */ + if (no_iommu || swiotlb || dmar_disabled) + return -ENODEV; + + iommu_init_mempool(); + dmar_init_reserved_ranges(); + + init_no_remapping_devices(); + + ret = init_dmars(); + if (ret) { + printk(KERN_ERR "IOMMU: dmar init failed\n"); + put_iova_domain(&reserved_iova_list); + iommu_exit_mempool(); + return ret; + } + printk(KERN_INFO + "PCI-DMA: Intel(R) Virtualization Technology for Directed I/O\n"); + + init_timer(&unmap_timer); + force_iommu = 1; + dma_ops = &intel_dma_ops; + return 0; +} + +void intel_iommu_domain_exit(struct dmar_domain *domain) +{ + u64 end; + + /* Domain 0 is reserved, so dont process it */ + if (!domain) + return; + + end = DOMAIN_MAX_ADDR(domain->gaw); + end = end & (~VTD_PAGE_MASK); + + /* clear ptes */ + dma_pte_clear_range(domain, 0, end); + + /* free page tables */ + dma_pte_free_pagetable(domain, 0, end); + + iommu_free_domain(domain); + free_domain_mem(domain); +} +EXPORT_SYMBOL_GPL(intel_iommu_domain_exit); + +struct dmar_domain *intel_iommu_domain_alloc(struct pci_dev *pdev) +{ + struct dmar_drhd_unit *drhd; + struct dmar_domain *domain; + struct intel_iommu *iommu; + + drhd = dmar_find_matched_drhd_unit(pdev); + if (!drhd) { + printk(KERN_ERR "intel_iommu_domain_alloc: drhd == NULL\n"); + return NULL; + } + + iommu = drhd->iommu; + if (!iommu) { + printk(KERN_ERR + "intel_iommu_domain_alloc: iommu == NULL\n"); + return NULL; + } + domain = iommu_alloc_domain(iommu); + if (!domain) { + printk(KERN_ERR + "intel_iommu_domain_alloc: domain == NULL\n"); + return NULL; + } + if (domain_init(domain, DEFAULT_DOMAIN_ADDRESS_WIDTH)) { + printk(KERN_ERR + "intel_iommu_domain_alloc: domain_init() failed\n"); + intel_iommu_domain_exit(domain); + return NULL; + } + return domain; +} +EXPORT_SYMBOL_GPL(intel_iommu_domain_alloc); + +int intel_iommu_context_mapping( + struct dmar_domain *domain, struct pci_dev *pdev) +{ + int rc; + rc = domain_context_mapping(domain, pdev); + return rc; +} +EXPORT_SYMBOL_GPL(intel_iommu_context_mapping); + +int intel_iommu_page_mapping( + struct dmar_domain *domain, dma_addr_t iova, + u64 hpa, size_t size, int prot) +{ + int rc; + rc = domain_page_mapping(domain, iova, hpa, size, prot); + return rc; +} +EXPORT_SYMBOL_GPL(intel_iommu_page_mapping); + +void intel_iommu_detach_dev(struct dmar_domain *domain, u8 bus, u8 devfn) +{ + detach_domain_for_dev(domain, bus, devfn); +} +EXPORT_SYMBOL_GPL(intel_iommu_detach_dev); + +struct dmar_domain * +intel_iommu_find_domain(struct pci_dev *pdev) +{ + return find_domain(pdev); +} +EXPORT_SYMBOL_GPL(intel_iommu_find_domain); + +int intel_iommu_found(void) +{ + return g_num_of_iommus; +} +EXPORT_SYMBOL_GPL(intel_iommu_found); + +u64 intel_iommu_iova_to_pfn(struct dmar_domain *domain, u64 iova) +{ + struct dma_pte *pte; + u64 pfn; + + pfn = 0; + pte = addr_to_dma_pte(domain, iova); + + if (pte) + pfn = dma_pte_addr(*pte); + + return pfn >> VTD_PAGE_SHIFT; +} +EXPORT_SYMBOL_GPL(intel_iommu_iova_to_pfn); + +static void __devinit quirk_iommu_rwbf(struct pci_dev *dev) +{ + /* Mobile 4 Series Chipset neglects to set RWBF capability, + but needs it */ + printk(KERN_INFO "DMAR: Forcing write-buffer flush capability\n"); + rwbf_quirk = 1; +} + +DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x2a40, quirk_iommu_rwbf); diff --git a/drivers/pci/intr_remapping.c b/drivers/pci/intr_remapping.c new file mode 100644 index 0000000..2de5a32 --- /dev/null +++ b/drivers/pci/intr_remapping.c @@ -0,0 +1,512 @@ +#include <linux/interrupt.h> +#include <linux/dmar.h> +#include <linux/spinlock.h> +#include <linux/jiffies.h> +#include <linux/pci.h> +#include <linux/irq.h> +#include <asm/io_apic.h> +#include <linux/intel-iommu.h> +#include "intr_remapping.h" + +static struct ioapic_scope ir_ioapic[MAX_IO_APICS]; +static int ir_ioapic_num; +int intr_remapping_enabled; + +struct irq_2_iommu { + struct intel_iommu *iommu; + u16 irte_index; + u16 sub_handle; + u8 irte_mask; +}; + +static struct irq_2_iommu irq_2_iommuX[NR_IRQS]; + +static struct irq_2_iommu *irq_2_iommu(unsigned int irq) +{ + return (irq < nr_irqs) ? irq_2_iommuX + irq : NULL; +} + +static struct irq_2_iommu *irq_2_iommu_alloc(unsigned int irq) +{ + return irq_2_iommu(irq); +} + +static DEFINE_SPINLOCK(irq_2_ir_lock); + +static struct irq_2_iommu *valid_irq_2_iommu(unsigned int irq) +{ + struct irq_2_iommu *irq_iommu; + + irq_iommu = irq_2_iommu(irq); + + if (!irq_iommu) + return NULL; + + if (!irq_iommu->iommu) + return NULL; + + return irq_iommu; +} + +int irq_remapped(int irq) +{ + return valid_irq_2_iommu(irq) != NULL; +} + +int get_irte(int irq, struct irte *entry) +{ + int index; + struct irq_2_iommu *irq_iommu; + + if (!entry) + return -1; + + spin_lock(&irq_2_ir_lock); + irq_iommu = valid_irq_2_iommu(irq); + if (!irq_iommu) { + spin_unlock(&irq_2_ir_lock); + return -1; + } + + index = irq_iommu->irte_index + irq_iommu->sub_handle; + *entry = *(irq_iommu->iommu->ir_table->base + index); + + spin_unlock(&irq_2_ir_lock); + return 0; +} + +int alloc_irte(struct intel_iommu *iommu, int irq, u16 count) +{ + struct ir_table *table = iommu->ir_table; + struct irq_2_iommu *irq_iommu; + u16 index, start_index; + unsigned int mask = 0; + int i; + + if (!count) + return -1; + + /* protect irq_2_iommu_alloc later */ + if (irq >= nr_irqs) + return -1; + + /* + * start the IRTE search from index 0. + */ + index = start_index = 0; + + if (count > 1) { + count = __roundup_pow_of_two(count); + mask = ilog2(count); + } + + if (mask > ecap_max_handle_mask(iommu->ecap)) { + printk(KERN_ERR + "Requested mask %x exceeds the max invalidation handle" + " mask value %Lx\n", mask, + ecap_max_handle_mask(iommu->ecap)); + return -1; + } + + spin_lock(&irq_2_ir_lock); + do { + for (i = index; i < index + count; i++) + if (table->base[i].present) + break; + /* empty index found */ + if (i == index + count) + break; + + index = (index + count) % INTR_REMAP_TABLE_ENTRIES; + + if (index == start_index) { + spin_unlock(&irq_2_ir_lock); + printk(KERN_ERR "can't allocate an IRTE\n"); + return -1; + } + } while (1); + + for (i = index; i < index + count; i++) + table->base[i].present = 1; + + irq_iommu = irq_2_iommu_alloc(irq); + irq_iommu->iommu = iommu; + irq_iommu->irte_index = index; + irq_iommu->sub_handle = 0; + irq_iommu->irte_mask = mask; + + spin_unlock(&irq_2_ir_lock); + + return index; +} + +static void qi_flush_iec(struct intel_iommu *iommu, int index, int mask) +{ + struct qi_desc desc; + + desc.low = QI_IEC_IIDEX(index) | QI_IEC_TYPE | QI_IEC_IM(mask) + | QI_IEC_SELECTIVE; + desc.high = 0; + + qi_submit_sync(&desc, iommu); +} + +int map_irq_to_irte_handle(int irq, u16 *sub_handle) +{ + int index; + struct irq_2_iommu *irq_iommu; + + spin_lock(&irq_2_ir_lock); + irq_iommu = valid_irq_2_iommu(irq); + if (!irq_iommu) { + spin_unlock(&irq_2_ir_lock); + return -1; + } + + *sub_handle = irq_iommu->sub_handle; + index = irq_iommu->irte_index; + spin_unlock(&irq_2_ir_lock); + return index; +} + +int set_irte_irq(int irq, struct intel_iommu *iommu, u16 index, u16 subhandle) +{ + struct irq_2_iommu *irq_iommu; + + spin_lock(&irq_2_ir_lock); + + irq_iommu = irq_2_iommu_alloc(irq); + + irq_iommu->iommu = iommu; + irq_iommu->irte_index = index; + irq_iommu->sub_handle = subhandle; + irq_iommu->irte_mask = 0; + + spin_unlock(&irq_2_ir_lock); + + return 0; +} + +int clear_irte_irq(int irq, struct intel_iommu *iommu, u16 index) +{ + struct irq_2_iommu *irq_iommu; + + spin_lock(&irq_2_ir_lock); + irq_iommu = valid_irq_2_iommu(irq); + if (!irq_iommu) { + spin_unlock(&irq_2_ir_lock); + return -1; + } + + irq_iommu->iommu = NULL; + irq_iommu->irte_index = 0; + irq_iommu->sub_handle = 0; + irq_2_iommu(irq)->irte_mask = 0; + + spin_unlock(&irq_2_ir_lock); + + return 0; +} + +int modify_irte(int irq, struct irte *irte_modified) +{ + int index; + struct irte *irte; + struct intel_iommu *iommu; + struct irq_2_iommu *irq_iommu; + + spin_lock(&irq_2_ir_lock); + irq_iommu = valid_irq_2_iommu(irq); + if (!irq_iommu) { + spin_unlock(&irq_2_ir_lock); + return -1; + } + + iommu = irq_iommu->iommu; + + index = irq_iommu->irte_index + irq_iommu->sub_handle; + irte = &iommu->ir_table->base[index]; + + set_64bit((unsigned long *)irte, irte_modified->low | (1 << 1)); + __iommu_flush_cache(iommu, irte, sizeof(*irte)); + + qi_flush_iec(iommu, index, 0); + + spin_unlock(&irq_2_ir_lock); + return 0; +} + +int flush_irte(int irq) +{ + int index; + struct intel_iommu *iommu; + struct irq_2_iommu *irq_iommu; + + spin_lock(&irq_2_ir_lock); + irq_iommu = valid_irq_2_iommu(irq); + if (!irq_iommu) { + spin_unlock(&irq_2_ir_lock); + return -1; + } + + iommu = irq_iommu->iommu; + + index = irq_iommu->irte_index + irq_iommu->sub_handle; + + qi_flush_iec(iommu, index, irq_iommu->irte_mask); + spin_unlock(&irq_2_ir_lock); + + return 0; +} + +struct intel_iommu *map_ioapic_to_ir(int apic) +{ + int i; + + for (i = 0; i < MAX_IO_APICS; i++) + if (ir_ioapic[i].id == apic) + return ir_ioapic[i].iommu; + return NULL; +} + +struct intel_iommu *map_dev_to_ir(struct pci_dev *dev) +{ + struct dmar_drhd_unit *drhd; + + drhd = dmar_find_matched_drhd_unit(dev); + if (!drhd) + return NULL; + + return drhd->iommu; +} + +int free_irte(int irq) +{ + int index, i; + struct irte *irte; + struct intel_iommu *iommu; + struct irq_2_iommu *irq_iommu; + + spin_lock(&irq_2_ir_lock); + irq_iommu = valid_irq_2_iommu(irq); + if (!irq_iommu) { + spin_unlock(&irq_2_ir_lock); + return -1; + } + + iommu = irq_iommu->iommu; + + index = irq_iommu->irte_index + irq_iommu->sub_handle; + irte = &iommu->ir_table->base[index]; + + if (!irq_iommu->sub_handle) { + for (i = 0; i < (1 << irq_iommu->irte_mask); i++) + set_64bit((unsigned long *)irte, 0); + qi_flush_iec(iommu, index, irq_iommu->irte_mask); + } + + irq_iommu->iommu = NULL; + irq_iommu->irte_index = 0; + irq_iommu->sub_handle = 0; + irq_iommu->irte_mask = 0; + + spin_unlock(&irq_2_ir_lock); + + return 0; +} + +static void iommu_set_intr_remapping(struct intel_iommu *iommu, int mode) +{ + u64 addr; + u32 cmd, sts; + unsigned long flags; + + addr = virt_to_phys((void *)iommu->ir_table->base); + + spin_lock_irqsave(&iommu->register_lock, flags); + + dmar_writeq(iommu->reg + DMAR_IRTA_REG, + (addr) | IR_X2APIC_MODE(mode) | INTR_REMAP_TABLE_REG_SIZE); + + /* Set interrupt-remapping table pointer */ + cmd = iommu->gcmd | DMA_GCMD_SIRTP; + writel(cmd, iommu->reg + DMAR_GCMD_REG); + + IOMMU_WAIT_OP(iommu, DMAR_GSTS_REG, + readl, (sts & DMA_GSTS_IRTPS), sts); + spin_unlock_irqrestore(&iommu->register_lock, flags); + + /* + * global invalidation of interrupt entry cache before enabling + * interrupt-remapping. + */ + qi_global_iec(iommu); + + spin_lock_irqsave(&iommu->register_lock, flags); + + /* Enable interrupt-remapping */ + cmd = iommu->gcmd | DMA_GCMD_IRE; + iommu->gcmd |= DMA_GCMD_IRE; + writel(cmd, iommu->reg + DMAR_GCMD_REG); + + IOMMU_WAIT_OP(iommu, DMAR_GSTS_REG, + readl, (sts & DMA_GSTS_IRES), sts); + + spin_unlock_irqrestore(&iommu->register_lock, flags); +} + + +static int setup_intr_remapping(struct intel_iommu *iommu, int mode) +{ + struct ir_table *ir_table; + struct page *pages; + + ir_table = iommu->ir_table = kzalloc(sizeof(struct ir_table), + GFP_KERNEL); + + if (!iommu->ir_table) + return -ENOMEM; + + pages = alloc_pages(GFP_KERNEL | __GFP_ZERO, INTR_REMAP_PAGE_ORDER); + + if (!pages) { + printk(KERN_ERR "failed to allocate pages of order %d\n", + INTR_REMAP_PAGE_ORDER); + kfree(iommu->ir_table); + return -ENOMEM; + } + + ir_table->base = page_address(pages); + + iommu_set_intr_remapping(iommu, mode); + return 0; +} + +int __init enable_intr_remapping(int eim) +{ + struct dmar_drhd_unit *drhd; + int setup = 0; + + /* + * check for the Interrupt-remapping support + */ + for_each_drhd_unit(drhd) { + struct intel_iommu *iommu = drhd->iommu; + + if (!ecap_ir_support(iommu->ecap)) + continue; + + if (eim && !ecap_eim_support(iommu->ecap)) { + printk(KERN_INFO "DRHD %Lx: EIM not supported by DRHD, " + " ecap %Lx\n", drhd->reg_base_addr, iommu->ecap); + return -1; + } + } + + /* + * Enable queued invalidation for all the DRHD's. + */ + for_each_drhd_unit(drhd) { + int ret; + struct intel_iommu *iommu = drhd->iommu; + ret = dmar_enable_qi(iommu); + + if (ret) { + printk(KERN_ERR "DRHD %Lx: failed to enable queued, " + " invalidation, ecap %Lx, ret %d\n", + drhd->reg_base_addr, iommu->ecap, ret); + return -1; + } + } + + /* + * Setup Interrupt-remapping for all the DRHD's now. + */ + for_each_drhd_unit(drhd) { + struct intel_iommu *iommu = drhd->iommu; + + if (!ecap_ir_support(iommu->ecap)) + continue; + + if (setup_intr_remapping(iommu, eim)) + goto error; + + setup = 1; + } + + if (!setup) + goto error; + + intr_remapping_enabled = 1; + + return 0; + +error: + /* + * handle error condition gracefully here! + */ + return -1; +} + +static int ir_parse_ioapic_scope(struct acpi_dmar_header *header, + struct intel_iommu *iommu) +{ + struct acpi_dmar_hardware_unit *drhd; + struct acpi_dmar_device_scope *scope; + void *start, *end; + + drhd = (struct acpi_dmar_hardware_unit *)header; + + start = (void *)(drhd + 1); + end = ((void *)drhd) + header->length; + + while (start < end) { + scope = start; + if (scope->entry_type == ACPI_DMAR_SCOPE_TYPE_IOAPIC) { + if (ir_ioapic_num == MAX_IO_APICS) { + printk(KERN_WARNING "Exceeded Max IO APICS\n"); + return -1; + } + + printk(KERN_INFO "IOAPIC id %d under DRHD base" + " 0x%Lx\n", scope->enumeration_id, + drhd->address); + + ir_ioapic[ir_ioapic_num].iommu = iommu; + ir_ioapic[ir_ioapic_num].id = scope->enumeration_id; + ir_ioapic_num++; + } + start += scope->length; + } + + return 0; +} + +/* + * Finds the assocaition between IOAPIC's and its Interrupt-remapping + * hardware unit. + */ +int __init parse_ioapics_under_ir(void) +{ + struct dmar_drhd_unit *drhd; + int ir_supported = 0; + + for_each_drhd_unit(drhd) { + struct intel_iommu *iommu = drhd->iommu; + + if (ecap_ir_support(iommu->ecap)) { + if (ir_parse_ioapic_scope(drhd->hdr, iommu)) + return -1; + + ir_supported = 1; + } + } + + if (ir_supported && ir_ioapic_num != nr_ioapics) { + printk(KERN_WARNING + "Not all IO-APIC's listed under remapping hardware\n"); + return -1; + } + + return ir_supported; +} diff --git a/drivers/pci/intr_remapping.h b/drivers/pci/intr_remapping.h new file mode 100644 index 0000000..ca48f0d --- /dev/null +++ b/drivers/pci/intr_remapping.h @@ -0,0 +1,8 @@ +#include <linux/intel-iommu.h> + +struct ioapic_scope { + struct intel_iommu *iommu; + unsigned int id; +}; + +#define IR_X2APIC_MODE(mode) (mode ? (1 << 11) : 0) diff --git a/drivers/pci/iova.c b/drivers/pci/iova.c new file mode 100644 index 0000000..2287116 --- /dev/null +++ b/drivers/pci/iova.c @@ -0,0 +1,417 @@ +/* + * Copyright (c) 2006, Intel Corporation. + * + * This file is released under the GPLv2. + * + * Copyright (C) 2006-2008 Intel Corporation + * Author: Anil S Keshavamurthy <anil.s.keshavamurthy@intel.com> + */ + +#include <linux/iova.h> + +void +init_iova_domain(struct iova_domain *iovad, unsigned long pfn_32bit) +{ + spin_lock_init(&iovad->iova_alloc_lock); + spin_lock_init(&iovad->iova_rbtree_lock); + iovad->rbroot = RB_ROOT; + iovad->cached32_node = NULL; + iovad->dma_32bit_pfn = pfn_32bit; +} + +static struct rb_node * +__get_cached_rbnode(struct iova_domain *iovad, unsigned long *limit_pfn) +{ + if ((*limit_pfn != iovad->dma_32bit_pfn) || + (iovad->cached32_node == NULL)) + return rb_last(&iovad->rbroot); + else { + struct rb_node *prev_node = rb_prev(iovad->cached32_node); + struct iova *curr_iova = + container_of(iovad->cached32_node, struct iova, node); + *limit_pfn = curr_iova->pfn_lo - 1; + return prev_node; + } +} + +static void +__cached_rbnode_insert_update(struct iova_domain *iovad, + unsigned long limit_pfn, struct iova *new) +{ + if (limit_pfn != iovad->dma_32bit_pfn) + return; + iovad->cached32_node = &new->node; +} + +static void +__cached_rbnode_delete_update(struct iova_domain *iovad, struct iova *free) +{ + struct iova *cached_iova; + struct rb_node *curr; + + if (!iovad->cached32_node) + return; + curr = iovad->cached32_node; + cached_iova = container_of(curr, struct iova, node); + + if (free->pfn_lo >= cached_iova->pfn_lo) + iovad->cached32_node = rb_next(&free->node); +} + +/* Computes the padding size required, to make the + * the start address naturally aligned on its size + */ +static int +iova_get_pad_size(int size, unsigned int limit_pfn) +{ + unsigned int pad_size = 0; + unsigned int order = ilog2(size); + + if (order) + pad_size = (limit_pfn + 1) % (1 << order); + + return pad_size; +} + +static int __alloc_and_insert_iova_range(struct iova_domain *iovad, + unsigned long size, unsigned long limit_pfn, + struct iova *new, bool size_aligned) +{ + struct rb_node *prev, *curr = NULL; + unsigned long flags; + unsigned long saved_pfn; + unsigned int pad_size = 0; + + /* Walk the tree backwards */ + spin_lock_irqsave(&iovad->iova_rbtree_lock, flags); + saved_pfn = limit_pfn; + curr = __get_cached_rbnode(iovad, &limit_pfn); + prev = curr; + while (curr) { + struct iova *curr_iova = container_of(curr, struct iova, node); + + if (limit_pfn < curr_iova->pfn_lo) + goto move_left; + else if (limit_pfn < curr_iova->pfn_hi) + goto adjust_limit_pfn; + else { + if (size_aligned) + pad_size = iova_get_pad_size(size, limit_pfn); + if ((curr_iova->pfn_hi + size + pad_size) <= limit_pfn) + break; /* found a free slot */ + } +adjust_limit_pfn: + limit_pfn = curr_iova->pfn_lo - 1; +move_left: + prev = curr; + curr = rb_prev(curr); + } + + if (!curr) { + if (size_aligned) + pad_size = iova_get_pad_size(size, limit_pfn); + if ((IOVA_START_PFN + size + pad_size) > limit_pfn) { + spin_unlock_irqrestore(&iovad->iova_rbtree_lock, flags); + return -ENOMEM; + } + } + + /* pfn_lo will point to size aligned address if size_aligned is set */ + new->pfn_lo = limit_pfn - (size + pad_size) + 1; + new->pfn_hi = new->pfn_lo + size - 1; + + /* Insert the new_iova into domain rbtree by holding writer lock */ + /* Add new node and rebalance tree. */ + { + struct rb_node **entry = &((prev)), *parent = NULL; + /* Figure out where to put new node */ + while (*entry) { + struct iova *this = container_of(*entry, + struct iova, node); + parent = *entry; + + if (new->pfn_lo < this->pfn_lo) + entry = &((*entry)->rb_left); + else if (new->pfn_lo > this->pfn_lo) + entry = &((*entry)->rb_right); + else + BUG(); /* this should not happen */ + } + + /* Add new node and rebalance tree. */ + rb_link_node(&new->node, parent, entry); + rb_insert_color(&new->node, &iovad->rbroot); + } + __cached_rbnode_insert_update(iovad, saved_pfn, new); + + spin_unlock_irqrestore(&iovad->iova_rbtree_lock, flags); + + + return 0; +} + +static void +iova_insert_rbtree(struct rb_root *root, struct iova *iova) +{ + struct rb_node **new = &(root->rb_node), *parent = NULL; + /* Figure out where to put new node */ + while (*new) { + struct iova *this = container_of(*new, struct iova, node); + parent = *new; + + if (iova->pfn_lo < this->pfn_lo) + new = &((*new)->rb_left); + else if (iova->pfn_lo > this->pfn_lo) + new = &((*new)->rb_right); + else + BUG(); /* this should not happen */ + } + /* Add new node and rebalance tree. */ + rb_link_node(&iova->node, parent, new); + rb_insert_color(&iova->node, root); +} + +/** + * alloc_iova - allocates an iova + * @iovad - iova domain in question + * @size - size of page frames to allocate + * @limit_pfn - max limit address + * @size_aligned - set if size_aligned address range is required + * This function allocates an iova in the range limit_pfn to IOVA_START_PFN + * looking from limit_pfn instead from IOVA_START_PFN. If the size_aligned + * flag is set then the allocated address iova->pfn_lo will be naturally + * aligned on roundup_power_of_two(size). + */ +struct iova * +alloc_iova(struct iova_domain *iovad, unsigned long size, + unsigned long limit_pfn, + bool size_aligned) +{ + unsigned long flags; + struct iova *new_iova; + int ret; + + new_iova = alloc_iova_mem(); + if (!new_iova) + return NULL; + + /* If size aligned is set then round the size to + * to next power of two. + */ + if (size_aligned) + size = __roundup_pow_of_two(size); + + spin_lock_irqsave(&iovad->iova_alloc_lock, flags); + ret = __alloc_and_insert_iova_range(iovad, size, limit_pfn, + new_iova, size_aligned); + + spin_unlock_irqrestore(&iovad->iova_alloc_lock, flags); + if (ret) { + free_iova_mem(new_iova); + return NULL; + } + + return new_iova; +} + +/** + * find_iova - find's an iova for a given pfn + * @iovad - iova domain in question. + * pfn - page frame number + * This function finds and returns an iova belonging to the + * given doamin which matches the given pfn. + */ +struct iova *find_iova(struct iova_domain *iovad, unsigned long pfn) +{ + unsigned long flags; + struct rb_node *node; + + /* Take the lock so that no other thread is manipulating the rbtree */ + spin_lock_irqsave(&iovad->iova_rbtree_lock, flags); + node = iovad->rbroot.rb_node; + while (node) { + struct iova *iova = container_of(node, struct iova, node); + + /* If pfn falls within iova's range, return iova */ + if ((pfn >= iova->pfn_lo) && (pfn <= iova->pfn_hi)) { + spin_unlock_irqrestore(&iovad->iova_rbtree_lock, flags); + /* We are not holding the lock while this iova + * is referenced by the caller as the same thread + * which called this function also calls __free_iova() + * and it is by desing that only one thread can possibly + * reference a particular iova and hence no conflict. + */ + return iova; + } + + if (pfn < iova->pfn_lo) + node = node->rb_left; + else if (pfn > iova->pfn_lo) + node = node->rb_right; + } + + spin_unlock_irqrestore(&iovad->iova_rbtree_lock, flags); + return NULL; +} + +/** + * __free_iova - frees the given iova + * @iovad: iova domain in question. + * @iova: iova in question. + * Frees the given iova belonging to the giving domain + */ +void +__free_iova(struct iova_domain *iovad, struct iova *iova) +{ + unsigned long flags; + + spin_lock_irqsave(&iovad->iova_rbtree_lock, flags); + __cached_rbnode_delete_update(iovad, iova); + rb_erase(&iova->node, &iovad->rbroot); + spin_unlock_irqrestore(&iovad->iova_rbtree_lock, flags); + free_iova_mem(iova); +} + +/** + * free_iova - finds and frees the iova for a given pfn + * @iovad: - iova domain in question. + * @pfn: - pfn that is allocated previously + * This functions finds an iova for a given pfn and then + * frees the iova from that domain. + */ +void +free_iova(struct iova_domain *iovad, unsigned long pfn) +{ + struct iova *iova = find_iova(iovad, pfn); + if (iova) + __free_iova(iovad, iova); + +} + +/** + * put_iova_domain - destroys the iova doamin + * @iovad: - iova domain in question. + * All the iova's in that domain are destroyed. + */ +void put_iova_domain(struct iova_domain *iovad) +{ + struct rb_node *node; + unsigned long flags; + + spin_lock_irqsave(&iovad->iova_rbtree_lock, flags); + node = rb_first(&iovad->rbroot); + while (node) { + struct iova *iova = container_of(node, struct iova, node); + rb_erase(node, &iovad->rbroot); + free_iova_mem(iova); + node = rb_first(&iovad->rbroot); + } + spin_unlock_irqrestore(&iovad->iova_rbtree_lock, flags); +} + +static int +__is_range_overlap(struct rb_node *node, + unsigned long pfn_lo, unsigned long pfn_hi) +{ + struct iova *iova = container_of(node, struct iova, node); + + if ((pfn_lo <= iova->pfn_hi) && (pfn_hi >= iova->pfn_lo)) + return 1; + return 0; +} + +static struct iova * +__insert_new_range(struct iova_domain *iovad, + unsigned long pfn_lo, unsigned long pfn_hi) +{ + struct iova *iova; + + iova = alloc_iova_mem(); + if (!iova) + return iova; + + iova->pfn_hi = pfn_hi; + iova->pfn_lo = pfn_lo; + iova_insert_rbtree(&iovad->rbroot, iova); + return iova; +} + +static void +__adjust_overlap_range(struct iova *iova, + unsigned long *pfn_lo, unsigned long *pfn_hi) +{ + if (*pfn_lo < iova->pfn_lo) + iova->pfn_lo = *pfn_lo; + if (*pfn_hi > iova->pfn_hi) + *pfn_lo = iova->pfn_hi + 1; +} + +/** + * reserve_iova - reserves an iova in the given range + * @iovad: - iova domain pointer + * @pfn_lo: - lower page frame address + * @pfn_hi:- higher pfn adderss + * This function allocates reserves the address range from pfn_lo to pfn_hi so + * that this address is not dished out as part of alloc_iova. + */ +struct iova * +reserve_iova(struct iova_domain *iovad, + unsigned long pfn_lo, unsigned long pfn_hi) +{ + struct rb_node *node; + unsigned long flags; + struct iova *iova; + unsigned int overlap = 0; + + spin_lock_irqsave(&iovad->iova_alloc_lock, flags); + spin_lock(&iovad->iova_rbtree_lock); + for (node = rb_first(&iovad->rbroot); node; node = rb_next(node)) { + if (__is_range_overlap(node, pfn_lo, pfn_hi)) { + iova = container_of(node, struct iova, node); + __adjust_overlap_range(iova, &pfn_lo, &pfn_hi); + if ((pfn_lo >= iova->pfn_lo) && + (pfn_hi <= iova->pfn_hi)) + goto finish; + overlap = 1; + + } else if (overlap) + break; + } + + /* We are here either becasue this is the first reserver node + * or need to insert remaining non overlap addr range + */ + iova = __insert_new_range(iovad, pfn_lo, pfn_hi); +finish: + + spin_unlock(&iovad->iova_rbtree_lock); + spin_unlock_irqrestore(&iovad->iova_alloc_lock, flags); + return iova; +} + +/** + * copy_reserved_iova - copies the reserved between domains + * @from: - source doamin from where to copy + * @to: - destination domin where to copy + * This function copies reserved iova's from one doamin to + * other. + */ +void +copy_reserved_iova(struct iova_domain *from, struct iova_domain *to) +{ + unsigned long flags; + struct rb_node *node; + + spin_lock_irqsave(&from->iova_alloc_lock, flags); + spin_lock(&from->iova_rbtree_lock); + for (node = rb_first(&from->rbroot); node; node = rb_next(node)) { + struct iova *iova = container_of(node, struct iova, node); + struct iova *new_iova; + new_iova = reserve_iova(to, iova->pfn_lo, iova->pfn_hi); + if (!new_iova) + printk(KERN_ERR "Reserve iova range %lx@%lx failed\n", + iova->pfn_lo, iova->pfn_lo); + } + spin_unlock(&from->iova_rbtree_lock); + spin_unlock_irqrestore(&from->iova_alloc_lock, flags); +} diff --git a/drivers/pci/irq.c b/drivers/pci/irq.c new file mode 100644 index 0000000..6441dfa --- /dev/null +++ b/drivers/pci/irq.c @@ -0,0 +1,60 @@ +/* + * PCI IRQ failure handing code + * + * Copyright (c) 2008 James Bottomley <James.Bottomley@HansenPartnership.com> + */ + +#include <linux/acpi.h> +#include <linux/device.h> +#include <linux/kernel.h> +#include <linux/pci.h> + +static void pci_note_irq_problem(struct pci_dev *pdev, const char *reason) +{ + struct pci_dev *parent = to_pci_dev(pdev->dev.parent); + + dev_printk(KERN_ERR, &pdev->dev, + "Potentially misrouted IRQ (Bridge %s %04x:%04x)\n", + parent->dev.bus_id, parent->vendor, parent->device); + dev_printk(KERN_ERR, &pdev->dev, "%s\n", reason); + dev_printk(KERN_ERR, &pdev->dev, "Please report to linux-kernel@vger.kernel.org\n"); + WARN_ON(1); +} + +/** + * pci_lost_interrupt - reports a lost PCI interrupt + * @pdev: device whose interrupt is lost + * + * The primary function of this routine is to report a lost interrupt + * in a standard way which users can recognise (instead of blaming the + * driver). + * + * Returns: + * a suggestion for fixing it (although the driver is not required to + * act on this). + */ +enum pci_lost_interrupt_reason pci_lost_interrupt(struct pci_dev *pdev) +{ + if (pdev->msi_enabled || pdev->msix_enabled) { + enum pci_lost_interrupt_reason ret; + + if (pdev->msix_enabled) { + pci_note_irq_problem(pdev, "MSIX routing failure"); + ret = PCI_LOST_IRQ_DISABLE_MSIX; + } else { + pci_note_irq_problem(pdev, "MSI routing failure"); + ret = PCI_LOST_IRQ_DISABLE_MSI; + } + return ret; + } +#ifdef CONFIG_ACPI + if (!(acpi_disabled || acpi_noirq)) { + pci_note_irq_problem(pdev, "Potential ACPI misrouting please reboot with acpi=noirq"); + /* currently no way to fix acpi on the fly */ + return PCI_LOST_IRQ_DISABLE_ACPI; + } +#endif + pci_note_irq_problem(pdev, "unknown cause (not MSI or ACPI)"); + return PCI_LOST_IRQ_NO_INFORMATION; +} +EXPORT_SYMBOL(pci_lost_interrupt); diff --git a/drivers/pci/msi.c b/drivers/pci/msi.c new file mode 100644 index 0000000..5aee7ee --- /dev/null +++ b/drivers/pci/msi.c @@ -0,0 +1,780 @@ +/* + * File: msi.c + * Purpose: PCI Message Signaled Interrupt (MSI) + * + * Copyright (C) 2003-2004 Intel + * Copyright (C) Tom Long Nguyen (tom.l.nguyen@intel.com) + */ + +#include <linux/err.h> +#include <linux/mm.h> +#include <linux/irq.h> +#include <linux/interrupt.h> +#include <linux/init.h> +#include <linux/ioport.h> +#include <linux/pci.h> +#include <linux/proc_fs.h> +#include <linux/msi.h> +#include <linux/smp.h> + +#include <asm/errno.h> +#include <asm/io.h> + +#include "pci.h" +#include "msi.h" + +static int pci_msi_enable = 1; + +/* Arch hooks */ + +int __attribute__ ((weak)) +arch_msi_check_device(struct pci_dev *dev, int nvec, int type) +{ + return 0; +} + +int __attribute__ ((weak)) +arch_setup_msi_irq(struct pci_dev *dev, struct msi_desc *entry) +{ + return 0; +} + +int __attribute__ ((weak)) +arch_setup_msi_irqs(struct pci_dev *dev, int nvec, int type) +{ + struct msi_desc *entry; + int ret; + + list_for_each_entry(entry, &dev->msi_list, list) { + ret = arch_setup_msi_irq(dev, entry); + if (ret) + return ret; + } + + return 0; +} + +void __attribute__ ((weak)) arch_teardown_msi_irq(unsigned int irq) +{ + return; +} + +void __attribute__ ((weak)) +arch_teardown_msi_irqs(struct pci_dev *dev) +{ + struct msi_desc *entry; + + list_for_each_entry(entry, &dev->msi_list, list) { + if (entry->irq != 0) + arch_teardown_msi_irq(entry->irq); + } +} + +static void __msi_set_enable(struct pci_dev *dev, int pos, int enable) +{ + u16 control; + + if (pos) { + pci_read_config_word(dev, pos + PCI_MSI_FLAGS, &control); + control &= ~PCI_MSI_FLAGS_ENABLE; + if (enable) + control |= PCI_MSI_FLAGS_ENABLE; + pci_write_config_word(dev, pos + PCI_MSI_FLAGS, control); + } +} + +static void msi_set_enable(struct pci_dev *dev, int enable) +{ + __msi_set_enable(dev, pci_find_capability(dev, PCI_CAP_ID_MSI), enable); +} + +static void msix_set_enable(struct pci_dev *dev, int enable) +{ + int pos; + u16 control; + + pos = pci_find_capability(dev, PCI_CAP_ID_MSIX); + if (pos) { + pci_read_config_word(dev, pos + PCI_MSIX_FLAGS, &control); + control &= ~PCI_MSIX_FLAGS_ENABLE; + if (enable) + control |= PCI_MSIX_FLAGS_ENABLE; + pci_write_config_word(dev, pos + PCI_MSIX_FLAGS, control); + } +} + +static void msix_flush_writes(unsigned int irq) +{ + struct msi_desc *entry; + + entry = get_irq_msi(irq); + BUG_ON(!entry || !entry->dev); + switch (entry->msi_attrib.type) { + case PCI_CAP_ID_MSI: + /* nothing to do */ + break; + case PCI_CAP_ID_MSIX: + { + int offset = entry->msi_attrib.entry_nr * PCI_MSIX_ENTRY_SIZE + + PCI_MSIX_ENTRY_VECTOR_CTRL_OFFSET; + readl(entry->mask_base + offset); + break; + } + default: + BUG(); + break; + } +} + +/* + * PCI 2.3 does not specify mask bits for each MSI interrupt. Attempting to + * mask all MSI interrupts by clearing the MSI enable bit does not work + * reliably as devices without an INTx disable bit will then generate a + * level IRQ which will never be cleared. + * + * Returns 1 if it succeeded in masking the interrupt and 0 if the device + * doesn't support MSI masking. + */ +static int msi_set_mask_bits(unsigned int irq, u32 mask, u32 flag) +{ + struct msi_desc *entry; + + entry = get_irq_msi(irq); + BUG_ON(!entry || !entry->dev); + switch (entry->msi_attrib.type) { + case PCI_CAP_ID_MSI: + if (entry->msi_attrib.maskbit) { + int pos; + u32 mask_bits; + + pos = (long)entry->mask_base; + pci_read_config_dword(entry->dev, pos, &mask_bits); + mask_bits &= ~(mask); + mask_bits |= flag & mask; + pci_write_config_dword(entry->dev, pos, mask_bits); + } else { + return 0; + } + break; + case PCI_CAP_ID_MSIX: + { + int offset = entry->msi_attrib.entry_nr * PCI_MSIX_ENTRY_SIZE + + PCI_MSIX_ENTRY_VECTOR_CTRL_OFFSET; + writel(flag, entry->mask_base + offset); + readl(entry->mask_base + offset); + break; + } + default: + BUG(); + break; + } + entry->msi_attrib.masked = !!flag; + return 1; +} + +void read_msi_msg(unsigned int irq, struct msi_msg *msg) +{ + struct msi_desc *entry = get_irq_msi(irq); + switch(entry->msi_attrib.type) { + case PCI_CAP_ID_MSI: + { + struct pci_dev *dev = entry->dev; + int pos = entry->msi_attrib.pos; + u16 data; + + pci_read_config_dword(dev, msi_lower_address_reg(pos), + &msg->address_lo); + if (entry->msi_attrib.is_64) { + pci_read_config_dword(dev, msi_upper_address_reg(pos), + &msg->address_hi); + pci_read_config_word(dev, msi_data_reg(pos, 1), &data); + } else { + msg->address_hi = 0; + pci_read_config_word(dev, msi_data_reg(pos, 0), &data); + } + msg->data = data; + break; + } + case PCI_CAP_ID_MSIX: + { + void __iomem *base; + base = entry->mask_base + + entry->msi_attrib.entry_nr * PCI_MSIX_ENTRY_SIZE; + + msg->address_lo = readl(base + PCI_MSIX_ENTRY_LOWER_ADDR_OFFSET); + msg->address_hi = readl(base + PCI_MSIX_ENTRY_UPPER_ADDR_OFFSET); + msg->data = readl(base + PCI_MSIX_ENTRY_DATA_OFFSET); + break; + } + default: + BUG(); + } +} + +void write_msi_msg(unsigned int irq, struct msi_msg *msg) +{ + struct msi_desc *entry = get_irq_msi(irq); + switch (entry->msi_attrib.type) { + case PCI_CAP_ID_MSI: + { + struct pci_dev *dev = entry->dev; + int pos = entry->msi_attrib.pos; + + pci_write_config_dword(dev, msi_lower_address_reg(pos), + msg->address_lo); + if (entry->msi_attrib.is_64) { + pci_write_config_dword(dev, msi_upper_address_reg(pos), + msg->address_hi); + pci_write_config_word(dev, msi_data_reg(pos, 1), + msg->data); + } else { + pci_write_config_word(dev, msi_data_reg(pos, 0), + msg->data); + } + break; + } + case PCI_CAP_ID_MSIX: + { + void __iomem *base; + base = entry->mask_base + + entry->msi_attrib.entry_nr * PCI_MSIX_ENTRY_SIZE; + + writel(msg->address_lo, + base + PCI_MSIX_ENTRY_LOWER_ADDR_OFFSET); + writel(msg->address_hi, + base + PCI_MSIX_ENTRY_UPPER_ADDR_OFFSET); + writel(msg->data, base + PCI_MSIX_ENTRY_DATA_OFFSET); + break; + } + default: + BUG(); + } + entry->msg = *msg; +} + +void mask_msi_irq(unsigned int irq) +{ + msi_set_mask_bits(irq, 1, 1); + msix_flush_writes(irq); +} + +void unmask_msi_irq(unsigned int irq) +{ + msi_set_mask_bits(irq, 1, 0); + msix_flush_writes(irq); +} + +static int msi_free_irqs(struct pci_dev* dev); + + +static struct msi_desc* alloc_msi_entry(void) +{ + struct msi_desc *entry; + + entry = kzalloc(sizeof(struct msi_desc), GFP_KERNEL); + if (!entry) + return NULL; + + INIT_LIST_HEAD(&entry->list); + entry->irq = 0; + entry->dev = NULL; + + return entry; +} + +static void pci_intx_for_msi(struct pci_dev *dev, int enable) +{ + if (!(dev->dev_flags & PCI_DEV_FLAGS_MSI_INTX_DISABLE_BUG)) + pci_intx(dev, enable); +} + +static void __pci_restore_msi_state(struct pci_dev *dev) +{ + int pos; + u16 control; + struct msi_desc *entry; + + if (!dev->msi_enabled) + return; + + entry = get_irq_msi(dev->irq); + pos = entry->msi_attrib.pos; + + pci_intx_for_msi(dev, 0); + msi_set_enable(dev, 0); + write_msi_msg(dev->irq, &entry->msg); + if (entry->msi_attrib.maskbit) + msi_set_mask_bits(dev->irq, entry->msi_attrib.maskbits_mask, + entry->msi_attrib.masked); + + pci_read_config_word(dev, pos + PCI_MSI_FLAGS, &control); + control &= ~PCI_MSI_FLAGS_QSIZE; + control |= PCI_MSI_FLAGS_ENABLE; + pci_write_config_word(dev, pos + PCI_MSI_FLAGS, control); +} + +static void __pci_restore_msix_state(struct pci_dev *dev) +{ + int pos; + struct msi_desc *entry; + u16 control; + + if (!dev->msix_enabled) + return; + + /* route the table */ + pci_intx_for_msi(dev, 0); + msix_set_enable(dev, 0); + + list_for_each_entry(entry, &dev->msi_list, list) { + write_msi_msg(entry->irq, &entry->msg); + msi_set_mask_bits(entry->irq, 1, entry->msi_attrib.masked); + } + + BUG_ON(list_empty(&dev->msi_list)); + entry = list_entry(dev->msi_list.next, struct msi_desc, list); + pos = entry->msi_attrib.pos; + pci_read_config_word(dev, pos + PCI_MSIX_FLAGS, &control); + control &= ~PCI_MSIX_FLAGS_MASKALL; + control |= PCI_MSIX_FLAGS_ENABLE; + pci_write_config_word(dev, pos + PCI_MSIX_FLAGS, control); +} + +void pci_restore_msi_state(struct pci_dev *dev) +{ + __pci_restore_msi_state(dev); + __pci_restore_msix_state(dev); +} +EXPORT_SYMBOL_GPL(pci_restore_msi_state); + +/** + * msi_capability_init - configure device's MSI capability structure + * @dev: pointer to the pci_dev data structure of MSI device function + * + * Setup the MSI capability structure of device function with a single + * MSI irq, regardless of device function is capable of handling + * multiple messages. A return of zero indicates the successful setup + * of an entry zero with the new MSI irq or non-zero for otherwise. + **/ +static int msi_capability_init(struct pci_dev *dev) +{ + struct msi_desc *entry; + int pos, ret; + u16 control; + + msi_set_enable(dev, 0); /* Ensure msi is disabled as I set it up */ + + pos = pci_find_capability(dev, PCI_CAP_ID_MSI); + pci_read_config_word(dev, msi_control_reg(pos), &control); + /* MSI Entry Initialization */ + entry = alloc_msi_entry(); + if (!entry) + return -ENOMEM; + + entry->msi_attrib.type = PCI_CAP_ID_MSI; + entry->msi_attrib.is_64 = is_64bit_address(control); + entry->msi_attrib.entry_nr = 0; + entry->msi_attrib.maskbit = is_mask_bit_support(control); + entry->msi_attrib.masked = 1; + entry->msi_attrib.default_irq = dev->irq; /* Save IOAPIC IRQ */ + entry->msi_attrib.pos = pos; + entry->dev = dev; + if (entry->msi_attrib.maskbit) { + unsigned int base, maskbits, temp; + + base = msi_mask_bits_reg(pos, entry->msi_attrib.is_64); + entry->mask_base = (void __iomem *)(long)base; + + /* All MSIs are unmasked by default, Mask them all */ + pci_read_config_dword(dev, base, &maskbits); + temp = (1 << multi_msi_capable(control)); + temp = ((temp - 1) & ~temp); + maskbits |= temp; + pci_write_config_dword(dev, base, maskbits); + entry->msi_attrib.maskbits_mask = temp; + } + list_add_tail(&entry->list, &dev->msi_list); + + /* Configure MSI capability structure */ + ret = arch_setup_msi_irqs(dev, 1, PCI_CAP_ID_MSI); + if (ret) { + msi_free_irqs(dev); + return ret; + } + + /* Set MSI enabled bits */ + pci_intx_for_msi(dev, 0); + msi_set_enable(dev, 1); + dev->msi_enabled = 1; + + dev->irq = entry->irq; + return 0; +} + +/** + * msix_capability_init - configure device's MSI-X capability + * @dev: pointer to the pci_dev data structure of MSI-X device function + * @entries: pointer to an array of struct msix_entry entries + * @nvec: number of @entries + * + * Setup the MSI-X capability structure of device function with a + * single MSI-X irq. A return of zero indicates the successful setup of + * requested MSI-X entries with allocated irqs or non-zero for otherwise. + **/ +static int msix_capability_init(struct pci_dev *dev, + struct msix_entry *entries, int nvec) +{ + struct msi_desc *entry; + int pos, i, j, nr_entries, ret; + unsigned long phys_addr; + u32 table_offset; + u16 control; + u8 bir; + void __iomem *base; + + msix_set_enable(dev, 0);/* Ensure msix is disabled as I set it up */ + + pos = pci_find_capability(dev, PCI_CAP_ID_MSIX); + /* Request & Map MSI-X table region */ + pci_read_config_word(dev, msi_control_reg(pos), &control); + nr_entries = multi_msix_capable(control); + + pci_read_config_dword(dev, msix_table_offset_reg(pos), &table_offset); + bir = (u8)(table_offset & PCI_MSIX_FLAGS_BIRMASK); + table_offset &= ~PCI_MSIX_FLAGS_BIRMASK; + phys_addr = pci_resource_start (dev, bir) + table_offset; + base = ioremap_nocache(phys_addr, nr_entries * PCI_MSIX_ENTRY_SIZE); + if (base == NULL) + return -ENOMEM; + + /* MSI-X Table Initialization */ + for (i = 0; i < nvec; i++) { + entry = alloc_msi_entry(); + if (!entry) + break; + + j = entries[i].entry; + entry->msi_attrib.type = PCI_CAP_ID_MSIX; + entry->msi_attrib.is_64 = 1; + entry->msi_attrib.entry_nr = j; + entry->msi_attrib.maskbit = 1; + entry->msi_attrib.masked = 1; + entry->msi_attrib.default_irq = dev->irq; + entry->msi_attrib.pos = pos; + entry->dev = dev; + entry->mask_base = base; + + list_add_tail(&entry->list, &dev->msi_list); + } + + ret = arch_setup_msi_irqs(dev, nvec, PCI_CAP_ID_MSIX); + if (ret) { + int avail = 0; + list_for_each_entry(entry, &dev->msi_list, list) { + if (entry->irq != 0) { + avail++; + } + } + + msi_free_irqs(dev); + + /* If we had some success report the number of irqs + * we succeeded in setting up. + */ + if (avail == 0) + avail = ret; + return avail; + } + + i = 0; + list_for_each_entry(entry, &dev->msi_list, list) { + entries[i].vector = entry->irq; + set_irq_msi(entry->irq, entry); + i++; + } + /* Set MSI-X enabled bits */ + pci_intx_for_msi(dev, 0); + msix_set_enable(dev, 1); + dev->msix_enabled = 1; + + return 0; +} + +/** + * pci_msi_check_device - check whether MSI may be enabled on a device + * @dev: pointer to the pci_dev data structure of MSI device function + * @nvec: how many MSIs have been requested ? + * @type: are we checking for MSI or MSI-X ? + * + * Look at global flags, the device itself, and its parent busses + * to determine if MSI/-X are supported for the device. If MSI/-X is + * supported return 0, else return an error code. + **/ +static int pci_msi_check_device(struct pci_dev* dev, int nvec, int type) +{ + struct pci_bus *bus; + int ret; + + /* MSI must be globally enabled and supported by the device */ + if (!pci_msi_enable || !dev || dev->no_msi) + return -EINVAL; + + /* + * You can't ask to have 0 or less MSIs configured. + * a) it's stupid .. + * b) the list manipulation code assumes nvec >= 1. + */ + if (nvec < 1) + return -ERANGE; + + /* Any bridge which does NOT route MSI transactions from it's + * secondary bus to it's primary bus must set NO_MSI flag on + * the secondary pci_bus. + * We expect only arch-specific PCI host bus controller driver + * or quirks for specific PCI bridges to be setting NO_MSI. + */ + for (bus = dev->bus; bus; bus = bus->parent) + if (bus->bus_flags & PCI_BUS_FLAGS_NO_MSI) + return -EINVAL; + + ret = arch_msi_check_device(dev, nvec, type); + if (ret) + return ret; + + if (!pci_find_capability(dev, type)) + return -EINVAL; + + return 0; +} + +/** + * pci_enable_msi - configure device's MSI capability structure + * @dev: pointer to the pci_dev data structure of MSI device function + * + * Setup the MSI capability structure of device function with + * a single MSI irq upon its software driver call to request for + * MSI mode enabled on its hardware device function. A return of zero + * indicates the successful setup of an entry zero with the new MSI + * irq or non-zero for otherwise. + **/ +int pci_enable_msi(struct pci_dev* dev) +{ + int status; + + status = pci_msi_check_device(dev, 1, PCI_CAP_ID_MSI); + if (status) + return status; + + WARN_ON(!!dev->msi_enabled); + + /* Check whether driver already requested for MSI-X irqs */ + if (dev->msix_enabled) { + dev_info(&dev->dev, "can't enable MSI " + "(MSI-X already enabled)\n"); + return -EINVAL; + } + status = msi_capability_init(dev); + return status; +} +EXPORT_SYMBOL(pci_enable_msi); + +void pci_msi_shutdown(struct pci_dev* dev) +{ + struct msi_desc *entry; + + if (!pci_msi_enable || !dev || !dev->msi_enabled) + return; + + msi_set_enable(dev, 0); + pci_intx_for_msi(dev, 1); + dev->msi_enabled = 0; + + BUG_ON(list_empty(&dev->msi_list)); + entry = list_entry(dev->msi_list.next, struct msi_desc, list); + /* Return the the pci reset with msi irqs unmasked */ + if (entry->msi_attrib.maskbit) { + u32 mask = entry->msi_attrib.maskbits_mask; + msi_set_mask_bits(dev->irq, mask, ~mask); + } + if (!entry->dev || entry->msi_attrib.type != PCI_CAP_ID_MSI) + return; + + /* Restore dev->irq to its default pin-assertion irq */ + dev->irq = entry->msi_attrib.default_irq; +} +void pci_disable_msi(struct pci_dev* dev) +{ + struct msi_desc *entry; + + if (!pci_msi_enable || !dev || !dev->msi_enabled) + return; + + pci_msi_shutdown(dev); + + entry = list_entry(dev->msi_list.next, struct msi_desc, list); + if (!entry->dev || entry->msi_attrib.type != PCI_CAP_ID_MSI) + return; + + msi_free_irqs(dev); +} +EXPORT_SYMBOL(pci_disable_msi); + +static int msi_free_irqs(struct pci_dev* dev) +{ + struct msi_desc *entry, *tmp; + + list_for_each_entry(entry, &dev->msi_list, list) { + if (entry->irq) + BUG_ON(irq_has_action(entry->irq)); + } + + arch_teardown_msi_irqs(dev); + + list_for_each_entry_safe(entry, tmp, &dev->msi_list, list) { + if (entry->msi_attrib.type == PCI_CAP_ID_MSIX) { + writel(1, entry->mask_base + entry->msi_attrib.entry_nr + * PCI_MSIX_ENTRY_SIZE + + PCI_MSIX_ENTRY_VECTOR_CTRL_OFFSET); + + if (list_is_last(&entry->list, &dev->msi_list)) + iounmap(entry->mask_base); + } + list_del(&entry->list); + kfree(entry); + } + + return 0; +} + +/** + * pci_enable_msix - configure device's MSI-X capability structure + * @dev: pointer to the pci_dev data structure of MSI-X device function + * @entries: pointer to an array of MSI-X entries + * @nvec: number of MSI-X irqs requested for allocation by device driver + * + * Setup the MSI-X capability structure of device function with the number + * of requested irqs upon its software driver call to request for + * MSI-X mode enabled on its hardware device function. A return of zero + * indicates the successful configuration of MSI-X capability structure + * with new allocated MSI-X irqs. A return of < 0 indicates a failure. + * Or a return of > 0 indicates that driver request is exceeding the number + * of irqs available. Driver should use the returned value to re-send + * its request. + **/ +int pci_enable_msix(struct pci_dev* dev, struct msix_entry *entries, int nvec) +{ + int status, pos, nr_entries; + int i, j; + u16 control; + + if (!entries) + return -EINVAL; + + status = pci_msi_check_device(dev, nvec, PCI_CAP_ID_MSIX); + if (status) + return status; + + pos = pci_find_capability(dev, PCI_CAP_ID_MSIX); + pci_read_config_word(dev, msi_control_reg(pos), &control); + nr_entries = multi_msix_capable(control); + if (nvec > nr_entries) + return -EINVAL; + + /* Check for any invalid entries */ + for (i = 0; i < nvec; i++) { + if (entries[i].entry >= nr_entries) + return -EINVAL; /* invalid entry */ + for (j = i + 1; j < nvec; j++) { + if (entries[i].entry == entries[j].entry) + return -EINVAL; /* duplicate entry */ + } + } + WARN_ON(!!dev->msix_enabled); + + /* Check whether driver already requested for MSI irq */ + if (dev->msi_enabled) { + dev_info(&dev->dev, "can't enable MSI-X " + "(MSI IRQ already assigned)\n"); + return -EINVAL; + } + status = msix_capability_init(dev, entries, nvec); + return status; +} +EXPORT_SYMBOL(pci_enable_msix); + +static void msix_free_all_irqs(struct pci_dev *dev) +{ + msi_free_irqs(dev); +} + +void pci_msix_shutdown(struct pci_dev* dev) +{ + if (!pci_msi_enable || !dev || !dev->msix_enabled) + return; + + msix_set_enable(dev, 0); + pci_intx_for_msi(dev, 1); + dev->msix_enabled = 0; +} +void pci_disable_msix(struct pci_dev* dev) +{ + if (!pci_msi_enable || !dev || !dev->msix_enabled) + return; + + pci_msix_shutdown(dev); + + msix_free_all_irqs(dev); +} +EXPORT_SYMBOL(pci_disable_msix); + +/** + * msi_remove_pci_irq_vectors - reclaim MSI(X) irqs to unused state + * @dev: pointer to the pci_dev data structure of MSI(X) device function + * + * Being called during hotplug remove, from which the device function + * is hot-removed. All previous assigned MSI/MSI-X irqs, if + * allocated for this device function, are reclaimed to unused state, + * which may be used later on. + **/ +void msi_remove_pci_irq_vectors(struct pci_dev* dev) +{ + if (!pci_msi_enable || !dev) + return; + + if (dev->msi_enabled) + msi_free_irqs(dev); + + if (dev->msix_enabled) + msix_free_all_irqs(dev); +} + +void pci_no_msi(void) +{ + pci_msi_enable = 0; +} + +void pci_msi_init_pci_dev(struct pci_dev *dev) +{ + INIT_LIST_HEAD(&dev->msi_list); +} + +#ifdef CONFIG_ACPI +#include <linux/acpi.h> +#include <linux/pci-acpi.h> +static void __devinit msi_acpi_init(void) +{ + if (acpi_pci_disabled) + return; + pci_osc_support_set(OSC_MSI_SUPPORT); + pcie_osc_support_set(OSC_MSI_SUPPORT); +} +#else +static inline void msi_acpi_init(void) { } +#endif /* CONFIG_ACPI */ + +void __devinit msi_init(void) +{ + if (!pci_msi_enable) + return; + msi_acpi_init(); +} diff --git a/drivers/pci/msi.h b/drivers/pci/msi.h new file mode 100644 index 0000000..3898f52 --- /dev/null +++ b/drivers/pci/msi.h @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2003-2004 Intel + * Copyright (C) Tom Long Nguyen (tom.l.nguyen@intel.com) + */ + +#ifndef MSI_H +#define MSI_H + +#define PCI_MSIX_ENTRY_SIZE 16 +#define PCI_MSIX_ENTRY_LOWER_ADDR_OFFSET 0 +#define PCI_MSIX_ENTRY_UPPER_ADDR_OFFSET 4 +#define PCI_MSIX_ENTRY_DATA_OFFSET 8 +#define PCI_MSIX_ENTRY_VECTOR_CTRL_OFFSET 12 + +#define msi_control_reg(base) (base + PCI_MSI_FLAGS) +#define msi_lower_address_reg(base) (base + PCI_MSI_ADDRESS_LO) +#define msi_upper_address_reg(base) (base + PCI_MSI_ADDRESS_HI) +#define msi_data_reg(base, is64bit) \ + ( (is64bit == 1) ? base+PCI_MSI_DATA_64 : base+PCI_MSI_DATA_32 ) +#define msi_mask_bits_reg(base, is64bit) \ + ( (is64bit == 1) ? base+PCI_MSI_MASK_BIT : base+PCI_MSI_MASK_BIT-4) +#define msi_disable(control) control &= ~PCI_MSI_FLAGS_ENABLE +#define multi_msi_capable(control) \ + (1 << ((control & PCI_MSI_FLAGS_QMASK) >> 1)) +#define multi_msi_enable(control, num) \ + control |= (((num >> 1) << 4) & PCI_MSI_FLAGS_QSIZE); +#define is_64bit_address(control) (!!(control & PCI_MSI_FLAGS_64BIT)) +#define is_mask_bit_support(control) (!!(control & PCI_MSI_FLAGS_MASKBIT)) +#define msi_enable(control, num) multi_msi_enable(control, num); \ + control |= PCI_MSI_FLAGS_ENABLE + +#define msix_table_offset_reg(base) (base + 0x04) +#define msix_pba_offset_reg(base) (base + 0x08) +#define msix_enable(control) control |= PCI_MSIX_FLAGS_ENABLE +#define msix_disable(control) control &= ~PCI_MSIX_FLAGS_ENABLE +#define msix_table_size(control) ((control & PCI_MSIX_FLAGS_QSIZE)+1) +#define multi_msix_capable msix_table_size +#define msix_unmask(address) (address & ~PCI_MSIX_FLAGS_BITMASK) +#define msix_mask(address) (address | PCI_MSIX_FLAGS_BITMASK) +#define msix_is_pending(address) (address & PCI_MSIX_FLAGS_PENDMASK) + +#endif /* MSI_H */ diff --git a/drivers/pci/pci-acpi.c b/drivers/pci/pci-acpi.c new file mode 100644 index 0000000..ae5ec76 --- /dev/null +++ b/drivers/pci/pci-acpi.c @@ -0,0 +1,413 @@ +/* + * File: pci-acpi.c + * Purpose: Provide PCI support in ACPI + * + * Copyright (C) 2005 David Shaohua Li <shaohua.li@intel.com> + * Copyright (C) 2004 Tom Long Nguyen <tom.l.nguyen@intel.com> + * Copyright (C) 2004 Intel Corp. + */ + +#include <linux/delay.h> +#include <linux/init.h> +#include <linux/pci.h> +#include <linux/module.h> +#include <linux/pci-aspm.h> +#include <acpi/acpi.h> +#include <acpi/acnamesp.h> +#include <acpi/acresrc.h> +#include <acpi/acpi_bus.h> + +#include <linux/pci-acpi.h> +#include "pci.h" + +struct acpi_osc_data { + acpi_handle handle; + u32 support_set; + u32 control_set; + struct list_head sibiling; +}; +static LIST_HEAD(acpi_osc_data_list); + +struct acpi_osc_args { + u32 capbuf[3]; + u32 ctrl_result; +}; + +static DEFINE_MUTEX(pci_acpi_lock); + +static struct acpi_osc_data *acpi_get_osc_data(acpi_handle handle) +{ + struct acpi_osc_data *data; + + list_for_each_entry(data, &acpi_osc_data_list, sibiling) { + if (data->handle == handle) + return data; + } + data = kzalloc(sizeof(*data), GFP_KERNEL); + if (!data) + return NULL; + INIT_LIST_HEAD(&data->sibiling); + data->handle = handle; + list_add_tail(&data->sibiling, &acpi_osc_data_list); + return data; +} + +static u8 OSC_UUID[16] = {0x5B, 0x4D, 0xDB, 0x33, 0xF7, 0x1F, 0x1C, 0x40, + 0x96, 0x57, 0x74, 0x41, 0xC0, 0x3D, 0xD7, 0x66}; + +static acpi_status acpi_run_osc(acpi_handle handle, + struct acpi_osc_args *osc_args) +{ + acpi_status status; + struct acpi_object_list input; + union acpi_object in_params[4]; + struct acpi_buffer output = {ACPI_ALLOCATE_BUFFER, NULL}; + union acpi_object *out_obj; + u32 errors, flags = osc_args->capbuf[OSC_QUERY_TYPE]; + + /* Setting up input parameters */ + input.count = 4; + input.pointer = in_params; + in_params[0].type = ACPI_TYPE_BUFFER; + in_params[0].buffer.length = 16; + in_params[0].buffer.pointer = OSC_UUID; + in_params[1].type = ACPI_TYPE_INTEGER; + in_params[1].integer.value = 1; + in_params[2].type = ACPI_TYPE_INTEGER; + in_params[2].integer.value = 3; + in_params[3].type = ACPI_TYPE_BUFFER; + in_params[3].buffer.length = 12; + in_params[3].buffer.pointer = (u8 *)osc_args->capbuf; + + status = acpi_evaluate_object(handle, "_OSC", &input, &output); + if (ACPI_FAILURE(status)) + return status; + + if (!output.length) + return AE_NULL_OBJECT; + + out_obj = output.pointer; + if (out_obj->type != ACPI_TYPE_BUFFER) { + printk(KERN_DEBUG "Evaluate _OSC returns wrong type\n"); + status = AE_TYPE; + goto out_kfree; + } + /* Need to ignore the bit0 in result code */ + errors = *((u32 *)out_obj->buffer.pointer) & ~(1 << 0); + if (errors) { + if (errors & OSC_REQUEST_ERROR) + printk(KERN_DEBUG "_OSC request fails\n"); + if (errors & OSC_INVALID_UUID_ERROR) + printk(KERN_DEBUG "_OSC invalid UUID\n"); + if (errors & OSC_INVALID_REVISION_ERROR) + printk(KERN_DEBUG "_OSC invalid revision\n"); + if (errors & OSC_CAPABILITIES_MASK_ERROR) { + if (flags & OSC_QUERY_ENABLE) + goto out_success; + printk(KERN_DEBUG "_OSC FW not grant req. control\n"); + status = AE_SUPPORT; + goto out_kfree; + } + status = AE_ERROR; + goto out_kfree; + } +out_success: + osc_args->ctrl_result = + *((u32 *)(out_obj->buffer.pointer + 8)); + status = AE_OK; + +out_kfree: + kfree(output.pointer); + return status; +} + +static acpi_status __acpi_query_osc(u32 flags, struct acpi_osc_data *osc_data, + u32 *result) +{ + acpi_status status; + u32 support_set; + struct acpi_osc_args osc_args; + + /* do _OSC query for all possible controls */ + support_set = osc_data->support_set | (flags & OSC_SUPPORT_MASKS); + osc_args.capbuf[OSC_QUERY_TYPE] = OSC_QUERY_ENABLE; + osc_args.capbuf[OSC_SUPPORT_TYPE] = support_set; + osc_args.capbuf[OSC_CONTROL_TYPE] = OSC_CONTROL_MASKS; + + status = acpi_run_osc(osc_data->handle, &osc_args); + if (ACPI_SUCCESS(status)) { + osc_data->support_set = support_set; + *result = osc_args.ctrl_result; + } + + return status; +} + +static acpi_status acpi_query_osc(acpi_handle handle, + u32 level, void *context, void **retval) +{ + acpi_status status; + struct acpi_osc_data *osc_data; + u32 flags = (unsigned long)context, dummy; + acpi_handle tmp; + + status = acpi_get_handle(handle, "_OSC", &tmp); + if (ACPI_FAILURE(status)) + return AE_OK; + + mutex_lock(&pci_acpi_lock); + osc_data = acpi_get_osc_data(handle); + if (!osc_data) { + printk(KERN_ERR "acpi osc data array is full\n"); + goto out; + } + + __acpi_query_osc(flags, osc_data, &dummy); +out: + mutex_unlock(&pci_acpi_lock); + return AE_OK; +} + +/** + * __pci_osc_support_set - register OS support to Firmware + * @flags: OS support bits + * @hid: hardware ID + * + * Update OS support fields and doing a _OSC Query to obtain an update + * from Firmware on supported control bits. + **/ +acpi_status __pci_osc_support_set(u32 flags, const char *hid) +{ + if (!(flags & OSC_SUPPORT_MASKS)) + return AE_TYPE; + + acpi_get_devices(hid, acpi_query_osc, + (void *)(unsigned long)flags, NULL); + return AE_OK; +} + +/** + * pci_osc_control_set - commit requested control to Firmware + * @handle: acpi_handle for the target ACPI object + * @flags: driver's requested control bits + * + * Attempt to take control from Firmware on requested control bits. + **/ +acpi_status pci_osc_control_set(acpi_handle handle, u32 flags) +{ + acpi_status status; + u32 ctrlset, control_set, result; + acpi_handle tmp; + struct acpi_osc_data *osc_data; + struct acpi_osc_args osc_args; + + status = acpi_get_handle(handle, "_OSC", &tmp); + if (ACPI_FAILURE(status)) + return status; + + mutex_lock(&pci_acpi_lock); + osc_data = acpi_get_osc_data(handle); + if (!osc_data) { + printk(KERN_ERR "acpi osc data array is full\n"); + status = AE_ERROR; + goto out; + } + + ctrlset = (flags & OSC_CONTROL_MASKS); + if (!ctrlset) { + status = AE_TYPE; + goto out; + } + + status = __acpi_query_osc(osc_data->support_set, osc_data, &result); + if (ACPI_FAILURE(status)) + goto out; + + if ((result & ctrlset) != ctrlset) { + status = AE_SUPPORT; + goto out; + } + + control_set = osc_data->control_set | ctrlset; + osc_args.capbuf[OSC_QUERY_TYPE] = 0; + osc_args.capbuf[OSC_SUPPORT_TYPE] = osc_data->support_set; + osc_args.capbuf[OSC_CONTROL_TYPE] = control_set; + status = acpi_run_osc(handle, &osc_args); + if (ACPI_SUCCESS(status)) + osc_data->control_set = control_set; +out: + mutex_unlock(&pci_acpi_lock); + return status; +} +EXPORT_SYMBOL(pci_osc_control_set); + +/* + * _SxD returns the D-state with the highest power + * (lowest D-state number) supported in the S-state "x". + * + * If the devices does not have a _PRW + * (Power Resources for Wake) supporting system wakeup from "x" + * then the OS is free to choose a lower power (higher number + * D-state) than the return value from _SxD. + * + * But if _PRW is enabled at S-state "x", the OS + * must not choose a power lower than _SxD -- + * unless the device has an _SxW method specifying + * the lowest power (highest D-state number) the device + * may enter while still able to wake the system. + * + * ie. depending on global OS policy: + * + * if (_PRW at S-state x) + * choose from highest power _SxD to lowest power _SxW + * else // no _PRW at S-state x + * choose highest power _SxD or any lower power + */ + +static pci_power_t acpi_pci_choose_state(struct pci_dev *pdev) +{ + int acpi_state; + + acpi_state = acpi_pm_device_sleep_state(&pdev->dev, NULL); + if (acpi_state < 0) + return PCI_POWER_ERROR; + + switch (acpi_state) { + case ACPI_STATE_D0: + return PCI_D0; + case ACPI_STATE_D1: + return PCI_D1; + case ACPI_STATE_D2: + return PCI_D2; + case ACPI_STATE_D3: + return PCI_D3hot; + } + return PCI_POWER_ERROR; +} + +static bool acpi_pci_power_manageable(struct pci_dev *dev) +{ + acpi_handle handle = DEVICE_ACPI_HANDLE(&dev->dev); + + return handle ? acpi_bus_power_manageable(handle) : false; +} + +static int acpi_pci_set_power_state(struct pci_dev *dev, pci_power_t state) +{ + acpi_handle handle = DEVICE_ACPI_HANDLE(&dev->dev); + acpi_handle tmp; + static const u8 state_conv[] = { + [PCI_D0] = ACPI_STATE_D0, + [PCI_D1] = ACPI_STATE_D1, + [PCI_D2] = ACPI_STATE_D2, + [PCI_D3hot] = ACPI_STATE_D3, + [PCI_D3cold] = ACPI_STATE_D3 + }; + int error = -EINVAL; + + /* If the ACPI device has _EJ0, ignore the device */ + if (!handle || ACPI_SUCCESS(acpi_get_handle(handle, "_EJ0", &tmp))) + return -ENODEV; + + switch (state) { + case PCI_D0: + case PCI_D1: + case PCI_D2: + case PCI_D3hot: + case PCI_D3cold: + error = acpi_bus_set_power(handle, state_conv[state]); + } + + if (!error) + dev_printk(KERN_INFO, &dev->dev, + "power state changed by ACPI to D%d\n", state); + + return error; +} + +static bool acpi_pci_can_wakeup(struct pci_dev *dev) +{ + acpi_handle handle = DEVICE_ACPI_HANDLE(&dev->dev); + + return handle ? acpi_bus_can_wakeup(handle) : false; +} + +static int acpi_pci_sleep_wake(struct pci_dev *dev, bool enable) +{ + int error = acpi_pm_device_sleep_wake(&dev->dev, enable); + + if (!error) + dev_printk(KERN_INFO, &dev->dev, + "wake-up capability %s by ACPI\n", + enable ? "enabled" : "disabled"); + return error; +} + +static struct pci_platform_pm_ops acpi_pci_platform_pm = { + .is_manageable = acpi_pci_power_manageable, + .set_state = acpi_pci_set_power_state, + .choose_state = acpi_pci_choose_state, + .can_wakeup = acpi_pci_can_wakeup, + .sleep_wake = acpi_pci_sleep_wake, +}; + +/* ACPI bus type */ +static int acpi_pci_find_device(struct device *dev, acpi_handle *handle) +{ + struct pci_dev * pci_dev; + acpi_integer addr; + + pci_dev = to_pci_dev(dev); + /* Please ref to ACPI spec for the syntax of _ADR */ + addr = (PCI_SLOT(pci_dev->devfn) << 16) | PCI_FUNC(pci_dev->devfn); + *handle = acpi_get_child(DEVICE_ACPI_HANDLE(dev->parent), addr); + if (!*handle) + return -ENODEV; + return 0; +} + +static int acpi_pci_find_root_bridge(struct device *dev, acpi_handle *handle) +{ + int num; + unsigned int seg, bus; + + /* + * The string should be the same as root bridge's name + * Please look at 'pci_scan_bus_parented' + */ + num = sscanf(dev->bus_id, "pci%04x:%02x", &seg, &bus); + if (num != 2) + return -ENODEV; + *handle = acpi_get_pci_rootbridge_handle(seg, bus); + if (!*handle) + return -ENODEV; + return 0; +} + +static struct acpi_bus_type acpi_pci_bus = { + .bus = &pci_bus_type, + .find_device = acpi_pci_find_device, + .find_bridge = acpi_pci_find_root_bridge, +}; + +static int __init acpi_pci_init(void) +{ + int ret; + + if (acpi_gbl_FADT.boot_flags & BAF_MSI_NOT_SUPPORTED) { + printk(KERN_INFO"ACPI FADT declares the system doesn't support MSI, so disable it\n"); + pci_no_msi(); + } + + if (acpi_gbl_FADT.boot_flags & BAF_PCIE_ASPM_CONTROL) { + printk(KERN_INFO"ACPI FADT declares the system doesn't support PCIe ASPM, so disable it\n"); + pcie_no_aspm(); + } + + ret = register_acpi_bus_type(&acpi_pci_bus); + if (ret) + return 0; + pci_set_platform_pm(&acpi_pci_platform_pm); + return 0; +} +arch_initcall(acpi_pci_init); diff --git a/drivers/pci/pci-driver.c b/drivers/pci/pci-driver.c new file mode 100644 index 0000000..b4cdd69 --- /dev/null +++ b/drivers/pci/pci-driver.c @@ -0,0 +1,852 @@ +/* + * drivers/pci/pci-driver.c + * + * (C) Copyright 2002-2004, 2007 Greg Kroah-Hartman <greg@kroah.com> + * (C) Copyright 2007 Novell Inc. + * + * Released under the GPL v2 only. + * + */ + +#include <linux/pci.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/device.h> +#include <linux/mempolicy.h> +#include <linux/string.h> +#include <linux/slab.h> +#include <linux/sched.h> +#include "pci.h" + +/* + * Dynamic device IDs are disabled for !CONFIG_HOTPLUG + */ + +struct pci_dynid { + struct list_head node; + struct pci_device_id id; +}; + +#ifdef CONFIG_HOTPLUG + +/** + * store_new_id - add a new PCI device ID to this driver and re-probe devices + * @driver: target device driver + * @buf: buffer for scanning device ID data + * @count: input size + * + * Adds a new dynamic pci device ID to this driver, + * and causes the driver to probe for all devices again. + */ +static ssize_t +store_new_id(struct device_driver *driver, const char *buf, size_t count) +{ + struct pci_dynid *dynid; + struct pci_driver *pdrv = to_pci_driver(driver); + const struct pci_device_id *ids = pdrv->id_table; + __u32 vendor, device, subvendor=PCI_ANY_ID, + subdevice=PCI_ANY_ID, class=0, class_mask=0; + unsigned long driver_data=0; + int fields=0; + int retval; + + fields = sscanf(buf, "%x %x %x %x %x %x %lx", + &vendor, &device, &subvendor, &subdevice, + &class, &class_mask, &driver_data); + if (fields < 2) + return -EINVAL; + + /* Only accept driver_data values that match an existing id_table + entry */ + retval = -EINVAL; + while (ids->vendor || ids->subvendor || ids->class_mask) { + if (driver_data == ids->driver_data) { + retval = 0; + break; + } + ids++; + } + if (retval) /* No match */ + return retval; + + dynid = kzalloc(sizeof(*dynid), GFP_KERNEL); + if (!dynid) + return -ENOMEM; + + dynid->id.vendor = vendor; + dynid->id.device = device; + dynid->id.subvendor = subvendor; + dynid->id.subdevice = subdevice; + dynid->id.class = class; + dynid->id.class_mask = class_mask; + dynid->id.driver_data = driver_data; + + spin_lock(&pdrv->dynids.lock); + list_add_tail(&dynid->node, &pdrv->dynids.list); + spin_unlock(&pdrv->dynids.lock); + + if (get_driver(&pdrv->driver)) { + retval = driver_attach(&pdrv->driver); + put_driver(&pdrv->driver); + } + + if (retval) + return retval; + return count; +} +static DRIVER_ATTR(new_id, S_IWUSR, NULL, store_new_id); + +static void +pci_free_dynids(struct pci_driver *drv) +{ + struct pci_dynid *dynid, *n; + + spin_lock(&drv->dynids.lock); + list_for_each_entry_safe(dynid, n, &drv->dynids.list, node) { + list_del(&dynid->node); + kfree(dynid); + } + spin_unlock(&drv->dynids.lock); +} + +static int +pci_create_newid_file(struct pci_driver *drv) +{ + int error = 0; + if (drv->probe != NULL) + error = driver_create_file(&drv->driver, &driver_attr_new_id); + return error; +} + +static void pci_remove_newid_file(struct pci_driver *drv) +{ + driver_remove_file(&drv->driver, &driver_attr_new_id); +} +#else /* !CONFIG_HOTPLUG */ +static inline void pci_free_dynids(struct pci_driver *drv) {} +static inline int pci_create_newid_file(struct pci_driver *drv) +{ + return 0; +} +static inline void pci_remove_newid_file(struct pci_driver *drv) {} +#endif + +/** + * pci_match_id - See if a pci device matches a given pci_id table + * @ids: array of PCI device id structures to search in + * @dev: the PCI device structure to match against. + * + * Used by a driver to check whether a PCI device present in the + * system is in its list of supported devices. Returns the matching + * pci_device_id structure or %NULL if there is no match. + * + * Deprecated, don't use this as it will not catch any dynamic ids + * that a driver might want to check for. + */ +const struct pci_device_id *pci_match_id(const struct pci_device_id *ids, + struct pci_dev *dev) +{ + if (ids) { + while (ids->vendor || ids->subvendor || ids->class_mask) { + if (pci_match_one_device(ids, dev)) + return ids; + ids++; + } + } + return NULL; +} + +/** + * pci_match_device - Tell if a PCI device structure has a matching PCI device id structure + * @drv: the PCI driver to match against + * @dev: the PCI device structure to match against + * + * Used by a driver to check whether a PCI device present in the + * system is in its list of supported devices. Returns the matching + * pci_device_id structure or %NULL if there is no match. + */ +static const struct pci_device_id *pci_match_device(struct pci_driver *drv, + struct pci_dev *dev) +{ + struct pci_dynid *dynid; + + /* Look at the dynamic ids first, before the static ones */ + spin_lock(&drv->dynids.lock); + list_for_each_entry(dynid, &drv->dynids.list, node) { + if (pci_match_one_device(&dynid->id, dev)) { + spin_unlock(&drv->dynids.lock); + return &dynid->id; + } + } + spin_unlock(&drv->dynids.lock); + + return pci_match_id(drv->id_table, dev); +} + +static int pci_call_probe(struct pci_driver *drv, struct pci_dev *dev, + const struct pci_device_id *id) +{ + int error; +#ifdef CONFIG_NUMA + /* Execute driver initialization on node where the + device's bus is attached to. This way the driver likely + allocates its local memory on the right node without + any need to change it. */ + struct mempolicy *oldpol; + cpumask_t oldmask = current->cpus_allowed; + int node = dev_to_node(&dev->dev); + + if (node >= 0) { + node_to_cpumask_ptr(nodecpumask, node); + set_cpus_allowed_ptr(current, nodecpumask); + } + /* And set default memory allocation policy */ + oldpol = current->mempolicy; + current->mempolicy = NULL; /* fall back to system default policy */ +#endif + error = drv->probe(dev, id); +#ifdef CONFIG_NUMA + set_cpus_allowed_ptr(current, &oldmask); + current->mempolicy = oldpol; +#endif + return error; +} + +/** + * __pci_device_probe() + * @drv: driver to call to check if it wants the PCI device + * @pci_dev: PCI device being probed + * + * returns 0 on success, else error. + * side-effect: pci_dev->driver is set to drv when drv claims pci_dev. + */ +static int +__pci_device_probe(struct pci_driver *drv, struct pci_dev *pci_dev) +{ + const struct pci_device_id *id; + int error = 0; + + if (!pci_dev->driver && drv->probe) { + error = -ENODEV; + + id = pci_match_device(drv, pci_dev); + if (id) + error = pci_call_probe(drv, pci_dev, id); + if (error >= 0) { + pci_dev->driver = drv; + error = 0; + } + } + return error; +} + +static int pci_device_probe(struct device * dev) +{ + int error = 0; + struct pci_driver *drv; + struct pci_dev *pci_dev; + + drv = to_pci_driver(dev->driver); + pci_dev = to_pci_dev(dev); + pci_dev_get(pci_dev); + error = __pci_device_probe(drv, pci_dev); + if (error) + pci_dev_put(pci_dev); + + return error; +} + +static int pci_device_remove(struct device * dev) +{ + struct pci_dev * pci_dev = to_pci_dev(dev); + struct pci_driver * drv = pci_dev->driver; + + if (drv) { + if (drv->remove) + drv->remove(pci_dev); + pci_dev->driver = NULL; + } + + /* + * If the device is still on, set the power state as "unknown", + * since it might change by the next time we load the driver. + */ + if (pci_dev->current_state == PCI_D0) + pci_dev->current_state = PCI_UNKNOWN; + + /* + * We would love to complain here if pci_dev->is_enabled is set, that + * the driver should have called pci_disable_device(), but the + * unfortunate fact is there are too many odd BIOS and bridge setups + * that don't like drivers doing that all of the time. + * Oh well, we can dream of sane hardware when we sleep, no matter how + * horrible the crap we have to deal with is when we are awake... + */ + + pci_dev_put(pci_dev); + return 0; +} + +static void pci_device_shutdown(struct device *dev) +{ + struct pci_dev *pci_dev = to_pci_dev(dev); + struct pci_driver *drv = pci_dev->driver; + + if (drv && drv->shutdown) + drv->shutdown(pci_dev); + pci_msi_shutdown(pci_dev); + pci_msix_shutdown(pci_dev); +} + +#ifdef CONFIG_PM_SLEEP + +/* + * Default "suspend" method for devices that have no driver provided suspend, + * or not even a driver at all. + */ +static void pci_default_pm_suspend(struct pci_dev *pci_dev) +{ + pci_save_state(pci_dev); + /* + * mark its power state as "unknown", since we don't know if + * e.g. the BIOS will change its device state when we suspend. + */ + if (pci_dev->current_state == PCI_D0) + pci_dev->current_state = PCI_UNKNOWN; +} + +/* + * Default "resume" method for devices that have no driver provided resume, + * or not even a driver at all. + */ +static int pci_default_pm_resume(struct pci_dev *pci_dev) +{ + int retval = 0; + + /* restore the PCI config space */ + pci_restore_state(pci_dev); + /* if the device was enabled before suspend, reenable */ + retval = pci_reenable_device(pci_dev); + /* + * if the device was busmaster before the suspend, make it busmaster + * again + */ + if (pci_dev->is_busmaster) + pci_set_master(pci_dev); + + return retval; +} + +static int pci_legacy_suspend(struct device *dev, pm_message_t state) +{ + struct pci_dev * pci_dev = to_pci_dev(dev); + struct pci_driver * drv = pci_dev->driver; + int i = 0; + + if (drv && drv->suspend) { + i = drv->suspend(pci_dev, state); + suspend_report_result(drv->suspend, i); + } else { + pci_default_pm_suspend(pci_dev); + } + return i; +} + +static int pci_legacy_suspend_late(struct device *dev, pm_message_t state) +{ + struct pci_dev * pci_dev = to_pci_dev(dev); + struct pci_driver * drv = pci_dev->driver; + int i = 0; + + if (drv && drv->suspend_late) { + i = drv->suspend_late(pci_dev, state); + suspend_report_result(drv->suspend_late, i); + } + return i; +} + +static int pci_legacy_resume(struct device *dev) +{ + int error; + struct pci_dev * pci_dev = to_pci_dev(dev); + struct pci_driver * drv = pci_dev->driver; + + if (drv && drv->resume) + error = drv->resume(pci_dev); + else + error = pci_default_pm_resume(pci_dev); + return error; +} + +static int pci_legacy_resume_early(struct device *dev) +{ + int error = 0; + struct pci_dev * pci_dev = to_pci_dev(dev); + struct pci_driver * drv = pci_dev->driver; + + if (drv && drv->resume_early) + error = drv->resume_early(pci_dev); + return error; +} + +static int pci_pm_prepare(struct device *dev) +{ + struct device_driver *drv = dev->driver; + int error = 0; + + if (drv && drv->pm && drv->pm->prepare) + error = drv->pm->prepare(dev); + + return error; +} + +static void pci_pm_complete(struct device *dev) +{ + struct device_driver *drv = dev->driver; + + if (drv && drv->pm && drv->pm->complete) + drv->pm->complete(dev); +} + +#ifdef CONFIG_SUSPEND + +static int pci_pm_suspend(struct device *dev) +{ + struct pci_dev *pci_dev = to_pci_dev(dev); + struct device_driver *drv = dev->driver; + int error = 0; + + if (drv && drv->pm) { + if (drv->pm->suspend) { + error = drv->pm->suspend(dev); + suspend_report_result(drv->pm->suspend, error); + } else { + pci_default_pm_suspend(pci_dev); + } + } else { + error = pci_legacy_suspend(dev, PMSG_SUSPEND); + } + pci_fixup_device(pci_fixup_suspend, pci_dev); + + return error; +} + +static int pci_pm_suspend_noirq(struct device *dev) +{ + struct pci_dev *pci_dev = to_pci_dev(dev); + struct pci_driver *drv = pci_dev->driver; + int error = 0; + + if (drv && drv->pm) { + if (drv->pm->suspend_noirq) { + error = drv->pm->suspend_noirq(dev); + suspend_report_result(drv->pm->suspend_noirq, error); + } + } else { + error = pci_legacy_suspend_late(dev, PMSG_SUSPEND); + } + + return error; +} + +static int pci_pm_resume(struct device *dev) +{ + struct pci_dev *pci_dev = to_pci_dev(dev); + struct device_driver *drv = dev->driver; + int error; + + pci_fixup_device(pci_fixup_resume, pci_dev); + + if (drv && drv->pm) { + error = drv->pm->resume ? drv->pm->resume(dev) : + pci_default_pm_resume(pci_dev); + } else { + error = pci_legacy_resume(dev); + } + + return error; +} + +static int pci_pm_resume_noirq(struct device *dev) +{ + struct pci_dev *pci_dev = to_pci_dev(dev); + struct pci_driver *drv = pci_dev->driver; + int error = 0; + + pci_fixup_device(pci_fixup_resume_early, pci_dev); + + if (drv && drv->pm) { + if (drv->pm->resume_noirq) + error = drv->pm->resume_noirq(dev); + } else { + error = pci_legacy_resume_early(dev); + } + + return error; +} + +#else /* !CONFIG_SUSPEND */ + +#define pci_pm_suspend NULL +#define pci_pm_suspend_noirq NULL +#define pci_pm_resume NULL +#define pci_pm_resume_noirq NULL + +#endif /* !CONFIG_SUSPEND */ + +#ifdef CONFIG_HIBERNATION + +static int pci_pm_freeze(struct device *dev) +{ + struct pci_dev *pci_dev = to_pci_dev(dev); + struct device_driver *drv = dev->driver; + int error = 0; + + if (drv && drv->pm) { + if (drv->pm->freeze) { + error = drv->pm->freeze(dev); + suspend_report_result(drv->pm->freeze, error); + } else { + pci_default_pm_suspend(pci_dev); + } + } else { + error = pci_legacy_suspend(dev, PMSG_FREEZE); + pci_fixup_device(pci_fixup_suspend, pci_dev); + } + + return error; +} + +static int pci_pm_freeze_noirq(struct device *dev) +{ + struct pci_dev *pci_dev = to_pci_dev(dev); + struct pci_driver *drv = pci_dev->driver; + int error = 0; + + if (drv && drv->pm) { + if (drv->pm->freeze_noirq) { + error = drv->pm->freeze_noirq(dev); + suspend_report_result(drv->pm->freeze_noirq, error); + } + } else { + error = pci_legacy_suspend_late(dev, PMSG_FREEZE); + } + + return error; +} + +static int pci_pm_thaw(struct device *dev) +{ + struct device_driver *drv = dev->driver; + int error = 0; + + if (drv && drv->pm) { + if (drv->pm->thaw) + error = drv->pm->thaw(dev); + } else { + pci_fixup_device(pci_fixup_resume, to_pci_dev(dev)); + error = pci_legacy_resume(dev); + } + + return error; +} + +static int pci_pm_thaw_noirq(struct device *dev) +{ + struct pci_dev *pci_dev = to_pci_dev(dev); + struct pci_driver *drv = pci_dev->driver; + int error = 0; + + if (drv && drv->pm) { + if (drv->pm->thaw_noirq) + error = drv->pm->thaw_noirq(dev); + } else { + pci_fixup_device(pci_fixup_resume_early, pci_dev); + error = pci_legacy_resume_early(dev); + } + + return error; +} + +static int pci_pm_poweroff(struct device *dev) +{ + struct device_driver *drv = dev->driver; + int error = 0; + + pci_fixup_device(pci_fixup_suspend, to_pci_dev(dev)); + + if (drv && drv->pm) { + if (drv->pm->poweroff) { + error = drv->pm->poweroff(dev); + suspend_report_result(drv->pm->poweroff, error); + } + } else { + error = pci_legacy_suspend(dev, PMSG_HIBERNATE); + } + + return error; +} + +static int pci_pm_poweroff_noirq(struct device *dev) +{ + struct pci_dev *pci_dev = to_pci_dev(dev); + struct pci_driver *drv = pci_dev->driver; + int error = 0; + + if (drv && drv->pm) { + if (drv->pm->poweroff_noirq) { + error = drv->pm->poweroff_noirq(dev); + suspend_report_result(drv->pm->poweroff_noirq, error); + } + } else { + error = pci_legacy_suspend_late(dev, PMSG_HIBERNATE); + } + + return error; +} + +static int pci_pm_restore(struct device *dev) +{ + struct pci_dev *pci_dev = to_pci_dev(dev); + struct device_driver *drv = dev->driver; + int error; + + if (drv && drv->pm) { + error = drv->pm->restore ? drv->pm->restore(dev) : + pci_default_pm_resume(pci_dev); + } else { + error = pci_legacy_resume(dev); + } + pci_fixup_device(pci_fixup_resume, pci_dev); + + return error; +} + +static int pci_pm_restore_noirq(struct device *dev) +{ + struct pci_dev *pci_dev = to_pci_dev(dev); + struct pci_driver *drv = pci_dev->driver; + int error = 0; + + pci_fixup_device(pci_fixup_resume, pci_dev); + + if (drv && drv->pm) { + if (drv->pm->restore_noirq) + error = drv->pm->restore_noirq(dev); + } else { + error = pci_legacy_resume_early(dev); + } + pci_fixup_device(pci_fixup_resume_early, pci_dev); + + return error; +} + +#else /* !CONFIG_HIBERNATION */ + +#define pci_pm_freeze NULL +#define pci_pm_freeze_noirq NULL +#define pci_pm_thaw NULL +#define pci_pm_thaw_noirq NULL +#define pci_pm_poweroff NULL +#define pci_pm_poweroff_noirq NULL +#define pci_pm_restore NULL +#define pci_pm_restore_noirq NULL + +#endif /* !CONFIG_HIBERNATION */ + +struct pm_ext_ops pci_pm_ops = { + .base = { + .prepare = pci_pm_prepare, + .complete = pci_pm_complete, + .suspend = pci_pm_suspend, + .resume = pci_pm_resume, + .freeze = pci_pm_freeze, + .thaw = pci_pm_thaw, + .poweroff = pci_pm_poweroff, + .restore = pci_pm_restore, + }, + .suspend_noirq = pci_pm_suspend_noirq, + .resume_noirq = pci_pm_resume_noirq, + .freeze_noirq = pci_pm_freeze_noirq, + .thaw_noirq = pci_pm_thaw_noirq, + .poweroff_noirq = pci_pm_poweroff_noirq, + .restore_noirq = pci_pm_restore_noirq, +}; + +#define PCI_PM_OPS_PTR &pci_pm_ops + +#else /* !CONFIG_PM_SLEEP */ + +#define PCI_PM_OPS_PTR NULL + +#endif /* !CONFIG_PM_SLEEP */ + +/** + * __pci_register_driver - register a new pci driver + * @drv: the driver structure to register + * @owner: owner module of drv + * @mod_name: module name string + * + * Adds the driver structure to the list of registered drivers. + * Returns a negative value on error, otherwise 0. + * If no error occurred, the driver remains registered even if + * no device was claimed during registration. + */ +int __pci_register_driver(struct pci_driver *drv, struct module *owner, + const char *mod_name) +{ + int error; + + /* initialize common driver fields */ + drv->driver.name = drv->name; + drv->driver.bus = &pci_bus_type; + drv->driver.owner = owner; + drv->driver.mod_name = mod_name; + + if (drv->pm) + drv->driver.pm = &drv->pm->base; + + spin_lock_init(&drv->dynids.lock); + INIT_LIST_HEAD(&drv->dynids.list); + + /* register with core */ + error = driver_register(&drv->driver); + if (error) + return error; + + error = pci_create_newid_file(drv); + if (error) + driver_unregister(&drv->driver); + + return error; +} + +/** + * pci_unregister_driver - unregister a pci driver + * @drv: the driver structure to unregister + * + * Deletes the driver structure from the list of registered PCI drivers, + * gives it a chance to clean up by calling its remove() function for + * each device it was responsible for, and marks those devices as + * driverless. + */ + +void +pci_unregister_driver(struct pci_driver *drv) +{ + pci_remove_newid_file(drv); + driver_unregister(&drv->driver); + pci_free_dynids(drv); +} + +static struct pci_driver pci_compat_driver = { + .name = "compat" +}; + +/** + * pci_dev_driver - get the pci_driver of a device + * @dev: the device to query + * + * Returns the appropriate pci_driver structure or %NULL if there is no + * registered driver for the device. + */ +struct pci_driver * +pci_dev_driver(const struct pci_dev *dev) +{ + if (dev->driver) + return dev->driver; + else { + int i; + for(i=0; i<=PCI_ROM_RESOURCE; i++) + if (dev->resource[i].flags & IORESOURCE_BUSY) + return &pci_compat_driver; + } + return NULL; +} + +/** + * pci_bus_match - Tell if a PCI device structure has a matching PCI device id structure + * @dev: the PCI device structure to match against + * @drv: the device driver to search for matching PCI device id structures + * + * Used by a driver to check whether a PCI device present in the + * system is in its list of supported devices. Returns the matching + * pci_device_id structure or %NULL if there is no match. + */ +static int pci_bus_match(struct device *dev, struct device_driver *drv) +{ + struct pci_dev *pci_dev = to_pci_dev(dev); + struct pci_driver *pci_drv = to_pci_driver(drv); + const struct pci_device_id *found_id; + + found_id = pci_match_device(pci_drv, pci_dev); + if (found_id) + return 1; + + return 0; +} + +/** + * pci_dev_get - increments the reference count of the pci device structure + * @dev: the device being referenced + * + * Each live reference to a device should be refcounted. + * + * Drivers for PCI devices should normally record such references in + * their probe() methods, when they bind to a device, and release + * them by calling pci_dev_put(), in their disconnect() methods. + * + * A pointer to the device with the incremented reference counter is returned. + */ +struct pci_dev *pci_dev_get(struct pci_dev *dev) +{ + if (dev) + get_device(&dev->dev); + return dev; +} + +/** + * pci_dev_put - release a use of the pci device structure + * @dev: device that's been disconnected + * + * Must be called when a user of a device is finished with it. When the last + * user of the device calls this function, the memory of the device is freed. + */ +void pci_dev_put(struct pci_dev *dev) +{ + if (dev) + put_device(&dev->dev); +} + +#ifndef CONFIG_HOTPLUG +int pci_uevent(struct device *dev, struct kobj_uevent_env *env) +{ + return -ENODEV; +} +#endif + +struct bus_type pci_bus_type = { + .name = "pci", + .match = pci_bus_match, + .uevent = pci_uevent, + .probe = pci_device_probe, + .remove = pci_device_remove, + .shutdown = pci_device_shutdown, + .dev_attrs = pci_dev_attrs, + .pm = PCI_PM_OPS_PTR, +}; + +static int __init pci_driver_init(void) +{ + return bus_register(&pci_bus_type); +} + +postcore_initcall(pci_driver_init); + +EXPORT_SYMBOL(pci_match_id); +EXPORT_SYMBOL(__pci_register_driver); +EXPORT_SYMBOL(pci_unregister_driver); +EXPORT_SYMBOL(pci_dev_driver); +EXPORT_SYMBOL(pci_bus_type); +EXPORT_SYMBOL(pci_dev_get); +EXPORT_SYMBOL(pci_dev_put); diff --git a/drivers/pci/pci-sysfs.c b/drivers/pci/pci-sysfs.c new file mode 100644 index 0000000..069dd11 --- /dev/null +++ b/drivers/pci/pci-sysfs.c @@ -0,0 +1,985 @@ +/* + * drivers/pci/pci-sysfs.c + * + * (C) Copyright 2002-2004 Greg Kroah-Hartman <greg@kroah.com> + * (C) Copyright 2002-2004 IBM Corp. + * (C) Copyright 2003 Matthew Wilcox + * (C) Copyright 2003 Hewlett-Packard + * (C) Copyright 2004 Jon Smirl <jonsmirl@yahoo.com> + * (C) Copyright 2004 Silicon Graphics, Inc. Jesse Barnes <jbarnes@sgi.com> + * + * File attributes for PCI devices + * + * Modeled after usb's driverfs.c + * + */ + + +#include <linux/kernel.h> +#include <linux/sched.h> +#include <linux/pci.h> +#include <linux/stat.h> +#include <linux/topology.h> +#include <linux/mm.h> +#include <linux/capability.h> +#include <linux/pci-aspm.h> +#include "pci.h" + +static int sysfs_initialized; /* = 0 */ + +/* show configuration fields */ +#define pci_config_attr(field, format_string) \ +static ssize_t \ +field##_show(struct device *dev, struct device_attribute *attr, char *buf) \ +{ \ + struct pci_dev *pdev; \ + \ + pdev = to_pci_dev (dev); \ + return sprintf (buf, format_string, pdev->field); \ +} + +pci_config_attr(vendor, "0x%04x\n"); +pci_config_attr(device, "0x%04x\n"); +pci_config_attr(subsystem_vendor, "0x%04x\n"); +pci_config_attr(subsystem_device, "0x%04x\n"); +pci_config_attr(class, "0x%06x\n"); +pci_config_attr(irq, "%u\n"); + +static ssize_t broken_parity_status_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct pci_dev *pdev = to_pci_dev(dev); + return sprintf (buf, "%u\n", pdev->broken_parity_status); +} + +static ssize_t broken_parity_status_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct pci_dev *pdev = to_pci_dev(dev); + ssize_t consumed = -EINVAL; + + if ((count > 0) && (*buf == '0' || *buf == '1')) { + pdev->broken_parity_status = *buf == '1' ? 1 : 0; + consumed = count; + } + return consumed; +} + +static ssize_t local_cpus_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + cpumask_t mask; + int len; + + mask = pcibus_to_cpumask(to_pci_dev(dev)->bus); + len = cpumask_scnprintf(buf, PAGE_SIZE-2, mask); + buf[len++] = '\n'; + buf[len] = '\0'; + return len; +} + + +static ssize_t local_cpulist_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + cpumask_t mask; + int len; + + mask = pcibus_to_cpumask(to_pci_dev(dev)->bus); + len = cpulist_scnprintf(buf, PAGE_SIZE-2, mask); + buf[len++] = '\n'; + buf[len] = '\0'; + return len; +} + +/* show resources */ +static ssize_t +resource_show(struct device * dev, struct device_attribute *attr, char * buf) +{ + struct pci_dev * pci_dev = to_pci_dev(dev); + char * str = buf; + int i; + int max = 7; + resource_size_t start, end; + + if (pci_dev->subordinate) + max = DEVICE_COUNT_RESOURCE; + + for (i = 0; i < max; i++) { + struct resource *res = &pci_dev->resource[i]; + pci_resource_to_user(pci_dev, i, res, &start, &end); + str += sprintf(str,"0x%016llx 0x%016llx 0x%016llx\n", + (unsigned long long)start, + (unsigned long long)end, + (unsigned long long)res->flags); + } + return (str - buf); +} + +static ssize_t modalias_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct pci_dev *pci_dev = to_pci_dev(dev); + + return sprintf(buf, "pci:v%08Xd%08Xsv%08Xsd%08Xbc%02Xsc%02Xi%02x\n", + pci_dev->vendor, pci_dev->device, + pci_dev->subsystem_vendor, pci_dev->subsystem_device, + (u8)(pci_dev->class >> 16), (u8)(pci_dev->class >> 8), + (u8)(pci_dev->class)); +} + +static ssize_t is_enabled_store(struct device *dev, + struct device_attribute *attr, const char *buf, + size_t count) +{ + ssize_t result = -EINVAL; + struct pci_dev *pdev = to_pci_dev(dev); + + /* this can crash the machine when done on the "wrong" device */ + if (!capable(CAP_SYS_ADMIN)) + return count; + + if (*buf == '0') { + if (atomic_read(&pdev->enable_cnt) != 0) + pci_disable_device(pdev); + else + result = -EIO; + } else if (*buf == '1') + result = pci_enable_device(pdev); + + return result < 0 ? result : count; +} + +static ssize_t is_enabled_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct pci_dev *pdev; + + pdev = to_pci_dev (dev); + return sprintf (buf, "%u\n", atomic_read(&pdev->enable_cnt)); +} + +#ifdef CONFIG_NUMA +static ssize_t +numa_node_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + return sprintf (buf, "%d\n", dev->numa_node); +} +#endif + +static ssize_t +msi_bus_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct pci_dev *pdev = to_pci_dev(dev); + + if (!pdev->subordinate) + return 0; + + return sprintf (buf, "%u\n", + !(pdev->subordinate->bus_flags & PCI_BUS_FLAGS_NO_MSI)); +} + +static ssize_t +msi_bus_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct pci_dev *pdev = to_pci_dev(dev); + + /* bad things may happen if the no_msi flag is changed + * while some drivers are loaded */ + if (!capable(CAP_SYS_ADMIN)) + return count; + + if (!pdev->subordinate) + return count; + + if (*buf == '0') { + pdev->subordinate->bus_flags |= PCI_BUS_FLAGS_NO_MSI; + dev_warn(&pdev->dev, "forced subordinate bus to not support MSI," + " bad things could happen.\n"); + } + + if (*buf == '1') { + pdev->subordinate->bus_flags &= ~PCI_BUS_FLAGS_NO_MSI; + dev_warn(&pdev->dev, "forced subordinate bus to support MSI," + " bad things could happen.\n"); + } + + return count; +} + +struct device_attribute pci_dev_attrs[] = { + __ATTR_RO(resource), + __ATTR_RO(vendor), + __ATTR_RO(device), + __ATTR_RO(subsystem_vendor), + __ATTR_RO(subsystem_device), + __ATTR_RO(class), + __ATTR_RO(irq), + __ATTR_RO(local_cpus), + __ATTR_RO(local_cpulist), + __ATTR_RO(modalias), +#ifdef CONFIG_NUMA + __ATTR_RO(numa_node), +#endif + __ATTR(enable, 0600, is_enabled_show, is_enabled_store), + __ATTR(broken_parity_status,(S_IRUGO|S_IWUSR), + broken_parity_status_show,broken_parity_status_store), + __ATTR(msi_bus, 0644, msi_bus_show, msi_bus_store), + __ATTR_NULL, +}; + +static ssize_t +pci_read_config(struct kobject *kobj, struct bin_attribute *bin_attr, + char *buf, loff_t off, size_t count) +{ + struct pci_dev *dev = to_pci_dev(container_of(kobj,struct device,kobj)); + unsigned int size = 64; + loff_t init_off = off; + u8 *data = (u8*) buf; + + /* Several chips lock up trying to read undefined config space */ + if (capable(CAP_SYS_ADMIN)) { + size = dev->cfg_size; + } else if (dev->hdr_type == PCI_HEADER_TYPE_CARDBUS) { + size = 128; + } + + if (off > size) + return 0; + if (off + count > size) { + size -= off; + count = size; + } else { + size = count; + } + + if ((off & 1) && size) { + u8 val; + pci_user_read_config_byte(dev, off, &val); + data[off - init_off] = val; + off++; + size--; + } + + if ((off & 3) && size > 2) { + u16 val; + pci_user_read_config_word(dev, off, &val); + data[off - init_off] = val & 0xff; + data[off - init_off + 1] = (val >> 8) & 0xff; + off += 2; + size -= 2; + } + + while (size > 3) { + u32 val; + pci_user_read_config_dword(dev, off, &val); + data[off - init_off] = val & 0xff; + data[off - init_off + 1] = (val >> 8) & 0xff; + data[off - init_off + 2] = (val >> 16) & 0xff; + data[off - init_off + 3] = (val >> 24) & 0xff; + off += 4; + size -= 4; + } + + if (size >= 2) { + u16 val; + pci_user_read_config_word(dev, off, &val); + data[off - init_off] = val & 0xff; + data[off - init_off + 1] = (val >> 8) & 0xff; + off += 2; + size -= 2; + } + + if (size > 0) { + u8 val; + pci_user_read_config_byte(dev, off, &val); + data[off - init_off] = val; + off++; + --size; + } + + return count; +} + +static ssize_t +pci_write_config(struct kobject *kobj, struct bin_attribute *bin_attr, + char *buf, loff_t off, size_t count) +{ + struct pci_dev *dev = to_pci_dev(container_of(kobj,struct device,kobj)); + unsigned int size = count; + loff_t init_off = off; + u8 *data = (u8*) buf; + + if (off > dev->cfg_size) + return 0; + if (off + count > dev->cfg_size) { + size = dev->cfg_size - off; + count = size; + } + + if ((off & 1) && size) { + pci_user_write_config_byte(dev, off, data[off - init_off]); + off++; + size--; + } + + if ((off & 3) && size > 2) { + u16 val = data[off - init_off]; + val |= (u16) data[off - init_off + 1] << 8; + pci_user_write_config_word(dev, off, val); + off += 2; + size -= 2; + } + + while (size > 3) { + u32 val = data[off - init_off]; + val |= (u32) data[off - init_off + 1] << 8; + val |= (u32) data[off - init_off + 2] << 16; + val |= (u32) data[off - init_off + 3] << 24; + pci_user_write_config_dword(dev, off, val); + off += 4; + size -= 4; + } + + if (size >= 2) { + u16 val = data[off - init_off]; + val |= (u16) data[off - init_off + 1] << 8; + pci_user_write_config_word(dev, off, val); + off += 2; + size -= 2; + } + + if (size) { + pci_user_write_config_byte(dev, off, data[off - init_off]); + off++; + --size; + } + + return count; +} + +static ssize_t +pci_read_vpd(struct kobject *kobj, struct bin_attribute *bin_attr, + char *buf, loff_t off, size_t count) +{ + struct pci_dev *dev = + to_pci_dev(container_of(kobj, struct device, kobj)); + int end; + int ret; + + if (off > bin_attr->size) + count = 0; + else if (count > bin_attr->size - off) + count = bin_attr->size - off; + end = off + count; + + while (off < end) { + ret = dev->vpd->ops->read(dev, off, end - off, buf); + if (ret < 0) + return ret; + buf += ret; + off += ret; + } + + return count; +} + +static ssize_t +pci_write_vpd(struct kobject *kobj, struct bin_attribute *bin_attr, + char *buf, loff_t off, size_t count) +{ + struct pci_dev *dev = + to_pci_dev(container_of(kobj, struct device, kobj)); + int end; + int ret; + + if (off > bin_attr->size) + count = 0; + else if (count > bin_attr->size - off) + count = bin_attr->size - off; + end = off + count; + + while (off < end) { + ret = dev->vpd->ops->write(dev, off, end - off, buf); + if (ret < 0) + return ret; + buf += ret; + off += ret; + } + + return count; +} + +#ifdef HAVE_PCI_LEGACY +/** + * pci_read_legacy_io - read byte(s) from legacy I/O port space + * @kobj: kobject corresponding to file to read from + * @buf: buffer to store results + * @off: offset into legacy I/O port space + * @count: number of bytes to read + * + * Reads 1, 2, or 4 bytes from legacy I/O port space using an arch specific + * callback routine (pci_legacy_read). + */ +static ssize_t +pci_read_legacy_io(struct kobject *kobj, struct bin_attribute *bin_attr, + char *buf, loff_t off, size_t count) +{ + struct pci_bus *bus = to_pci_bus(container_of(kobj, + struct device, + kobj)); + + /* Only support 1, 2 or 4 byte accesses */ + if (count != 1 && count != 2 && count != 4) + return -EINVAL; + + return pci_legacy_read(bus, off, (u32 *)buf, count); +} + +/** + * pci_write_legacy_io - write byte(s) to legacy I/O port space + * @kobj: kobject corresponding to file to read from + * @buf: buffer containing value to be written + * @off: offset into legacy I/O port space + * @count: number of bytes to write + * + * Writes 1, 2, or 4 bytes from legacy I/O port space using an arch specific + * callback routine (pci_legacy_write). + */ +static ssize_t +pci_write_legacy_io(struct kobject *kobj, struct bin_attribute *bin_attr, + char *buf, loff_t off, size_t count) +{ + struct pci_bus *bus = to_pci_bus(container_of(kobj, + struct device, + kobj)); + /* Only support 1, 2 or 4 byte accesses */ + if (count != 1 && count != 2 && count != 4) + return -EINVAL; + + return pci_legacy_write(bus, off, *(u32 *)buf, count); +} + +/** + * pci_mmap_legacy_mem - map legacy PCI memory into user memory space + * @kobj: kobject corresponding to device to be mapped + * @attr: struct bin_attribute for this file + * @vma: struct vm_area_struct passed to mmap + * + * Uses an arch specific callback, pci_mmap_legacy_mem_page_range, to mmap + * legacy memory space (first meg of bus space) into application virtual + * memory space. + */ +static int +pci_mmap_legacy_mem(struct kobject *kobj, struct bin_attribute *attr, + struct vm_area_struct *vma) +{ + struct pci_bus *bus = to_pci_bus(container_of(kobj, + struct device, + kobj)); + + return pci_mmap_legacy_page_range(bus, vma, pci_mmap_mem); +} + +/** + * pci_mmap_legacy_io - map legacy PCI IO into user memory space + * @kobj: kobject corresponding to device to be mapped + * @attr: struct bin_attribute for this file + * @vma: struct vm_area_struct passed to mmap + * + * Uses an arch specific callback, pci_mmap_legacy_io_page_range, to mmap + * legacy IO space (first meg of bus space) into application virtual + * memory space. Returns -ENOSYS if the operation isn't supported + */ +static int +pci_mmap_legacy_io(struct kobject *kobj, struct bin_attribute *attr, + struct vm_area_struct *vma) +{ + struct pci_bus *bus = to_pci_bus(container_of(kobj, + struct device, + kobj)); + + return pci_mmap_legacy_page_range(bus, vma, pci_mmap_io); +} + +/** + * pci_create_legacy_files - create legacy I/O port and memory files + * @b: bus to create files under + * + * Some platforms allow access to legacy I/O port and ISA memory space on + * a per-bus basis. This routine creates the files and ties them into + * their associated read, write and mmap files from pci-sysfs.c + * + * On error unwind, but don't propogate the error to the caller + * as it is ok to set up the PCI bus without these files. + */ +void pci_create_legacy_files(struct pci_bus *b) +{ + int error; + + b->legacy_io = kzalloc(sizeof(struct bin_attribute) * 2, + GFP_ATOMIC); + if (!b->legacy_io) + goto kzalloc_err; + + b->legacy_io->attr.name = "legacy_io"; + b->legacy_io->size = 0xffff; + b->legacy_io->attr.mode = S_IRUSR | S_IWUSR; + b->legacy_io->read = pci_read_legacy_io; + b->legacy_io->write = pci_write_legacy_io; + b->legacy_io->mmap = pci_mmap_legacy_io; + error = device_create_bin_file(&b->dev, b->legacy_io); + if (error) + goto legacy_io_err; + + /* Allocated above after the legacy_io struct */ + b->legacy_mem = b->legacy_io + 1; + b->legacy_mem->attr.name = "legacy_mem"; + b->legacy_mem->size = 1024*1024; + b->legacy_mem->attr.mode = S_IRUSR | S_IWUSR; + b->legacy_mem->mmap = pci_mmap_legacy_mem; + error = device_create_bin_file(&b->dev, b->legacy_mem); + if (error) + goto legacy_mem_err; + + return; + +legacy_mem_err: + device_remove_bin_file(&b->dev, b->legacy_io); +legacy_io_err: + kfree(b->legacy_io); + b->legacy_io = NULL; +kzalloc_err: + printk(KERN_WARNING "pci: warning: could not create legacy I/O port " + "and ISA memory resources to sysfs\n"); + return; +} + +void pci_remove_legacy_files(struct pci_bus *b) +{ + if (b->legacy_io) { + device_remove_bin_file(&b->dev, b->legacy_io); + device_remove_bin_file(&b->dev, b->legacy_mem); + kfree(b->legacy_io); /* both are allocated here */ + } +} +#endif /* HAVE_PCI_LEGACY */ + +#ifdef HAVE_PCI_MMAP + +static int pci_mmap_fits(struct pci_dev *pdev, int resno, struct vm_area_struct *vma) +{ + unsigned long nr, start, size; + + nr = (vma->vm_end - vma->vm_start) >> PAGE_SHIFT; + start = vma->vm_pgoff; + size = ((pci_resource_len(pdev, resno) - 1) >> PAGE_SHIFT) + 1; + if (start < size && size - start >= nr) + return 1; + WARN(1, "process \"%s\" tried to map 0x%08lx-0x%08lx on %s BAR %d (size 0x%08lx)\n", + current->comm, start, start+nr, pci_name(pdev), resno, size); + return 0; +} + +/** + * pci_mmap_resource - map a PCI resource into user memory space + * @kobj: kobject for mapping + * @attr: struct bin_attribute for the file being mapped + * @vma: struct vm_area_struct passed into the mmap + * @write_combine: 1 for write_combine mapping + * + * Use the regular PCI mapping routines to map a PCI resource into userspace. + */ +static int +pci_mmap_resource(struct kobject *kobj, struct bin_attribute *attr, + struct vm_area_struct *vma, int write_combine) +{ + struct pci_dev *pdev = to_pci_dev(container_of(kobj, + struct device, kobj)); + struct resource *res = (struct resource *)attr->private; + enum pci_mmap_state mmap_type; + resource_size_t start, end; + int i; + + for (i = 0; i < PCI_ROM_RESOURCE; i++) + if (res == &pdev->resource[i]) + break; + if (i >= PCI_ROM_RESOURCE) + return -ENODEV; + + if (!pci_mmap_fits(pdev, i, vma)) + return -EINVAL; + + /* pci_mmap_page_range() expects the same kind of entry as coming + * from /proc/bus/pci/ which is a "user visible" value. If this is + * different from the resource itself, arch will do necessary fixup. + */ + pci_resource_to_user(pdev, i, res, &start, &end); + vma->vm_pgoff += start >> PAGE_SHIFT; + mmap_type = res->flags & IORESOURCE_MEM ? pci_mmap_mem : pci_mmap_io; + + return pci_mmap_page_range(pdev, vma, mmap_type, write_combine); +} + +static int +pci_mmap_resource_uc(struct kobject *kobj, struct bin_attribute *attr, + struct vm_area_struct *vma) +{ + return pci_mmap_resource(kobj, attr, vma, 0); +} + +static int +pci_mmap_resource_wc(struct kobject *kobj, struct bin_attribute *attr, + struct vm_area_struct *vma) +{ + return pci_mmap_resource(kobj, attr, vma, 1); +} + +/** + * pci_remove_resource_files - cleanup resource files + * @dev: dev to cleanup + * + * If we created resource files for @dev, remove them from sysfs and + * free their resources. + */ +static void +pci_remove_resource_files(struct pci_dev *pdev) +{ + int i; + + for (i = 0; i < PCI_ROM_RESOURCE; i++) { + struct bin_attribute *res_attr; + + res_attr = pdev->res_attr[i]; + if (res_attr) { + sysfs_remove_bin_file(&pdev->dev.kobj, res_attr); + kfree(res_attr); + } + + res_attr = pdev->res_attr_wc[i]; + if (res_attr) { + sysfs_remove_bin_file(&pdev->dev.kobj, res_attr); + kfree(res_attr); + } + } +} + +static int pci_create_attr(struct pci_dev *pdev, int num, int write_combine) +{ + /* allocate attribute structure, piggyback attribute name */ + int name_len = write_combine ? 13 : 10; + struct bin_attribute *res_attr; + int retval; + + res_attr = kzalloc(sizeof(*res_attr) + name_len, GFP_ATOMIC); + if (res_attr) { + char *res_attr_name = (char *)(res_attr + 1); + + if (write_combine) { + pdev->res_attr_wc[num] = res_attr; + sprintf(res_attr_name, "resource%d_wc", num); + res_attr->mmap = pci_mmap_resource_wc; + } else { + pdev->res_attr[num] = res_attr; + sprintf(res_attr_name, "resource%d", num); + res_attr->mmap = pci_mmap_resource_uc; + } + res_attr->attr.name = res_attr_name; + res_attr->attr.mode = S_IRUSR | S_IWUSR; + res_attr->size = pci_resource_len(pdev, num); + res_attr->private = &pdev->resource[num]; + retval = sysfs_create_bin_file(&pdev->dev.kobj, res_attr); + } else + retval = -ENOMEM; + + return retval; +} + +/** + * pci_create_resource_files - create resource files in sysfs for @dev + * @dev: dev in question + * + * Walk the resources in @dev creating files for each resource available. + */ +static int pci_create_resource_files(struct pci_dev *pdev) +{ + int i; + int retval; + + /* Expose the PCI resources from this device as files */ + for (i = 0; i < PCI_ROM_RESOURCE; i++) { + + /* skip empty resources */ + if (!pci_resource_len(pdev, i)) + continue; + + retval = pci_create_attr(pdev, i, 0); + /* for prefetchable resources, create a WC mappable file */ + if (!retval && pdev->resource[i].flags & IORESOURCE_PREFETCH) + retval = pci_create_attr(pdev, i, 1); + + if (retval) { + pci_remove_resource_files(pdev); + return retval; + } + } + return 0; +} +#else /* !HAVE_PCI_MMAP */ +static inline int pci_create_resource_files(struct pci_dev *dev) { return 0; } +static inline void pci_remove_resource_files(struct pci_dev *dev) { return; } +#endif /* HAVE_PCI_MMAP */ + +/** + * pci_write_rom - used to enable access to the PCI ROM display + * @kobj: kernel object handle + * @buf: user input + * @off: file offset + * @count: number of byte in input + * + * writing anything except 0 enables it + */ +static ssize_t +pci_write_rom(struct kobject *kobj, struct bin_attribute *bin_attr, + char *buf, loff_t off, size_t count) +{ + struct pci_dev *pdev = to_pci_dev(container_of(kobj, struct device, kobj)); + + if ((off == 0) && (*buf == '0') && (count == 2)) + pdev->rom_attr_enabled = 0; + else + pdev->rom_attr_enabled = 1; + + return count; +} + +/** + * pci_read_rom - read a PCI ROM + * @kobj: kernel object handle + * @buf: where to put the data we read from the ROM + * @off: file offset + * @count: number of bytes to read + * + * Put @count bytes starting at @off into @buf from the ROM in the PCI + * device corresponding to @kobj. + */ +static ssize_t +pci_read_rom(struct kobject *kobj, struct bin_attribute *bin_attr, + char *buf, loff_t off, size_t count) +{ + struct pci_dev *pdev = to_pci_dev(container_of(kobj, struct device, kobj)); + void __iomem *rom; + size_t size; + + if (!pdev->rom_attr_enabled) + return -EINVAL; + + rom = pci_map_rom(pdev, &size); /* size starts out as PCI window size */ + if (!rom || !size) + return -EIO; + + if (off >= size) + count = 0; + else { + if (off + count > size) + count = size - off; + + memcpy_fromio(buf, rom + off, count); + } + pci_unmap_rom(pdev, rom); + + return count; +} + +static struct bin_attribute pci_config_attr = { + .attr = { + .name = "config", + .mode = S_IRUGO | S_IWUSR, + }, + .size = PCI_CFG_SPACE_SIZE, + .read = pci_read_config, + .write = pci_write_config, +}; + +static struct bin_attribute pcie_config_attr = { + .attr = { + .name = "config", + .mode = S_IRUGO | S_IWUSR, + }, + .size = PCI_CFG_SPACE_EXP_SIZE, + .read = pci_read_config, + .write = pci_write_config, +}; + +int __attribute__ ((weak)) pcibios_add_platform_entries(struct pci_dev *dev) +{ + return 0; +} + +static int pci_create_capabilities_sysfs(struct pci_dev *dev) +{ + int retval; + struct bin_attribute *attr; + + /* If the device has VPD, try to expose it in sysfs. */ + if (dev->vpd) { + attr = kzalloc(sizeof(*attr), GFP_ATOMIC); + if (!attr) + return -ENOMEM; + + attr->size = dev->vpd->len; + attr->attr.name = "vpd"; + attr->attr.mode = S_IRUSR | S_IWUSR; + attr->read = pci_read_vpd; + attr->write = pci_write_vpd; + retval = sysfs_create_bin_file(&dev->dev.kobj, attr); + if (retval) { + kfree(dev->vpd->attr); + return retval; + } + dev->vpd->attr = attr; + } + + /* Active State Power Management */ + pcie_aspm_create_sysfs_dev_files(dev); + + return 0; +} + +int __must_check pci_create_sysfs_dev_files (struct pci_dev *pdev) +{ + int retval; + int rom_size = 0; + struct bin_attribute *attr; + + if (!sysfs_initialized) + return -EACCES; + + if (pdev->cfg_size < PCI_CFG_SPACE_EXP_SIZE) + retval = sysfs_create_bin_file(&pdev->dev.kobj, &pci_config_attr); + else + retval = sysfs_create_bin_file(&pdev->dev.kobj, &pcie_config_attr); + if (retval) + goto err; + + retval = pci_create_resource_files(pdev); + if (retval) + goto err_config_file; + + if (pci_resource_len(pdev, PCI_ROM_RESOURCE)) + rom_size = pci_resource_len(pdev, PCI_ROM_RESOURCE); + else if (pdev->resource[PCI_ROM_RESOURCE].flags & IORESOURCE_ROM_SHADOW) + rom_size = 0x20000; + + /* If the device has a ROM, try to expose it in sysfs. */ + if (rom_size) { + attr = kzalloc(sizeof(*attr), GFP_ATOMIC); + if (!attr) { + retval = -ENOMEM; + goto err_resource_files; + } + attr->size = rom_size; + attr->attr.name = "rom"; + attr->attr.mode = S_IRUSR; + attr->read = pci_read_rom; + attr->write = pci_write_rom; + retval = sysfs_create_bin_file(&pdev->dev.kobj, attr); + if (retval) { + kfree(attr); + goto err_resource_files; + } + pdev->rom_attr = attr; + } + + /* add platform-specific attributes */ + retval = pcibios_add_platform_entries(pdev); + if (retval) + goto err_rom_file; + + /* add sysfs entries for various capabilities */ + retval = pci_create_capabilities_sysfs(pdev); + if (retval) + goto err_rom_file; + + return 0; + +err_rom_file: + if (rom_size) { + sysfs_remove_bin_file(&pdev->dev.kobj, pdev->rom_attr); + kfree(pdev->rom_attr); + pdev->rom_attr = NULL; + } +err_resource_files: + pci_remove_resource_files(pdev); +err_config_file: + if (pdev->cfg_size < PCI_CFG_SPACE_EXP_SIZE) + sysfs_remove_bin_file(&pdev->dev.kobj, &pci_config_attr); + else + sysfs_remove_bin_file(&pdev->dev.kobj, &pcie_config_attr); +err: + return retval; +} + +static void pci_remove_capabilities_sysfs(struct pci_dev *dev) +{ + if (dev->vpd && dev->vpd->attr) { + sysfs_remove_bin_file(&dev->dev.kobj, dev->vpd->attr); + kfree(dev->vpd->attr); + } + + pcie_aspm_remove_sysfs_dev_files(dev); +} + +/** + * pci_remove_sysfs_dev_files - cleanup PCI specific sysfs files + * @pdev: device whose entries we should free + * + * Cleanup when @pdev is removed from sysfs. + */ +void pci_remove_sysfs_dev_files(struct pci_dev *pdev) +{ + int rom_size = 0; + + if (!sysfs_initialized) + return; + + pci_remove_capabilities_sysfs(pdev); + + if (pdev->cfg_size < PCI_CFG_SPACE_EXP_SIZE) + sysfs_remove_bin_file(&pdev->dev.kobj, &pci_config_attr); + else + sysfs_remove_bin_file(&pdev->dev.kobj, &pcie_config_attr); + + pci_remove_resource_files(pdev); + + if (pci_resource_len(pdev, PCI_ROM_RESOURCE)) + rom_size = pci_resource_len(pdev, PCI_ROM_RESOURCE); + else if (pdev->resource[PCI_ROM_RESOURCE].flags & IORESOURCE_ROM_SHADOW) + rom_size = 0x20000; + + if (rom_size && pdev->rom_attr) { + sysfs_remove_bin_file(&pdev->dev.kobj, pdev->rom_attr); + kfree(pdev->rom_attr); + } +} + +static int __init pci_sysfs_init(void) +{ + struct pci_dev *pdev = NULL; + int retval; + + sysfs_initialized = 1; + for_each_pci_dev(pdev) { + retval = pci_create_sysfs_dev_files(pdev); + if (retval) { + pci_dev_put(pdev); + return retval; + } + } + + return 0; +} + +late_initcall(pci_sysfs_init); diff --git a/drivers/pci/pci.c b/drivers/pci/pci.c new file mode 100644 index 0000000..061d1ee --- /dev/null +++ b/drivers/pci/pci.c @@ -0,0 +1,2112 @@ +/* + * PCI Bus Services, see include/linux/pci.h for further explanation. + * + * Copyright 1993 -- 1997 Drew Eckhardt, Frederic Potter, + * David Mosberger-Tang + * + * Copyright 1997 -- 2000 Martin Mares <mj@ucw.cz> + */ + +#include <linux/kernel.h> +#include <linux/delay.h> +#include <linux/init.h> +#include <linux/pci.h> +#include <linux/pm.h> +#include <linux/module.h> +#include <linux/spinlock.h> +#include <linux/string.h> +#include <linux/log2.h> +#include <linux/pci-aspm.h> +#include <linux/pm_wakeup.h> +#include <linux/interrupt.h> +#include <asm/dma.h> /* isa_dma_bridge_buggy */ +#include "pci.h" + +unsigned int pci_pm_d3_delay = 10; + +#ifdef CONFIG_PCI_DOMAINS +int pci_domains_supported = 1; +#endif + +#define DEFAULT_CARDBUS_IO_SIZE (256) +#define DEFAULT_CARDBUS_MEM_SIZE (64*1024*1024) +/* pci=cbmemsize=nnM,cbiosize=nn can override this */ +unsigned long pci_cardbus_io_size = DEFAULT_CARDBUS_IO_SIZE; +unsigned long pci_cardbus_mem_size = DEFAULT_CARDBUS_MEM_SIZE; + +/** + * pci_bus_max_busnr - returns maximum PCI bus number of given bus' children + * @bus: pointer to PCI bus structure to search + * + * Given a PCI bus, returns the highest PCI bus number present in the set + * including the given PCI bus and its list of child PCI buses. + */ +unsigned char pci_bus_max_busnr(struct pci_bus* bus) +{ + struct list_head *tmp; + unsigned char max, n; + + max = bus->subordinate; + list_for_each(tmp, &bus->children) { + n = pci_bus_max_busnr(pci_bus_b(tmp)); + if(n > max) + max = n; + } + return max; +} +EXPORT_SYMBOL_GPL(pci_bus_max_busnr); + +#if 0 +/** + * pci_max_busnr - returns maximum PCI bus number + * + * Returns the highest PCI bus number present in the system global list of + * PCI buses. + */ +unsigned char __devinit +pci_max_busnr(void) +{ + struct pci_bus *bus = NULL; + unsigned char max, n; + + max = 0; + while ((bus = pci_find_next_bus(bus)) != NULL) { + n = pci_bus_max_busnr(bus); + if(n > max) + max = n; + } + return max; +} + +#endif /* 0 */ + +#define PCI_FIND_CAP_TTL 48 + +static int __pci_find_next_cap_ttl(struct pci_bus *bus, unsigned int devfn, + u8 pos, int cap, int *ttl) +{ + u8 id; + + while ((*ttl)--) { + pci_bus_read_config_byte(bus, devfn, pos, &pos); + if (pos < 0x40) + break; + pos &= ~3; + pci_bus_read_config_byte(bus, devfn, pos + PCI_CAP_LIST_ID, + &id); + if (id == 0xff) + break; + if (id == cap) + return pos; + pos += PCI_CAP_LIST_NEXT; + } + return 0; +} + +static int __pci_find_next_cap(struct pci_bus *bus, unsigned int devfn, + u8 pos, int cap) +{ + int ttl = PCI_FIND_CAP_TTL; + + return __pci_find_next_cap_ttl(bus, devfn, pos, cap, &ttl); +} + +int pci_find_next_capability(struct pci_dev *dev, u8 pos, int cap) +{ + return __pci_find_next_cap(dev->bus, dev->devfn, + pos + PCI_CAP_LIST_NEXT, cap); +} +EXPORT_SYMBOL_GPL(pci_find_next_capability); + +static int __pci_bus_find_cap_start(struct pci_bus *bus, + unsigned int devfn, u8 hdr_type) +{ + u16 status; + + pci_bus_read_config_word(bus, devfn, PCI_STATUS, &status); + if (!(status & PCI_STATUS_CAP_LIST)) + return 0; + + switch (hdr_type) { + case PCI_HEADER_TYPE_NORMAL: + case PCI_HEADER_TYPE_BRIDGE: + return PCI_CAPABILITY_LIST; + case PCI_HEADER_TYPE_CARDBUS: + return PCI_CB_CAPABILITY_LIST; + default: + return 0; + } + + return 0; +} + +/** + * pci_find_capability - query for devices' capabilities + * @dev: PCI device to query + * @cap: capability code + * + * Tell if a device supports a given PCI capability. + * Returns the address of the requested capability structure within the + * device's PCI configuration space or 0 in case the device does not + * support it. Possible values for @cap: + * + * %PCI_CAP_ID_PM Power Management + * %PCI_CAP_ID_AGP Accelerated Graphics Port + * %PCI_CAP_ID_VPD Vital Product Data + * %PCI_CAP_ID_SLOTID Slot Identification + * %PCI_CAP_ID_MSI Message Signalled Interrupts + * %PCI_CAP_ID_CHSWP CompactPCI HotSwap + * %PCI_CAP_ID_PCIX PCI-X + * %PCI_CAP_ID_EXP PCI Express + */ +int pci_find_capability(struct pci_dev *dev, int cap) +{ + int pos; + + pos = __pci_bus_find_cap_start(dev->bus, dev->devfn, dev->hdr_type); + if (pos) + pos = __pci_find_next_cap(dev->bus, dev->devfn, pos, cap); + + return pos; +} + +/** + * pci_bus_find_capability - query for devices' capabilities + * @bus: the PCI bus to query + * @devfn: PCI device to query + * @cap: capability code + * + * Like pci_find_capability() but works for pci devices that do not have a + * pci_dev structure set up yet. + * + * Returns the address of the requested capability structure within the + * device's PCI configuration space or 0 in case the device does not + * support it. + */ +int pci_bus_find_capability(struct pci_bus *bus, unsigned int devfn, int cap) +{ + int pos; + u8 hdr_type; + + pci_bus_read_config_byte(bus, devfn, PCI_HEADER_TYPE, &hdr_type); + + pos = __pci_bus_find_cap_start(bus, devfn, hdr_type & 0x7f); + if (pos) + pos = __pci_find_next_cap(bus, devfn, pos, cap); + + return pos; +} + +/** + * pci_find_ext_capability - Find an extended capability + * @dev: PCI device to query + * @cap: capability code + * + * Returns the address of the requested extended capability structure + * within the device's PCI configuration space or 0 if the device does + * not support it. Possible values for @cap: + * + * %PCI_EXT_CAP_ID_ERR Advanced Error Reporting + * %PCI_EXT_CAP_ID_VC Virtual Channel + * %PCI_EXT_CAP_ID_DSN Device Serial Number + * %PCI_EXT_CAP_ID_PWR Power Budgeting + */ +int pci_find_ext_capability(struct pci_dev *dev, int cap) +{ + u32 header; + int ttl; + int pos = PCI_CFG_SPACE_SIZE; + + /* minimum 8 bytes per capability */ + ttl = (PCI_CFG_SPACE_EXP_SIZE - PCI_CFG_SPACE_SIZE) / 8; + + if (dev->cfg_size <= PCI_CFG_SPACE_SIZE) + return 0; + + if (pci_read_config_dword(dev, pos, &header) != PCIBIOS_SUCCESSFUL) + return 0; + + /* + * If we have no capabilities, this is indicated by cap ID, + * cap version and next pointer all being 0. + */ + if (header == 0) + return 0; + + while (ttl-- > 0) { + if (PCI_EXT_CAP_ID(header) == cap) + return pos; + + pos = PCI_EXT_CAP_NEXT(header); + if (pos < PCI_CFG_SPACE_SIZE) + break; + + if (pci_read_config_dword(dev, pos, &header) != PCIBIOS_SUCCESSFUL) + break; + } + + return 0; +} +EXPORT_SYMBOL_GPL(pci_find_ext_capability); + +static int __pci_find_next_ht_cap(struct pci_dev *dev, int pos, int ht_cap) +{ + int rc, ttl = PCI_FIND_CAP_TTL; + u8 cap, mask; + + if (ht_cap == HT_CAPTYPE_SLAVE || ht_cap == HT_CAPTYPE_HOST) + mask = HT_3BIT_CAP_MASK; + else + mask = HT_5BIT_CAP_MASK; + + pos = __pci_find_next_cap_ttl(dev->bus, dev->devfn, pos, + PCI_CAP_ID_HT, &ttl); + while (pos) { + rc = pci_read_config_byte(dev, pos + 3, &cap); + if (rc != PCIBIOS_SUCCESSFUL) + return 0; + + if ((cap & mask) == ht_cap) + return pos; + + pos = __pci_find_next_cap_ttl(dev->bus, dev->devfn, + pos + PCI_CAP_LIST_NEXT, + PCI_CAP_ID_HT, &ttl); + } + + return 0; +} +/** + * pci_find_next_ht_capability - query a device's Hypertransport capabilities + * @dev: PCI device to query + * @pos: Position from which to continue searching + * @ht_cap: Hypertransport capability code + * + * To be used in conjunction with pci_find_ht_capability() to search for + * all capabilities matching @ht_cap. @pos should always be a value returned + * from pci_find_ht_capability(). + * + * NB. To be 100% safe against broken PCI devices, the caller should take + * steps to avoid an infinite loop. + */ +int pci_find_next_ht_capability(struct pci_dev *dev, int pos, int ht_cap) +{ + return __pci_find_next_ht_cap(dev, pos + PCI_CAP_LIST_NEXT, ht_cap); +} +EXPORT_SYMBOL_GPL(pci_find_next_ht_capability); + +/** + * pci_find_ht_capability - query a device's Hypertransport capabilities + * @dev: PCI device to query + * @ht_cap: Hypertransport capability code + * + * Tell if a device supports a given Hypertransport capability. + * Returns an address within the device's PCI configuration space + * or 0 in case the device does not support the request capability. + * The address points to the PCI capability, of type PCI_CAP_ID_HT, + * which has a Hypertransport capability matching @ht_cap. + */ +int pci_find_ht_capability(struct pci_dev *dev, int ht_cap) +{ + int pos; + + pos = __pci_bus_find_cap_start(dev->bus, dev->devfn, dev->hdr_type); + if (pos) + pos = __pci_find_next_ht_cap(dev, pos, ht_cap); + + return pos; +} +EXPORT_SYMBOL_GPL(pci_find_ht_capability); + +/** + * pci_find_parent_resource - return resource region of parent bus of given region + * @dev: PCI device structure contains resources to be searched + * @res: child resource record for which parent is sought + * + * For given resource region of given device, return the resource + * region of parent bus the given region is contained in or where + * it should be allocated from. + */ +struct resource * +pci_find_parent_resource(const struct pci_dev *dev, struct resource *res) +{ + const struct pci_bus *bus = dev->bus; + int i; + struct resource *best = NULL; + + for(i = 0; i < PCI_BUS_NUM_RESOURCES; i++) { + struct resource *r = bus->resource[i]; + if (!r) + continue; + if (res->start && !(res->start >= r->start && res->end <= r->end)) + continue; /* Not contained */ + if ((res->flags ^ r->flags) & (IORESOURCE_IO | IORESOURCE_MEM)) + continue; /* Wrong type */ + if (!((res->flags ^ r->flags) & IORESOURCE_PREFETCH)) + return r; /* Exact match */ + if ((res->flags & IORESOURCE_PREFETCH) && !(r->flags & IORESOURCE_PREFETCH)) + best = r; /* Approximating prefetchable by non-prefetchable */ + } + return best; +} + +/** + * pci_restore_bars - restore a devices BAR values (e.g. after wake-up) + * @dev: PCI device to have its BARs restored + * + * Restore the BAR values for a given device, so as to make it + * accessible by its driver. + */ +static void +pci_restore_bars(struct pci_dev *dev) +{ + int i, numres; + + switch (dev->hdr_type) { + case PCI_HEADER_TYPE_NORMAL: + numres = 6; + break; + case PCI_HEADER_TYPE_BRIDGE: + numres = 2; + break; + case PCI_HEADER_TYPE_CARDBUS: + numres = 1; + break; + default: + /* Should never get here, but just in case... */ + return; + } + + for (i = 0; i < numres; i ++) + pci_update_resource(dev, &dev->resource[i], i); +} + +static struct pci_platform_pm_ops *pci_platform_pm; + +int pci_set_platform_pm(struct pci_platform_pm_ops *ops) +{ + if (!ops->is_manageable || !ops->set_state || !ops->choose_state + || !ops->sleep_wake || !ops->can_wakeup) + return -EINVAL; + pci_platform_pm = ops; + return 0; +} + +static inline bool platform_pci_power_manageable(struct pci_dev *dev) +{ + return pci_platform_pm ? pci_platform_pm->is_manageable(dev) : false; +} + +static inline int platform_pci_set_power_state(struct pci_dev *dev, + pci_power_t t) +{ + return pci_platform_pm ? pci_platform_pm->set_state(dev, t) : -ENOSYS; +} + +static inline pci_power_t platform_pci_choose_state(struct pci_dev *dev) +{ + return pci_platform_pm ? + pci_platform_pm->choose_state(dev) : PCI_POWER_ERROR; +} + +static inline bool platform_pci_can_wakeup(struct pci_dev *dev) +{ + return pci_platform_pm ? pci_platform_pm->can_wakeup(dev) : false; +} + +static inline int platform_pci_sleep_wake(struct pci_dev *dev, bool enable) +{ + return pci_platform_pm ? + pci_platform_pm->sleep_wake(dev, enable) : -ENODEV; +} + +/** + * pci_raw_set_power_state - Use PCI PM registers to set the power state of + * given PCI device + * @dev: PCI device to handle. + * @state: PCI power state (D0, D1, D2, D3hot) to put the device into. + * + * RETURN VALUE: + * -EINVAL if the requested state is invalid. + * -EIO if device does not support PCI PM or its PM capabilities register has a + * wrong version, or device doesn't support the requested state. + * 0 if device already is in the requested state. + * 0 if device's power state has been successfully changed. + */ +static int +pci_raw_set_power_state(struct pci_dev *dev, pci_power_t state) +{ + u16 pmcsr; + bool need_restore = false; + + if (!dev->pm_cap) + return -EIO; + + if (state < PCI_D0 || state > PCI_D3hot) + return -EINVAL; + + /* Validate current state: + * Can enter D0 from any state, but if we can only go deeper + * to sleep if we're already in a low power state + */ + if (dev->current_state == state) { + /* we're already there */ + return 0; + } else if (state != PCI_D0 && dev->current_state <= PCI_D3cold + && dev->current_state > state) { + dev_err(&dev->dev, "invalid power transition " + "(from state %d to %d)\n", dev->current_state, state); + return -EINVAL; + } + + /* check if this device supports the desired state */ + if ((state == PCI_D1 && !dev->d1_support) + || (state == PCI_D2 && !dev->d2_support)) + return -EIO; + + pci_read_config_word(dev, dev->pm_cap + PCI_PM_CTRL, &pmcsr); + + /* If we're (effectively) in D3, force entire word to 0. + * This doesn't affect PME_Status, disables PME_En, and + * sets PowerState to 0. + */ + switch (dev->current_state) { + case PCI_D0: + case PCI_D1: + case PCI_D2: + pmcsr &= ~PCI_PM_CTRL_STATE_MASK; + pmcsr |= state; + break; + case PCI_UNKNOWN: /* Boot-up */ + if ((pmcsr & PCI_PM_CTRL_STATE_MASK) == PCI_D3hot + && !(pmcsr & PCI_PM_CTRL_NO_SOFT_RESET)) + need_restore = true; + /* Fall-through: force to D0 */ + default: + pmcsr = 0; + break; + } + + /* enter specified state */ + pci_write_config_word(dev, dev->pm_cap + PCI_PM_CTRL, pmcsr); + + /* Mandatory power management transition delays */ + /* see PCI PM 1.1 5.6.1 table 18 */ + if (state == PCI_D3hot || dev->current_state == PCI_D3hot) + msleep(pci_pm_d3_delay); + else if (state == PCI_D2 || dev->current_state == PCI_D2) + udelay(200); + + dev->current_state = state; + + /* According to section 5.4.1 of the "PCI BUS POWER MANAGEMENT + * INTERFACE SPECIFICATION, REV. 1.2", a device transitioning + * from D3hot to D0 _may_ perform an internal reset, thereby + * going to "D0 Uninitialized" rather than "D0 Initialized". + * For example, at least some versions of the 3c905B and the + * 3c556B exhibit this behaviour. + * + * At least some laptop BIOSen (e.g. the Thinkpad T21) leave + * devices in a D3hot state at boot. Consequently, we need to + * restore at least the BARs so that the device will be + * accessible to its driver. + */ + if (need_restore) + pci_restore_bars(dev); + + if (dev->bus->self) + pcie_aspm_pm_state_change(dev->bus->self); + + return 0; +} + +/** + * pci_update_current_state - Read PCI power state of given device from its + * PCI PM registers and cache it + * @dev: PCI device to handle. + */ +static void pci_update_current_state(struct pci_dev *dev) +{ + if (dev->pm_cap) { + u16 pmcsr; + + pci_read_config_word(dev, dev->pm_cap + PCI_PM_CTRL, &pmcsr); + dev->current_state = (pmcsr & PCI_PM_CTRL_STATE_MASK); + } +} + +/** + * pci_set_power_state - Set the power state of a PCI device + * @dev: PCI device to handle. + * @state: PCI power state (D0, D1, D2, D3hot) to put the device into. + * + * Transition a device to a new power state, using the platform formware and/or + * the device's PCI PM registers. + * + * RETURN VALUE: + * -EINVAL if the requested state is invalid. + * -EIO if device does not support PCI PM or its PM capabilities register has a + * wrong version, or device doesn't support the requested state. + * 0 if device already is in the requested state. + * 0 if device's power state has been successfully changed. + */ +int pci_set_power_state(struct pci_dev *dev, pci_power_t state) +{ + int error; + + /* bound the state we're entering */ + if (state > PCI_D3hot) + state = PCI_D3hot; + else if (state < PCI_D0) + state = PCI_D0; + else if ((state == PCI_D1 || state == PCI_D2) && pci_no_d1d2(dev)) + /* + * If the device or the parent bridge do not support PCI PM, + * ignore the request if we're doing anything other than putting + * it into D0 (which would only happen on boot). + */ + return 0; + + if (state == PCI_D0 && platform_pci_power_manageable(dev)) { + /* + * Allow the platform to change the state, for example via ACPI + * _PR0, _PS0 and some such, but do not trust it. + */ + int ret = platform_pci_set_power_state(dev, PCI_D0); + if (!ret) + pci_update_current_state(dev); + } + /* This device is quirked not to be put into D3, so + don't put it in D3 */ + if (state == PCI_D3hot && (dev->dev_flags & PCI_DEV_FLAGS_NO_D3)) + return 0; + + error = pci_raw_set_power_state(dev, state); + + if (state > PCI_D0 && platform_pci_power_manageable(dev)) { + /* Allow the platform to finalize the transition */ + int ret = platform_pci_set_power_state(dev, state); + if (!ret) { + pci_update_current_state(dev); + error = 0; + } + } + + return error; +} + +/** + * pci_choose_state - Choose the power state of a PCI device + * @dev: PCI device to be suspended + * @state: target sleep state for the whole system. This is the value + * that is passed to suspend() function. + * + * Returns PCI power state suitable for given device and given system + * message. + */ + +pci_power_t pci_choose_state(struct pci_dev *dev, pm_message_t state) +{ + pci_power_t ret; + + if (!pci_find_capability(dev, PCI_CAP_ID_PM)) + return PCI_D0; + + ret = platform_pci_choose_state(dev); + if (ret != PCI_POWER_ERROR) + return ret; + + switch (state.event) { + case PM_EVENT_ON: + return PCI_D0; + case PM_EVENT_FREEZE: + case PM_EVENT_PRETHAW: + /* REVISIT both freeze and pre-thaw "should" use D0 */ + case PM_EVENT_SUSPEND: + case PM_EVENT_HIBERNATE: + return PCI_D3hot; + default: + dev_info(&dev->dev, "unrecognized suspend event %d\n", + state.event); + BUG(); + } + return PCI_D0; +} + +EXPORT_SYMBOL(pci_choose_state); + +static int pci_save_pcie_state(struct pci_dev *dev) +{ + int pos, i = 0; + struct pci_cap_saved_state *save_state; + u16 *cap; + int found = 0; + + pos = pci_find_capability(dev, PCI_CAP_ID_EXP); + if (pos <= 0) + return 0; + + save_state = pci_find_saved_cap(dev, PCI_CAP_ID_EXP); + if (!save_state) + save_state = kzalloc(sizeof(*save_state) + sizeof(u16) * 4, GFP_KERNEL); + else + found = 1; + if (!save_state) { + dev_err(&dev->dev, "out of memory in pci_save_pcie_state\n"); + return -ENOMEM; + } + cap = (u16 *)&save_state->data[0]; + + pci_read_config_word(dev, pos + PCI_EXP_DEVCTL, &cap[i++]); + pci_read_config_word(dev, pos + PCI_EXP_LNKCTL, &cap[i++]); + pci_read_config_word(dev, pos + PCI_EXP_SLTCTL, &cap[i++]); + pci_read_config_word(dev, pos + PCI_EXP_RTCTL, &cap[i++]); + save_state->cap_nr = PCI_CAP_ID_EXP; + if (!found) + pci_add_saved_cap(dev, save_state); + return 0; +} + +static void pci_restore_pcie_state(struct pci_dev *dev) +{ + int i = 0, pos; + struct pci_cap_saved_state *save_state; + u16 *cap; + + save_state = pci_find_saved_cap(dev, PCI_CAP_ID_EXP); + pos = pci_find_capability(dev, PCI_CAP_ID_EXP); + if (!save_state || pos <= 0) + return; + cap = (u16 *)&save_state->data[0]; + + pci_write_config_word(dev, pos + PCI_EXP_DEVCTL, cap[i++]); + pci_write_config_word(dev, pos + PCI_EXP_LNKCTL, cap[i++]); + pci_write_config_word(dev, pos + PCI_EXP_SLTCTL, cap[i++]); + pci_write_config_word(dev, pos + PCI_EXP_RTCTL, cap[i++]); +} + + +static int pci_save_pcix_state(struct pci_dev *dev) +{ + int pos, i = 0; + struct pci_cap_saved_state *save_state; + u16 *cap; + int found = 0; + + pos = pci_find_capability(dev, PCI_CAP_ID_PCIX); + if (pos <= 0) + return 0; + + save_state = pci_find_saved_cap(dev, PCI_CAP_ID_PCIX); + if (!save_state) + save_state = kzalloc(sizeof(*save_state) + sizeof(u16), GFP_KERNEL); + else + found = 1; + if (!save_state) { + dev_err(&dev->dev, "out of memory in pci_save_pcie_state\n"); + return -ENOMEM; + } + cap = (u16 *)&save_state->data[0]; + + pci_read_config_word(dev, pos + PCI_X_CMD, &cap[i++]); + save_state->cap_nr = PCI_CAP_ID_PCIX; + if (!found) + pci_add_saved_cap(dev, save_state); + return 0; +} + +static void pci_restore_pcix_state(struct pci_dev *dev) +{ + int i = 0, pos; + struct pci_cap_saved_state *save_state; + u16 *cap; + + save_state = pci_find_saved_cap(dev, PCI_CAP_ID_PCIX); + pos = pci_find_capability(dev, PCI_CAP_ID_PCIX); + if (!save_state || pos <= 0) + return; + cap = (u16 *)&save_state->data[0]; + + pci_write_config_word(dev, pos + PCI_X_CMD, cap[i++]); +} + + +/** + * pci_save_state - save the PCI configuration space of a device before suspending + * @dev: - PCI device that we're dealing with + */ +int +pci_save_state(struct pci_dev *dev) +{ + int i; + /* XXX: 100% dword access ok here? */ + for (i = 0; i < 16; i++) + pci_read_config_dword(dev, i * 4,&dev->saved_config_space[i]); + if ((i = pci_save_pcie_state(dev)) != 0) + return i; + if ((i = pci_save_pcix_state(dev)) != 0) + return i; + return 0; +} + +/** + * pci_restore_state - Restore the saved state of a PCI device + * @dev: - PCI device that we're dealing with + */ +int +pci_restore_state(struct pci_dev *dev) +{ + int i; + u32 val; + + /* PCI Express register must be restored first */ + pci_restore_pcie_state(dev); + + /* + * The Base Address register should be programmed before the command + * register(s) + */ + for (i = 15; i >= 0; i--) { + pci_read_config_dword(dev, i * 4, &val); + if (val != dev->saved_config_space[i]) { + dev_printk(KERN_DEBUG, &dev->dev, "restoring config " + "space at offset %#x (was %#x, writing %#x)\n", + i, val, (int)dev->saved_config_space[i]); + pci_write_config_dword(dev,i * 4, + dev->saved_config_space[i]); + } + } + pci_restore_pcix_state(dev); + pci_restore_msi_state(dev); + + return 0; +} + +static int do_pci_enable_device(struct pci_dev *dev, int bars) +{ + int err; + + err = pci_set_power_state(dev, PCI_D0); + if (err < 0 && err != -EIO) + return err; + err = pcibios_enable_device(dev, bars); + if (err < 0) + return err; + pci_fixup_device(pci_fixup_enable, dev); + + return 0; +} + +/** + * pci_reenable_device - Resume abandoned device + * @dev: PCI device to be resumed + * + * Note this function is a backend of pci_default_resume and is not supposed + * to be called by normal code, write proper resume handler and use it instead. + */ +int pci_reenable_device(struct pci_dev *dev) +{ + if (atomic_read(&dev->enable_cnt)) + return do_pci_enable_device(dev, (1 << PCI_NUM_RESOURCES) - 1); + return 0; +} + +static int __pci_enable_device_flags(struct pci_dev *dev, + resource_size_t flags) +{ + int err; + int i, bars = 0; + + if (atomic_add_return(1, &dev->enable_cnt) > 1) + return 0; /* already enabled */ + + for (i = 0; i < DEVICE_COUNT_RESOURCE; i++) + if (dev->resource[i].flags & flags) + bars |= (1 << i); + + err = do_pci_enable_device(dev, bars); + if (err < 0) + atomic_dec(&dev->enable_cnt); + return err; +} + +/** + * pci_enable_device_io - Initialize a device for use with IO space + * @dev: PCI device to be initialized + * + * Initialize device before it's used by a driver. Ask low-level code + * to enable I/O resources. Wake up the device if it was suspended. + * Beware, this function can fail. + */ +int pci_enable_device_io(struct pci_dev *dev) +{ + return __pci_enable_device_flags(dev, IORESOURCE_IO); +} + +/** + * pci_enable_device_mem - Initialize a device for use with Memory space + * @dev: PCI device to be initialized + * + * Initialize device before it's used by a driver. Ask low-level code + * to enable Memory resources. Wake up the device if it was suspended. + * Beware, this function can fail. + */ +int pci_enable_device_mem(struct pci_dev *dev) +{ + return __pci_enable_device_flags(dev, IORESOURCE_MEM); +} + +/** + * pci_enable_device - Initialize device before it's used by a driver. + * @dev: PCI device to be initialized + * + * Initialize device before it's used by a driver. Ask low-level code + * to enable I/O and memory. Wake up the device if it was suspended. + * Beware, this function can fail. + * + * Note we don't actually enable the device many times if we call + * this function repeatedly (we just increment the count). + */ +int pci_enable_device(struct pci_dev *dev) +{ + return __pci_enable_device_flags(dev, IORESOURCE_MEM | IORESOURCE_IO); +} + +/* + * Managed PCI resources. This manages device on/off, intx/msi/msix + * on/off and BAR regions. pci_dev itself records msi/msix status, so + * there's no need to track it separately. pci_devres is initialized + * when a device is enabled using managed PCI device enable interface. + */ +struct pci_devres { + unsigned int enabled:1; + unsigned int pinned:1; + unsigned int orig_intx:1; + unsigned int restore_intx:1; + u32 region_mask; +}; + +static void pcim_release(struct device *gendev, void *res) +{ + struct pci_dev *dev = container_of(gendev, struct pci_dev, dev); + struct pci_devres *this = res; + int i; + + if (dev->msi_enabled) + pci_disable_msi(dev); + if (dev->msix_enabled) + pci_disable_msix(dev); + + for (i = 0; i < DEVICE_COUNT_RESOURCE; i++) + if (this->region_mask & (1 << i)) + pci_release_region(dev, i); + + if (this->restore_intx) + pci_intx(dev, this->orig_intx); + + if (this->enabled && !this->pinned) + pci_disable_device(dev); +} + +static struct pci_devres * get_pci_dr(struct pci_dev *pdev) +{ + struct pci_devres *dr, *new_dr; + + dr = devres_find(&pdev->dev, pcim_release, NULL, NULL); + if (dr) + return dr; + + new_dr = devres_alloc(pcim_release, sizeof(*new_dr), GFP_KERNEL); + if (!new_dr) + return NULL; + return devres_get(&pdev->dev, new_dr, NULL, NULL); +} + +static struct pci_devres * find_pci_dr(struct pci_dev *pdev) +{ + if (pci_is_managed(pdev)) + return devres_find(&pdev->dev, pcim_release, NULL, NULL); + return NULL; +} + +/** + * pcim_enable_device - Managed pci_enable_device() + * @pdev: PCI device to be initialized + * + * Managed pci_enable_device(). + */ +int pcim_enable_device(struct pci_dev *pdev) +{ + struct pci_devres *dr; + int rc; + + dr = get_pci_dr(pdev); + if (unlikely(!dr)) + return -ENOMEM; + if (dr->enabled) + return 0; + + rc = pci_enable_device(pdev); + if (!rc) { + pdev->is_managed = 1; + dr->enabled = 1; + } + return rc; +} + +/** + * pcim_pin_device - Pin managed PCI device + * @pdev: PCI device to pin + * + * Pin managed PCI device @pdev. Pinned device won't be disabled on + * driver detach. @pdev must have been enabled with + * pcim_enable_device(). + */ +void pcim_pin_device(struct pci_dev *pdev) +{ + struct pci_devres *dr; + + dr = find_pci_dr(pdev); + WARN_ON(!dr || !dr->enabled); + if (dr) + dr->pinned = 1; +} + +/** + * pcibios_disable_device - disable arch specific PCI resources for device dev + * @dev: the PCI device to disable + * + * Disables architecture specific PCI resources for the device. This + * is the default implementation. Architecture implementations can + * override this. + */ +void __attribute__ ((weak)) pcibios_disable_device (struct pci_dev *dev) {} + +/** + * pci_disable_device - Disable PCI device after use + * @dev: PCI device to be disabled + * + * Signal to the system that the PCI device is not in use by the system + * anymore. This only involves disabling PCI bus-mastering, if active. + * + * Note we don't actually disable the device until all callers of + * pci_device_enable() have called pci_device_disable(). + */ +void +pci_disable_device(struct pci_dev *dev) +{ + struct pci_devres *dr; + u16 pci_command; + + dr = find_pci_dr(dev); + if (dr) + dr->enabled = 0; + + if (atomic_sub_return(1, &dev->enable_cnt) != 0) + return; + + pci_read_config_word(dev, PCI_COMMAND, &pci_command); + if (pci_command & PCI_COMMAND_MASTER) { + pci_command &= ~PCI_COMMAND_MASTER; + pci_write_config_word(dev, PCI_COMMAND, pci_command); + } + dev->is_busmaster = 0; + + pcibios_disable_device(dev); +} + +/** + * pcibios_set_pcie_reset_state - set reset state for device dev + * @dev: the PCI-E device reset + * @state: Reset state to enter into + * + * + * Sets the PCI-E reset state for the device. This is the default + * implementation. Architecture implementations can override this. + */ +int __attribute__ ((weak)) pcibios_set_pcie_reset_state(struct pci_dev *dev, + enum pcie_reset_state state) +{ + return -EINVAL; +} + +/** + * pci_set_pcie_reset_state - set reset state for device dev + * @dev: the PCI-E device reset + * @state: Reset state to enter into + * + * + * Sets the PCI reset state for the device. + */ +int pci_set_pcie_reset_state(struct pci_dev *dev, enum pcie_reset_state state) +{ + return pcibios_set_pcie_reset_state(dev, state); +} + +/** + * pci_pme_capable - check the capability of PCI device to generate PME# + * @dev: PCI device to handle. + * @state: PCI state from which device will issue PME#. + */ +bool pci_pme_capable(struct pci_dev *dev, pci_power_t state) +{ + if (!dev->pm_cap) + return false; + + return !!(dev->pme_support & (1 << state)); +} + +/** + * pci_pme_active - enable or disable PCI device's PME# function + * @dev: PCI device to handle. + * @enable: 'true' to enable PME# generation; 'false' to disable it. + * + * The caller must verify that the device is capable of generating PME# before + * calling this function with @enable equal to 'true'. + */ +void pci_pme_active(struct pci_dev *dev, bool enable) +{ + u16 pmcsr; + + if (!dev->pm_cap) + return; + + pci_read_config_word(dev, dev->pm_cap + PCI_PM_CTRL, &pmcsr); + /* Clear PME_Status by writing 1 to it and enable PME# */ + pmcsr |= PCI_PM_CTRL_PME_STATUS | PCI_PM_CTRL_PME_ENABLE; + if (!enable) + pmcsr &= ~PCI_PM_CTRL_PME_ENABLE; + + pci_write_config_word(dev, dev->pm_cap + PCI_PM_CTRL, pmcsr); + + dev_printk(KERN_INFO, &dev->dev, "PME# %s\n", + enable ? "enabled" : "disabled"); +} + +/** + * pci_enable_wake - enable PCI device as wakeup event source + * @dev: PCI device affected + * @state: PCI state from which device will issue wakeup events + * @enable: True to enable event generation; false to disable + * + * This enables the device as a wakeup event source, or disables it. + * When such events involves platform-specific hooks, those hooks are + * called automatically by this routine. + * + * Devices with legacy power management (no standard PCI PM capabilities) + * always require such platform hooks. + * + * RETURN VALUE: + * 0 is returned on success + * -EINVAL is returned if device is not supposed to wake up the system + * Error code depending on the platform is returned if both the platform and + * the native mechanism fail to enable the generation of wake-up events + */ +int pci_enable_wake(struct pci_dev *dev, pci_power_t state, int enable) +{ + int error = 0; + bool pme_done = false; + + if (!device_may_wakeup(&dev->dev)) + return -EINVAL; + + /* + * According to "PCI System Architecture" 4th ed. by Tom Shanley & Don + * Anderson we should be doing PME# wake enable followed by ACPI wake + * enable. To disable wake-up we call the platform first, for symmetry. + */ + + if (!enable && platform_pci_can_wakeup(dev)) + error = platform_pci_sleep_wake(dev, false); + + if (!enable || pci_pme_capable(dev, state)) { + pci_pme_active(dev, enable); + pme_done = true; + } + + if (enable && platform_pci_can_wakeup(dev)) + error = platform_pci_sleep_wake(dev, true); + + return pme_done ? 0 : error; +} + +/** + * pci_wake_from_d3 - enable/disable device to wake up from D3_hot or D3_cold + * @dev: PCI device to prepare + * @enable: True to enable wake-up event generation; false to disable + * + * Many drivers want the device to wake up the system from D3_hot or D3_cold + * and this function allows them to set that up cleanly - pci_enable_wake() + * should not be called twice in a row to enable wake-up due to PCI PM vs ACPI + * ordering constraints. + * + * This function only returns error code if the device is not capable of + * generating PME# from both D3_hot and D3_cold, and the platform is unable to + * enable wake-up power for it. + */ +int pci_wake_from_d3(struct pci_dev *dev, bool enable) +{ + return pci_pme_capable(dev, PCI_D3cold) ? + pci_enable_wake(dev, PCI_D3cold, enable) : + pci_enable_wake(dev, PCI_D3hot, enable); +} + +/** + * pci_target_state - find an appropriate low power state for a given PCI dev + * @dev: PCI device + * + * Use underlying platform code to find a supported low power state for @dev. + * If the platform can't manage @dev, return the deepest state from which it + * can generate wake events, based on any available PME info. + */ +pci_power_t pci_target_state(struct pci_dev *dev) +{ + pci_power_t target_state = PCI_D3hot; + + if (platform_pci_power_manageable(dev)) { + /* + * Call the platform to choose the target state of the device + * and enable wake-up from this state if supported. + */ + pci_power_t state = platform_pci_choose_state(dev); + + switch (state) { + case PCI_POWER_ERROR: + case PCI_UNKNOWN: + break; + case PCI_D1: + case PCI_D2: + if (pci_no_d1d2(dev)) + break; + default: + target_state = state; + } + } else if (device_may_wakeup(&dev->dev)) { + /* + * Find the deepest state from which the device can generate + * wake-up events, make it the target state and enable device + * to generate PME#. + */ + if (!dev->pm_cap) + return PCI_POWER_ERROR; + + if (dev->pme_support) { + while (target_state + && !(dev->pme_support & (1 << target_state))) + target_state--; + } + } + + return target_state; +} + +/** + * pci_prepare_to_sleep - prepare PCI device for system-wide transition into a sleep state + * @dev: Device to handle. + * + * Choose the power state appropriate for the device depending on whether + * it can wake up the system and/or is power manageable by the platform + * (PCI_D3hot is the default) and put the device into that state. + */ +int pci_prepare_to_sleep(struct pci_dev *dev) +{ + pci_power_t target_state = pci_target_state(dev); + int error; + + if (target_state == PCI_POWER_ERROR) + return -EIO; + + pci_enable_wake(dev, target_state, true); + + error = pci_set_power_state(dev, target_state); + + if (error) + pci_enable_wake(dev, target_state, false); + + return error; +} + +/** + * pci_back_from_sleep - turn PCI device on during system-wide transition into working state + * @dev: Device to handle. + * + * Disable device's sytem wake-up capability and put it into D0. + */ +int pci_back_from_sleep(struct pci_dev *dev) +{ + pci_enable_wake(dev, PCI_D0, false); + return pci_set_power_state(dev, PCI_D0); +} + +/** + * pci_pm_init - Initialize PM functions of given PCI device + * @dev: PCI device to handle. + */ +void pci_pm_init(struct pci_dev *dev) +{ + int pm; + u16 pmc; + + dev->pm_cap = 0; + + /* find PCI PM capability in list */ + pm = pci_find_capability(dev, PCI_CAP_ID_PM); + if (!pm) + return; + /* Check device's ability to generate PME# */ + pci_read_config_word(dev, pm + PCI_PM_PMC, &pmc); + + if ((pmc & PCI_PM_CAP_VER_MASK) > 3) { + dev_err(&dev->dev, "unsupported PM cap regs version (%u)\n", + pmc & PCI_PM_CAP_VER_MASK); + return; + } + + dev->pm_cap = pm; + + dev->d1_support = false; + dev->d2_support = false; + if (!pci_no_d1d2(dev)) { + if (pmc & PCI_PM_CAP_D1) + dev->d1_support = true; + if (pmc & PCI_PM_CAP_D2) + dev->d2_support = true; + + if (dev->d1_support || dev->d2_support) + dev_printk(KERN_DEBUG, &dev->dev, "supports%s%s\n", + dev->d1_support ? " D1" : "", + dev->d2_support ? " D2" : ""); + } + + pmc &= PCI_PM_CAP_PME_MASK; + if (pmc) { + dev_info(&dev->dev, "PME# supported from%s%s%s%s%s\n", + (pmc & PCI_PM_CAP_PME_D0) ? " D0" : "", + (pmc & PCI_PM_CAP_PME_D1) ? " D1" : "", + (pmc & PCI_PM_CAP_PME_D2) ? " D2" : "", + (pmc & PCI_PM_CAP_PME_D3) ? " D3hot" : "", + (pmc & PCI_PM_CAP_PME_D3cold) ? " D3cold" : ""); + dev->pme_support = pmc >> PCI_PM_CAP_PME_SHIFT; + /* + * Make device's PM flags reflect the wake-up capability, but + * let the user space enable it to wake up the system as needed. + */ + device_set_wakeup_capable(&dev->dev, true); + device_set_wakeup_enable(&dev->dev, false); + /* Disable the PME# generation functionality */ + pci_pme_active(dev, false); + } else { + dev->pme_support = 0; + } +} + +/** + * pci_enable_ari - enable ARI forwarding if hardware support it + * @dev: the PCI device + */ +void pci_enable_ari(struct pci_dev *dev) +{ + int pos; + u32 cap; + u16 ctrl; + struct pci_dev *bridge; + + if (!dev->is_pcie || dev->devfn) + return; + + pos = pci_find_ext_capability(dev, PCI_EXT_CAP_ID_ARI); + if (!pos) + return; + + bridge = dev->bus->self; + if (!bridge || !bridge->is_pcie) + return; + + pos = pci_find_capability(bridge, PCI_CAP_ID_EXP); + if (!pos) + return; + + pci_read_config_dword(bridge, pos + PCI_EXP_DEVCAP2, &cap); + if (!(cap & PCI_EXP_DEVCAP2_ARI)) + return; + + pci_read_config_word(bridge, pos + PCI_EXP_DEVCTL2, &ctrl); + ctrl |= PCI_EXP_DEVCTL2_ARI; + pci_write_config_word(bridge, pos + PCI_EXP_DEVCTL2, ctrl); + + bridge->ari_enabled = 1; +} + +int +pci_get_interrupt_pin(struct pci_dev *dev, struct pci_dev **bridge) +{ + u8 pin; + + pin = dev->pin; + if (!pin) + return -1; + pin--; + while (dev->bus->self) { + pin = (pin + PCI_SLOT(dev->devfn)) % 4; + dev = dev->bus->self; + } + *bridge = dev; + return pin; +} + +/** + * pci_release_region - Release a PCI bar + * @pdev: PCI device whose resources were previously reserved by pci_request_region + * @bar: BAR to release + * + * Releases the PCI I/O and memory resources previously reserved by a + * successful call to pci_request_region. Call this function only + * after all use of the PCI regions has ceased. + */ +void pci_release_region(struct pci_dev *pdev, int bar) +{ + struct pci_devres *dr; + + if (pci_resource_len(pdev, bar) == 0) + return; + if (pci_resource_flags(pdev, bar) & IORESOURCE_IO) + release_region(pci_resource_start(pdev, bar), + pci_resource_len(pdev, bar)); + else if (pci_resource_flags(pdev, bar) & IORESOURCE_MEM) + release_mem_region(pci_resource_start(pdev, bar), + pci_resource_len(pdev, bar)); + + dr = find_pci_dr(pdev); + if (dr) + dr->region_mask &= ~(1 << bar); +} + +/** + * pci_request_region - Reserved PCI I/O and memory resource + * @pdev: PCI device whose resources are to be reserved + * @bar: BAR to be reserved + * @res_name: Name to be associated with resource. + * + * Mark the PCI region associated with PCI device @pdev BR @bar as + * being reserved by owner @res_name. Do not access any + * address inside the PCI regions unless this call returns + * successfully. + * + * Returns 0 on success, or %EBUSY on error. A warning + * message is also printed on failure. + */ +int pci_request_region(struct pci_dev *pdev, int bar, const char *res_name) +{ + struct pci_devres *dr; + + if (pci_resource_len(pdev, bar) == 0) + return 0; + + if (pci_resource_flags(pdev, bar) & IORESOURCE_IO) { + if (!request_region(pci_resource_start(pdev, bar), + pci_resource_len(pdev, bar), res_name)) + goto err_out; + } + else if (pci_resource_flags(pdev, bar) & IORESOURCE_MEM) { + if (!request_mem_region(pci_resource_start(pdev, bar), + pci_resource_len(pdev, bar), res_name)) + goto err_out; + } + + dr = find_pci_dr(pdev); + if (dr) + dr->region_mask |= 1 << bar; + + return 0; + +err_out: + dev_warn(&pdev->dev, "BAR %d: can't reserve %s region %pR\n", + bar, + pci_resource_flags(pdev, bar) & IORESOURCE_IO ? "I/O" : "mem", + &pdev->resource[bar]); + return -EBUSY; +} + +/** + * pci_release_selected_regions - Release selected PCI I/O and memory resources + * @pdev: PCI device whose resources were previously reserved + * @bars: Bitmask of BARs to be released + * + * Release selected PCI I/O and memory resources previously reserved. + * Call this function only after all use of the PCI regions has ceased. + */ +void pci_release_selected_regions(struct pci_dev *pdev, int bars) +{ + int i; + + for (i = 0; i < 6; i++) + if (bars & (1 << i)) + pci_release_region(pdev, i); +} + +/** + * pci_request_selected_regions - Reserve selected PCI I/O and memory resources + * @pdev: PCI device whose resources are to be reserved + * @bars: Bitmask of BARs to be requested + * @res_name: Name to be associated with resource + */ +int pci_request_selected_regions(struct pci_dev *pdev, int bars, + const char *res_name) +{ + int i; + + for (i = 0; i < 6; i++) + if (bars & (1 << i)) + if(pci_request_region(pdev, i, res_name)) + goto err_out; + return 0; + +err_out: + while(--i >= 0) + if (bars & (1 << i)) + pci_release_region(pdev, i); + + return -EBUSY; +} + +/** + * pci_release_regions - Release reserved PCI I/O and memory resources + * @pdev: PCI device whose resources were previously reserved by pci_request_regions + * + * Releases all PCI I/O and memory resources previously reserved by a + * successful call to pci_request_regions. Call this function only + * after all use of the PCI regions has ceased. + */ + +void pci_release_regions(struct pci_dev *pdev) +{ + pci_release_selected_regions(pdev, (1 << 6) - 1); +} + +/** + * pci_request_regions - Reserved PCI I/O and memory resources + * @pdev: PCI device whose resources are to be reserved + * @res_name: Name to be associated with resource. + * + * Mark all PCI regions associated with PCI device @pdev as + * being reserved by owner @res_name. Do not access any + * address inside the PCI regions unless this call returns + * successfully. + * + * Returns 0 on success, or %EBUSY on error. A warning + * message is also printed on failure. + */ +int pci_request_regions(struct pci_dev *pdev, const char *res_name) +{ + return pci_request_selected_regions(pdev, ((1 << 6) - 1), res_name); +} + +/** + * pci_set_master - enables bus-mastering for device dev + * @dev: the PCI device to enable + * + * Enables bus-mastering on the device and calls pcibios_set_master() + * to do the needed arch specific settings. + */ +void +pci_set_master(struct pci_dev *dev) +{ + u16 cmd; + + pci_read_config_word(dev, PCI_COMMAND, &cmd); + if (! (cmd & PCI_COMMAND_MASTER)) { + dev_dbg(&dev->dev, "enabling bus mastering\n"); + cmd |= PCI_COMMAND_MASTER; + pci_write_config_word(dev, PCI_COMMAND, cmd); + } + dev->is_busmaster = 1; + pcibios_set_master(dev); +} + +#ifdef PCI_DISABLE_MWI +int pci_set_mwi(struct pci_dev *dev) +{ + return 0; +} + +int pci_try_set_mwi(struct pci_dev *dev) +{ + return 0; +} + +void pci_clear_mwi(struct pci_dev *dev) +{ +} + +#else + +#ifndef PCI_CACHE_LINE_BYTES +#define PCI_CACHE_LINE_BYTES L1_CACHE_BYTES +#endif + +/* This can be overridden by arch code. */ +/* Don't forget this is measured in 32-bit words, not bytes */ +u8 pci_cache_line_size = PCI_CACHE_LINE_BYTES / 4; + +/** + * pci_set_cacheline_size - ensure the CACHE_LINE_SIZE register is programmed + * @dev: the PCI device for which MWI is to be enabled + * + * Helper function for pci_set_mwi. + * Originally copied from drivers/net/acenic.c. + * Copyright 1998-2001 by Jes Sorensen, <jes@trained-monkey.org>. + * + * RETURNS: An appropriate -ERRNO error value on error, or zero for success. + */ +static int +pci_set_cacheline_size(struct pci_dev *dev) +{ + u8 cacheline_size; + + if (!pci_cache_line_size) + return -EINVAL; /* The system doesn't support MWI. */ + + /* Validate current setting: the PCI_CACHE_LINE_SIZE must be + equal to or multiple of the right value. */ + pci_read_config_byte(dev, PCI_CACHE_LINE_SIZE, &cacheline_size); + if (cacheline_size >= pci_cache_line_size && + (cacheline_size % pci_cache_line_size) == 0) + return 0; + + /* Write the correct value. */ + pci_write_config_byte(dev, PCI_CACHE_LINE_SIZE, pci_cache_line_size); + /* Read it back. */ + pci_read_config_byte(dev, PCI_CACHE_LINE_SIZE, &cacheline_size); + if (cacheline_size == pci_cache_line_size) + return 0; + + dev_printk(KERN_DEBUG, &dev->dev, "cache line size of %d is not " + "supported\n", pci_cache_line_size << 2); + + return -EINVAL; +} + +/** + * pci_set_mwi - enables memory-write-invalidate PCI transaction + * @dev: the PCI device for which MWI is enabled + * + * Enables the Memory-Write-Invalidate transaction in %PCI_COMMAND. + * + * RETURNS: An appropriate -ERRNO error value on error, or zero for success. + */ +int +pci_set_mwi(struct pci_dev *dev) +{ + int rc; + u16 cmd; + + rc = pci_set_cacheline_size(dev); + if (rc) + return rc; + + pci_read_config_word(dev, PCI_COMMAND, &cmd); + if (! (cmd & PCI_COMMAND_INVALIDATE)) { + dev_dbg(&dev->dev, "enabling Mem-Wr-Inval\n"); + cmd |= PCI_COMMAND_INVALIDATE; + pci_write_config_word(dev, PCI_COMMAND, cmd); + } + + return 0; +} + +/** + * pci_try_set_mwi - enables memory-write-invalidate PCI transaction + * @dev: the PCI device for which MWI is enabled + * + * Enables the Memory-Write-Invalidate transaction in %PCI_COMMAND. + * Callers are not required to check the return value. + * + * RETURNS: An appropriate -ERRNO error value on error, or zero for success. + */ +int pci_try_set_mwi(struct pci_dev *dev) +{ + int rc = pci_set_mwi(dev); + return rc; +} + +/** + * pci_clear_mwi - disables Memory-Write-Invalidate for device dev + * @dev: the PCI device to disable + * + * Disables PCI Memory-Write-Invalidate transaction on the device + */ +void +pci_clear_mwi(struct pci_dev *dev) +{ + u16 cmd; + + pci_read_config_word(dev, PCI_COMMAND, &cmd); + if (cmd & PCI_COMMAND_INVALIDATE) { + cmd &= ~PCI_COMMAND_INVALIDATE; + pci_write_config_word(dev, PCI_COMMAND, cmd); + } +} +#endif /* ! PCI_DISABLE_MWI */ + +/** + * pci_intx - enables/disables PCI INTx for device dev + * @pdev: the PCI device to operate on + * @enable: boolean: whether to enable or disable PCI INTx + * + * Enables/disables PCI INTx for device dev + */ +void +pci_intx(struct pci_dev *pdev, int enable) +{ + u16 pci_command, new; + + pci_read_config_word(pdev, PCI_COMMAND, &pci_command); + + if (enable) { + new = pci_command & ~PCI_COMMAND_INTX_DISABLE; + } else { + new = pci_command | PCI_COMMAND_INTX_DISABLE; + } + + if (new != pci_command) { + struct pci_devres *dr; + + pci_write_config_word(pdev, PCI_COMMAND, new); + + dr = find_pci_dr(pdev); + if (dr && !dr->restore_intx) { + dr->restore_intx = 1; + dr->orig_intx = !enable; + } + } +} + +/** + * pci_msi_off - disables any msi or msix capabilities + * @dev: the PCI device to operate on + * + * If you want to use msi see pci_enable_msi and friends. + * This is a lower level primitive that allows us to disable + * msi operation at the device level. + */ +void pci_msi_off(struct pci_dev *dev) +{ + int pos; + u16 control; + + pos = pci_find_capability(dev, PCI_CAP_ID_MSI); + if (pos) { + pci_read_config_word(dev, pos + PCI_MSI_FLAGS, &control); + control &= ~PCI_MSI_FLAGS_ENABLE; + pci_write_config_word(dev, pos + PCI_MSI_FLAGS, control); + } + pos = pci_find_capability(dev, PCI_CAP_ID_MSIX); + if (pos) { + pci_read_config_word(dev, pos + PCI_MSIX_FLAGS, &control); + control &= ~PCI_MSIX_FLAGS_ENABLE; + pci_write_config_word(dev, pos + PCI_MSIX_FLAGS, control); + } +} + +#ifndef HAVE_ARCH_PCI_SET_DMA_MASK +/* + * These can be overridden by arch-specific implementations + */ +int +pci_set_dma_mask(struct pci_dev *dev, u64 mask) +{ + if (!pci_dma_supported(dev, mask)) + return -EIO; + + dev->dma_mask = mask; + + return 0; +} + +int +pci_set_consistent_dma_mask(struct pci_dev *dev, u64 mask) +{ + if (!pci_dma_supported(dev, mask)) + return -EIO; + + dev->dev.coherent_dma_mask = mask; + + return 0; +} +#endif + +#ifndef HAVE_ARCH_PCI_SET_DMA_MAX_SEGMENT_SIZE +int pci_set_dma_max_seg_size(struct pci_dev *dev, unsigned int size) +{ + return dma_set_max_seg_size(&dev->dev, size); +} +EXPORT_SYMBOL(pci_set_dma_max_seg_size); +#endif + +#ifndef HAVE_ARCH_PCI_SET_DMA_SEGMENT_BOUNDARY +int pci_set_dma_seg_boundary(struct pci_dev *dev, unsigned long mask) +{ + return dma_set_seg_boundary(&dev->dev, mask); +} +EXPORT_SYMBOL(pci_set_dma_seg_boundary); +#endif + +/** + * pci_execute_reset_function() - Reset a PCI device function + * @dev: Device function to reset + * + * Some devices allow an individual function to be reset without affecting + * other functions in the same device. The PCI device must be responsive + * to PCI config space in order to use this function. + * + * The device function is presumed to be unused when this function is called. + * Resetting the device will make the contents of PCI configuration space + * random, so any caller of this must be prepared to reinitialise the + * device including MSI, bus mastering, BARs, decoding IO and memory spaces, + * etc. + * + * Returns 0 if the device function was successfully reset or -ENOTTY if the + * device doesn't support resetting a single function. + */ +int pci_execute_reset_function(struct pci_dev *dev) +{ + u16 status; + u32 cap; + int exppos = pci_find_capability(dev, PCI_CAP_ID_EXP); + + if (!exppos) + return -ENOTTY; + pci_read_config_dword(dev, exppos + PCI_EXP_DEVCAP, &cap); + if (!(cap & PCI_EXP_DEVCAP_FLR)) + return -ENOTTY; + + pci_block_user_cfg_access(dev); + + /* Wait for Transaction Pending bit clean */ + msleep(100); + pci_read_config_word(dev, exppos + PCI_EXP_DEVSTA, &status); + if (status & PCI_EXP_DEVSTA_TRPND) { + dev_info(&dev->dev, "Busy after 100ms while trying to reset; " + "sleeping for 1 second\n"); + ssleep(1); + pci_read_config_word(dev, exppos + PCI_EXP_DEVSTA, &status); + if (status & PCI_EXP_DEVSTA_TRPND) + dev_info(&dev->dev, "Still busy after 1s; " + "proceeding with reset anyway\n"); + } + + pci_write_config_word(dev, exppos + PCI_EXP_DEVCTL, + PCI_EXP_DEVCTL_BCR_FLR); + mdelay(100); + + pci_unblock_user_cfg_access(dev); + return 0; +} +EXPORT_SYMBOL_GPL(pci_execute_reset_function); + +/** + * pci_reset_function() - quiesce and reset a PCI device function + * @dev: Device function to reset + * + * Some devices allow an individual function to be reset without affecting + * other functions in the same device. The PCI device must be responsive + * to PCI config space in order to use this function. + * + * This function does not just reset the PCI portion of a device, but + * clears all the state associated with the device. This function differs + * from pci_execute_reset_function in that it saves and restores device state + * over the reset. + * + * Returns 0 if the device function was successfully reset or -ENOTTY if the + * device doesn't support resetting a single function. + */ +int pci_reset_function(struct pci_dev *dev) +{ + u32 cap; + int exppos = pci_find_capability(dev, PCI_CAP_ID_EXP); + int r; + + if (!exppos) + return -ENOTTY; + pci_read_config_dword(dev, exppos + PCI_EXP_DEVCAP, &cap); + if (!(cap & PCI_EXP_DEVCAP_FLR)) + return -ENOTTY; + + if (!dev->msi_enabled && !dev->msix_enabled && dev->irq != 0) + disable_irq(dev->irq); + pci_save_state(dev); + + pci_write_config_word(dev, PCI_COMMAND, PCI_COMMAND_INTX_DISABLE); + + r = pci_execute_reset_function(dev); + + pci_restore_state(dev); + if (!dev->msi_enabled && !dev->msix_enabled && dev->irq != 0) + enable_irq(dev->irq); + + return r; +} +EXPORT_SYMBOL_GPL(pci_reset_function); + +/** + * pcix_get_max_mmrbc - get PCI-X maximum designed memory read byte count + * @dev: PCI device to query + * + * Returns mmrbc: maximum designed memory read count in bytes + * or appropriate error value. + */ +int pcix_get_max_mmrbc(struct pci_dev *dev) +{ + int err, cap; + u32 stat; + + cap = pci_find_capability(dev, PCI_CAP_ID_PCIX); + if (!cap) + return -EINVAL; + + err = pci_read_config_dword(dev, cap + PCI_X_STATUS, &stat); + if (err) + return -EINVAL; + + return (stat & PCI_X_STATUS_MAX_READ) >> 12; +} +EXPORT_SYMBOL(pcix_get_max_mmrbc); + +/** + * pcix_get_mmrbc - get PCI-X maximum memory read byte count + * @dev: PCI device to query + * + * Returns mmrbc: maximum memory read count in bytes + * or appropriate error value. + */ +int pcix_get_mmrbc(struct pci_dev *dev) +{ + int ret, cap; + u32 cmd; + + cap = pci_find_capability(dev, PCI_CAP_ID_PCIX); + if (!cap) + return -EINVAL; + + ret = pci_read_config_dword(dev, cap + PCI_X_CMD, &cmd); + if (!ret) + ret = 512 << ((cmd & PCI_X_CMD_MAX_READ) >> 2); + + return ret; +} +EXPORT_SYMBOL(pcix_get_mmrbc); + +/** + * pcix_set_mmrbc - set PCI-X maximum memory read byte count + * @dev: PCI device to query + * @mmrbc: maximum memory read count in bytes + * valid values are 512, 1024, 2048, 4096 + * + * If possible sets maximum memory read byte count, some bridges have erratas + * that prevent this. + */ +int pcix_set_mmrbc(struct pci_dev *dev, int mmrbc) +{ + int cap, err = -EINVAL; + u32 stat, cmd, v, o; + + if (mmrbc < 512 || mmrbc > 4096 || !is_power_of_2(mmrbc)) + goto out; + + v = ffs(mmrbc) - 10; + + cap = pci_find_capability(dev, PCI_CAP_ID_PCIX); + if (!cap) + goto out; + + err = pci_read_config_dword(dev, cap + PCI_X_STATUS, &stat); + if (err) + goto out; + + if (v > (stat & PCI_X_STATUS_MAX_READ) >> 21) + return -E2BIG; + + err = pci_read_config_dword(dev, cap + PCI_X_CMD, &cmd); + if (err) + goto out; + + o = (cmd & PCI_X_CMD_MAX_READ) >> 2; + if (o != v) { + if (v > o && dev->bus && + (dev->bus->bus_flags & PCI_BUS_FLAGS_NO_MMRBC)) + return -EIO; + + cmd &= ~PCI_X_CMD_MAX_READ; + cmd |= v << 2; + err = pci_write_config_dword(dev, cap + PCI_X_CMD, cmd); + } +out: + return err; +} +EXPORT_SYMBOL(pcix_set_mmrbc); + +/** + * pcie_get_readrq - get PCI Express read request size + * @dev: PCI device to query + * + * Returns maximum memory read request in bytes + * or appropriate error value. + */ +int pcie_get_readrq(struct pci_dev *dev) +{ + int ret, cap; + u16 ctl; + + cap = pci_find_capability(dev, PCI_CAP_ID_EXP); + if (!cap) + return -EINVAL; + + ret = pci_read_config_word(dev, cap + PCI_EXP_DEVCTL, &ctl); + if (!ret) + ret = 128 << ((ctl & PCI_EXP_DEVCTL_READRQ) >> 12); + + return ret; +} +EXPORT_SYMBOL(pcie_get_readrq); + +/** + * pcie_set_readrq - set PCI Express maximum memory read request + * @dev: PCI device to query + * @rq: maximum memory read count in bytes + * valid values are 128, 256, 512, 1024, 2048, 4096 + * + * If possible sets maximum read byte count + */ +int pcie_set_readrq(struct pci_dev *dev, int rq) +{ + int cap, err = -EINVAL; + u16 ctl, v; + + if (rq < 128 || rq > 4096 || !is_power_of_2(rq)) + goto out; + + v = (ffs(rq) - 8) << 12; + + cap = pci_find_capability(dev, PCI_CAP_ID_EXP); + if (!cap) + goto out; + + err = pci_read_config_word(dev, cap + PCI_EXP_DEVCTL, &ctl); + if (err) + goto out; + + if ((ctl & PCI_EXP_DEVCTL_READRQ) != v) { + ctl &= ~PCI_EXP_DEVCTL_READRQ; + ctl |= v; + err = pci_write_config_dword(dev, cap + PCI_EXP_DEVCTL, ctl); + } + +out: + return err; +} +EXPORT_SYMBOL(pcie_set_readrq); + +/** + * pci_select_bars - Make BAR mask from the type of resource + * @dev: the PCI device for which BAR mask is made + * @flags: resource type mask to be selected + * + * This helper routine makes bar mask from the type of resource. + */ +int pci_select_bars(struct pci_dev *dev, unsigned long flags) +{ + int i, bars = 0; + for (i = 0; i < PCI_NUM_RESOURCES; i++) + if (pci_resource_flags(dev, i) & flags) + bars |= (1 << i); + return bars; +} + +static void __devinit pci_no_domains(void) +{ +#ifdef CONFIG_PCI_DOMAINS + pci_domains_supported = 0; +#endif +} + +static int __devinit pci_init(void) +{ + struct pci_dev *dev = NULL; + + while ((dev = pci_get_device(PCI_ANY_ID, PCI_ANY_ID, dev)) != NULL) { + pci_fixup_device(pci_fixup_final, dev); + } + + msi_init(); + + return 0; +} + +static int __init pci_setup(char *str) +{ + while (str) { + char *k = strchr(str, ','); + if (k) + *k++ = 0; + if (*str && (str = pcibios_setup(str)) && *str) { + if (!strcmp(str, "nomsi")) { + pci_no_msi(); + } else if (!strcmp(str, "noaer")) { + pci_no_aer(); + } else if (!strcmp(str, "nodomains")) { + pci_no_domains(); + } else if (!strncmp(str, "cbiosize=", 9)) { + pci_cardbus_io_size = memparse(str + 9, &str); + } else if (!strncmp(str, "cbmemsize=", 10)) { + pci_cardbus_mem_size = memparse(str + 10, &str); + } else { + printk(KERN_ERR "PCI: Unknown option `%s'\n", + str); + } + } + str = k; + } + return 0; +} +early_param("pci", pci_setup); + +device_initcall(pci_init); + +EXPORT_SYMBOL(pci_reenable_device); +EXPORT_SYMBOL(pci_enable_device_io); +EXPORT_SYMBOL(pci_enable_device_mem); +EXPORT_SYMBOL(pci_enable_device); +EXPORT_SYMBOL(pcim_enable_device); +EXPORT_SYMBOL(pcim_pin_device); +EXPORT_SYMBOL(pci_disable_device); +EXPORT_SYMBOL(pci_find_capability); +EXPORT_SYMBOL(pci_bus_find_capability); +EXPORT_SYMBOL(pci_release_regions); +EXPORT_SYMBOL(pci_request_regions); +EXPORT_SYMBOL(pci_release_region); +EXPORT_SYMBOL(pci_request_region); +EXPORT_SYMBOL(pci_release_selected_regions); +EXPORT_SYMBOL(pci_request_selected_regions); +EXPORT_SYMBOL(pci_set_master); +EXPORT_SYMBOL(pci_set_mwi); +EXPORT_SYMBOL(pci_try_set_mwi); +EXPORT_SYMBOL(pci_clear_mwi); +EXPORT_SYMBOL_GPL(pci_intx); +EXPORT_SYMBOL(pci_set_dma_mask); +EXPORT_SYMBOL(pci_set_consistent_dma_mask); +EXPORT_SYMBOL(pci_assign_resource); +EXPORT_SYMBOL(pci_find_parent_resource); +EXPORT_SYMBOL(pci_select_bars); + +EXPORT_SYMBOL(pci_set_power_state); +EXPORT_SYMBOL(pci_save_state); +EXPORT_SYMBOL(pci_restore_state); +EXPORT_SYMBOL(pci_pme_capable); +EXPORT_SYMBOL(pci_pme_active); +EXPORT_SYMBOL(pci_enable_wake); +EXPORT_SYMBOL(pci_wake_from_d3); +EXPORT_SYMBOL(pci_target_state); +EXPORT_SYMBOL(pci_prepare_to_sleep); +EXPORT_SYMBOL(pci_back_from_sleep); +EXPORT_SYMBOL_GPL(pci_set_pcie_reset_state); + diff --git a/drivers/pci/pci.h b/drivers/pci/pci.h new file mode 100644 index 0000000..9de87e9 --- /dev/null +++ b/drivers/pci/pci.h @@ -0,0 +1,174 @@ +#ifndef DRIVERS_PCI_H +#define DRIVERS_PCI_H + +#define PCI_CFG_SPACE_SIZE 256 +#define PCI_CFG_SPACE_EXP_SIZE 4096 + +/* Functions internal to the PCI core code */ + +extern int pci_uevent(struct device *dev, struct kobj_uevent_env *env); +extern int pci_create_sysfs_dev_files(struct pci_dev *pdev); +extern void pci_remove_sysfs_dev_files(struct pci_dev *pdev); +extern void pci_cleanup_rom(struct pci_dev *dev); + +/** + * Firmware PM callbacks + * + * @is_manageable - returns 'true' if given device is power manageable by the + * platform firmware + * + * @set_state - invokes the platform firmware to set the device's power state + * + * @choose_state - returns PCI power state of given device preferred by the + * platform; to be used during system-wide transitions from a + * sleeping state to the working state and vice versa + * + * @can_wakeup - returns 'true' if given device is capable of waking up the + * system from a sleeping state + * + * @sleep_wake - enables/disables the system wake up capability of given device + * + * If given platform is generally capable of power managing PCI devices, all of + * these callbacks are mandatory. + */ +struct pci_platform_pm_ops { + bool (*is_manageable)(struct pci_dev *dev); + int (*set_state)(struct pci_dev *dev, pci_power_t state); + pci_power_t (*choose_state)(struct pci_dev *dev); + bool (*can_wakeup)(struct pci_dev *dev); + int (*sleep_wake)(struct pci_dev *dev, bool enable); +}; + +extern int pci_set_platform_pm(struct pci_platform_pm_ops *ops); +extern void pci_pm_init(struct pci_dev *dev); + +extern int pci_user_read_config_byte(struct pci_dev *dev, int where, u8 *val); +extern int pci_user_read_config_word(struct pci_dev *dev, int where, u16 *val); +extern int pci_user_read_config_dword(struct pci_dev *dev, int where, u32 *val); +extern int pci_user_write_config_byte(struct pci_dev *dev, int where, u8 val); +extern int pci_user_write_config_word(struct pci_dev *dev, int where, u16 val); +extern int pci_user_write_config_dword(struct pci_dev *dev, int where, u32 val); + +struct pci_vpd_ops { + int (*read)(struct pci_dev *dev, int pos, int size, char *buf); + int (*write)(struct pci_dev *dev, int pos, int size, const char *buf); + void (*release)(struct pci_dev *dev); +}; + +struct pci_vpd { + unsigned int len; + struct pci_vpd_ops *ops; + struct bin_attribute *attr; /* descriptor for sysfs VPD entry */ +}; + +extern int pci_vpd_pci22_init(struct pci_dev *dev); +static inline void pci_vpd_release(struct pci_dev *dev) +{ + if (dev->vpd) + dev->vpd->ops->release(dev); +} + +/* PCI /proc functions */ +#ifdef CONFIG_PROC_FS +extern int pci_proc_attach_device(struct pci_dev *dev); +extern int pci_proc_detach_device(struct pci_dev *dev); +extern int pci_proc_detach_bus(struct pci_bus *bus); +#else +static inline int pci_proc_attach_device(struct pci_dev *dev) { return 0; } +static inline int pci_proc_detach_device(struct pci_dev *dev) { return 0; } +static inline int pci_proc_detach_bus(struct pci_bus *bus) { return 0; } +#endif + +/* Functions for PCI Hotplug drivers to use */ +extern unsigned int pci_do_scan_bus(struct pci_bus *bus); + +#ifdef HAVE_PCI_LEGACY +extern void pci_create_legacy_files(struct pci_bus *bus); +extern void pci_remove_legacy_files(struct pci_bus *bus); +#else +static inline void pci_create_legacy_files(struct pci_bus *bus) { return; } +static inline void pci_remove_legacy_files(struct pci_bus *bus) { return; } +#endif + +/* Lock for read/write access to pci device and bus lists */ +extern struct rw_semaphore pci_bus_sem; + +extern unsigned int pci_pm_d3_delay; + +#ifdef CONFIG_PCI_MSI +void pci_no_msi(void); +extern void pci_msi_init_pci_dev(struct pci_dev *dev); +extern void __devinit msi_init(void); +#else +static inline void pci_no_msi(void) { } +static inline void pci_msi_init_pci_dev(struct pci_dev *dev) { } +static inline void msi_init(void) { } +#endif + +#ifdef CONFIG_PCIEAER +void pci_no_aer(void); +#else +static inline void pci_no_aer(void) { } +#endif + +static inline int pci_no_d1d2(struct pci_dev *dev) +{ + unsigned int parent_dstates = 0; + + if (dev->bus->self) + parent_dstates = dev->bus->self->no_d1d2; + return (dev->no_d1d2 || parent_dstates); + +} +extern int pcie_mch_quirk; +extern struct device_attribute pci_dev_attrs[]; +extern struct device_attribute dev_attr_cpuaffinity; +extern struct device_attribute dev_attr_cpulistaffinity; + +/** + * pci_match_one_device - Tell if a PCI device structure has a matching + * PCI device id structure + * @id: single PCI device id structure to match + * @dev: the PCI device structure to match against + * + * Returns the matching pci_device_id structure or %NULL if there is no match. + */ +static inline const struct pci_device_id * +pci_match_one_device(const struct pci_device_id *id, const struct pci_dev *dev) +{ + if ((id->vendor == PCI_ANY_ID || id->vendor == dev->vendor) && + (id->device == PCI_ANY_ID || id->device == dev->device) && + (id->subvendor == PCI_ANY_ID || id->subvendor == dev->subsystem_vendor) && + (id->subdevice == PCI_ANY_ID || id->subdevice == dev->subsystem_device) && + !((id->class ^ dev->class) & id->class_mask)) + return id; + return NULL; +} + +struct pci_dev *pci_find_upstream_pcie_bridge(struct pci_dev *pdev); + +/* PCI slot sysfs helper code */ +#define to_pci_slot(s) container_of(s, struct pci_slot, kobj) + +extern struct kset *pci_slots_kset; + +struct pci_slot_attribute { + struct attribute attr; + ssize_t (*show)(struct pci_slot *, char *); + ssize_t (*store)(struct pci_slot *, const char *, size_t); +}; +#define to_pci_slot_attr(s) container_of(s, struct pci_slot_attribute, attr) + +extern void pci_enable_ari(struct pci_dev *dev); +/** + * pci_ari_enabled - query ARI forwarding status + * @dev: the PCI device + * + * Returns 1 if ARI forwarding is enabled, or 0 if not enabled; + */ +static inline int pci_ari_enabled(struct pci_dev *dev) +{ + return dev->ari_enabled; +} + +#endif /* DRIVERS_PCI_H */ diff --git a/drivers/pci/pcie/Kconfig b/drivers/pci/pcie/Kconfig new file mode 100644 index 0000000..5a0c6ad --- /dev/null +++ b/drivers/pci/pcie/Kconfig @@ -0,0 +1,48 @@ +# +# PCI Express Port Bus Configuration +# +config PCIEPORTBUS + bool "PCI Express support" + depends on PCI + help + This automatically enables PCI Express Port Bus support. Users can + choose Native Hot-Plug support, Advanced Error Reporting support, + Power Management Event support and Virtual Channel support to run + on PCI Express Ports (Root or Switch). + +# +# Include service Kconfig here +# +config HOTPLUG_PCI_PCIE + tristate "PCI Express Hotplug driver" + depends on HOTPLUG_PCI && PCIEPORTBUS + help + Say Y here if you have a motherboard that supports PCI Express Native + Hotplug + + To compile this driver as a module, choose M here: the + module will be called pciehp. + + When in doubt, say N. + +source "drivers/pci/pcie/aer/Kconfig" + +# +# PCI Express ASPM +# +config PCIEASPM + bool "PCI Express ASPM support(Experimental)" + depends on PCI && EXPERIMENTAL && PCIEPORTBUS + default n + help + This enables PCI Express ASPM (Active State Power Management) and + Clock Power Management. ASPM supports state L0/L0s/L1. + + When in doubt, say N. +config PCIEASPM_DEBUG + bool "Debug PCI Express ASPM" + depends on PCIEASPM + default n + help + This enables PCI Express ASPM debug support. It will add per-device + interface to control ASPM. diff --git a/drivers/pci/pcie/Makefile b/drivers/pci/pcie/Makefile new file mode 100644 index 0000000..11f6bb1 --- /dev/null +++ b/drivers/pci/pcie/Makefile @@ -0,0 +1,13 @@ +# +# Makefile for PCI-Express PORT Driver +# + +# Build PCI Express ASPM if needed +obj-$(CONFIG_PCIEASPM) += aspm.o + +pcieportdrv-y := portdrv_core.o portdrv_pci.o portdrv_bus.o + +obj-$(CONFIG_PCIEPORTBUS) += pcieportdrv.o + +# Build PCI Express AER if needed +obj-$(CONFIG_PCIEAER) += aer/ diff --git a/drivers/pci/pcie/aer/Kconfig b/drivers/pci/pcie/aer/Kconfig new file mode 100644 index 0000000..c3bde58 --- /dev/null +++ b/drivers/pci/pcie/aer/Kconfig @@ -0,0 +1,12 @@ +# +# PCI Express Root Port Device AER Configuration +# + +config PCIEAER + boolean "Root Port Advanced Error Reporting support" + depends on PCIEPORTBUS + default y + help + This enables PCI Express Root Port Advanced Error Reporting + (AER) driver support. Error reporting messages sent to Root + Port will be handled by PCI Express AER driver. diff --git a/drivers/pci/pcie/aer/Makefile b/drivers/pci/pcie/aer/Makefile new file mode 100644 index 0000000..8da3bd8 --- /dev/null +++ b/drivers/pci/pcie/aer/Makefile @@ -0,0 +1,9 @@ +# +# Makefile for PCI-Express Root Port Advanced Error Reporting Driver +# + +obj-$(CONFIG_PCIEAER) += aerdriver.o + +aerdriver-objs := aerdrv_errprint.o aerdrv_core.o aerdrv.o +aerdriver-$(CONFIG_ACPI) += aerdrv_acpi.o + diff --git a/drivers/pci/pcie/aer/aerdrv.c b/drivers/pci/pcie/aer/aerdrv.c new file mode 100644 index 0000000..e390707 --- /dev/null +++ b/drivers/pci/pcie/aer/aerdrv.c @@ -0,0 +1,352 @@ +/* + * drivers/pci/pcie/aer/aerdrv.c + * + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file "COPYING" in the main directory of this archive + * for more details. + * + * This file implements the AER root port service driver. The driver will + * register an irq handler. When root port triggers an AER interrupt, the irq + * handler will collect root port status and schedule a work. + * + * Copyright (C) 2006 Intel Corp. + * Tom Long Nguyen (tom.l.nguyen@intel.com) + * Zhang Yanmin (yanmin.zhang@intel.com) + * + */ + +#include <linux/module.h> +#include <linux/pci.h> +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/pm.h> +#include <linux/init.h> +#include <linux/interrupt.h> +#include <linux/delay.h> +#include <linux/pcieport_if.h> + +#include "aerdrv.h" +#include "../../pci.h" + +/* + * Version Information + */ +#define DRIVER_VERSION "v1.0" +#define DRIVER_AUTHOR "tom.l.nguyen@intel.com" +#define DRIVER_DESC "Root Port Advanced Error Reporting Driver" +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); + +static int __devinit aer_probe (struct pcie_device *dev, + const struct pcie_port_service_id *id ); +static void aer_remove(struct pcie_device *dev); +static int aer_suspend(struct pcie_device *dev, pm_message_t state) +{return 0;} +static int aer_resume(struct pcie_device *dev) {return 0;} +static pci_ers_result_t aer_error_detected(struct pci_dev *dev, + enum pci_channel_state error); +static void aer_error_resume(struct pci_dev *dev); +static pci_ers_result_t aer_root_reset(struct pci_dev *dev); + +/* + * PCI Express bus's AER Root service driver data structure + */ +static struct pcie_port_service_id aer_id[] = { + { + .vendor = PCI_ANY_ID, + .device = PCI_ANY_ID, + .port_type = PCIE_RC_PORT, + .service_type = PCIE_PORT_SERVICE_AER, + }, + { /* end: all zeroes */ } +}; + +static struct pci_error_handlers aer_error_handlers = { + .error_detected = aer_error_detected, + .resume = aer_error_resume, +}; + +static struct pcie_port_service_driver aerdriver = { + .name = "aer", + .id_table = &aer_id[0], + + .probe = aer_probe, + .remove = aer_remove, + + .suspend = aer_suspend, + .resume = aer_resume, + + .err_handler = &aer_error_handlers, + + .reset_link = aer_root_reset, +}; + +static int pcie_aer_disable; + +void pci_no_aer(void) +{ + pcie_aer_disable = 1; /* has priority over 'forceload' */ +} + +/** + * aer_irq - Root Port's ISR + * @irq: IRQ assigned to Root Port + * @context: pointer to Root Port data structure + * + * Invoked when Root Port detects AER messages. + **/ +static irqreturn_t aer_irq(int irq, void *context) +{ + unsigned int status, id; + struct pcie_device *pdev = (struct pcie_device *)context; + struct aer_rpc *rpc = get_service_data(pdev); + int next_prod_idx; + unsigned long flags; + int pos; + + pos = pci_find_ext_capability(pdev->port, PCI_EXT_CAP_ID_ERR); + /* + * Must lock access to Root Error Status Reg, Root Error ID Reg, + * and Root error producer/consumer index + */ + spin_lock_irqsave(&rpc->e_lock, flags); + + /* Read error status */ + pci_read_config_dword(pdev->port, pos + PCI_ERR_ROOT_STATUS, &status); + if (!(status & ROOT_ERR_STATUS_MASKS)) { + spin_unlock_irqrestore(&rpc->e_lock, flags); + return IRQ_NONE; + } + + /* Read error source and clear error status */ + pci_read_config_dword(pdev->port, pos + PCI_ERR_ROOT_COR_SRC, &id); + pci_write_config_dword(pdev->port, pos + PCI_ERR_ROOT_STATUS, status); + + /* Store error source for later DPC handler */ + next_prod_idx = rpc->prod_idx + 1; + if (next_prod_idx == AER_ERROR_SOURCES_MAX) + next_prod_idx = 0; + if (next_prod_idx == rpc->cons_idx) { + /* + * Error Storm Condition - possibly the same error occurred. + * Drop the error. + */ + spin_unlock_irqrestore(&rpc->e_lock, flags); + return IRQ_HANDLED; + } + rpc->e_sources[rpc->prod_idx].status = status; + rpc->e_sources[rpc->prod_idx].id = id; + rpc->prod_idx = next_prod_idx; + spin_unlock_irqrestore(&rpc->e_lock, flags); + + /* Invoke DPC handler */ + schedule_work(&rpc->dpc_handler); + + return IRQ_HANDLED; +} + +/** + * aer_alloc_rpc - allocate Root Port data structure + * @dev: pointer to the pcie_dev data structure + * + * Invoked when Root Port's AER service is loaded. + **/ +static struct aer_rpc* aer_alloc_rpc(struct pcie_device *dev) +{ + struct aer_rpc *rpc; + + if (!(rpc = kzalloc(sizeof(struct aer_rpc), + GFP_KERNEL))) + return NULL; + + /* + * Initialize Root lock access, e_lock, to Root Error Status Reg, + * Root Error ID Reg, and Root error producer/consumer index. + */ + spin_lock_init(&rpc->e_lock); + + rpc->rpd = dev; + INIT_WORK(&rpc->dpc_handler, aer_isr); + rpc->prod_idx = rpc->cons_idx = 0; + mutex_init(&rpc->rpc_mutex); + init_waitqueue_head(&rpc->wait_release); + + /* Use PCIE bus function to store rpc into PCIE device */ + set_service_data(dev, rpc); + + return rpc; +} + +/** + * aer_remove - clean up resources + * @dev: pointer to the pcie_dev data structure + * + * Invoked when PCI Express bus unloads or AER probe fails. + **/ +static void aer_remove(struct pcie_device *dev) +{ + struct aer_rpc *rpc = get_service_data(dev); + + if (rpc) { + /* If register interrupt service, it must be free. */ + if (rpc->isr) + free_irq(dev->irq, dev); + + wait_event(rpc->wait_release, rpc->prod_idx == rpc->cons_idx); + + aer_delete_rootport(rpc); + set_service_data(dev, NULL); + } +} + +/** + * aer_probe - initialize resources + * @dev: pointer to the pcie_dev data structure + * @id: pointer to the service id data structure + * + * Invoked when PCI Express bus loads AER service driver. + **/ +static int __devinit aer_probe (struct pcie_device *dev, + const struct pcie_port_service_id *id ) +{ + int status; + struct aer_rpc *rpc; + struct device *device = &dev->device; + + /* Init */ + if ((status = aer_init(dev))) + return status; + + /* Alloc rpc data structure */ + if (!(rpc = aer_alloc_rpc(dev))) { + dev_printk(KERN_DEBUG, device, "alloc rpc failed\n"); + aer_remove(dev); + return -ENOMEM; + } + + /* Request IRQ ISR */ + if ((status = request_irq(dev->irq, aer_irq, IRQF_SHARED, "aerdrv", + dev))) { + dev_printk(KERN_DEBUG, device, "request IRQ failed\n"); + aer_remove(dev); + return status; + } + + rpc->isr = 1; + + aer_enable_rootport(rpc); + + return status; +} + +/** + * aer_root_reset - reset link on Root Port + * @dev: pointer to Root Port's pci_dev data structure + * + * Invoked by Port Bus driver when performing link reset at Root Port. + **/ +static pci_ers_result_t aer_root_reset(struct pci_dev *dev) +{ + u16 p2p_ctrl; + u32 status; + int pos; + + pos = pci_find_ext_capability(dev, PCI_EXT_CAP_ID_ERR); + + /* Disable Root's interrupt in response to error messages */ + pci_write_config_dword(dev, pos + PCI_ERR_ROOT_COMMAND, 0); + + /* Assert Secondary Bus Reset */ + pci_read_config_word(dev, PCI_BRIDGE_CONTROL, &p2p_ctrl); + p2p_ctrl |= PCI_CB_BRIDGE_CTL_CB_RESET; + pci_write_config_word(dev, PCI_BRIDGE_CONTROL, p2p_ctrl); + + /* De-assert Secondary Bus Reset */ + p2p_ctrl &= ~PCI_CB_BRIDGE_CTL_CB_RESET; + pci_write_config_word(dev, PCI_BRIDGE_CONTROL, p2p_ctrl); + + /* + * System software must wait for at least 100ms from the end + * of a reset of one or more device before it is permitted + * to issue Configuration Requests to those devices. + */ + msleep(200); + dev_printk(KERN_DEBUG, &dev->dev, "Root Port link has been reset\n"); + + /* Enable Root Port's interrupt in response to error messages */ + pci_read_config_dword(dev, pos + PCI_ERR_ROOT_STATUS, &status); + pci_write_config_dword(dev, pos + PCI_ERR_ROOT_STATUS, status); + pci_write_config_dword(dev, + pos + PCI_ERR_ROOT_COMMAND, + ROOT_PORT_INTR_ON_MESG_MASK); + + return PCI_ERS_RESULT_RECOVERED; +} + +/** + * aer_error_detected - update severity status + * @dev: pointer to Root Port's pci_dev data structure + * @error: error severity being notified by port bus + * + * Invoked by Port Bus driver during error recovery. + **/ +static pci_ers_result_t aer_error_detected(struct pci_dev *dev, + enum pci_channel_state error) +{ + /* Root Port has no impact. Always recovers. */ + return PCI_ERS_RESULT_CAN_RECOVER; +} + +/** + * aer_error_resume - clean up corresponding error status bits + * @dev: pointer to Root Port's pci_dev data structure + * + * Invoked by Port Bus driver during nonfatal recovery. + **/ +static void aer_error_resume(struct pci_dev *dev) +{ + int pos; + u32 status, mask; + u16 reg16; + + /* Clean up Root device status */ + pos = pci_find_capability(dev, PCI_CAP_ID_EXP); + pci_read_config_word(dev, pos + PCI_EXP_DEVSTA, ®16); + pci_write_config_word(dev, pos + PCI_EXP_DEVSTA, reg16); + + /* Clean AER Root Error Status */ + pos = pci_find_ext_capability(dev, PCI_EXT_CAP_ID_ERR); + pci_read_config_dword(dev, pos + PCI_ERR_UNCOR_STATUS, &status); + pci_read_config_dword(dev, pos + PCI_ERR_UNCOR_SEVER, &mask); + if (dev->error_state == pci_channel_io_normal) + status &= ~mask; /* Clear corresponding nonfatal bits */ + else + status &= mask; /* Clear corresponding fatal bits */ + pci_write_config_dword(dev, pos + PCI_ERR_UNCOR_STATUS, status); +} + +/** + * aer_service_init - register AER root service driver + * + * Invoked when AER root service driver is loaded. + **/ +static int __init aer_service_init(void) +{ + if (pcie_aer_disable) + return -ENXIO; + return pcie_port_service_register(&aerdriver); +} + +/** + * aer_service_exit - unregister AER root service driver + * + * Invoked when AER root service driver is unloaded. + **/ +static void __exit aer_service_exit(void) +{ + pcie_port_service_unregister(&aerdriver); +} + +module_init(aer_service_init); +module_exit(aer_service_exit); diff --git a/drivers/pci/pcie/aer/aerdrv.h b/drivers/pci/pcie/aer/aerdrv.h new file mode 100644 index 0000000..c7ad68b --- /dev/null +++ b/drivers/pci/pcie/aer/aerdrv.h @@ -0,0 +1,130 @@ +/* + * Copyright (C) 2006 Intel Corp. + * Tom Long Nguyen (tom.l.nguyen@intel.com) + * Zhang Yanmin (yanmin.zhang@intel.com) + * + */ + +#ifndef _AERDRV_H_ +#define _AERDRV_H_ + +#include <linux/workqueue.h> +#include <linux/pcieport_if.h> +#include <linux/aer.h> + +#define AER_NONFATAL 0 +#define AER_FATAL 1 +#define AER_CORRECTABLE 2 +#define AER_UNCORRECTABLE 4 +#define AER_ERROR_MASK 0x001fffff +#define AER_ERROR(d) (d & AER_ERROR_MASK) + +/* Root Error Status Register Bits */ +#define ROOT_ERR_STATUS_MASKS 0x0f + +#define SYSTEM_ERROR_INTR_ON_MESG_MASK (PCI_EXP_RTCTL_SECEE| \ + PCI_EXP_RTCTL_SENFEE| \ + PCI_EXP_RTCTL_SEFEE) +#define ROOT_PORT_INTR_ON_MESG_MASK (PCI_ERR_ROOT_CMD_COR_EN| \ + PCI_ERR_ROOT_CMD_NONFATAL_EN| \ + PCI_ERR_ROOT_CMD_FATAL_EN) +#define ERR_COR_ID(d) (d & 0xffff) +#define ERR_UNCOR_ID(d) (d >> 16) + +#define AER_SUCCESS 0 +#define AER_UNSUCCESS 1 +#define AER_ERROR_SOURCES_MAX 100 + +#define AER_LOG_TLP_MASKS (PCI_ERR_UNC_POISON_TLP| \ + PCI_ERR_UNC_ECRC| \ + PCI_ERR_UNC_UNSUP| \ + PCI_ERR_UNC_COMP_ABORT| \ + PCI_ERR_UNC_UNX_COMP| \ + PCI_ERR_UNC_MALF_TLP) + +/* AER Error Info Flags */ +#define AER_TLP_HEADER_VALID_FLAG 0x00000001 +#define AER_MULTI_ERROR_VALID_FLAG 0x00000002 + +#define ERR_CORRECTABLE_ERROR_MASK 0x000031c1 +#define ERR_UNCORRECTABLE_ERROR_MASK 0x001ff010 + +struct header_log_regs { + unsigned int dw0; + unsigned int dw1; + unsigned int dw2; + unsigned int dw3; +}; + +struct aer_err_info { + int severity; /* 0:NONFATAL | 1:FATAL | 2:COR */ + int flags; + unsigned int status; /* COR/UNCOR Error Status */ + struct header_log_regs tlp; /* TLP Header */ +}; + +struct aer_err_source { + unsigned int status; + unsigned int id; +}; + +struct aer_rpc { + struct pcie_device *rpd; /* Root Port device */ + struct work_struct dpc_handler; + struct aer_err_source e_sources[AER_ERROR_SOURCES_MAX]; + unsigned short prod_idx; /* Error Producer Index */ + unsigned short cons_idx; /* Error Consumer Index */ + int isr; + spinlock_t e_lock; /* + * Lock access to Error Status/ID Regs + * and error producer/consumer index + */ + struct mutex rpc_mutex; /* + * only one thread could do + * recovery on the same + * root port hierarchy + */ + wait_queue_head_t wait_release; +}; + +struct aer_broadcast_data { + enum pci_channel_state state; + enum pci_ers_result result; +}; + +static inline pci_ers_result_t merge_result(enum pci_ers_result orig, + enum pci_ers_result new) +{ + switch (orig) { + case PCI_ERS_RESULT_CAN_RECOVER: + case PCI_ERS_RESULT_RECOVERED: + orig = new; + break; + case PCI_ERS_RESULT_DISCONNECT: + if (new == PCI_ERS_RESULT_NEED_RESET) + orig = new; + break; + default: + break; + } + + return orig; +} + +extern struct bus_type pcie_port_bus_type; +extern void aer_enable_rootport(struct aer_rpc *rpc); +extern void aer_delete_rootport(struct aer_rpc *rpc); +extern int aer_init(struct pcie_device *dev); +extern void aer_isr(struct work_struct *work); +extern void aer_print_error(struct pci_dev *dev, struct aer_err_info *info); + +#ifdef CONFIG_ACPI +extern int aer_osc_setup(struct pcie_device *pciedev); +#else +static inline int aer_osc_setup(struct pcie_device *pciedev) +{ + return 0; +} +#endif + +#endif //_AERDRV_H_ diff --git a/drivers/pci/pcie/aer/aerdrv_acpi.c b/drivers/pci/pcie/aer/aerdrv_acpi.c new file mode 100644 index 0000000..6dd7b13 --- /dev/null +++ b/drivers/pci/pcie/aer/aerdrv_acpi.c @@ -0,0 +1,56 @@ +/* + * Access ACPI _OSC method + * + * Copyright (C) 2006 Intel Corp. + * Tom Long Nguyen (tom.l.nguyen@intel.com) + * Zhang Yanmin (yanmin.zhang@intel.com) + * + */ + +#include <linux/module.h> +#include <linux/pci.h> +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/pm.h> +#include <linux/suspend.h> +#include <linux/acpi.h> +#include <linux/pci-acpi.h> +#include <linux/delay.h> +#include "aerdrv.h" + +/** + * aer_osc_setup - run ACPI _OSC method + * @pciedev: pcie_device which AER is being enabled on + * + * @return: Zero on success. Nonzero otherwise. + * + * Invoked when PCIE bus loads AER service driver. To avoid conflict with + * BIOS AER support requires BIOS to yield AER control to OS native driver. + **/ +int aer_osc_setup(struct pcie_device *pciedev) +{ + acpi_status status = AE_NOT_FOUND; + struct pci_dev *pdev = pciedev->port; + acpi_handle handle = NULL; + + if (acpi_pci_disabled) + return -1; + + handle = acpi_find_root_bridge_handle(pdev); + if (handle) { + pcie_osc_support_set(OSC_EXT_PCI_CONFIG_SUPPORT); + status = pci_osc_control_set(handle, + OSC_PCI_EXPRESS_AER_CONTROL | + OSC_PCI_EXPRESS_CAP_STRUCTURE_CONTROL); + } + + if (ACPI_FAILURE(status)) { + dev_printk(KERN_DEBUG, &pciedev->device, "AER service couldn't " + "init device: %s\n", + (status == AE_SUPPORT || status == AE_NOT_FOUND) ? + "no _OSC support" : "_OSC failed"); + return -1; + } + + return 0; +} diff --git a/drivers/pci/pcie/aer/aerdrv_core.c b/drivers/pci/pcie/aer/aerdrv_core.c new file mode 100644 index 0000000..d0c9736 --- /dev/null +++ b/drivers/pci/pcie/aer/aerdrv_core.c @@ -0,0 +1,765 @@ +/* + * drivers/pci/pcie/aer/aerdrv_core.c + * + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file "COPYING" in the main directory of this archive + * for more details. + * + * This file implements the core part of PCI-Express AER. When an pci-express + * error is delivered, an error message will be collected and printed to + * console, then, an error recovery procedure will be executed by following + * the pci error recovery rules. + * + * Copyright (C) 2006 Intel Corp. + * Tom Long Nguyen (tom.l.nguyen@intel.com) + * Zhang Yanmin (yanmin.zhang@intel.com) + * + */ + +#include <linux/module.h> +#include <linux/pci.h> +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/pm.h> +#include <linux/suspend.h> +#include <linux/delay.h> +#include "aerdrv.h" + +static int forceload; +module_param(forceload, bool, 0); + +int pci_enable_pcie_error_reporting(struct pci_dev *dev) +{ + u16 reg16 = 0; + int pos; + + pos = pci_find_ext_capability(dev, PCI_EXT_CAP_ID_ERR); + if (!pos) + return -EIO; + + pos = pci_find_capability(dev, PCI_CAP_ID_EXP); + if (!pos) + return -EIO; + + pci_read_config_word(dev, pos+PCI_EXP_DEVCTL, ®16); + reg16 = reg16 | + PCI_EXP_DEVCTL_CERE | + PCI_EXP_DEVCTL_NFERE | + PCI_EXP_DEVCTL_FERE | + PCI_EXP_DEVCTL_URRE; + pci_write_config_word(dev, pos+PCI_EXP_DEVCTL, + reg16); + return 0; +} + +int pci_disable_pcie_error_reporting(struct pci_dev *dev) +{ + u16 reg16 = 0; + int pos; + + pos = pci_find_capability(dev, PCI_CAP_ID_EXP); + if (!pos) + return -EIO; + + pci_read_config_word(dev, pos+PCI_EXP_DEVCTL, ®16); + reg16 = reg16 & ~(PCI_EXP_DEVCTL_CERE | + PCI_EXP_DEVCTL_NFERE | + PCI_EXP_DEVCTL_FERE | + PCI_EXP_DEVCTL_URRE); + pci_write_config_word(dev, pos+PCI_EXP_DEVCTL, + reg16); + return 0; +} + +int pci_cleanup_aer_uncorrect_error_status(struct pci_dev *dev) +{ + int pos; + u32 status, mask; + + pos = pci_find_ext_capability(dev, PCI_EXT_CAP_ID_ERR); + if (!pos) + return -EIO; + + pci_read_config_dword(dev, pos + PCI_ERR_UNCOR_STATUS, &status); + pci_read_config_dword(dev, pos + PCI_ERR_UNCOR_SEVER, &mask); + if (dev->error_state == pci_channel_io_normal) + status &= ~mask; /* Clear corresponding nonfatal bits */ + else + status &= mask; /* Clear corresponding fatal bits */ + pci_write_config_dword(dev, pos + PCI_ERR_UNCOR_STATUS, status); + + return 0; +} + +#if 0 +int pci_cleanup_aer_correct_error_status(struct pci_dev *dev) +{ + int pos; + u32 status; + + pos = pci_find_ext_capability(dev, PCI_EXT_CAP_ID_ERR); + if (!pos) + return -EIO; + + pci_read_config_dword(dev, pos + PCI_ERR_COR_STATUS, &status); + pci_write_config_dword(dev, pos + PCI_ERR_COR_STATUS, status); + + return 0; +} +#endif /* 0 */ + + +static void set_device_error_reporting(struct pci_dev *dev, void *data) +{ + bool enable = *((bool *)data); + + if (dev->pcie_type != PCIE_RC_PORT && + dev->pcie_type != PCIE_SW_UPSTREAM_PORT && + dev->pcie_type != PCIE_SW_DOWNSTREAM_PORT) + return; + + if (enable) + pci_enable_pcie_error_reporting(dev); + else + pci_disable_pcie_error_reporting(dev); +} + +/** + * set_downstream_devices_error_reporting - enable/disable the error reporting bits on the root port and its downstream ports. + * @dev: pointer to root port's pci_dev data structure + * @enable: true = enable error reporting, false = disable error reporting. + */ +static void set_downstream_devices_error_reporting(struct pci_dev *dev, + bool enable) +{ + set_device_error_reporting(dev, &enable); + pci_walk_bus(dev->subordinate, set_device_error_reporting, &enable); +} + +static int find_device_iter(struct device *device, void *data) +{ + struct pci_dev *dev; + u16 id = *(unsigned long *)data; + u8 secondary, subordinate, d_bus = id >> 8; + + if (device->bus == &pci_bus_type) { + dev = to_pci_dev(device); + if (id == ((dev->bus->number << 8) | dev->devfn)) { + /* + * Device ID match + */ + *(unsigned long*)data = (unsigned long)device; + return 1; + } + + /* + * If device is P2P, check if it is an upstream? + */ + if (dev->hdr_type & PCI_HEADER_TYPE_BRIDGE) { + pci_read_config_byte(dev, PCI_SECONDARY_BUS, + &secondary); + pci_read_config_byte(dev, PCI_SUBORDINATE_BUS, + &subordinate); + if (d_bus >= secondary && d_bus <= subordinate) { + *(unsigned long*)data = (unsigned long)device; + return 1; + } + } + } + + return 0; +} + +/** + * find_source_device - search through device hierarchy for source device + * @parent: pointer to Root Port pci_dev data structure + * @id: device ID of agent who sends an error message to this Root Port + * + * Invoked when error is detected at the Root Port. + */ +static struct device* find_source_device(struct pci_dev *parent, u16 id) +{ + struct pci_dev *dev = parent; + struct device *device; + unsigned long device_addr; + int status; + + /* Is Root Port an agent that sends error message? */ + if (id == ((dev->bus->number << 8) | dev->devfn)) + return &dev->dev; + + do { + device_addr = id; + if ((status = device_for_each_child(&dev->dev, + &device_addr, find_device_iter))) { + device = (struct device*)device_addr; + dev = to_pci_dev(device); + if (id == ((dev->bus->number << 8) | dev->devfn)) + return device; + } + }while (status); + + return NULL; +} + +static void report_error_detected(struct pci_dev *dev, void *data) +{ + pci_ers_result_t vote; + struct pci_error_handlers *err_handler; + struct aer_broadcast_data *result_data; + result_data = (struct aer_broadcast_data *) data; + + dev->error_state = result_data->state; + + if (!dev->driver || + !dev->driver->err_handler || + !dev->driver->err_handler->error_detected) { + if (result_data->state == pci_channel_io_frozen && + !(dev->hdr_type & PCI_HEADER_TYPE_BRIDGE)) { + /* + * In case of fatal recovery, if one of down- + * stream device has no driver. We might be + * unable to recover because a later insmod + * of a driver for this device is unaware of + * its hw state. + */ + dev_printk(KERN_DEBUG, &dev->dev, "device has %s\n", + dev->driver ? + "no AER-aware driver" : "no driver"); + } + return; + } + + err_handler = dev->driver->err_handler; + vote = err_handler->error_detected(dev, result_data->state); + result_data->result = merge_result(result_data->result, vote); + return; +} + +static void report_mmio_enabled(struct pci_dev *dev, void *data) +{ + pci_ers_result_t vote; + struct pci_error_handlers *err_handler; + struct aer_broadcast_data *result_data; + result_data = (struct aer_broadcast_data *) data; + + if (!dev->driver || + !dev->driver->err_handler || + !dev->driver->err_handler->mmio_enabled) + return; + + err_handler = dev->driver->err_handler; + vote = err_handler->mmio_enabled(dev); + result_data->result = merge_result(result_data->result, vote); + return; +} + +static void report_slot_reset(struct pci_dev *dev, void *data) +{ + pci_ers_result_t vote; + struct pci_error_handlers *err_handler; + struct aer_broadcast_data *result_data; + result_data = (struct aer_broadcast_data *) data; + + if (!dev->driver || + !dev->driver->err_handler || + !dev->driver->err_handler->slot_reset) + return; + + err_handler = dev->driver->err_handler; + vote = err_handler->slot_reset(dev); + result_data->result = merge_result(result_data->result, vote); + return; +} + +static void report_resume(struct pci_dev *dev, void *data) +{ + struct pci_error_handlers *err_handler; + + dev->error_state = pci_channel_io_normal; + + if (!dev->driver || + !dev->driver->err_handler || + !dev->driver->err_handler->resume) + return; + + err_handler = dev->driver->err_handler; + err_handler->resume(dev); + return; +} + +/** + * broadcast_error_message - handle message broadcast to downstream drivers + * @dev: pointer to from where in a hierarchy message is broadcasted down + * @state: error state + * @error_mesg: message to print + * @cb: callback to be broadcasted + * + * Invoked during error recovery process. Once being invoked, the content + * of error severity will be broadcasted to all downstream drivers in a + * hierarchy in question. + */ +static pci_ers_result_t broadcast_error_message(struct pci_dev *dev, + enum pci_channel_state state, + char *error_mesg, + void (*cb)(struct pci_dev *, void *)) +{ + struct aer_broadcast_data result_data; + + dev_printk(KERN_DEBUG, &dev->dev, "broadcast %s message\n", error_mesg); + result_data.state = state; + if (cb == report_error_detected) + result_data.result = PCI_ERS_RESULT_CAN_RECOVER; + else + result_data.result = PCI_ERS_RESULT_RECOVERED; + + if (dev->hdr_type & PCI_HEADER_TYPE_BRIDGE) { + /* + * If the error is reported by a bridge, we think this error + * is related to the downstream link of the bridge, so we + * do error recovery on all subordinates of the bridge instead + * of the bridge and clear the error status of the bridge. + */ + if (cb == report_error_detected) + dev->error_state = state; + pci_walk_bus(dev->subordinate, cb, &result_data); + if (cb == report_resume) { + pci_cleanup_aer_uncorrect_error_status(dev); + dev->error_state = pci_channel_io_normal; + } + } + else { + /* + * If the error is reported by an end point, we think this + * error is related to the upstream link of the end point. + */ + pci_walk_bus(dev->bus, cb, &result_data); + } + + return result_data.result; +} + +struct find_aer_service_data { + struct pcie_port_service_driver *aer_driver; + int is_downstream; +}; + +static int find_aer_service_iter(struct device *device, void *data) +{ + struct device_driver *driver; + struct pcie_port_service_driver *service_driver; + struct pcie_device *pcie_dev; + struct find_aer_service_data *result; + + result = (struct find_aer_service_data *) data; + + if (device->bus == &pcie_port_bus_type) { + pcie_dev = to_pcie_device(device); + if (pcie_dev->id.port_type == PCIE_SW_DOWNSTREAM_PORT) + result->is_downstream = 1; + + driver = device->driver; + if (driver) { + service_driver = to_service_driver(driver); + if (service_driver->id_table->service_type == + PCIE_PORT_SERVICE_AER) { + result->aer_driver = service_driver; + return 1; + } + } + } + + return 0; +} + +static void find_aer_service(struct pci_dev *dev, + struct find_aer_service_data *data) +{ + int retval; + retval = device_for_each_child(&dev->dev, data, find_aer_service_iter); +} + +static pci_ers_result_t reset_link(struct pcie_device *aerdev, + struct pci_dev *dev) +{ + struct pci_dev *udev; + pci_ers_result_t status; + struct find_aer_service_data data; + + if (dev->hdr_type & PCI_HEADER_TYPE_BRIDGE) + udev = dev; + else + udev= dev->bus->self; + + data.is_downstream = 0; + data.aer_driver = NULL; + find_aer_service(udev, &data); + + /* + * Use the aer driver of the error agent firstly. + * If it hasn't the aer driver, use the root port's + */ + if (!data.aer_driver || !data.aer_driver->reset_link) { + if (data.is_downstream && + aerdev->device.driver && + to_service_driver(aerdev->device.driver)->reset_link) { + data.aer_driver = + to_service_driver(aerdev->device.driver); + } else { + dev_printk(KERN_DEBUG, &dev->dev, "no link-reset " + "support\n"); + return PCI_ERS_RESULT_DISCONNECT; + } + } + + status = data.aer_driver->reset_link(udev); + if (status != PCI_ERS_RESULT_RECOVERED) { + dev_printk(KERN_DEBUG, &dev->dev, "link reset at upstream " + "device %s failed\n", pci_name(udev)); + return PCI_ERS_RESULT_DISCONNECT; + } + + return status; +} + +/** + * do_recovery - handle nonfatal/fatal error recovery process + * @aerdev: pointer to a pcie_device data structure of root port + * @dev: pointer to a pci_dev data structure of agent detecting an error + * @severity: error severity type + * + * Invoked when an error is nonfatal/fatal. Once being invoked, broadcast + * error detected message to all downstream drivers within a hierarchy in + * question and return the returned code. + */ +static pci_ers_result_t do_recovery(struct pcie_device *aerdev, + struct pci_dev *dev, + int severity) +{ + pci_ers_result_t status, result = PCI_ERS_RESULT_RECOVERED; + enum pci_channel_state state; + + if (severity == AER_FATAL) + state = pci_channel_io_frozen; + else + state = pci_channel_io_normal; + + status = broadcast_error_message(dev, + state, + "error_detected", + report_error_detected); + + if (severity == AER_FATAL) { + result = reset_link(aerdev, dev); + if (result != PCI_ERS_RESULT_RECOVERED) { + /* TODO: Should panic here? */ + return result; + } + } + + if (status == PCI_ERS_RESULT_CAN_RECOVER) + status = broadcast_error_message(dev, + state, + "mmio_enabled", + report_mmio_enabled); + + if (status == PCI_ERS_RESULT_NEED_RESET) { + /* + * TODO: Should call platform-specific + * functions to reset slot before calling + * drivers' slot_reset callbacks? + */ + status = broadcast_error_message(dev, + state, + "slot_reset", + report_slot_reset); + } + + if (status == PCI_ERS_RESULT_RECOVERED) + broadcast_error_message(dev, + state, + "resume", + report_resume); + + return status; +} + +/** + * handle_error_source - handle logging error into an event log + * @aerdev: pointer to pcie_device data structure of the root port + * @dev: pointer to pci_dev data structure of error source device + * @info: comprehensive error information + * + * Invoked when an error being detected by Root Port. + */ +static void handle_error_source(struct pcie_device * aerdev, + struct pci_dev *dev, + struct aer_err_info info) +{ + pci_ers_result_t status = 0; + int pos; + + if (info.severity == AER_CORRECTABLE) { + /* + * Correctable error does not need software intevention. + * No need to go through error recovery process. + */ + pos = pci_find_ext_capability(dev, PCI_EXT_CAP_ID_ERR); + if (pos) + pci_write_config_dword(dev, pos + PCI_ERR_COR_STATUS, + info.status); + } else { + status = do_recovery(aerdev, dev, info.severity); + if (status == PCI_ERS_RESULT_RECOVERED) { + dev_printk(KERN_DEBUG, &dev->dev, "AER driver " + "successfully recovered\n"); + } else { + /* TODO: Should kernel panic here? */ + dev_printk(KERN_DEBUG, &dev->dev, "AER driver didn't " + "recover\n"); + } + } +} + +/** + * aer_enable_rootport - enable Root Port's interrupts when receiving messages + * @rpc: pointer to a Root Port data structure + * + * Invoked when PCIE bus loads AER service driver. + */ +void aer_enable_rootport(struct aer_rpc *rpc) +{ + struct pci_dev *pdev = rpc->rpd->port; + int pos, aer_pos; + u16 reg16; + u32 reg32; + + pos = pci_find_capability(pdev, PCI_CAP_ID_EXP); + /* Clear PCIE Capability's Device Status */ + pci_read_config_word(pdev, pos+PCI_EXP_DEVSTA, ®16); + pci_write_config_word(pdev, pos+PCI_EXP_DEVSTA, reg16); + + /* Disable system error generation in response to error messages */ + pci_read_config_word(pdev, pos + PCI_EXP_RTCTL, ®16); + reg16 &= ~(SYSTEM_ERROR_INTR_ON_MESG_MASK); + pci_write_config_word(pdev, pos + PCI_EXP_RTCTL, reg16); + + aer_pos = pci_find_ext_capability(pdev, PCI_EXT_CAP_ID_ERR); + /* Clear error status */ + pci_read_config_dword(pdev, aer_pos + PCI_ERR_ROOT_STATUS, ®32); + pci_write_config_dword(pdev, aer_pos + PCI_ERR_ROOT_STATUS, reg32); + pci_read_config_dword(pdev, aer_pos + PCI_ERR_COR_STATUS, ®32); + pci_write_config_dword(pdev, aer_pos + PCI_ERR_COR_STATUS, reg32); + pci_read_config_dword(pdev, aer_pos + PCI_ERR_UNCOR_STATUS, ®32); + pci_write_config_dword(pdev, aer_pos + PCI_ERR_UNCOR_STATUS, reg32); + + /* + * Enable error reporting for the root port device and downstream port + * devices. + */ + set_downstream_devices_error_reporting(pdev, true); + + /* Enable Root Port's interrupt in response to error messages */ + pci_write_config_dword(pdev, + aer_pos + PCI_ERR_ROOT_COMMAND, + ROOT_PORT_INTR_ON_MESG_MASK); +} + +/** + * disable_root_aer - disable Root Port's interrupts when receiving messages + * @rpc: pointer to a Root Port data structure + * + * Invoked when PCIE bus unloads AER service driver. + */ +static void disable_root_aer(struct aer_rpc *rpc) +{ + struct pci_dev *pdev = rpc->rpd->port; + u32 reg32; + int pos; + + /* + * Disable error reporting for the root port device and downstream port + * devices. + */ + set_downstream_devices_error_reporting(pdev, false); + + pos = pci_find_ext_capability(pdev, PCI_EXT_CAP_ID_ERR); + /* Disable Root's interrupt in response to error messages */ + pci_write_config_dword(pdev, pos + PCI_ERR_ROOT_COMMAND, 0); + + /* Clear Root's error status reg */ + pci_read_config_dword(pdev, pos + PCI_ERR_ROOT_STATUS, ®32); + pci_write_config_dword(pdev, pos + PCI_ERR_ROOT_STATUS, reg32); +} + +/** + * get_e_source - retrieve an error source + * @rpc: pointer to the root port which holds an error + * + * Invoked by DPC handler to consume an error. + */ +static struct aer_err_source* get_e_source(struct aer_rpc *rpc) +{ + struct aer_err_source *e_source; + unsigned long flags; + + /* Lock access to Root error producer/consumer index */ + spin_lock_irqsave(&rpc->e_lock, flags); + if (rpc->prod_idx == rpc->cons_idx) { + spin_unlock_irqrestore(&rpc->e_lock, flags); + return NULL; + } + e_source = &rpc->e_sources[rpc->cons_idx]; + rpc->cons_idx++; + if (rpc->cons_idx == AER_ERROR_SOURCES_MAX) + rpc->cons_idx = 0; + spin_unlock_irqrestore(&rpc->e_lock, flags); + + return e_source; +} + +static int get_device_error_info(struct pci_dev *dev, struct aer_err_info *info) +{ + int pos; + + pos = pci_find_ext_capability(dev, PCI_EXT_CAP_ID_ERR); + + /* The device might not support AER */ + if (!pos) + return AER_SUCCESS; + + if (info->severity == AER_CORRECTABLE) { + pci_read_config_dword(dev, pos + PCI_ERR_COR_STATUS, + &info->status); + if (!(info->status & ERR_CORRECTABLE_ERROR_MASK)) + return AER_UNSUCCESS; + } else if (dev->hdr_type & PCI_HEADER_TYPE_BRIDGE || + info->severity == AER_NONFATAL) { + + /* Link is still healthy for IO reads */ + pci_read_config_dword(dev, pos + PCI_ERR_UNCOR_STATUS, + &info->status); + if (!(info->status & ERR_UNCORRECTABLE_ERROR_MASK)) + return AER_UNSUCCESS; + + if (info->status & AER_LOG_TLP_MASKS) { + info->flags |= AER_TLP_HEADER_VALID_FLAG; + pci_read_config_dword(dev, + pos + PCI_ERR_HEADER_LOG, &info->tlp.dw0); + pci_read_config_dword(dev, + pos + PCI_ERR_HEADER_LOG + 4, &info->tlp.dw1); + pci_read_config_dword(dev, + pos + PCI_ERR_HEADER_LOG + 8, &info->tlp.dw2); + pci_read_config_dword(dev, + pos + PCI_ERR_HEADER_LOG + 12, &info->tlp.dw3); + } + } + + return AER_SUCCESS; +} + +/** + * aer_isr_one_error - consume an error detected by root port + * @p_device: pointer to error root port service device + * @e_src: pointer to an error source + */ +static void aer_isr_one_error(struct pcie_device *p_device, + struct aer_err_source *e_src) +{ + struct device *s_device; + struct aer_err_info e_info = {0, 0, 0,}; + int i; + u16 id; + + /* + * There is a possibility that both correctable error and + * uncorrectable error being logged. Report correctable error first. + */ + for (i = 1; i & ROOT_ERR_STATUS_MASKS ; i <<= 2) { + if (i > 4) + break; + if (!(e_src->status & i)) + continue; + + /* Init comprehensive error information */ + if (i & PCI_ERR_ROOT_COR_RCV) { + id = ERR_COR_ID(e_src->id); + e_info.severity = AER_CORRECTABLE; + } else { + id = ERR_UNCOR_ID(e_src->id); + e_info.severity = ((e_src->status >> 6) & 1); + } + if (e_src->status & + (PCI_ERR_ROOT_MULTI_COR_RCV | + PCI_ERR_ROOT_MULTI_UNCOR_RCV)) + e_info.flags |= AER_MULTI_ERROR_VALID_FLAG; + if (!(s_device = find_source_device(p_device->port, id))) { + printk(KERN_DEBUG "%s->can't find device of ID%04x\n", + __func__, id); + continue; + } + if (get_device_error_info(to_pci_dev(s_device), &e_info) == + AER_SUCCESS) { + aer_print_error(to_pci_dev(s_device), &e_info); + handle_error_source(p_device, + to_pci_dev(s_device), + e_info); + } + } +} + +/** + * aer_isr - consume errors detected by root port + * @work: definition of this work item + * + * Invoked, as DPC, when root port records new detected error + */ +void aer_isr(struct work_struct *work) +{ + struct aer_rpc *rpc = container_of(work, struct aer_rpc, dpc_handler); + struct pcie_device *p_device = rpc->rpd; + struct aer_err_source *e_src; + + mutex_lock(&rpc->rpc_mutex); + e_src = get_e_source(rpc); + while (e_src) { + aer_isr_one_error(p_device, e_src); + e_src = get_e_source(rpc); + } + mutex_unlock(&rpc->rpc_mutex); + + wake_up(&rpc->wait_release); +} + +/** + * aer_delete_rootport - disable root port aer and delete service data + * @rpc: pointer to a root port device being deleted + * + * Invoked when AER service unloaded on a specific Root Port + */ +void aer_delete_rootport(struct aer_rpc *rpc) +{ + /* Disable root port AER itself */ + disable_root_aer(rpc); + + kfree(rpc); +} + +/** + * aer_init - provide AER initialization + * @dev: pointer to AER pcie device + * + * Invoked when AER service driver is loaded. + */ +int aer_init(struct pcie_device *dev) +{ + if (aer_osc_setup(dev) && !forceload) + return -ENXIO; + + return AER_SUCCESS; +} + +EXPORT_SYMBOL_GPL(pci_enable_pcie_error_reporting); +EXPORT_SYMBOL_GPL(pci_disable_pcie_error_reporting); +EXPORT_SYMBOL_GPL(pci_cleanup_aer_uncorrect_error_status); + diff --git a/drivers/pci/pcie/aer/aerdrv_errprint.c b/drivers/pci/pcie/aer/aerdrv_errprint.c new file mode 100644 index 0000000..3933d4f --- /dev/null +++ b/drivers/pci/pcie/aer/aerdrv_errprint.c @@ -0,0 +1,248 @@ +/* + * drivers/pci/pcie/aer/aerdrv_errprint.c + * + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file "COPYING" in the main directory of this archive + * for more details. + * + * Format error messages and print them to console. + * + * Copyright (C) 2006 Intel Corp. + * Tom Long Nguyen (tom.l.nguyen@intel.com) + * Zhang Yanmin (yanmin.zhang@intel.com) + * + */ + +#include <linux/module.h> +#include <linux/pci.h> +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/pm.h> +#include <linux/suspend.h> + +#include "aerdrv.h" + +#define AER_AGENT_RECEIVER 0 +#define AER_AGENT_REQUESTER 1 +#define AER_AGENT_COMPLETER 2 +#define AER_AGENT_TRANSMITTER 3 + +#define AER_AGENT_REQUESTER_MASK (PCI_ERR_UNC_COMP_TIME| \ + PCI_ERR_UNC_UNSUP) + +#define AER_AGENT_COMPLETER_MASK PCI_ERR_UNC_COMP_ABORT + +#define AER_AGENT_TRANSMITTER_MASK(t, e) (e & (PCI_ERR_COR_REP_ROLL| \ + ((t == AER_CORRECTABLE) ? PCI_ERR_COR_REP_TIMER: 0))) + +#define AER_GET_AGENT(t, e) \ + ((e & AER_AGENT_COMPLETER_MASK) ? AER_AGENT_COMPLETER : \ + (e & AER_AGENT_REQUESTER_MASK) ? AER_AGENT_REQUESTER : \ + (AER_AGENT_TRANSMITTER_MASK(t, e)) ? AER_AGENT_TRANSMITTER : \ + AER_AGENT_RECEIVER) + +#define AER_PHYSICAL_LAYER_ERROR_MASK PCI_ERR_COR_RCVR +#define AER_DATA_LINK_LAYER_ERROR_MASK(t, e) \ + (PCI_ERR_UNC_DLP| \ + PCI_ERR_COR_BAD_TLP| \ + PCI_ERR_COR_BAD_DLLP| \ + PCI_ERR_COR_REP_ROLL| \ + ((t == AER_CORRECTABLE) ? \ + PCI_ERR_COR_REP_TIMER: 0)) + +#define AER_PHYSICAL_LAYER_ERROR 0 +#define AER_DATA_LINK_LAYER_ERROR 1 +#define AER_TRANSACTION_LAYER_ERROR 2 + +#define AER_GET_LAYER_ERROR(t, e) \ + ((e & AER_PHYSICAL_LAYER_ERROR_MASK) ? \ + AER_PHYSICAL_LAYER_ERROR : \ + (e & AER_DATA_LINK_LAYER_ERROR_MASK(t, e)) ? \ + AER_DATA_LINK_LAYER_ERROR : \ + AER_TRANSACTION_LAYER_ERROR) + +/* + * AER error strings + */ +static char* aer_error_severity_string[] = { + "Uncorrected (Non-Fatal)", + "Uncorrected (Fatal)", + "Corrected" +}; + +static char* aer_error_layer[] = { + "Physical Layer", + "Data Link Layer", + "Transaction Layer" +}; +static char* aer_correctable_error_string[] = { + "Receiver Error ", /* Bit Position 0 */ + NULL, + NULL, + NULL, + NULL, + NULL, + "Bad TLP ", /* Bit Position 6 */ + "Bad DLLP ", /* Bit Position 7 */ + "RELAY_NUM Rollover ", /* Bit Position 8 */ + NULL, + NULL, + NULL, + "Replay Timer Timeout ", /* Bit Position 12 */ + "Advisory Non-Fatal ", /* Bit Position 13 */ + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, +}; + +static char* aer_uncorrectable_error_string[] = { + NULL, + NULL, + NULL, + NULL, + "Data Link Protocol ", /* Bit Position 4 */ + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + "Poisoned TLP ", /* Bit Position 12 */ + "Flow Control Protocol ", /* Bit Position 13 */ + "Completion Timeout ", /* Bit Position 14 */ + "Completer Abort ", /* Bit Position 15 */ + "Unexpected Completion ", /* Bit Position 16 */ + "Receiver Overflow ", /* Bit Position 17 */ + "Malformed TLP ", /* Bit Position 18 */ + "ECRC ", /* Bit Position 19 */ + "Unsupported Request ", /* Bit Position 20 */ + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, +}; + +static char* aer_agent_string[] = { + "Receiver ID", + "Requester ID", + "Completer ID", + "Transmitter ID" +}; + +static char * aer_get_error_source_name(int severity, + unsigned int status, + char errmsg_buff[]) +{ + int i; + char * errmsg = NULL; + + for (i = 0; i < 32; i++) { + if (!(status & (1 << i))) + continue; + + if (severity == AER_CORRECTABLE) + errmsg = aer_correctable_error_string[i]; + else + errmsg = aer_uncorrectable_error_string[i]; + + if (!errmsg) { + sprintf(errmsg_buff, "Unknown Error Bit %2d ", i); + errmsg = errmsg_buff; + } + + break; + } + + return errmsg; +} + +static DEFINE_SPINLOCK(logbuf_lock); +static char errmsg_buff[100]; +void aer_print_error(struct pci_dev *dev, struct aer_err_info *info) +{ + char * errmsg; + int err_layer, agent; + char * loglevel; + + if (info->severity == AER_CORRECTABLE) + loglevel = KERN_WARNING; + else + loglevel = KERN_ERR; + + printk("%s+------ PCI-Express Device Error ------+\n", loglevel); + printk("%sError Severity\t\t: %s\n", loglevel, + aer_error_severity_string[info->severity]); + + if ( info->status == 0) { + printk("%sPCIE Bus Error type\t: (Unaccessible)\n", loglevel); + printk("%sUnaccessible Received\t: %s\n", loglevel, + info->flags & AER_MULTI_ERROR_VALID_FLAG ? + "Multiple" : "First"); + printk("%sUnregistered Agent ID\t: %04x\n", loglevel, + (dev->bus->number << 8) | dev->devfn); + } else { + err_layer = AER_GET_LAYER_ERROR(info->severity, info->status); + printk("%sPCIE Bus Error type\t: %s\n", loglevel, + aer_error_layer[err_layer]); + + spin_lock(&logbuf_lock); + errmsg = aer_get_error_source_name(info->severity, + info->status, + errmsg_buff); + printk("%s%s\t: %s\n", loglevel, errmsg, + info->flags & AER_MULTI_ERROR_VALID_FLAG ? + "Multiple" : "First"); + spin_unlock(&logbuf_lock); + + agent = AER_GET_AGENT(info->severity, info->status); + printk("%s%s\t\t: %04x\n", loglevel, + aer_agent_string[agent], + (dev->bus->number << 8) | dev->devfn); + + printk("%sVendorID=%04xh, DeviceID=%04xh," + " Bus=%02xh, Device=%02xh, Function=%02xh\n", + loglevel, + dev->vendor, + dev->device, + dev->bus->number, + PCI_SLOT(dev->devfn), + PCI_FUNC(dev->devfn)); + + if (info->flags & AER_TLP_HEADER_VALID_FLAG) { + unsigned char *tlp = (unsigned char *) &info->tlp; + printk("%sTLB Header:\n", loglevel); + printk("%s%02x%02x%02x%02x %02x%02x%02x%02x" + " %02x%02x%02x%02x %02x%02x%02x%02x\n", + loglevel, + *(tlp + 3), *(tlp + 2), *(tlp + 1), *tlp, + *(tlp + 7), *(tlp + 6), *(tlp + 5), *(tlp + 4), + *(tlp + 11), *(tlp + 10), *(tlp + 9), + *(tlp + 8), *(tlp + 15), *(tlp + 14), + *(tlp + 13), *(tlp + 12)); + } + } +} + diff --git a/drivers/pci/pcie/aspm.c b/drivers/pci/pcie/aspm.c new file mode 100644 index 0000000..654938a --- /dev/null +++ b/drivers/pci/pcie/aspm.c @@ -0,0 +1,967 @@ +/* + * File: drivers/pci/pcie/aspm.c + * Enabling PCIE link L0s/L1 state and Clock Power Management + * + * Copyright (C) 2007 Intel + * Copyright (C) Zhang Yanmin (yanmin.zhang@intel.com) + * Copyright (C) Shaohua Li (shaohua.li@intel.com) + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/pci.h> +#include <linux/pci_regs.h> +#include <linux/errno.h> +#include <linux/pm.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/jiffies.h> +#include <linux/pci-aspm.h> +#include "../pci.h" + +#ifdef MODULE_PARAM_PREFIX +#undef MODULE_PARAM_PREFIX +#endif +#define MODULE_PARAM_PREFIX "pcie_aspm." + +struct endpoint_state { + unsigned int l0s_acceptable_latency; + unsigned int l1_acceptable_latency; +}; + +struct pcie_link_state { + struct list_head sibiling; + struct pci_dev *pdev; + bool downstream_has_switch; + + struct pcie_link_state *parent; + struct list_head children; + struct list_head link; + + /* ASPM state */ + unsigned int support_state; + unsigned int enabled_state; + unsigned int bios_aspm_state; + /* upstream component */ + unsigned int l0s_upper_latency; + unsigned int l1_upper_latency; + /* downstream component */ + unsigned int l0s_down_latency; + unsigned int l1_down_latency; + /* Clock PM state*/ + unsigned int clk_pm_capable; + unsigned int clk_pm_enabled; + unsigned int bios_clk_state; + + /* + * A pcie downstream port only has one slot under it, so at most there + * are 8 functions + */ + struct endpoint_state endpoints[8]; +}; + +static int aspm_disabled, aspm_force; +static DEFINE_MUTEX(aspm_lock); +static LIST_HEAD(link_list); + +#define POLICY_DEFAULT 0 /* BIOS default setting */ +#define POLICY_PERFORMANCE 1 /* high performance */ +#define POLICY_POWERSAVE 2 /* high power saving */ +static int aspm_policy; +static const char *policy_str[] = { + [POLICY_DEFAULT] = "default", + [POLICY_PERFORMANCE] = "performance", + [POLICY_POWERSAVE] = "powersave" +}; + +static int policy_to_aspm_state(struct pci_dev *pdev) +{ + struct pcie_link_state *link_state = pdev->link_state; + + switch (aspm_policy) { + case POLICY_PERFORMANCE: + /* Disable ASPM and Clock PM */ + return 0; + case POLICY_POWERSAVE: + /* Enable ASPM L0s/L1 */ + return PCIE_LINK_STATE_L0S|PCIE_LINK_STATE_L1; + case POLICY_DEFAULT: + return link_state->bios_aspm_state; + } + return 0; +} + +static int policy_to_clkpm_state(struct pci_dev *pdev) +{ + struct pcie_link_state *link_state = pdev->link_state; + + switch (aspm_policy) { + case POLICY_PERFORMANCE: + /* Disable ASPM and Clock PM */ + return 0; + case POLICY_POWERSAVE: + /* Disable Clock PM */ + return 1; + case POLICY_DEFAULT: + return link_state->bios_clk_state; + } + return 0; +} + +static void pcie_set_clock_pm(struct pci_dev *pdev, int enable) +{ + struct pci_dev *child_dev; + int pos; + u16 reg16; + struct pcie_link_state *link_state = pdev->link_state; + + list_for_each_entry(child_dev, &pdev->subordinate->devices, bus_list) { + pos = pci_find_capability(child_dev, PCI_CAP_ID_EXP); + if (!pos) + return; + pci_read_config_word(child_dev, pos + PCI_EXP_LNKCTL, ®16); + if (enable) + reg16 |= PCI_EXP_LNKCTL_CLKREQ_EN; + else + reg16 &= ~PCI_EXP_LNKCTL_CLKREQ_EN; + pci_write_config_word(child_dev, pos + PCI_EXP_LNKCTL, reg16); + } + link_state->clk_pm_enabled = !!enable; +} + +static void pcie_check_clock_pm(struct pci_dev *pdev, int blacklist) +{ + int pos; + u32 reg32; + u16 reg16; + int capable = 1, enabled = 1; + struct pci_dev *child_dev; + struct pcie_link_state *link_state = pdev->link_state; + + /* All functions should have the same cap and state, take the worst */ + list_for_each_entry(child_dev, &pdev->subordinate->devices, bus_list) { + pos = pci_find_capability(child_dev, PCI_CAP_ID_EXP); + if (!pos) + return; + pci_read_config_dword(child_dev, pos + PCI_EXP_LNKCAP, ®32); + if (!(reg32 & PCI_EXP_LNKCAP_CLKPM)) { + capable = 0; + enabled = 0; + break; + } + pci_read_config_word(child_dev, pos + PCI_EXP_LNKCTL, ®16); + if (!(reg16 & PCI_EXP_LNKCTL_CLKREQ_EN)) + enabled = 0; + } + link_state->clk_pm_enabled = enabled; + link_state->bios_clk_state = enabled; + if (!blacklist) { + link_state->clk_pm_capable = capable; + pcie_set_clock_pm(pdev, policy_to_clkpm_state(pdev)); + } else { + link_state->clk_pm_capable = 0; + pcie_set_clock_pm(pdev, 0); + } +} + +static bool pcie_aspm_downstream_has_switch(struct pci_dev *pdev) +{ + struct pci_dev *child_dev; + + list_for_each_entry(child_dev, &pdev->subordinate->devices, bus_list) { + if (child_dev->pcie_type == PCI_EXP_TYPE_UPSTREAM) + return true; + } + return false; +} + +/* + * pcie_aspm_configure_common_clock: check if the 2 ends of a link + * could use common clock. If they are, configure them to use the + * common clock. That will reduce the ASPM state exit latency. + */ +static void pcie_aspm_configure_common_clock(struct pci_dev *pdev) +{ + int pos, child_pos, i = 0; + u16 reg16 = 0; + struct pci_dev *child_dev; + int same_clock = 1; + unsigned long start_jiffies; + u16 child_regs[8], parent_reg; + /* + * all functions of a slot should have the same Slot Clock + * Configuration, so just check one function + * */ + child_dev = list_entry(pdev->subordinate->devices.next, struct pci_dev, + bus_list); + BUG_ON(!child_dev->is_pcie); + + /* Check downstream component if bit Slot Clock Configuration is 1 */ + child_pos = pci_find_capability(child_dev, PCI_CAP_ID_EXP); + pci_read_config_word(child_dev, child_pos + PCI_EXP_LNKSTA, ®16); + if (!(reg16 & PCI_EXP_LNKSTA_SLC)) + same_clock = 0; + + /* Check upstream component if bit Slot Clock Configuration is 1 */ + pos = pci_find_capability(pdev, PCI_CAP_ID_EXP); + pci_read_config_word(pdev, pos + PCI_EXP_LNKSTA, ®16); + if (!(reg16 & PCI_EXP_LNKSTA_SLC)) + same_clock = 0; + + /* Configure downstream component, all functions */ + list_for_each_entry(child_dev, &pdev->subordinate->devices, bus_list) { + child_pos = pci_find_capability(child_dev, PCI_CAP_ID_EXP); + pci_read_config_word(child_dev, child_pos + PCI_EXP_LNKCTL, + ®16); + child_regs[i] = reg16; + if (same_clock) + reg16 |= PCI_EXP_LNKCTL_CCC; + else + reg16 &= ~PCI_EXP_LNKCTL_CCC; + pci_write_config_word(child_dev, child_pos + PCI_EXP_LNKCTL, + reg16); + i++; + } + + /* Configure upstream component */ + pci_read_config_word(pdev, pos + PCI_EXP_LNKCTL, ®16); + parent_reg = reg16; + if (same_clock) + reg16 |= PCI_EXP_LNKCTL_CCC; + else + reg16 &= ~PCI_EXP_LNKCTL_CCC; + pci_write_config_word(pdev, pos + PCI_EXP_LNKCTL, reg16); + + /* retrain link */ + reg16 |= PCI_EXP_LNKCTL_RL; + pci_write_config_word(pdev, pos + PCI_EXP_LNKCTL, reg16); + + /* Wait for link training end */ + /* break out after waiting for 1 second */ + start_jiffies = jiffies; + while ((jiffies - start_jiffies) < HZ) { + pci_read_config_word(pdev, pos + PCI_EXP_LNKSTA, ®16); + if (!(reg16 & PCI_EXP_LNKSTA_LT)) + break; + cpu_relax(); + } + /* training failed -> recover */ + if ((jiffies - start_jiffies) >= HZ) { + dev_printk (KERN_ERR, &pdev->dev, "ASPM: Could not configure" + " common clock\n"); + i = 0; + list_for_each_entry(child_dev, &pdev->subordinate->devices, + bus_list) { + child_pos = pci_find_capability(child_dev, + PCI_CAP_ID_EXP); + pci_write_config_word(child_dev, + child_pos + PCI_EXP_LNKCTL, + child_regs[i]); + i++; + } + pci_write_config_word(pdev, pos + PCI_EXP_LNKCTL, parent_reg); + } +} + +/* + * calc_L0S_latency: Convert L0s latency encoding to ns + */ +static unsigned int calc_L0S_latency(unsigned int latency_encoding, int ac) +{ + unsigned int ns = 64; + + if (latency_encoding == 0x7) { + if (ac) + ns = -1U; + else + ns = 5*1000; /* > 4us */ + } else + ns *= (1 << latency_encoding); + return ns; +} + +/* + * calc_L1_latency: Convert L1 latency encoding to ns + */ +static unsigned int calc_L1_latency(unsigned int latency_encoding, int ac) +{ + unsigned int ns = 1000; + + if (latency_encoding == 0x7) { + if (ac) + ns = -1U; + else + ns = 65*1000; /* > 64us */ + } else + ns *= (1 << latency_encoding); + return ns; +} + +static void pcie_aspm_get_cap_device(struct pci_dev *pdev, u32 *state, + unsigned int *l0s, unsigned int *l1, unsigned int *enabled) +{ + int pos; + u16 reg16; + u32 reg32; + unsigned int latency; + + pos = pci_find_capability(pdev, PCI_CAP_ID_EXP); + pci_read_config_dword(pdev, pos + PCI_EXP_LNKCAP, ®32); + *state = (reg32 & PCI_EXP_LNKCAP_ASPMS) >> 10; + if (*state != PCIE_LINK_STATE_L0S && + *state != (PCIE_LINK_STATE_L1|PCIE_LINK_STATE_L0S)) + *state = 0; + if (*state == 0) + return; + + latency = (reg32 & PCI_EXP_LNKCAP_L0SEL) >> 12; + *l0s = calc_L0S_latency(latency, 0); + if (*state & PCIE_LINK_STATE_L1) { + latency = (reg32 & PCI_EXP_LNKCAP_L1EL) >> 15; + *l1 = calc_L1_latency(latency, 0); + } + pci_read_config_word(pdev, pos + PCI_EXP_LNKCTL, ®16); + *enabled = reg16 & (PCIE_LINK_STATE_L0S|PCIE_LINK_STATE_L1); +} + +static void pcie_aspm_cap_init(struct pci_dev *pdev) +{ + struct pci_dev *child_dev; + u32 state, tmp; + struct pcie_link_state *link_state = pdev->link_state; + + /* upstream component states */ + pcie_aspm_get_cap_device(pdev, &link_state->support_state, + &link_state->l0s_upper_latency, + &link_state->l1_upper_latency, + &link_state->enabled_state); + /* downstream component states, all functions have the same setting */ + child_dev = list_entry(pdev->subordinate->devices.next, struct pci_dev, + bus_list); + pcie_aspm_get_cap_device(child_dev, &state, + &link_state->l0s_down_latency, + &link_state->l1_down_latency, + &tmp); + link_state->support_state &= state; + if (!link_state->support_state) + return; + link_state->enabled_state &= link_state->support_state; + link_state->bios_aspm_state = link_state->enabled_state; + + /* ENDPOINT states*/ + list_for_each_entry(child_dev, &pdev->subordinate->devices, bus_list) { + int pos; + u32 reg32; + unsigned int latency; + struct endpoint_state *ep_state = + &link_state->endpoints[PCI_FUNC(child_dev->devfn)]; + + if (child_dev->pcie_type != PCI_EXP_TYPE_ENDPOINT && + child_dev->pcie_type != PCI_EXP_TYPE_LEG_END) + continue; + + pos = pci_find_capability(child_dev, PCI_CAP_ID_EXP); + pci_read_config_dword(child_dev, pos + PCI_EXP_DEVCAP, ®32); + latency = (reg32 & PCI_EXP_DEVCAP_L0S) >> 6; + latency = calc_L0S_latency(latency, 1); + ep_state->l0s_acceptable_latency = latency; + if (link_state->support_state & PCIE_LINK_STATE_L1) { + latency = (reg32 & PCI_EXP_DEVCAP_L1) >> 9; + latency = calc_L1_latency(latency, 1); + ep_state->l1_acceptable_latency = latency; + } + } +} + +static unsigned int __pcie_aspm_check_state_one(struct pci_dev *pdev, + unsigned int state) +{ + struct pci_dev *parent_dev, *tmp_dev; + unsigned int latency, l1_latency = 0; + struct pcie_link_state *link_state; + struct endpoint_state *ep_state; + + parent_dev = pdev->bus->self; + link_state = parent_dev->link_state; + state &= link_state->support_state; + if (state == 0) + return 0; + ep_state = &link_state->endpoints[PCI_FUNC(pdev->devfn)]; + + /* + * Check latency for endpoint device. + * TBD: The latency from the endpoint to root complex vary per + * switch's upstream link state above the device. Here we just do a + * simple check which assumes all links above the device can be in L1 + * state, that is we just consider the worst case. If switch's upstream + * link can't be put into L0S/L1, then our check is too strictly. + */ + tmp_dev = pdev; + while (state & (PCIE_LINK_STATE_L0S | PCIE_LINK_STATE_L1)) { + parent_dev = tmp_dev->bus->self; + link_state = parent_dev->link_state; + if (state & PCIE_LINK_STATE_L0S) { + latency = max_t(unsigned int, + link_state->l0s_upper_latency, + link_state->l0s_down_latency); + if (latency > ep_state->l0s_acceptable_latency) + state &= ~PCIE_LINK_STATE_L0S; + } + if (state & PCIE_LINK_STATE_L1) { + latency = max_t(unsigned int, + link_state->l1_upper_latency, + link_state->l1_down_latency); + if (latency + l1_latency > + ep_state->l1_acceptable_latency) + state &= ~PCIE_LINK_STATE_L1; + } + if (!parent_dev->bus->self) /* parent_dev is a root port */ + break; + else { + /* + * parent_dev is the downstream port of a switch, make + * tmp_dev the upstream port of the switch + */ + tmp_dev = parent_dev->bus->self; + /* + * every switch on the path to root complex need 1 more + * microsecond for L1. Spec doesn't mention L0S. + */ + if (state & PCIE_LINK_STATE_L1) + l1_latency += 1000; + } + } + return state; +} + +static unsigned int pcie_aspm_check_state(struct pci_dev *pdev, + unsigned int state) +{ + struct pci_dev *child_dev; + + /* If no child, ignore the link */ + if (list_empty(&pdev->subordinate->devices)) + return state; + list_for_each_entry(child_dev, &pdev->subordinate->devices, bus_list) { + if (child_dev->pcie_type == PCI_EXP_TYPE_PCI_BRIDGE) { + /* + * If downstream component of a link is pci bridge, we + * disable ASPM for now for the link + * */ + state = 0; + break; + } + if ((child_dev->pcie_type != PCI_EXP_TYPE_ENDPOINT && + child_dev->pcie_type != PCI_EXP_TYPE_LEG_END)) + continue; + /* Device not in D0 doesn't need check latency */ + if (child_dev->current_state == PCI_D1 || + child_dev->current_state == PCI_D2 || + child_dev->current_state == PCI_D3hot || + child_dev->current_state == PCI_D3cold) + continue; + state = __pcie_aspm_check_state_one(child_dev, state); + } + return state; +} + +static void __pcie_aspm_config_one_dev(struct pci_dev *pdev, unsigned int state) +{ + u16 reg16; + int pos = pci_find_capability(pdev, PCI_CAP_ID_EXP); + + pci_read_config_word(pdev, pos + PCI_EXP_LNKCTL, ®16); + reg16 &= ~0x3; + reg16 |= state; + pci_write_config_word(pdev, pos + PCI_EXP_LNKCTL, reg16); +} + +static void __pcie_aspm_config_link(struct pci_dev *pdev, unsigned int state) +{ + struct pci_dev *child_dev; + int valid = 1; + struct pcie_link_state *link_state = pdev->link_state; + + /* If no child, disable the link */ + if (list_empty(&pdev->subordinate->devices)) + state = 0; + /* + * if the downstream component has pci bridge function, don't do ASPM + * now + */ + list_for_each_entry(child_dev, &pdev->subordinate->devices, bus_list) { + if (child_dev->pcie_type == PCI_EXP_TYPE_PCI_BRIDGE) { + valid = 0; + break; + } + } + if (!valid) + return; + + /* + * spec 2.0 suggests all functions should be configured the same + * setting for ASPM. Enabling ASPM L1 should be done in upstream + * component first and then downstream, and vice versa for disabling + * ASPM L1. Spec doesn't mention L0S. + */ + if (state & PCIE_LINK_STATE_L1) + __pcie_aspm_config_one_dev(pdev, state); + + list_for_each_entry(child_dev, &pdev->subordinate->devices, bus_list) + __pcie_aspm_config_one_dev(child_dev, state); + + if (!(state & PCIE_LINK_STATE_L1)) + __pcie_aspm_config_one_dev(pdev, state); + + link_state->enabled_state = state; +} + +static struct pcie_link_state *get_root_port_link(struct pcie_link_state *link) +{ + struct pcie_link_state *root_port_link = link; + while (root_port_link->parent) + root_port_link = root_port_link->parent; + return root_port_link; +} + +/* check the whole hierarchy, and configure each link in the hierarchy */ +static void __pcie_aspm_configure_link_state(struct pci_dev *pdev, + unsigned int state) +{ + struct pcie_link_state *link_state = pdev->link_state; + struct pcie_link_state *root_port_link = get_root_port_link(link_state); + struct pcie_link_state *leaf; + + state &= PCIE_LINK_STATE_L0S|PCIE_LINK_STATE_L1; + + /* check all links who have specific root port link */ + list_for_each_entry(leaf, &link_list, sibiling) { + if (!list_empty(&leaf->children) || + get_root_port_link(leaf) != root_port_link) + continue; + state = pcie_aspm_check_state(leaf->pdev, state); + } + /* check root port link too in case it hasn't children */ + state = pcie_aspm_check_state(root_port_link->pdev, state); + + if (link_state->enabled_state == state) + return; + + /* + * we must change the hierarchy. See comments in + * __pcie_aspm_config_link for the order + **/ + if (state & PCIE_LINK_STATE_L1) { + list_for_each_entry(leaf, &link_list, sibiling) { + if (get_root_port_link(leaf) == root_port_link) + __pcie_aspm_config_link(leaf->pdev, state); + } + } else { + list_for_each_entry_reverse(leaf, &link_list, sibiling) { + if (get_root_port_link(leaf) == root_port_link) + __pcie_aspm_config_link(leaf->pdev, state); + } + } +} + +/* + * pcie_aspm_configure_link_state: enable/disable PCI express link state + * @pdev: the root port or switch downstream port + */ +static void pcie_aspm_configure_link_state(struct pci_dev *pdev, + unsigned int state) +{ + down_read(&pci_bus_sem); + mutex_lock(&aspm_lock); + __pcie_aspm_configure_link_state(pdev, state); + mutex_unlock(&aspm_lock); + up_read(&pci_bus_sem); +} + +static void free_link_state(struct pci_dev *pdev) +{ + kfree(pdev->link_state); + pdev->link_state = NULL; +} + +static int pcie_aspm_sanity_check(struct pci_dev *pdev) +{ + struct pci_dev *child_dev; + int child_pos; + u32 reg32; + + /* + * Some functions in a slot might not all be PCIE functions, very + * strange. Disable ASPM for the whole slot + */ + list_for_each_entry(child_dev, &pdev->subordinate->devices, bus_list) { + child_pos = pci_find_capability(child_dev, PCI_CAP_ID_EXP); + if (!child_pos) + return -EINVAL; + + /* + * Disable ASPM for pre-1.1 PCIe device, we follow MS to use + * RBER bit to determine if a function is 1.1 version device + */ + pci_read_config_dword(child_dev, child_pos + PCI_EXP_DEVCAP, + ®32); + if (!(reg32 & PCI_EXP_DEVCAP_RBER) && !aspm_force) { + dev_printk(KERN_INFO, &child_dev->dev, "disabling ASPM" + " on pre-1.1 PCIe device. You can enable it" + " with 'pcie_aspm=force'\n"); + return -EINVAL; + } + } + return 0; +} + +/* + * pcie_aspm_init_link_state: Initiate PCI express link state. + * It is called after the pcie and its children devices are scaned. + * @pdev: the root port or switch downstream port + */ +void pcie_aspm_init_link_state(struct pci_dev *pdev) +{ + unsigned int state; + struct pcie_link_state *link_state; + int error = 0; + int blacklist; + + if (aspm_disabled || !pdev->is_pcie || pdev->link_state) + return; + if (pdev->pcie_type != PCI_EXP_TYPE_ROOT_PORT && + pdev->pcie_type != PCI_EXP_TYPE_DOWNSTREAM) + return; + down_read(&pci_bus_sem); + if (list_empty(&pdev->subordinate->devices)) + goto out; + + blacklist = !!pcie_aspm_sanity_check(pdev); + + mutex_lock(&aspm_lock); + + link_state = kzalloc(sizeof(*link_state), GFP_KERNEL); + if (!link_state) + goto unlock_out; + + link_state->downstream_has_switch = pcie_aspm_downstream_has_switch(pdev); + INIT_LIST_HEAD(&link_state->children); + INIT_LIST_HEAD(&link_state->link); + if (pdev->bus->self) {/* this is a switch */ + struct pcie_link_state *parent_link_state; + + parent_link_state = pdev->bus->parent->self->link_state; + if (!parent_link_state) { + kfree(link_state); + goto unlock_out; + } + list_add(&link_state->link, &parent_link_state->children); + link_state->parent = parent_link_state; + } + + pdev->link_state = link_state; + + if (!blacklist) { + pcie_aspm_configure_common_clock(pdev); + pcie_aspm_cap_init(pdev); + } else { + link_state->enabled_state = PCIE_LINK_STATE_L0S|PCIE_LINK_STATE_L1; + link_state->bios_aspm_state = 0; + /* Set support state to 0, so we will disable ASPM later */ + link_state->support_state = 0; + } + + link_state->pdev = pdev; + list_add(&link_state->sibiling, &link_list); + + if (link_state->downstream_has_switch) { + /* + * If link has switch, delay the link config. The leaf link + * initialization will config the whole hierarchy. but we must + * make sure BIOS doesn't set unsupported link state + **/ + state = pcie_aspm_check_state(pdev, link_state->bios_aspm_state); + __pcie_aspm_config_link(pdev, state); + } else + __pcie_aspm_configure_link_state(pdev, + policy_to_aspm_state(pdev)); + + pcie_check_clock_pm(pdev, blacklist); + +unlock_out: + if (error) + free_link_state(pdev); + mutex_unlock(&aspm_lock); +out: + up_read(&pci_bus_sem); +} + +/* @pdev: the endpoint device */ +void pcie_aspm_exit_link_state(struct pci_dev *pdev) +{ + struct pci_dev *parent = pdev->bus->self; + struct pcie_link_state *link_state = parent->link_state; + + if (aspm_disabled || !pdev->is_pcie || !parent || !link_state) + return; + if (parent->pcie_type != PCI_EXP_TYPE_ROOT_PORT && + parent->pcie_type != PCI_EXP_TYPE_DOWNSTREAM) + return; + down_read(&pci_bus_sem); + mutex_lock(&aspm_lock); + + /* + * All PCIe functions are in one slot, remove one function will remove + * the whole slot, so just wait until we are the last function left. + */ + if (!list_is_last(&pdev->bus_list, &parent->subordinate->devices)) + goto out; + + /* All functions are removed, so just disable ASPM for the link */ + __pcie_aspm_config_one_dev(parent, 0); + list_del(&link_state->sibiling); + list_del(&link_state->link); + /* Clock PM is for endpoint device */ + + free_link_state(parent); +out: + mutex_unlock(&aspm_lock); + up_read(&pci_bus_sem); +} + +/* @pdev: the root port or switch downstream port */ +void pcie_aspm_pm_state_change(struct pci_dev *pdev) +{ + struct pcie_link_state *link_state = pdev->link_state; + + if (aspm_disabled || !pdev->is_pcie || !pdev->link_state) + return; + if (pdev->pcie_type != PCI_EXP_TYPE_ROOT_PORT && + pdev->pcie_type != PCI_EXP_TYPE_DOWNSTREAM) + return; + /* + * devices changed PM state, we should recheck if latency meets all + * functions' requirement + */ + pcie_aspm_configure_link_state(pdev, link_state->enabled_state); +} + +/* + * pci_disable_link_state - disable pci device's link state, so the link will + * never enter specific states + */ +void pci_disable_link_state(struct pci_dev *pdev, int state) +{ + struct pci_dev *parent = pdev->bus->self; + struct pcie_link_state *link_state; + + if (aspm_disabled || !pdev->is_pcie) + return; + if (pdev->pcie_type == PCI_EXP_TYPE_ROOT_PORT || + pdev->pcie_type == PCI_EXP_TYPE_DOWNSTREAM) + parent = pdev; + if (!parent || !parent->link_state) + return; + + down_read(&pci_bus_sem); + mutex_lock(&aspm_lock); + link_state = parent->link_state; + link_state->support_state &= + ~(state & (PCIE_LINK_STATE_L0S|PCIE_LINK_STATE_L1)); + if (state & PCIE_LINK_STATE_CLKPM) + link_state->clk_pm_capable = 0; + + __pcie_aspm_configure_link_state(parent, link_state->enabled_state); + if (!link_state->clk_pm_capable && link_state->clk_pm_enabled) + pcie_set_clock_pm(parent, 0); + mutex_unlock(&aspm_lock); + up_read(&pci_bus_sem); +} +EXPORT_SYMBOL(pci_disable_link_state); + +static int pcie_aspm_set_policy(const char *val, struct kernel_param *kp) +{ + int i; + struct pci_dev *pdev; + struct pcie_link_state *link_state; + + for (i = 0; i < ARRAY_SIZE(policy_str); i++) + if (!strncmp(val, policy_str[i], strlen(policy_str[i]))) + break; + if (i >= ARRAY_SIZE(policy_str)) + return -EINVAL; + if (i == aspm_policy) + return 0; + + down_read(&pci_bus_sem); + mutex_lock(&aspm_lock); + aspm_policy = i; + list_for_each_entry(link_state, &link_list, sibiling) { + pdev = link_state->pdev; + __pcie_aspm_configure_link_state(pdev, + policy_to_aspm_state(pdev)); + if (link_state->clk_pm_capable && + link_state->clk_pm_enabled != policy_to_clkpm_state(pdev)) + pcie_set_clock_pm(pdev, policy_to_clkpm_state(pdev)); + + } + mutex_unlock(&aspm_lock); + up_read(&pci_bus_sem); + return 0; +} + +static int pcie_aspm_get_policy(char *buffer, struct kernel_param *kp) +{ + int i, cnt = 0; + for (i = 0; i < ARRAY_SIZE(policy_str); i++) + if (i == aspm_policy) + cnt += sprintf(buffer + cnt, "[%s] ", policy_str[i]); + else + cnt += sprintf(buffer + cnt, "%s ", policy_str[i]); + return cnt; +} + +module_param_call(policy, pcie_aspm_set_policy, pcie_aspm_get_policy, + NULL, 0644); + +#ifdef CONFIG_PCIEASPM_DEBUG +static ssize_t link_state_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct pci_dev *pci_device = to_pci_dev(dev); + struct pcie_link_state *link_state = pci_device->link_state; + + return sprintf(buf, "%d\n", link_state->enabled_state); +} + +static ssize_t link_state_store(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t n) +{ + struct pci_dev *pci_device = to_pci_dev(dev); + int state; + + if (n < 1) + return -EINVAL; + state = buf[0]-'0'; + if (state >= 0 && state <= 3) { + /* setup link aspm state */ + pcie_aspm_configure_link_state(pci_device, state); + return n; + } + + return -EINVAL; +} + +static ssize_t clk_ctl_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct pci_dev *pci_device = to_pci_dev(dev); + struct pcie_link_state *link_state = pci_device->link_state; + + return sprintf(buf, "%d\n", link_state->clk_pm_enabled); +} + +static ssize_t clk_ctl_store(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t n) +{ + struct pci_dev *pci_device = to_pci_dev(dev); + int state; + + if (n < 1) + return -EINVAL; + state = buf[0]-'0'; + + down_read(&pci_bus_sem); + mutex_lock(&aspm_lock); + pcie_set_clock_pm(pci_device, !!state); + mutex_unlock(&aspm_lock); + up_read(&pci_bus_sem); + + return n; +} + +static DEVICE_ATTR(link_state, 0644, link_state_show, link_state_store); +static DEVICE_ATTR(clk_ctl, 0644, clk_ctl_show, clk_ctl_store); + +static char power_group[] = "power"; +void pcie_aspm_create_sysfs_dev_files(struct pci_dev *pdev) +{ + struct pcie_link_state *link_state = pdev->link_state; + + if (!pdev->is_pcie || (pdev->pcie_type != PCI_EXP_TYPE_ROOT_PORT && + pdev->pcie_type != PCI_EXP_TYPE_DOWNSTREAM) || !link_state) + return; + + if (link_state->support_state) + sysfs_add_file_to_group(&pdev->dev.kobj, + &dev_attr_link_state.attr, power_group); + if (link_state->clk_pm_capable) + sysfs_add_file_to_group(&pdev->dev.kobj, + &dev_attr_clk_ctl.attr, power_group); +} + +void pcie_aspm_remove_sysfs_dev_files(struct pci_dev *pdev) +{ + struct pcie_link_state *link_state = pdev->link_state; + + if (!pdev->is_pcie || (pdev->pcie_type != PCI_EXP_TYPE_ROOT_PORT && + pdev->pcie_type != PCI_EXP_TYPE_DOWNSTREAM) || !link_state) + return; + + if (link_state->support_state) + sysfs_remove_file_from_group(&pdev->dev.kobj, + &dev_attr_link_state.attr, power_group); + if (link_state->clk_pm_capable) + sysfs_remove_file_from_group(&pdev->dev.kobj, + &dev_attr_clk_ctl.attr, power_group); +} +#endif + +static int __init pcie_aspm_disable(char *str) +{ + if (!strcmp(str, "off")) { + aspm_disabled = 1; + printk(KERN_INFO "PCIe ASPM is disabled\n"); + } else if (!strcmp(str, "force")) { + aspm_force = 1; + printk(KERN_INFO "PCIe ASPM is forcedly enabled\n"); + } + return 1; +} + +__setup("pcie_aspm=", pcie_aspm_disable); + +void pcie_no_aspm(void) +{ + if (!aspm_force) + aspm_disabled = 1; +} + +#ifdef CONFIG_ACPI +#include <acpi/acpi_bus.h> +#include <linux/pci-acpi.h> +static void pcie_aspm_platform_init(void) +{ + pcie_osc_support_set(OSC_ACTIVE_STATE_PWR_SUPPORT| + OSC_CLOCK_PWR_CAPABILITY_SUPPORT); +} +#else +static inline void pcie_aspm_platform_init(void) { } +#endif + +static int __init pcie_aspm_init(void) +{ + if (aspm_disabled) + return 0; + pcie_aspm_platform_init(); + return 0; +} + +fs_initcall(pcie_aspm_init); diff --git a/drivers/pci/pcie/portdrv.h b/drivers/pci/pcie/portdrv.h new file mode 100644 index 0000000..2529f3f --- /dev/null +++ b/drivers/pci/pcie/portdrv.h @@ -0,0 +1,46 @@ +/* + * File: portdrv.h + * Purpose: PCI Express Port Bus Driver's Internal Data Structures + * + * Copyright (C) 2004 Intel + * Copyright (C) Tom Long Nguyen (tom.l.nguyen@intel.com) + */ + +#ifndef _PORTDRV_H_ +#define _PORTDRV_H_ + +#include <linux/compiler.h> + +#if !defined(PCI_CAP_ID_PME) +#define PCI_CAP_ID_PME 1 +#endif + +#if !defined(PCI_CAP_ID_EXP) +#define PCI_CAP_ID_EXP 0x10 +#endif + +#define PORT_TYPE_MASK 0xf +#define PORT_TO_SLOT_MASK 0x100 +#define SLOT_HP_CAPABLE_MASK 0x40 +#define PCIE_CAPABILITIES_REG 0x2 +#define PCIE_SLOT_CAPABILITIES_REG 0x14 +#define PCIE_PORT_DEVICE_MAXSERVICES 4 + +#define get_descriptor_id(type, service) (((type - 4) << 4) | service) + +struct pcie_port_device_ext { + int interrupt_mode; /* [0:INTx | 1:MSI | 2:MSI-X] */ +}; + +extern struct bus_type pcie_port_bus_type; +extern int pcie_port_device_probe(struct pci_dev *dev); +extern int pcie_port_device_register(struct pci_dev *dev); +#ifdef CONFIG_PM +extern int pcie_port_device_suspend(struct pci_dev *dev, pm_message_t state); +extern int pcie_port_device_resume(struct pci_dev *dev); +#endif +extern void pcie_port_device_remove(struct pci_dev *dev); +extern int __must_check pcie_port_bus_register(void); +extern void pcie_port_bus_unregister(void); + +#endif /* _PORTDRV_H_ */ diff --git a/drivers/pci/pcie/portdrv_bus.c b/drivers/pci/pcie/portdrv_bus.c new file mode 100644 index 0000000..359fe55 --- /dev/null +++ b/drivers/pci/pcie/portdrv_bus.c @@ -0,0 +1,80 @@ +/* + * File: portdrv_bus.c + * Purpose: PCI Express Port Bus Driver's Bus Overloading Functions + * + * Copyright (C) 2004 Intel + * Copyright (C) Tom Long Nguyen (tom.l.nguyen@intel.com) + */ + +#include <linux/module.h> +#include <linux/pci.h> +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/pm.h> + +#include <linux/pcieport_if.h> +#include "portdrv.h" + +static int pcie_port_bus_match(struct device *dev, struct device_driver *drv); +static int pcie_port_bus_suspend(struct device *dev, pm_message_t state); +static int pcie_port_bus_resume(struct device *dev); + +struct bus_type pcie_port_bus_type = { + .name = "pci_express", + .match = pcie_port_bus_match, + .suspend = pcie_port_bus_suspend, + .resume = pcie_port_bus_resume, +}; +EXPORT_SYMBOL_GPL(pcie_port_bus_type); + +static int pcie_port_bus_match(struct device *dev, struct device_driver *drv) +{ + struct pcie_device *pciedev; + struct pcie_port_service_driver *driver; + + if (drv->bus != &pcie_port_bus_type || dev->bus != &pcie_port_bus_type) + return 0; + + pciedev = to_pcie_device(dev); + driver = to_service_driver(drv); + if ( (driver->id_table->vendor != PCI_ANY_ID && + driver->id_table->vendor != pciedev->id.vendor) || + (driver->id_table->device != PCI_ANY_ID && + driver->id_table->device != pciedev->id.device) || + (driver->id_table->port_type != PCIE_ANY_PORT && + driver->id_table->port_type != pciedev->id.port_type) || + driver->id_table->service_type != pciedev->id.service_type ) + return 0; + + return 1; +} + +static int pcie_port_bus_suspend(struct device *dev, pm_message_t state) +{ + struct pcie_device *pciedev; + struct pcie_port_service_driver *driver; + + if (!dev || !dev->driver) + return 0; + + pciedev = to_pcie_device(dev); + driver = to_service_driver(dev->driver); + if (driver && driver->suspend) + driver->suspend(pciedev, state); + return 0; +} + +static int pcie_port_bus_resume(struct device *dev) +{ + struct pcie_device *pciedev; + struct pcie_port_service_driver *driver; + + if (!dev || !dev->driver) + return 0; + + pciedev = to_pcie_device(dev); + driver = to_service_driver(dev->driver); + if (driver && driver->resume) + driver->resume(pciedev); + return 0; +} diff --git a/drivers/pci/pcie/portdrv_core.c b/drivers/pci/pcie/portdrv_core.c new file mode 100644 index 0000000..2e091e0 --- /dev/null +++ b/drivers/pci/pcie/portdrv_core.c @@ -0,0 +1,418 @@ +/* + * File: portdrv_core.c + * Purpose: PCI Express Port Bus Driver's Core Functions + * + * Copyright (C) 2004 Intel + * Copyright (C) Tom Long Nguyen (tom.l.nguyen@intel.com) + */ + +#include <linux/module.h> +#include <linux/pci.h> +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/pm.h> +#include <linux/string.h> +#include <linux/slab.h> +#include <linux/pcieport_if.h> + +#include "portdrv.h" + +extern int pcie_mch_quirk; /* MSI-quirk Indicator */ + +static int pcie_port_probe_service(struct device *dev) +{ + struct pcie_device *pciedev; + struct pcie_port_service_driver *driver; + int status; + + if (!dev || !dev->driver) + return -ENODEV; + + driver = to_service_driver(dev->driver); + if (!driver || !driver->probe) + return -ENODEV; + + pciedev = to_pcie_device(dev); + status = driver->probe(pciedev, driver->id_table); + if (!status) { + dev_printk(KERN_DEBUG, dev, "service driver %s loaded\n", + driver->name); + get_device(dev); + } + return status; +} + +static int pcie_port_remove_service(struct device *dev) +{ + struct pcie_device *pciedev; + struct pcie_port_service_driver *driver; + + if (!dev || !dev->driver) + return 0; + + pciedev = to_pcie_device(dev); + driver = to_service_driver(dev->driver); + if (driver && driver->remove) { + dev_printk(KERN_DEBUG, dev, "unloading service driver %s\n", + driver->name); + driver->remove(pciedev); + put_device(dev); + } + return 0; +} + +static void pcie_port_shutdown_service(struct device *dev) {} + +static int pcie_port_suspend_service(struct device *dev, pm_message_t state) +{ + struct pcie_device *pciedev; + struct pcie_port_service_driver *driver; + + if (!dev || !dev->driver) + return 0; + + pciedev = to_pcie_device(dev); + driver = to_service_driver(dev->driver); + if (driver && driver->suspend) + driver->suspend(pciedev, state); + return 0; +} + +static int pcie_port_resume_service(struct device *dev) +{ + struct pcie_device *pciedev; + struct pcie_port_service_driver *driver; + + if (!dev || !dev->driver) + return 0; + + pciedev = to_pcie_device(dev); + driver = to_service_driver(dev->driver); + + if (driver && driver->resume) + driver->resume(pciedev); + return 0; +} + +/* + * release_pcie_device + * + * Being invoked automatically when device is being removed + * in response to device_unregister(dev) call. + * Release all resources being claimed. + */ +static void release_pcie_device(struct device *dev) +{ + dev_printk(KERN_DEBUG, dev, "free port service\n"); + kfree(to_pcie_device(dev)); +} + +static int is_msi_quirked(struct pci_dev *dev) +{ + int port_type, quirk = 0; + u16 reg16; + + pci_read_config_word(dev, + pci_find_capability(dev, PCI_CAP_ID_EXP) + + PCIE_CAPABILITIES_REG, ®16); + port_type = (reg16 >> 4) & PORT_TYPE_MASK; + switch(port_type) { + case PCIE_RC_PORT: + if (pcie_mch_quirk == 1) + quirk = 1; + break; + case PCIE_SW_UPSTREAM_PORT: + case PCIE_SW_DOWNSTREAM_PORT: + default: + break; + } + return quirk; +} + +static int assign_interrupt_mode(struct pci_dev *dev, int *vectors, int mask) +{ + int i, pos, nvec, status = -EINVAL; + int interrupt_mode = PCIE_PORT_INTx_MODE; + + /* Set INTx as default */ + for (i = 0, nvec = 0; i < PCIE_PORT_DEVICE_MAXSERVICES; i++) { + if (mask & (1 << i)) + nvec++; + vectors[i] = dev->irq; + } + + /* Check MSI quirk */ + if (is_msi_quirked(dev)) + return interrupt_mode; + + /* Select MSI-X over MSI if supported */ + pos = pci_find_capability(dev, PCI_CAP_ID_MSIX); + if (pos) { + struct msix_entry msix_entries[PCIE_PORT_DEVICE_MAXSERVICES] = + {{0, 0}, {0, 1}, {0, 2}, {0, 3}}; + dev_info(&dev->dev, "found MSI-X capability\n"); + status = pci_enable_msix(dev, msix_entries, nvec); + if (!status) { + int j = 0; + + interrupt_mode = PCIE_PORT_MSIX_MODE; + for (i = 0; i < PCIE_PORT_DEVICE_MAXSERVICES; i++) { + if (mask & (1 << i)) + vectors[i] = msix_entries[j++].vector; + } + } + } + if (status) { + pos = pci_find_capability(dev, PCI_CAP_ID_MSI); + if (pos) { + dev_info(&dev->dev, "found MSI capability\n"); + status = pci_enable_msi(dev); + if (!status) { + interrupt_mode = PCIE_PORT_MSI_MODE; + for (i = 0;i < PCIE_PORT_DEVICE_MAXSERVICES;i++) + vectors[i] = dev->irq; + } + } + } + return interrupt_mode; +} + +static int get_port_device_capability(struct pci_dev *dev) +{ + int services = 0, pos; + u16 reg16; + u32 reg32; + + pos = pci_find_capability(dev, PCI_CAP_ID_EXP); + pci_read_config_word(dev, pos + PCIE_CAPABILITIES_REG, ®16); + /* Hot-Plug Capable */ + if (reg16 & PORT_TO_SLOT_MASK) { + pci_read_config_dword(dev, + pos + PCIE_SLOT_CAPABILITIES_REG, ®32); + if (reg32 & SLOT_HP_CAPABLE_MASK) + services |= PCIE_PORT_SERVICE_HP; + } + /* PME Capable - root port capability */ + if (((reg16 >> 4) & PORT_TYPE_MASK) == PCIE_RC_PORT) + services |= PCIE_PORT_SERVICE_PME; + + if (pci_find_ext_capability(dev, PCI_EXT_CAP_ID_ERR)) + services |= PCIE_PORT_SERVICE_AER; + if (pci_find_ext_capability(dev, PCI_EXT_CAP_ID_VC)) + services |= PCIE_PORT_SERVICE_VC; + + return services; +} + +static void pcie_device_init(struct pci_dev *parent, struct pcie_device *dev, + int port_type, int service_type, int irq, int irq_mode) +{ + struct device *device; + + dev->port = parent; + dev->interrupt_mode = irq_mode; + dev->irq = irq; + dev->id.vendor = parent->vendor; + dev->id.device = parent->device; + dev->id.port_type = port_type; + dev->id.service_type = (1 << service_type); + + /* Initialize generic device interface */ + device = &dev->device; + memset(device, 0, sizeof(struct device)); + device->bus = &pcie_port_bus_type; + device->driver = NULL; + device->driver_data = NULL; + device->release = release_pcie_device; /* callback to free pcie dev */ + snprintf(device->bus_id, sizeof(device->bus_id), "%s:pcie%02x", + pci_name(parent), get_descriptor_id(port_type, service_type)); + device->parent = &parent->dev; +} + +static struct pcie_device* alloc_pcie_device(struct pci_dev *parent, + int port_type, int service_type, int irq, int irq_mode) +{ + struct pcie_device *device; + + device = kzalloc(sizeof(struct pcie_device), GFP_KERNEL); + if (!device) + return NULL; + + pcie_device_init(parent, device, port_type, service_type, irq,irq_mode); + dev_printk(KERN_DEBUG, &device->device, "allocate port service\n"); + return device; +} + +int pcie_port_device_probe(struct pci_dev *dev) +{ + int pos, type; + u16 reg; + + if (!(pos = pci_find_capability(dev, PCI_CAP_ID_EXP))) + return -ENODEV; + + pci_read_config_word(dev, pos + PCIE_CAPABILITIES_REG, ®); + type = (reg >> 4) & PORT_TYPE_MASK; + if ( type == PCIE_RC_PORT || type == PCIE_SW_UPSTREAM_PORT || + type == PCIE_SW_DOWNSTREAM_PORT ) + return 0; + + return -ENODEV; +} + +int pcie_port_device_register(struct pci_dev *dev) +{ + struct pcie_port_device_ext *p_ext; + int status, type, capabilities, irq_mode, i; + int vectors[PCIE_PORT_DEVICE_MAXSERVICES]; + u16 reg16; + + /* Allocate port device extension */ + if (!(p_ext = kmalloc(sizeof(struct pcie_port_device_ext), GFP_KERNEL))) + return -ENOMEM; + + pci_set_drvdata(dev, p_ext); + + /* Get port type */ + pci_read_config_word(dev, + pci_find_capability(dev, PCI_CAP_ID_EXP) + + PCIE_CAPABILITIES_REG, ®16); + type = (reg16 >> 4) & PORT_TYPE_MASK; + + /* Now get port services */ + capabilities = get_port_device_capability(dev); + irq_mode = assign_interrupt_mode(dev, vectors, capabilities); + p_ext->interrupt_mode = irq_mode; + + /* Allocate child services if any */ + for (i = 0; i < PCIE_PORT_DEVICE_MAXSERVICES; i++) { + struct pcie_device *child; + + if (capabilities & (1 << i)) { + child = alloc_pcie_device( + dev, /* parent */ + type, /* port type */ + i, /* service type */ + vectors[i], /* irq */ + irq_mode /* interrupt mode */); + if (child) { + status = device_register(&child->device); + if (status) { + kfree(child); + continue; + } + get_device(&child->device); + } + } + } + return 0; +} + +#ifdef CONFIG_PM +static int suspend_iter(struct device *dev, void *data) +{ + struct pcie_port_service_driver *service_driver; + pm_message_t state = * (pm_message_t *) data; + + if ((dev->bus == &pcie_port_bus_type) && + (dev->driver)) { + service_driver = to_service_driver(dev->driver); + if (service_driver->suspend) + service_driver->suspend(to_pcie_device(dev), state); + } + return 0; +} + +int pcie_port_device_suspend(struct pci_dev *dev, pm_message_t state) +{ + return device_for_each_child(&dev->dev, &state, suspend_iter); +} + +static int resume_iter(struct device *dev, void *data) +{ + struct pcie_port_service_driver *service_driver; + + if ((dev->bus == &pcie_port_bus_type) && + (dev->driver)) { + service_driver = to_service_driver(dev->driver); + if (service_driver->resume) + service_driver->resume(to_pcie_device(dev)); + } + return 0; +} + +int pcie_port_device_resume(struct pci_dev *dev) +{ + return device_for_each_child(&dev->dev, NULL, resume_iter); +} +#endif + +static int remove_iter(struct device *dev, void *data) +{ + struct pcie_port_service_driver *service_driver; + + if (dev->bus == &pcie_port_bus_type) { + if (dev->driver) { + service_driver = to_service_driver(dev->driver); + if (service_driver->remove) + service_driver->remove(to_pcie_device(dev)); + } + *(unsigned long*)data = (unsigned long)dev; + return 1; + } + return 0; +} + +void pcie_port_device_remove(struct pci_dev *dev) +{ + struct device *device; + unsigned long device_addr; + int interrupt_mode = PCIE_PORT_INTx_MODE; + int status; + + do { + status = device_for_each_child(&dev->dev, &device_addr, remove_iter); + if (status) { + device = (struct device*)device_addr; + interrupt_mode = (to_pcie_device(device))->interrupt_mode; + put_device(device); + device_unregister(device); + } + } while (status); + /* Switch to INTx by default if MSI enabled */ + if (interrupt_mode == PCIE_PORT_MSIX_MODE) + pci_disable_msix(dev); + else if (interrupt_mode == PCIE_PORT_MSI_MODE) + pci_disable_msi(dev); +} + +int pcie_port_bus_register(void) +{ + return bus_register(&pcie_port_bus_type); +} + +void pcie_port_bus_unregister(void) +{ + bus_unregister(&pcie_port_bus_type); +} + +int pcie_port_service_register(struct pcie_port_service_driver *new) +{ + new->driver.name = (char *)new->name; + new->driver.bus = &pcie_port_bus_type; + new->driver.probe = pcie_port_probe_service; + new->driver.remove = pcie_port_remove_service; + new->driver.shutdown = pcie_port_shutdown_service; + new->driver.suspend = pcie_port_suspend_service; + new->driver.resume = pcie_port_resume_service; + + return driver_register(&new->driver); +} + +void pcie_port_service_unregister(struct pcie_port_service_driver *new) +{ + driver_unregister(&new->driver); +} + +EXPORT_SYMBOL(pcie_port_service_register); +EXPORT_SYMBOL(pcie_port_service_unregister); diff --git a/drivers/pci/pcie/portdrv_pci.c b/drivers/pci/pcie/portdrv_pci.c new file mode 100644 index 0000000..e22e7fb --- /dev/null +++ b/drivers/pci/pcie/portdrv_pci.c @@ -0,0 +1,313 @@ +/* + * File: portdrv_pci.c + * Purpose: PCI Express Port Bus Driver + * + * Copyright (C) 2004 Intel + * Copyright (C) Tom Long Nguyen (tom.l.nguyen@intel.com) + */ + +#include <linux/module.h> +#include <linux/pci.h> +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/pm.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/pcieport_if.h> +#include <linux/aer.h> + +#include "portdrv.h" +#include "aer/aerdrv.h" + +/* + * Version Information + */ +#define DRIVER_VERSION "v1.0" +#define DRIVER_AUTHOR "tom.l.nguyen@intel.com" +#define DRIVER_DESC "PCIE Port Bus Driver" +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); + +/* global data */ +static const char device_name[] = "pcieport-driver"; + +static int pcie_portdrv_save_config(struct pci_dev *dev) +{ + return pci_save_state(dev); +} + +static int pcie_portdrv_restore_config(struct pci_dev *dev) +{ + int retval; + + pci_restore_state(dev); + retval = pci_enable_device(dev); + if (retval) + return retval; + pci_set_master(dev); + return 0; +} + +#ifdef CONFIG_PM +static int pcie_portdrv_suspend(struct pci_dev *dev, pm_message_t state) +{ + int ret = pcie_port_device_suspend(dev, state); + + if (!ret) + ret = pcie_portdrv_save_config(dev); + return ret; +} + +static int pcie_portdrv_resume(struct pci_dev *dev) +{ + pcie_portdrv_restore_config(dev); + return pcie_port_device_resume(dev); +} +#else +#define pcie_portdrv_suspend NULL +#define pcie_portdrv_resume NULL +#endif + +/* + * pcie_portdrv_probe - Probe PCI-Express port devices + * @dev: PCI-Express port device being probed + * + * If detected invokes the pcie_port_device_register() method for + * this port device. + * + */ +static int __devinit pcie_portdrv_probe (struct pci_dev *dev, + const struct pci_device_id *id ) +{ + int status; + + status = pcie_port_device_probe(dev); + if (status) + return status; + + if (pci_enable_device(dev) < 0) + return -ENODEV; + + pci_set_master(dev); + if (!dev->irq && dev->pin) { + dev_warn(&dev->dev, "device [%04x:%04x] has invalid IRQ; " + "check vendor BIOS\n", dev->vendor, dev->device); + } + if (pcie_port_device_register(dev)) { + pci_disable_device(dev); + return -ENOMEM; + } + + pcie_portdrv_save_config(dev); + + return 0; +} + +static void pcie_portdrv_remove (struct pci_dev *dev) +{ + pcie_port_device_remove(dev); + pci_disable_device(dev); + kfree(pci_get_drvdata(dev)); +} + +static int error_detected_iter(struct device *device, void *data) +{ + struct pcie_device *pcie_device; + struct pcie_port_service_driver *driver; + struct aer_broadcast_data *result_data; + pci_ers_result_t status; + + result_data = (struct aer_broadcast_data *) data; + + if (device->bus == &pcie_port_bus_type && device->driver) { + driver = to_service_driver(device->driver); + if (!driver || + !driver->err_handler || + !driver->err_handler->error_detected) + return 0; + + pcie_device = to_pcie_device(device); + + /* Forward error detected message to service drivers */ + status = driver->err_handler->error_detected( + pcie_device->port, + result_data->state); + result_data->result = + merge_result(result_data->result, status); + } + + return 0; +} + +static pci_ers_result_t pcie_portdrv_error_detected(struct pci_dev *dev, + enum pci_channel_state error) +{ + struct aer_broadcast_data result_data = + {error, PCI_ERS_RESULT_CAN_RECOVER}; + int retval; + + /* can not fail */ + retval = device_for_each_child(&dev->dev, &result_data, error_detected_iter); + + return result_data.result; +} + +static int mmio_enabled_iter(struct device *device, void *data) +{ + struct pcie_device *pcie_device; + struct pcie_port_service_driver *driver; + pci_ers_result_t status, *result; + + result = (pci_ers_result_t *) data; + + if (device->bus == &pcie_port_bus_type && device->driver) { + driver = to_service_driver(device->driver); + if (driver && + driver->err_handler && + driver->err_handler->mmio_enabled) { + pcie_device = to_pcie_device(device); + + /* Forward error message to service drivers */ + status = driver->err_handler->mmio_enabled( + pcie_device->port); + *result = merge_result(*result, status); + } + } + + return 0; +} + +static pci_ers_result_t pcie_portdrv_mmio_enabled(struct pci_dev *dev) +{ + pci_ers_result_t status = PCI_ERS_RESULT_RECOVERED; + int retval; + + /* get true return value from &status */ + retval = device_for_each_child(&dev->dev, &status, mmio_enabled_iter); + return status; +} + +static int slot_reset_iter(struct device *device, void *data) +{ + struct pcie_device *pcie_device; + struct pcie_port_service_driver *driver; + pci_ers_result_t status, *result; + + result = (pci_ers_result_t *) data; + + if (device->bus == &pcie_port_bus_type && device->driver) { + driver = to_service_driver(device->driver); + if (driver && + driver->err_handler && + driver->err_handler->slot_reset) { + pcie_device = to_pcie_device(device); + + /* Forward error message to service drivers */ + status = driver->err_handler->slot_reset( + pcie_device->port); + *result = merge_result(*result, status); + } + } + + return 0; +} + +static pci_ers_result_t pcie_portdrv_slot_reset(struct pci_dev *dev) +{ + pci_ers_result_t status = PCI_ERS_RESULT_NONE; + int retval; + + /* If fatal, restore cfg space for possible link reset at upstream */ + if (dev->error_state == pci_channel_io_frozen) { + pcie_portdrv_restore_config(dev); + pci_enable_pcie_error_reporting(dev); + } + + /* get true return value from &status */ + retval = device_for_each_child(&dev->dev, &status, slot_reset_iter); + + return status; +} + +static int resume_iter(struct device *device, void *data) +{ + struct pcie_device *pcie_device; + struct pcie_port_service_driver *driver; + + if (device->bus == &pcie_port_bus_type && device->driver) { + driver = to_service_driver(device->driver); + if (driver && + driver->err_handler && + driver->err_handler->resume) { + pcie_device = to_pcie_device(device); + + /* Forward error message to service drivers */ + driver->err_handler->resume(pcie_device->port); + } + } + + return 0; +} + +static void pcie_portdrv_err_resume(struct pci_dev *dev) +{ + int retval; + /* nothing to do with error value, if it ever happens */ + retval = device_for_each_child(&dev->dev, NULL, resume_iter); +} + +/* + * LINUX Device Driver Model + */ +static const struct pci_device_id port_pci_ids[] = { { + /* handle any PCI-Express port */ + PCI_DEVICE_CLASS(((PCI_CLASS_BRIDGE_PCI << 8) | 0x00), ~0), + }, { /* end: all zeroes */ } +}; +MODULE_DEVICE_TABLE(pci, port_pci_ids); + +static struct pci_error_handlers pcie_portdrv_err_handler = { + .error_detected = pcie_portdrv_error_detected, + .mmio_enabled = pcie_portdrv_mmio_enabled, + .slot_reset = pcie_portdrv_slot_reset, + .resume = pcie_portdrv_err_resume, +}; + +static struct pci_driver pcie_portdriver = { + .name = (char *)device_name, + .id_table = &port_pci_ids[0], + + .probe = pcie_portdrv_probe, + .remove = pcie_portdrv_remove, + + .suspend = pcie_portdrv_suspend, + .resume = pcie_portdrv_resume, + + .err_handler = &pcie_portdrv_err_handler, +}; + +static int __init pcie_portdrv_init(void) +{ + int retval; + + retval = pcie_port_bus_register(); + if (retval) { + printk(KERN_WARNING "PCIE: bus_register error: %d\n", retval); + goto out; + } + retval = pci_register_driver(&pcie_portdriver); + if (retval) + pcie_port_bus_unregister(); + out: + return retval; +} + +static void __exit pcie_portdrv_exit(void) +{ + pci_unregister_driver(&pcie_portdriver); + pcie_port_bus_unregister(); +} + +module_init(pcie_portdrv_init); +module_exit(pcie_portdrv_exit); diff --git a/drivers/pci/probe.c b/drivers/pci/probe.c new file mode 100644 index 0000000..003a9b3 --- /dev/null +++ b/drivers/pci/probe.c @@ -0,0 +1,1216 @@ +/* + * probe.c - PCI detection and setup code + */ + +#include <linux/kernel.h> +#include <linux/delay.h> +#include <linux/init.h> +#include <linux/pci.h> +#include <linux/slab.h> +#include <linux/module.h> +#include <linux/cpumask.h> +#include <linux/pci-aspm.h> +#include "pci.h" + +#define CARDBUS_LATENCY_TIMER 176 /* secondary latency timer */ +#define CARDBUS_RESERVE_BUSNR 3 + +/* Ugh. Need to stop exporting this to modules. */ +LIST_HEAD(pci_root_buses); +EXPORT_SYMBOL(pci_root_buses); + + +static int find_anything(struct device *dev, void *data) +{ + return 1; +} + +/* + * Some device drivers need know if pci is initiated. + * Basically, we think pci is not initiated when there + * is no device to be found on the pci_bus_type. + */ +int no_pci_devices(void) +{ + struct device *dev; + int no_devices; + + dev = bus_find_device(&pci_bus_type, NULL, NULL, find_anything); + no_devices = (dev == NULL); + put_device(dev); + return no_devices; +} +EXPORT_SYMBOL(no_pci_devices); + +/* + * PCI Bus Class Devices + */ +static ssize_t pci_bus_show_cpuaffinity(struct device *dev, + int type, + struct device_attribute *attr, + char *buf) +{ + int ret; + cpumask_t cpumask; + + cpumask = pcibus_to_cpumask(to_pci_bus(dev)); + ret = type? + cpulist_scnprintf(buf, PAGE_SIZE-2, cpumask): + cpumask_scnprintf(buf, PAGE_SIZE-2, cpumask); + buf[ret++] = '\n'; + buf[ret] = '\0'; + return ret; +} + +static ssize_t inline pci_bus_show_cpumaskaffinity(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + return pci_bus_show_cpuaffinity(dev, 0, attr, buf); +} + +static ssize_t inline pci_bus_show_cpulistaffinity(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + return pci_bus_show_cpuaffinity(dev, 1, attr, buf); +} + +DEVICE_ATTR(cpuaffinity, S_IRUGO, pci_bus_show_cpumaskaffinity, NULL); +DEVICE_ATTR(cpulistaffinity, S_IRUGO, pci_bus_show_cpulistaffinity, NULL); + +/* + * PCI Bus Class + */ +static void release_pcibus_dev(struct device *dev) +{ + struct pci_bus *pci_bus = to_pci_bus(dev); + + if (pci_bus->bridge) + put_device(pci_bus->bridge); + kfree(pci_bus); +} + +static struct class pcibus_class = { + .name = "pci_bus", + .dev_release = &release_pcibus_dev, +}; + +static int __init pcibus_class_init(void) +{ + return class_register(&pcibus_class); +} +postcore_initcall(pcibus_class_init); + +/* + * Translate the low bits of the PCI base + * to the resource type + */ +static inline unsigned int pci_calc_resource_flags(unsigned int flags) +{ + if (flags & PCI_BASE_ADDRESS_SPACE_IO) + return IORESOURCE_IO; + + if (flags & PCI_BASE_ADDRESS_MEM_PREFETCH) + return IORESOURCE_MEM | IORESOURCE_PREFETCH; + + return IORESOURCE_MEM; +} + +static u64 pci_size(u64 base, u64 maxbase, u64 mask) +{ + u64 size = mask & maxbase; /* Find the significant bits */ + if (!size) + return 0; + + /* Get the lowest of them to find the decode size, and + from that the extent. */ + size = (size & ~(size-1)) - 1; + + /* base == maxbase can be valid only if the BAR has + already been programmed with all 1s. */ + if (base == maxbase && ((base | size) & mask) != mask) + return 0; + + return size; +} + +enum pci_bar_type { + pci_bar_unknown, /* Standard PCI BAR probe */ + pci_bar_io, /* An io port BAR */ + pci_bar_mem32, /* A 32-bit memory BAR */ + pci_bar_mem64, /* A 64-bit memory BAR */ +}; + +static inline enum pci_bar_type decode_bar(struct resource *res, u32 bar) +{ + if ((bar & PCI_BASE_ADDRESS_SPACE) == PCI_BASE_ADDRESS_SPACE_IO) { + res->flags = bar & ~PCI_BASE_ADDRESS_IO_MASK; + return pci_bar_io; + } + + res->flags = bar & ~PCI_BASE_ADDRESS_MEM_MASK; + + if (res->flags & PCI_BASE_ADDRESS_MEM_TYPE_64) + return pci_bar_mem64; + return pci_bar_mem32; +} + +/* + * If the type is not unknown, we assume that the lowest bit is 'enable'. + * Returns 1 if the BAR was 64-bit and 0 if it was 32-bit. + */ +static int __pci_read_base(struct pci_dev *dev, enum pci_bar_type type, + struct resource *res, unsigned int pos) +{ + u32 l, sz, mask; + + mask = type ? ~PCI_ROM_ADDRESS_ENABLE : ~0; + + res->name = pci_name(dev); + + pci_read_config_dword(dev, pos, &l); + pci_write_config_dword(dev, pos, mask); + pci_read_config_dword(dev, pos, &sz); + pci_write_config_dword(dev, pos, l); + + /* + * All bits set in sz means the device isn't working properly. + * If the BAR isn't implemented, all bits must be 0. If it's a + * memory BAR or a ROM, bit 0 must be clear; if it's an io BAR, bit + * 1 must be clear. + */ + if (!sz || sz == 0xffffffff) + goto fail; + + /* + * I don't know how l can have all bits set. Copied from old code. + * Maybe it fixes a bug on some ancient platform. + */ + if (l == 0xffffffff) + l = 0; + + if (type == pci_bar_unknown) { + type = decode_bar(res, l); + res->flags |= pci_calc_resource_flags(l) | IORESOURCE_SIZEALIGN; + if (type == pci_bar_io) { + l &= PCI_BASE_ADDRESS_IO_MASK; + mask = PCI_BASE_ADDRESS_IO_MASK & 0xffff; + } else { + l &= PCI_BASE_ADDRESS_MEM_MASK; + mask = (u32)PCI_BASE_ADDRESS_MEM_MASK; + } + } else { + res->flags |= (l & IORESOURCE_ROM_ENABLE); + l &= PCI_ROM_ADDRESS_MASK; + mask = (u32)PCI_ROM_ADDRESS_MASK; + } + + if (type == pci_bar_mem64) { + u64 l64 = l; + u64 sz64 = sz; + u64 mask64 = mask | (u64)~0 << 32; + + pci_read_config_dword(dev, pos + 4, &l); + pci_write_config_dword(dev, pos + 4, ~0); + pci_read_config_dword(dev, pos + 4, &sz); + pci_write_config_dword(dev, pos + 4, l); + + l64 |= ((u64)l << 32); + sz64 |= ((u64)sz << 32); + + sz64 = pci_size(l64, sz64, mask64); + + if (!sz64) + goto fail; + + if ((sizeof(resource_size_t) < 8) && (sz64 > 0x100000000ULL)) { + dev_err(&dev->dev, "can't handle 64-bit BAR\n"); + goto fail; + } else if ((sizeof(resource_size_t) < 8) && l) { + /* Address above 32-bit boundary; disable the BAR */ + pci_write_config_dword(dev, pos, 0); + pci_write_config_dword(dev, pos + 4, 0); + res->start = 0; + res->end = sz64; + } else { + res->start = l64; + res->end = l64 + sz64; + dev_printk(KERN_DEBUG, &dev->dev, + "reg %x 64bit mmio: %pR\n", pos, res); + } + } else { + sz = pci_size(l, sz, mask); + + if (!sz) + goto fail; + + res->start = l; + res->end = l + sz; + + dev_printk(KERN_DEBUG, &dev->dev, "reg %x %s: %pR\n", pos, + (res->flags & IORESOURCE_IO) ? "io port" : "32bit mmio", + res); + } + + out: + return (type == pci_bar_mem64) ? 1 : 0; + fail: + res->flags = 0; + goto out; +} + +static void pci_read_bases(struct pci_dev *dev, unsigned int howmany, int rom) +{ + unsigned int pos, reg; + + for (pos = 0; pos < howmany; pos++) { + struct resource *res = &dev->resource[pos]; + reg = PCI_BASE_ADDRESS_0 + (pos << 2); + pos += __pci_read_base(dev, pci_bar_unknown, res, reg); + } + + if (rom) { + struct resource *res = &dev->resource[PCI_ROM_RESOURCE]; + dev->rom_base_reg = rom; + res->flags = IORESOURCE_MEM | IORESOURCE_PREFETCH | + IORESOURCE_READONLY | IORESOURCE_CACHEABLE | + IORESOURCE_SIZEALIGN; + __pci_read_base(dev, pci_bar_mem32, res, rom); + } +} + +void __devinit pci_read_bridge_bases(struct pci_bus *child) +{ + struct pci_dev *dev = child->self; + u8 io_base_lo, io_limit_lo; + u16 mem_base_lo, mem_limit_lo; + unsigned long base, limit; + struct resource *res; + int i; + + if (!dev) /* It's a host bus, nothing to read */ + return; + + if (dev->transparent) { + dev_info(&dev->dev, "transparent bridge\n"); + for(i = 3; i < PCI_BUS_NUM_RESOURCES; i++) + child->resource[i] = child->parent->resource[i - 3]; + } + + res = child->resource[0]; + pci_read_config_byte(dev, PCI_IO_BASE, &io_base_lo); + pci_read_config_byte(dev, PCI_IO_LIMIT, &io_limit_lo); + base = (io_base_lo & PCI_IO_RANGE_MASK) << 8; + limit = (io_limit_lo & PCI_IO_RANGE_MASK) << 8; + + if ((io_base_lo & PCI_IO_RANGE_TYPE_MASK) == PCI_IO_RANGE_TYPE_32) { + u16 io_base_hi, io_limit_hi; + pci_read_config_word(dev, PCI_IO_BASE_UPPER16, &io_base_hi); + pci_read_config_word(dev, PCI_IO_LIMIT_UPPER16, &io_limit_hi); + base |= (io_base_hi << 16); + limit |= (io_limit_hi << 16); + } + + if (base <= limit) { + res->flags = (io_base_lo & PCI_IO_RANGE_TYPE_MASK) | IORESOURCE_IO; + if (!res->start) + res->start = base; + if (!res->end) + res->end = limit + 0xfff; + dev_printk(KERN_DEBUG, &dev->dev, "bridge io port: %pR\n", res); + } + + res = child->resource[1]; + pci_read_config_word(dev, PCI_MEMORY_BASE, &mem_base_lo); + pci_read_config_word(dev, PCI_MEMORY_LIMIT, &mem_limit_lo); + base = (mem_base_lo & PCI_MEMORY_RANGE_MASK) << 16; + limit = (mem_limit_lo & PCI_MEMORY_RANGE_MASK) << 16; + if (base <= limit) { + res->flags = (mem_base_lo & PCI_MEMORY_RANGE_TYPE_MASK) | IORESOURCE_MEM; + res->start = base; + res->end = limit + 0xfffff; + dev_printk(KERN_DEBUG, &dev->dev, "bridge 32bit mmio: %pR\n", + res); + } + + res = child->resource[2]; + pci_read_config_word(dev, PCI_PREF_MEMORY_BASE, &mem_base_lo); + pci_read_config_word(dev, PCI_PREF_MEMORY_LIMIT, &mem_limit_lo); + base = (mem_base_lo & PCI_PREF_RANGE_MASK) << 16; + limit = (mem_limit_lo & PCI_PREF_RANGE_MASK) << 16; + + if ((mem_base_lo & PCI_PREF_RANGE_TYPE_MASK) == PCI_PREF_RANGE_TYPE_64) { + u32 mem_base_hi, mem_limit_hi; + pci_read_config_dword(dev, PCI_PREF_BASE_UPPER32, &mem_base_hi); + pci_read_config_dword(dev, PCI_PREF_LIMIT_UPPER32, &mem_limit_hi); + + /* + * Some bridges set the base > limit by default, and some + * (broken) BIOSes do not initialize them. If we find + * this, just assume they are not being used. + */ + if (mem_base_hi <= mem_limit_hi) { +#if BITS_PER_LONG == 64 + base |= ((long) mem_base_hi) << 32; + limit |= ((long) mem_limit_hi) << 32; +#else + if (mem_base_hi || mem_limit_hi) { + dev_err(&dev->dev, "can't handle 64-bit " + "address space for bridge\n"); + return; + } +#endif + } + } + if (base <= limit) { + res->flags = (mem_base_lo & PCI_MEMORY_RANGE_TYPE_MASK) | IORESOURCE_MEM | IORESOURCE_PREFETCH; + res->start = base; + res->end = limit + 0xfffff; + dev_printk(KERN_DEBUG, &dev->dev, "bridge %sbit mmio pref: %pR\n", + (res->flags & PCI_PREF_RANGE_TYPE_64) ? "64" : "32", + res); + } +} + +static struct pci_bus * pci_alloc_bus(void) +{ + struct pci_bus *b; + + b = kzalloc(sizeof(*b), GFP_KERNEL); + if (b) { + INIT_LIST_HEAD(&b->node); + INIT_LIST_HEAD(&b->children); + INIT_LIST_HEAD(&b->devices); + INIT_LIST_HEAD(&b->slots); + } + return b; +} + +static struct pci_bus *pci_alloc_child_bus(struct pci_bus *parent, + struct pci_dev *bridge, int busnr) +{ + struct pci_bus *child; + int i; + + /* + * Allocate a new bus, and inherit stuff from the parent.. + */ + child = pci_alloc_bus(); + if (!child) + return NULL; + + child->self = bridge; + child->parent = parent; + child->ops = parent->ops; + child->sysdata = parent->sysdata; + child->bus_flags = parent->bus_flags; + child->bridge = get_device(&bridge->dev); + + /* initialize some portions of the bus device, but don't register it + * now as the parent is not properly set up yet. This device will get + * registered later in pci_bus_add_devices() + */ + child->dev.class = &pcibus_class; + sprintf(child->dev.bus_id, "%04x:%02x", pci_domain_nr(child), busnr); + + /* + * Set up the primary, secondary and subordinate + * bus numbers. + */ + child->number = child->secondary = busnr; + child->primary = parent->secondary; + child->subordinate = 0xff; + + /* Set up default resource pointers and names.. */ + for (i = 0; i < 4; i++) { + child->resource[i] = &bridge->resource[PCI_BRIDGE_RESOURCES+i]; + child->resource[i]->name = child->name; + } + bridge->subordinate = child; + + return child; +} + +struct pci_bus *__ref pci_add_new_bus(struct pci_bus *parent, struct pci_dev *dev, int busnr) +{ + struct pci_bus *child; + + child = pci_alloc_child_bus(parent, dev, busnr); + if (child) { + down_write(&pci_bus_sem); + list_add_tail(&child->node, &parent->children); + up_write(&pci_bus_sem); + } + return child; +} + +static void pci_fixup_parent_subordinate_busnr(struct pci_bus *child, int max) +{ + struct pci_bus *parent = child->parent; + + /* Attempts to fix that up are really dangerous unless + we're going to re-assign all bus numbers. */ + if (!pcibios_assign_all_busses()) + return; + + while (parent->parent && parent->subordinate < max) { + parent->subordinate = max; + pci_write_config_byte(parent->self, PCI_SUBORDINATE_BUS, max); + parent = parent->parent; + } +} + +/* + * If it's a bridge, configure it and scan the bus behind it. + * For CardBus bridges, we don't scan behind as the devices will + * be handled by the bridge driver itself. + * + * We need to process bridges in two passes -- first we scan those + * already configured by the BIOS and after we are done with all of + * them, we proceed to assigning numbers to the remaining buses in + * order to avoid overlaps between old and new bus numbers. + */ +int __devinit pci_scan_bridge(struct pci_bus *bus, struct pci_dev *dev, int max, int pass) +{ + struct pci_bus *child; + int is_cardbus = (dev->hdr_type == PCI_HEADER_TYPE_CARDBUS); + u32 buses, i, j = 0; + u16 bctl; + int broken = 0; + + pci_read_config_dword(dev, PCI_PRIMARY_BUS, &buses); + + dev_dbg(&dev->dev, "scanning behind bridge, config %06x, pass %d\n", + buses & 0xffffff, pass); + + /* Check if setup is sensible at all */ + if (!pass && + ((buses & 0xff) != bus->number || ((buses >> 8) & 0xff) <= bus->number)) { + dev_dbg(&dev->dev, "bus configuration invalid, reconfiguring\n"); + broken = 1; + } + + /* Disable MasterAbortMode during probing to avoid reporting + of bus errors (in some architectures) */ + pci_read_config_word(dev, PCI_BRIDGE_CONTROL, &bctl); + pci_write_config_word(dev, PCI_BRIDGE_CONTROL, + bctl & ~PCI_BRIDGE_CTL_MASTER_ABORT); + + if ((buses & 0xffff00) && !pcibios_assign_all_busses() && !is_cardbus && !broken) { + unsigned int cmax, busnr; + /* + * Bus already configured by firmware, process it in the first + * pass and just note the configuration. + */ + if (pass) + goto out; + busnr = (buses >> 8) & 0xFF; + + /* + * If we already got to this bus through a different bridge, + * ignore it. This can happen with the i450NX chipset. + */ + if (pci_find_bus(pci_domain_nr(bus), busnr)) { + dev_info(&dev->dev, "bus %04x:%02x already known\n", + pci_domain_nr(bus), busnr); + goto out; + } + + child = pci_add_new_bus(bus, dev, busnr); + if (!child) + goto out; + child->primary = buses & 0xFF; + child->subordinate = (buses >> 16) & 0xFF; + child->bridge_ctl = bctl; + + cmax = pci_scan_child_bus(child); + if (cmax > max) + max = cmax; + if (child->subordinate > max) + max = child->subordinate; + } else { + /* + * We need to assign a number to this bus which we always + * do in the second pass. + */ + if (!pass) { + if (pcibios_assign_all_busses() || broken) + /* Temporarily disable forwarding of the + configuration cycles on all bridges in + this bus segment to avoid possible + conflicts in the second pass between two + bridges programmed with overlapping + bus ranges. */ + pci_write_config_dword(dev, PCI_PRIMARY_BUS, + buses & ~0xffffff); + goto out; + } + + /* Clear errors */ + pci_write_config_word(dev, PCI_STATUS, 0xffff); + + /* Prevent assigning a bus number that already exists. + * This can happen when a bridge is hot-plugged */ + if (pci_find_bus(pci_domain_nr(bus), max+1)) + goto out; + child = pci_add_new_bus(bus, dev, ++max); + buses = (buses & 0xff000000) + | ((unsigned int)(child->primary) << 0) + | ((unsigned int)(child->secondary) << 8) + | ((unsigned int)(child->subordinate) << 16); + + /* + * yenta.c forces a secondary latency timer of 176. + * Copy that behaviour here. + */ + if (is_cardbus) { + buses &= ~0xff000000; + buses |= CARDBUS_LATENCY_TIMER << 24; + } + + /* + * We need to blast all three values with a single write. + */ + pci_write_config_dword(dev, PCI_PRIMARY_BUS, buses); + + if (!is_cardbus) { + child->bridge_ctl = bctl; + /* + * Adjust subordinate busnr in parent buses. + * We do this before scanning for children because + * some devices may not be detected if the bios + * was lazy. + */ + pci_fixup_parent_subordinate_busnr(child, max); + /* Now we can scan all subordinate buses... */ + max = pci_scan_child_bus(child); + /* + * now fix it up again since we have found + * the real value of max. + */ + pci_fixup_parent_subordinate_busnr(child, max); + } else { + /* + * For CardBus bridges, we leave 4 bus numbers + * as cards with a PCI-to-PCI bridge can be + * inserted later. + */ + for (i=0; i<CARDBUS_RESERVE_BUSNR; i++) { + struct pci_bus *parent = bus; + if (pci_find_bus(pci_domain_nr(bus), + max+i+1)) + break; + while (parent->parent) { + if ((!pcibios_assign_all_busses()) && + (parent->subordinate > max) && + (parent->subordinate <= max+i)) { + j = 1; + } + parent = parent->parent; + } + if (j) { + /* + * Often, there are two cardbus bridges + * -- try to leave one valid bus number + * for each one. + */ + i /= 2; + break; + } + } + max += i; + pci_fixup_parent_subordinate_busnr(child, max); + } + /* + * Set the subordinate bus number to its real value. + */ + child->subordinate = max; + pci_write_config_byte(dev, PCI_SUBORDINATE_BUS, max); + } + + sprintf(child->name, + (is_cardbus ? "PCI CardBus %04x:%02x" : "PCI Bus %04x:%02x"), + pci_domain_nr(bus), child->number); + + /* Has only triggered on CardBus, fixup is in yenta_socket */ + while (bus->parent) { + if ((child->subordinate > bus->subordinate) || + (child->number > bus->subordinate) || + (child->number < bus->number) || + (child->subordinate < bus->number)) { + pr_debug("PCI: Bus #%02x (-#%02x) is %s " + "hidden behind%s bridge #%02x (-#%02x)\n", + child->number, child->subordinate, + (bus->number > child->subordinate && + bus->subordinate < child->number) ? + "wholly" : "partially", + bus->self->transparent ? " transparent" : "", + bus->number, bus->subordinate); + } + bus = bus->parent; + } + +out: + pci_write_config_word(dev, PCI_BRIDGE_CONTROL, bctl); + + return max; +} + +/* + * Read interrupt line and base address registers. + * The architecture-dependent code can tweak these, of course. + */ +static void pci_read_irq(struct pci_dev *dev) +{ + unsigned char irq; + + pci_read_config_byte(dev, PCI_INTERRUPT_PIN, &irq); + dev->pin = irq; + if (irq) + pci_read_config_byte(dev, PCI_INTERRUPT_LINE, &irq); + dev->irq = irq; +} + +#define LEGACY_IO_RESOURCE (IORESOURCE_IO | IORESOURCE_PCI_FIXED) + +/** + * pci_setup_device - fill in class and map information of a device + * @dev: the device structure to fill + * + * Initialize the device structure with information about the device's + * vendor,class,memory and IO-space addresses,IRQ lines etc. + * Called at initialisation of the PCI subsystem and by CardBus services. + * Returns 0 on success and -1 if unknown type of device (not normal, bridge + * or CardBus). + */ +static int pci_setup_device(struct pci_dev * dev) +{ + u32 class; + + dev_set_name(&dev->dev, "%04x:%02x:%02x.%d", pci_domain_nr(dev->bus), + dev->bus->number, PCI_SLOT(dev->devfn), + PCI_FUNC(dev->devfn)); + + pci_read_config_dword(dev, PCI_CLASS_REVISION, &class); + dev->revision = class & 0xff; + class >>= 8; /* upper 3 bytes */ + dev->class = class; + class >>= 8; + + dev_dbg(&dev->dev, "found [%04x:%04x] class %06x header type %02x\n", + dev->vendor, dev->device, class, dev->hdr_type); + + /* "Unknown power state" */ + dev->current_state = PCI_UNKNOWN; + + /* Early fixups, before probing the BARs */ + pci_fixup_device(pci_fixup_early, dev); + class = dev->class >> 8; + + switch (dev->hdr_type) { /* header type */ + case PCI_HEADER_TYPE_NORMAL: /* standard header */ + if (class == PCI_CLASS_BRIDGE_PCI) + goto bad; + pci_read_irq(dev); + pci_read_bases(dev, 6, PCI_ROM_ADDRESS); + pci_read_config_word(dev, PCI_SUBSYSTEM_VENDOR_ID, &dev->subsystem_vendor); + pci_read_config_word(dev, PCI_SUBSYSTEM_ID, &dev->subsystem_device); + + /* + * Do the ugly legacy mode stuff here rather than broken chip + * quirk code. Legacy mode ATA controllers have fixed + * addresses. These are not always echoed in BAR0-3, and + * BAR0-3 in a few cases contain junk! + */ + if (class == PCI_CLASS_STORAGE_IDE) { + u8 progif; + pci_read_config_byte(dev, PCI_CLASS_PROG, &progif); + if ((progif & 1) == 0) { + dev->resource[0].start = 0x1F0; + dev->resource[0].end = 0x1F7; + dev->resource[0].flags = LEGACY_IO_RESOURCE; + dev->resource[1].start = 0x3F6; + dev->resource[1].end = 0x3F6; + dev->resource[1].flags = LEGACY_IO_RESOURCE; + } + if ((progif & 4) == 0) { + dev->resource[2].start = 0x170; + dev->resource[2].end = 0x177; + dev->resource[2].flags = LEGACY_IO_RESOURCE; + dev->resource[3].start = 0x376; + dev->resource[3].end = 0x376; + dev->resource[3].flags = LEGACY_IO_RESOURCE; + } + } + break; + + case PCI_HEADER_TYPE_BRIDGE: /* bridge header */ + if (class != PCI_CLASS_BRIDGE_PCI) + goto bad; + /* The PCI-to-PCI bridge spec requires that subtractive + decoding (i.e. transparent) bridge must have programming + interface code of 0x01. */ + pci_read_irq(dev); + dev->transparent = ((dev->class & 0xff) == 1); + pci_read_bases(dev, 2, PCI_ROM_ADDRESS1); + break; + + case PCI_HEADER_TYPE_CARDBUS: /* CardBus bridge header */ + if (class != PCI_CLASS_BRIDGE_CARDBUS) + goto bad; + pci_read_irq(dev); + pci_read_bases(dev, 1, 0); + pci_read_config_word(dev, PCI_CB_SUBSYSTEM_VENDOR_ID, &dev->subsystem_vendor); + pci_read_config_word(dev, PCI_CB_SUBSYSTEM_ID, &dev->subsystem_device); + break; + + default: /* unknown header */ + dev_err(&dev->dev, "unknown header type %02x, " + "ignoring device\n", dev->hdr_type); + return -1; + + bad: + dev_err(&dev->dev, "ignoring class %02x (doesn't match header " + "type %02x)\n", class, dev->hdr_type); + dev->class = PCI_CLASS_NOT_DEFINED; + } + + /* We found a fine healthy device, go go go... */ + return 0; +} + +static void pci_release_capabilities(struct pci_dev *dev) +{ + pci_vpd_release(dev); +} + +/** + * pci_release_dev - free a pci device structure when all users of it are finished. + * @dev: device that's been disconnected + * + * Will be called only by the device core when all users of this pci device are + * done. + */ +static void pci_release_dev(struct device *dev) +{ + struct pci_dev *pci_dev; + + pci_dev = to_pci_dev(dev); + pci_release_capabilities(pci_dev); + kfree(pci_dev); +} + +static void set_pcie_port_type(struct pci_dev *pdev) +{ + int pos; + u16 reg16; + + pos = pci_find_capability(pdev, PCI_CAP_ID_EXP); + if (!pos) + return; + pdev->is_pcie = 1; + pci_read_config_word(pdev, pos + PCI_EXP_FLAGS, ®16); + pdev->pcie_type = (reg16 & PCI_EXP_FLAGS_TYPE) >> 4; +} + +/** + * pci_cfg_space_size - get the configuration space size of the PCI device. + * @dev: PCI device + * + * Regular PCI devices have 256 bytes, but PCI-X 2 and PCI Express devices + * have 4096 bytes. Even if the device is capable, that doesn't mean we can + * access it. Maybe we don't have a way to generate extended config space + * accesses, or the device is behind a reverse Express bridge. So we try + * reading the dword at 0x100 which must either be 0 or a valid extended + * capability header. + */ +int pci_cfg_space_size_ext(struct pci_dev *dev) +{ + u32 status; + int pos = PCI_CFG_SPACE_SIZE; + + if (pci_read_config_dword(dev, pos, &status) != PCIBIOS_SUCCESSFUL) + goto fail; + if (status == 0xffffffff) + goto fail; + + return PCI_CFG_SPACE_EXP_SIZE; + + fail: + return PCI_CFG_SPACE_SIZE; +} + +int pci_cfg_space_size(struct pci_dev *dev) +{ + int pos; + u32 status; + + pos = pci_find_capability(dev, PCI_CAP_ID_EXP); + if (!pos) { + pos = pci_find_capability(dev, PCI_CAP_ID_PCIX); + if (!pos) + goto fail; + + pci_read_config_dword(dev, pos + PCI_X_STATUS, &status); + if (!(status & (PCI_X_STATUS_266MHZ | PCI_X_STATUS_533MHZ))) + goto fail; + } + + return pci_cfg_space_size_ext(dev); + + fail: + return PCI_CFG_SPACE_SIZE; +} + +static void pci_release_bus_bridge_dev(struct device *dev) +{ + kfree(dev); +} + +struct pci_dev *alloc_pci_dev(void) +{ + struct pci_dev *dev; + + dev = kzalloc(sizeof(struct pci_dev), GFP_KERNEL); + if (!dev) + return NULL; + + INIT_LIST_HEAD(&dev->bus_list); + + return dev; +} +EXPORT_SYMBOL(alloc_pci_dev); + +/* + * Read the config data for a PCI device, sanity-check it + * and fill in the dev structure... + */ +static struct pci_dev *pci_scan_device(struct pci_bus *bus, int devfn) +{ + struct pci_dev *dev; + struct pci_slot *slot; + u32 l; + u8 hdr_type; + int delay = 1; + + if (pci_bus_read_config_dword(bus, devfn, PCI_VENDOR_ID, &l)) + return NULL; + + /* some broken boards return 0 or ~0 if a slot is empty: */ + if (l == 0xffffffff || l == 0x00000000 || + l == 0x0000ffff || l == 0xffff0000) + return NULL; + + /* Configuration request Retry Status */ + while (l == 0xffff0001) { + msleep(delay); + delay *= 2; + if (pci_bus_read_config_dword(bus, devfn, PCI_VENDOR_ID, &l)) + return NULL; + /* Card hasn't responded in 60 seconds? Must be stuck. */ + if (delay > 60 * 1000) { + printk(KERN_WARNING "pci %04x:%02x:%02x.%d: not " + "responding\n", pci_domain_nr(bus), + bus->number, PCI_SLOT(devfn), + PCI_FUNC(devfn)); + return NULL; + } + } + + if (pci_bus_read_config_byte(bus, devfn, PCI_HEADER_TYPE, &hdr_type)) + return NULL; + + dev = alloc_pci_dev(); + if (!dev) + return NULL; + + dev->bus = bus; + dev->sysdata = bus->sysdata; + dev->dev.parent = bus->bridge; + dev->dev.bus = &pci_bus_type; + dev->devfn = devfn; + dev->hdr_type = hdr_type & 0x7f; + dev->multifunction = !!(hdr_type & 0x80); + dev->vendor = l & 0xffff; + dev->device = (l >> 16) & 0xffff; + dev->cfg_size = pci_cfg_space_size(dev); + dev->error_state = pci_channel_io_normal; + set_pcie_port_type(dev); + + list_for_each_entry(slot, &bus->slots, list) + if (PCI_SLOT(devfn) == slot->number) + dev->slot = slot; + + /* Assume 32-bit PCI; let 64-bit PCI cards (which are far rarer) + set this higher, assuming the system even supports it. */ + dev->dma_mask = 0xffffffff; + if (pci_setup_device(dev) < 0) { + kfree(dev); + return NULL; + } + + return dev; +} + +static void pci_init_capabilities(struct pci_dev *dev) +{ + /* MSI/MSI-X list */ + pci_msi_init_pci_dev(dev); + + /* Power Management */ + pci_pm_init(dev); + + /* Vital Product Data */ + pci_vpd_pci22_init(dev); + + /* Alternative Routing-ID Forwarding */ + pci_enable_ari(dev); +} + +void pci_device_add(struct pci_dev *dev, struct pci_bus *bus) +{ + device_initialize(&dev->dev); + dev->dev.release = pci_release_dev; + pci_dev_get(dev); + + dev->dev.dma_mask = &dev->dma_mask; + dev->dev.dma_parms = &dev->dma_parms; + dev->dev.coherent_dma_mask = 0xffffffffull; + + pci_set_dma_max_seg_size(dev, 65536); + pci_set_dma_seg_boundary(dev, 0xffffffff); + + /* Fix up broken headers */ + pci_fixup_device(pci_fixup_header, dev); + + /* Initialize various capabilities */ + pci_init_capabilities(dev); + + /* + * Add the device to our list of discovered devices + * and the bus list for fixup functions, etc. + */ + down_write(&pci_bus_sem); + list_add_tail(&dev->bus_list, &bus->devices); + up_write(&pci_bus_sem); +} + +struct pci_dev *__ref pci_scan_single_device(struct pci_bus *bus, int devfn) +{ + struct pci_dev *dev; + + dev = pci_scan_device(bus, devfn); + if (!dev) + return NULL; + + pci_device_add(dev, bus); + + return dev; +} +EXPORT_SYMBOL(pci_scan_single_device); + +/** + * pci_scan_slot - scan a PCI slot on a bus for devices. + * @bus: PCI bus to scan + * @devfn: slot number to scan (must have zero function.) + * + * Scan a PCI slot on the specified PCI bus for devices, adding + * discovered devices to the @bus->devices list. New devices + * will not have is_added set. + */ +int pci_scan_slot(struct pci_bus *bus, int devfn) +{ + int func, nr = 0; + int scan_all_fns; + + scan_all_fns = pcibios_scan_all_fns(bus, devfn); + + for (func = 0; func < 8; func++, devfn++) { + struct pci_dev *dev; + + dev = pci_scan_single_device(bus, devfn); + if (dev) { + nr++; + + /* + * If this is a single function device, + * don't scan past the first function. + */ + if (!dev->multifunction) { + if (func > 0) { + dev->multifunction = 1; + } else { + break; + } + } + } else { + if (func == 0 && !scan_all_fns) + break; + } + } + + /* only one slot has pcie device */ + if (bus->self && nr) + pcie_aspm_init_link_state(bus->self); + + return nr; +} + +unsigned int __devinit pci_scan_child_bus(struct pci_bus *bus) +{ + unsigned int devfn, pass, max = bus->secondary; + struct pci_dev *dev; + + pr_debug("PCI: Scanning bus %04x:%02x\n", pci_domain_nr(bus), bus->number); + + /* Go find them, Rover! */ + for (devfn = 0; devfn < 0x100; devfn += 8) + pci_scan_slot(bus, devfn); + + /* + * After performing arch-dependent fixup of the bus, look behind + * all PCI-to-PCI bridges on this bus. + */ + pr_debug("PCI: Fixups for bus %04x:%02x\n", pci_domain_nr(bus), bus->number); + pcibios_fixup_bus(bus); + for (pass=0; pass < 2; pass++) + list_for_each_entry(dev, &bus->devices, bus_list) { + if (dev->hdr_type == PCI_HEADER_TYPE_BRIDGE || + dev->hdr_type == PCI_HEADER_TYPE_CARDBUS) + max = pci_scan_bridge(bus, dev, max, pass); + } + + /* + * We've scanned the bus and so we know all about what's on + * the other side of any bridges that may be on this bus plus + * any devices. + * + * Return how far we've got finding sub-buses. + */ + pr_debug("PCI: Bus scan for %04x:%02x returning with max=%02x\n", + pci_domain_nr(bus), bus->number, max); + return max; +} + +void __attribute__((weak)) set_pci_bus_resources_arch_default(struct pci_bus *b) +{ +} + +struct pci_bus * pci_create_bus(struct device *parent, + int bus, struct pci_ops *ops, void *sysdata) +{ + int error; + struct pci_bus *b; + struct device *dev; + + b = pci_alloc_bus(); + if (!b) + return NULL; + + dev = kmalloc(sizeof(*dev), GFP_KERNEL); + if (!dev){ + kfree(b); + return NULL; + } + + b->sysdata = sysdata; + b->ops = ops; + + if (pci_find_bus(pci_domain_nr(b), bus)) { + /* If we already got to this bus through a different bridge, ignore it */ + pr_debug("PCI: Bus %04x:%02x already known\n", pci_domain_nr(b), bus); + goto err_out; + } + + down_write(&pci_bus_sem); + list_add_tail(&b->node, &pci_root_buses); + up_write(&pci_bus_sem); + + memset(dev, 0, sizeof(*dev)); + dev->parent = parent; + dev->release = pci_release_bus_bridge_dev; + sprintf(dev->bus_id, "pci%04x:%02x", pci_domain_nr(b), bus); + error = device_register(dev); + if (error) + goto dev_reg_err; + b->bridge = get_device(dev); + + if (!parent) + set_dev_node(b->bridge, pcibus_to_node(b)); + + b->dev.class = &pcibus_class; + b->dev.parent = b->bridge; + sprintf(b->dev.bus_id, "%04x:%02x", pci_domain_nr(b), bus); + error = device_register(&b->dev); + if (error) + goto class_dev_reg_err; + error = device_create_file(&b->dev, &dev_attr_cpuaffinity); + if (error) + goto dev_create_file_err; + + /* Create legacy_io and legacy_mem files for this bus */ + pci_create_legacy_files(b); + + b->number = b->secondary = bus; + b->resource[0] = &ioport_resource; + b->resource[1] = &iomem_resource; + + set_pci_bus_resources_arch_default(b); + + return b; + +dev_create_file_err: + device_unregister(&b->dev); +class_dev_reg_err: + device_unregister(dev); +dev_reg_err: + down_write(&pci_bus_sem); + list_del(&b->node); + up_write(&pci_bus_sem); +err_out: + kfree(dev); + kfree(b); + return NULL; +} + +struct pci_bus * __devinit pci_scan_bus_parented(struct device *parent, + int bus, struct pci_ops *ops, void *sysdata) +{ + struct pci_bus *b; + + b = pci_create_bus(parent, bus, ops, sysdata); + if (b) + b->subordinate = pci_scan_child_bus(b); + return b; +} +EXPORT_SYMBOL(pci_scan_bus_parented); + +#ifdef CONFIG_HOTPLUG +EXPORT_SYMBOL(pci_add_new_bus); +EXPORT_SYMBOL(pci_scan_slot); +EXPORT_SYMBOL(pci_scan_bridge); +EXPORT_SYMBOL_GPL(pci_scan_child_bus); +#endif + +static int __init pci_sort_bf_cmp(const struct device *d_a, const struct device *d_b) +{ + const struct pci_dev *a = to_pci_dev(d_a); + const struct pci_dev *b = to_pci_dev(d_b); + + if (pci_domain_nr(a->bus) < pci_domain_nr(b->bus)) return -1; + else if (pci_domain_nr(a->bus) > pci_domain_nr(b->bus)) return 1; + + if (a->bus->number < b->bus->number) return -1; + else if (a->bus->number > b->bus->number) return 1; + + if (a->devfn < b->devfn) return -1; + else if (a->devfn > b->devfn) return 1; + + return 0; +} + +void __init pci_sort_breadthfirst(void) +{ + bus_sort_breadthfirst(&pci_bus_type, &pci_sort_bf_cmp); +} diff --git a/drivers/pci/proc.c b/drivers/pci/proc.c new file mode 100644 index 0000000..e1098c3 --- /dev/null +++ b/drivers/pci/proc.c @@ -0,0 +1,484 @@ +/* + * Procfs interface for the PCI bus. + * + * Copyright (c) 1997--1999 Martin Mares <mj@ucw.cz> + */ + +#include <linux/init.h> +#include <linux/pci.h> +#include <linux/module.h> +#include <linux/proc_fs.h> +#include <linux/seq_file.h> +#include <linux/smp_lock.h> +#include <linux/capability.h> +#include <asm/uaccess.h> +#include <asm/byteorder.h> +#include "pci.h" + +static int proc_initialized; /* = 0 */ + +static loff_t +proc_bus_pci_lseek(struct file *file, loff_t off, int whence) +{ + loff_t new = -1; + struct inode *inode = file->f_path.dentry->d_inode; + + mutex_lock(&inode->i_mutex); + switch (whence) { + case 0: + new = off; + break; + case 1: + new = file->f_pos + off; + break; + case 2: + new = inode->i_size + off; + break; + } + if (new < 0 || new > inode->i_size) + new = -EINVAL; + else + file->f_pos = new; + mutex_unlock(&inode->i_mutex); + return new; +} + +static ssize_t +proc_bus_pci_read(struct file *file, char __user *buf, size_t nbytes, loff_t *ppos) +{ + const struct inode *ino = file->f_path.dentry->d_inode; + const struct proc_dir_entry *dp = PDE(ino); + struct pci_dev *dev = dp->data; + unsigned int pos = *ppos; + unsigned int cnt, size; + + /* + * Normal users can read only the standardized portion of the + * configuration space as several chips lock up when trying to read + * undefined locations (think of Intel PIIX4 as a typical example). + */ + + if (capable(CAP_SYS_ADMIN)) + size = dp->size; + else if (dev->hdr_type == PCI_HEADER_TYPE_CARDBUS) + size = 128; + else + size = 64; + + if (pos >= size) + return 0; + if (nbytes >= size) + nbytes = size; + if (pos + nbytes > size) + nbytes = size - pos; + cnt = nbytes; + + if (!access_ok(VERIFY_WRITE, buf, cnt)) + return -EINVAL; + + if ((pos & 1) && cnt) { + unsigned char val; + pci_user_read_config_byte(dev, pos, &val); + __put_user(val, buf); + buf++; + pos++; + cnt--; + } + + if ((pos & 3) && cnt > 2) { + unsigned short val; + pci_user_read_config_word(dev, pos, &val); + __put_user(cpu_to_le16(val), (__le16 __user *) buf); + buf += 2; + pos += 2; + cnt -= 2; + } + + while (cnt >= 4) { + unsigned int val; + pci_user_read_config_dword(dev, pos, &val); + __put_user(cpu_to_le32(val), (__le32 __user *) buf); + buf += 4; + pos += 4; + cnt -= 4; + } + + if (cnt >= 2) { + unsigned short val; + pci_user_read_config_word(dev, pos, &val); + __put_user(cpu_to_le16(val), (__le16 __user *) buf); + buf += 2; + pos += 2; + cnt -= 2; + } + + if (cnt) { + unsigned char val; + pci_user_read_config_byte(dev, pos, &val); + __put_user(val, buf); + buf++; + pos++; + cnt--; + } + + *ppos = pos; + return nbytes; +} + +static ssize_t +proc_bus_pci_write(struct file *file, const char __user *buf, size_t nbytes, loff_t *ppos) +{ + struct inode *ino = file->f_path.dentry->d_inode; + const struct proc_dir_entry *dp = PDE(ino); + struct pci_dev *dev = dp->data; + int pos = *ppos; + int size = dp->size; + int cnt; + + if (pos >= size) + return 0; + if (nbytes >= size) + nbytes = size; + if (pos + nbytes > size) + nbytes = size - pos; + cnt = nbytes; + + if (!access_ok(VERIFY_READ, buf, cnt)) + return -EINVAL; + + if ((pos & 1) && cnt) { + unsigned char val; + __get_user(val, buf); + pci_user_write_config_byte(dev, pos, val); + buf++; + pos++; + cnt--; + } + + if ((pos & 3) && cnt > 2) { + __le16 val; + __get_user(val, (__le16 __user *) buf); + pci_user_write_config_word(dev, pos, le16_to_cpu(val)); + buf += 2; + pos += 2; + cnt -= 2; + } + + while (cnt >= 4) { + __le32 val; + __get_user(val, (__le32 __user *) buf); + pci_user_write_config_dword(dev, pos, le32_to_cpu(val)); + buf += 4; + pos += 4; + cnt -= 4; + } + + if (cnt >= 2) { + __le16 val; + __get_user(val, (__le16 __user *) buf); + pci_user_write_config_word(dev, pos, le16_to_cpu(val)); + buf += 2; + pos += 2; + cnt -= 2; + } + + if (cnt) { + unsigned char val; + __get_user(val, buf); + pci_user_write_config_byte(dev, pos, val); + buf++; + pos++; + cnt--; + } + + *ppos = pos; + i_size_write(ino, dp->size); + return nbytes; +} + +struct pci_filp_private { + enum pci_mmap_state mmap_state; + int write_combine; +}; + +static long proc_bus_pci_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) +{ + const struct proc_dir_entry *dp = PDE(file->f_dentry->d_inode); + struct pci_dev *dev = dp->data; +#ifdef HAVE_PCI_MMAP + struct pci_filp_private *fpriv = file->private_data; +#endif /* HAVE_PCI_MMAP */ + int ret = 0; + + lock_kernel(); + + switch (cmd) { + case PCIIOC_CONTROLLER: + ret = pci_domain_nr(dev->bus); + break; + +#ifdef HAVE_PCI_MMAP + case PCIIOC_MMAP_IS_IO: + fpriv->mmap_state = pci_mmap_io; + break; + + case PCIIOC_MMAP_IS_MEM: + fpriv->mmap_state = pci_mmap_mem; + break; + + case PCIIOC_WRITE_COMBINE: + if (arg) + fpriv->write_combine = 1; + else + fpriv->write_combine = 0; + break; + +#endif /* HAVE_PCI_MMAP */ + + default: + ret = -EINVAL; + break; + }; + + unlock_kernel(); + return ret; +} + +#ifdef HAVE_PCI_MMAP +static int proc_bus_pci_mmap(struct file *file, struct vm_area_struct *vma) +{ + struct inode *inode = file->f_path.dentry->d_inode; + const struct proc_dir_entry *dp = PDE(inode); + struct pci_dev *dev = dp->data; + struct pci_filp_private *fpriv = file->private_data; + int ret; + + if (!capable(CAP_SYS_RAWIO)) + return -EPERM; + + ret = pci_mmap_page_range(dev, vma, + fpriv->mmap_state, + fpriv->write_combine); + if (ret < 0) + return ret; + + return 0; +} + +static int proc_bus_pci_open(struct inode *inode, struct file *file) +{ + struct pci_filp_private *fpriv = kmalloc(sizeof(*fpriv), GFP_KERNEL); + + if (!fpriv) + return -ENOMEM; + + fpriv->mmap_state = pci_mmap_io; + fpriv->write_combine = 0; + + file->private_data = fpriv; + + return 0; +} + +static int proc_bus_pci_release(struct inode *inode, struct file *file) +{ + kfree(file->private_data); + file->private_data = NULL; + + return 0; +} +#endif /* HAVE_PCI_MMAP */ + +static const struct file_operations proc_bus_pci_operations = { + .owner = THIS_MODULE, + .llseek = proc_bus_pci_lseek, + .read = proc_bus_pci_read, + .write = proc_bus_pci_write, + .unlocked_ioctl = proc_bus_pci_ioctl, +#ifdef HAVE_PCI_MMAP + .open = proc_bus_pci_open, + .release = proc_bus_pci_release, + .mmap = proc_bus_pci_mmap, +#ifdef HAVE_ARCH_PCI_GET_UNMAPPED_AREA + .get_unmapped_area = get_pci_unmapped_area, +#endif /* HAVE_ARCH_PCI_GET_UNMAPPED_AREA */ +#endif /* HAVE_PCI_MMAP */ +}; + +/* iterator */ +static void *pci_seq_start(struct seq_file *m, loff_t *pos) +{ + struct pci_dev *dev = NULL; + loff_t n = *pos; + + for_each_pci_dev(dev) { + if (!n--) + break; + } + return dev; +} + +static void *pci_seq_next(struct seq_file *m, void *v, loff_t *pos) +{ + struct pci_dev *dev = v; + + (*pos)++; + dev = pci_get_device(PCI_ANY_ID, PCI_ANY_ID, dev); + return dev; +} + +static void pci_seq_stop(struct seq_file *m, void *v) +{ + if (v) { + struct pci_dev *dev = v; + pci_dev_put(dev); + } +} + +static int show_device(struct seq_file *m, void *v) +{ + const struct pci_dev *dev = v; + const struct pci_driver *drv; + int i; + + if (dev == NULL) + return 0; + + drv = pci_dev_driver(dev); + seq_printf(m, "%02x%02x\t%04x%04x\t%x", + dev->bus->number, + dev->devfn, + dev->vendor, + dev->device, + dev->irq); + /* Here should be 7 and not PCI_NUM_RESOURCES as we need to preserve compatibility */ + for (i=0; i<7; i++) { + resource_size_t start, end; + pci_resource_to_user(dev, i, &dev->resource[i], &start, &end); + seq_printf(m, "\t%16llx", + (unsigned long long)(start | + (dev->resource[i].flags & PCI_REGION_FLAG_MASK))); + } + for (i=0; i<7; i++) { + resource_size_t start, end; + pci_resource_to_user(dev, i, &dev->resource[i], &start, &end); + seq_printf(m, "\t%16llx", + dev->resource[i].start < dev->resource[i].end ? + (unsigned long long)(end - start) + 1 : 0); + } + seq_putc(m, '\t'); + if (drv) + seq_printf(m, "%s", drv->name); + seq_putc(m, '\n'); + return 0; +} + +static const struct seq_operations proc_bus_pci_devices_op = { + .start = pci_seq_start, + .next = pci_seq_next, + .stop = pci_seq_stop, + .show = show_device +}; + +static struct proc_dir_entry *proc_bus_pci_dir; + +int pci_proc_attach_device(struct pci_dev *dev) +{ + struct pci_bus *bus = dev->bus; + struct proc_dir_entry *e; + char name[16]; + + if (!proc_initialized) + return -EACCES; + + if (!bus->procdir) { + if (pci_proc_domain(bus)) { + sprintf(name, "%04x:%02x", pci_domain_nr(bus), + bus->number); + } else { + sprintf(name, "%02x", bus->number); + } + bus->procdir = proc_mkdir(name, proc_bus_pci_dir); + if (!bus->procdir) + return -ENOMEM; + } + + sprintf(name, "%02x.%x", PCI_SLOT(dev->devfn), PCI_FUNC(dev->devfn)); + e = proc_create_data(name, S_IFREG | S_IRUGO | S_IWUSR, bus->procdir, + &proc_bus_pci_operations, dev); + if (!e) + return -ENOMEM; + e->size = dev->cfg_size; + dev->procent = e; + + return 0; +} + +int pci_proc_detach_device(struct pci_dev *dev) +{ + struct proc_dir_entry *e; + + if ((e = dev->procent)) { + if (atomic_read(&e->count) > 1) + return -EBUSY; + remove_proc_entry(e->name, dev->bus->procdir); + dev->procent = NULL; + } + return 0; +} + +#if 0 +int pci_proc_attach_bus(struct pci_bus* bus) +{ + struct proc_dir_entry *de = bus->procdir; + + if (!proc_initialized) + return -EACCES; + + if (!de) { + char name[16]; + sprintf(name, "%02x", bus->number); + de = bus->procdir = proc_mkdir(name, proc_bus_pci_dir); + if (!de) + return -ENOMEM; + } + return 0; +} +#endif /* 0 */ + +int pci_proc_detach_bus(struct pci_bus* bus) +{ + struct proc_dir_entry *de = bus->procdir; + if (de) + remove_proc_entry(de->name, proc_bus_pci_dir); + return 0; +} + +static int proc_bus_pci_dev_open(struct inode *inode, struct file *file) +{ + return seq_open(file, &proc_bus_pci_devices_op); +} +static const struct file_operations proc_bus_pci_dev_operations = { + .owner = THIS_MODULE, + .open = proc_bus_pci_dev_open, + .read = seq_read, + .llseek = seq_lseek, + .release = seq_release, +}; + +static int __init pci_proc_init(void) +{ + struct pci_dev *dev = NULL; + proc_bus_pci_dir = proc_mkdir("bus/pci", NULL); + proc_create("devices", 0, proc_bus_pci_dir, + &proc_bus_pci_dev_operations); + proc_initialized = 1; + while ((dev = pci_get_device(PCI_ANY_ID, PCI_ANY_ID, dev)) != NULL) { + pci_proc_attach_device(dev); + } + return 0; +} + +device_initcall(pci_proc_init); + diff --git a/drivers/pci/quirks.c b/drivers/pci/quirks.c new file mode 100644 index 0000000..4fafcec --- /dev/null +++ b/drivers/pci/quirks.c @@ -0,0 +1,2053 @@ +/* + * This file contains work-arounds for many known PCI hardware + * bugs. Devices present only on certain architectures (host + * bridges et cetera) should be handled in arch-specific code. + * + * Note: any quirks for hotpluggable devices must _NOT_ be declared __init. + * + * Copyright (c) 1999 Martin Mares <mj@ucw.cz> + * + * Init/reset quirks for USB host controllers should be in the + * USB quirks file, where their drivers can access reuse it. + * + * The bridge optimization stuff has been removed. If you really + * have a silly BIOS which is unable to set your host bridge right, + * use the PowerTweak utility (see http://powertweak.sourceforge.net). + */ + +#include <linux/types.h> +#include <linux/kernel.h> +#include <linux/pci.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/acpi.h> +#include <linux/kallsyms.h> +#include <linux/dmi.h> +#include <linux/pci-aspm.h> +#include "pci.h" + +int isa_dma_bridge_buggy; +EXPORT_SYMBOL(isa_dma_bridge_buggy); +int pci_pci_problems; +EXPORT_SYMBOL(pci_pci_problems); +int pcie_mch_quirk; +EXPORT_SYMBOL(pcie_mch_quirk); + +#ifdef CONFIG_PCI_QUIRKS +/* The Mellanox Tavor device gives false positive parity errors + * Mark this device with a broken_parity_status, to allow + * PCI scanning code to "skip" this now blacklisted device. + */ +static void __devinit quirk_mellanox_tavor(struct pci_dev *dev) +{ + dev->broken_parity_status = 1; /* This device gives false positives */ +} +DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_MELLANOX,PCI_DEVICE_ID_MELLANOX_TAVOR,quirk_mellanox_tavor); +DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_MELLANOX,PCI_DEVICE_ID_MELLANOX_TAVOR_BRIDGE,quirk_mellanox_tavor); + +/* Deal with broken BIOS'es that neglect to enable passive release, + which can cause problems in combination with the 82441FX/PPro MTRRs */ +static void quirk_passive_release(struct pci_dev *dev) +{ + struct pci_dev *d = NULL; + unsigned char dlc; + + /* We have to make sure a particular bit is set in the PIIX3 + ISA bridge, so we have to go out and find it. */ + while ((d = pci_get_device(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82371SB_0, d))) { + pci_read_config_byte(d, 0x82, &dlc); + if (!(dlc & 1<<1)) { + dev_err(&d->dev, "PIIX3: Enabling Passive Release\n"); + dlc |= 1<<1; + pci_write_config_byte(d, 0x82, dlc); + } + } +} +DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82441, quirk_passive_release); +DECLARE_PCI_FIXUP_RESUME(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82441, quirk_passive_release); + +/* The VIA VP2/VP3/MVP3 seem to have some 'features'. There may be a workaround + but VIA don't answer queries. If you happen to have good contacts at VIA + ask them for me please -- Alan + + This appears to be BIOS not version dependent. So presumably there is a + chipset level fix */ + +static void __devinit quirk_isa_dma_hangs(struct pci_dev *dev) +{ + if (!isa_dma_bridge_buggy) { + isa_dma_bridge_buggy=1; + dev_info(&dev->dev, "Activating ISA DMA hang workarounds\n"); + } +} + /* + * Its not totally clear which chipsets are the problematic ones + * We know 82C586 and 82C596 variants are affected. + */ +DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_VIA, PCI_DEVICE_ID_VIA_82C586_0, quirk_isa_dma_hangs); +DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_VIA, PCI_DEVICE_ID_VIA_82C596, quirk_isa_dma_hangs); +DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82371SB_0, quirk_isa_dma_hangs); +DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_AL, PCI_DEVICE_ID_AL_M1533, quirk_isa_dma_hangs); +DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_NEC, PCI_DEVICE_ID_NEC_CBUS_1, quirk_isa_dma_hangs); +DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_NEC, PCI_DEVICE_ID_NEC_CBUS_2, quirk_isa_dma_hangs); +DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_NEC, PCI_DEVICE_ID_NEC_CBUS_3, quirk_isa_dma_hangs); + +/* + * Chipsets where PCI->PCI transfers vanish or hang + */ +static void __devinit quirk_nopcipci(struct pci_dev *dev) +{ + if ((pci_pci_problems & PCIPCI_FAIL)==0) { + dev_info(&dev->dev, "Disabling direct PCI/PCI transfers\n"); + pci_pci_problems |= PCIPCI_FAIL; + } +} +DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_SI, PCI_DEVICE_ID_SI_5597, quirk_nopcipci); +DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_SI, PCI_DEVICE_ID_SI_496, quirk_nopcipci); + +static void __devinit quirk_nopciamd(struct pci_dev *dev) +{ + u8 rev; + pci_read_config_byte(dev, 0x08, &rev); + if (rev == 0x13) { + /* Erratum 24 */ + dev_info(&dev->dev, "Chipset erratum: Disabling direct PCI/AGP transfers\n"); + pci_pci_problems |= PCIAGP_FAIL; + } +} +DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_8151_0, quirk_nopciamd); + +/* + * Triton requires workarounds to be used by the drivers + */ +static void __devinit quirk_triton(struct pci_dev *dev) +{ + if ((pci_pci_problems&PCIPCI_TRITON)==0) { + dev_info(&dev->dev, "Limiting direct PCI/PCI transfers\n"); + pci_pci_problems |= PCIPCI_TRITON; + } +} +DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82437, quirk_triton); +DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82437VX, quirk_triton); +DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82439, quirk_triton); +DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82439TX, quirk_triton); + +/* + * VIA Apollo KT133 needs PCI latency patch + * Made according to a windows driver based patch by George E. Breese + * see PCI Latency Adjust on http://www.viahardware.com/download/viatweak.shtm + * Also see http://www.au-ja.org/review-kt133a-1-en.phtml for + * the info on which Mr Breese based his work. + * + * Updated based on further information from the site and also on + * information provided by VIA + */ +static void quirk_vialatency(struct pci_dev *dev) +{ + struct pci_dev *p; + u8 busarb; + /* Ok we have a potential problem chipset here. Now see if we have + a buggy southbridge */ + + p = pci_get_device(PCI_VENDOR_ID_VIA, PCI_DEVICE_ID_VIA_82C686, NULL); + if (p!=NULL) { + /* 0x40 - 0x4f == 686B, 0x10 - 0x2f == 686A; thanks Dan Hollis */ + /* Check for buggy part revisions */ + if (p->revision < 0x40 || p->revision > 0x42) + goto exit; + } else { + p = pci_get_device(PCI_VENDOR_ID_VIA, PCI_DEVICE_ID_VIA_8231, NULL); + if (p==NULL) /* No problem parts */ + goto exit; + /* Check for buggy part revisions */ + if (p->revision < 0x10 || p->revision > 0x12) + goto exit; + } + + /* + * Ok we have the problem. Now set the PCI master grant to + * occur every master grant. The apparent bug is that under high + * PCI load (quite common in Linux of course) you can get data + * loss when the CPU is held off the bus for 3 bus master requests + * This happens to include the IDE controllers.... + * + * VIA only apply this fix when an SB Live! is present but under + * both Linux and Windows this isnt enough, and we have seen + * corruption without SB Live! but with things like 3 UDMA IDE + * controllers. So we ignore that bit of the VIA recommendation.. + */ + + pci_read_config_byte(dev, 0x76, &busarb); + /* Set bit 4 and bi 5 of byte 76 to 0x01 + "Master priority rotation on every PCI master grant */ + busarb &= ~(1<<5); + busarb |= (1<<4); + pci_write_config_byte(dev, 0x76, busarb); + dev_info(&dev->dev, "Applying VIA southbridge workaround\n"); +exit: + pci_dev_put(p); +} +DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_VIA, PCI_DEVICE_ID_VIA_8363_0, quirk_vialatency); +DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_VIA, PCI_DEVICE_ID_VIA_8371_1, quirk_vialatency); +DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_VIA, PCI_DEVICE_ID_VIA_8361, quirk_vialatency); +/* Must restore this on a resume from RAM */ +DECLARE_PCI_FIXUP_RESUME(PCI_VENDOR_ID_VIA, PCI_DEVICE_ID_VIA_8363_0, quirk_vialatency); +DECLARE_PCI_FIXUP_RESUME(PCI_VENDOR_ID_VIA, PCI_DEVICE_ID_VIA_8371_1, quirk_vialatency); +DECLARE_PCI_FIXUP_RESUME(PCI_VENDOR_ID_VIA, PCI_DEVICE_ID_VIA_8361, quirk_vialatency); + +/* + * VIA Apollo VP3 needs ETBF on BT848/878 + */ +static void __devinit quirk_viaetbf(struct pci_dev *dev) +{ + if ((pci_pci_problems&PCIPCI_VIAETBF)==0) { + dev_info(&dev->dev, "Limiting direct PCI/PCI transfers\n"); + pci_pci_problems |= PCIPCI_VIAETBF; + } +} +DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_VIA, PCI_DEVICE_ID_VIA_82C597_0, quirk_viaetbf); + +static void __devinit quirk_vsfx(struct pci_dev *dev) +{ + if ((pci_pci_problems&PCIPCI_VSFX)==0) { + dev_info(&dev->dev, "Limiting direct PCI/PCI transfers\n"); + pci_pci_problems |= PCIPCI_VSFX; + } +} +DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_VIA, PCI_DEVICE_ID_VIA_82C576, quirk_vsfx); + +/* + * Ali Magik requires workarounds to be used by the drivers + * that DMA to AGP space. Latency must be set to 0xA and triton + * workaround applied too + * [Info kindly provided by ALi] + */ +static void __init quirk_alimagik(struct pci_dev *dev) +{ + if ((pci_pci_problems&PCIPCI_ALIMAGIK)==0) { + dev_info(&dev->dev, "Limiting direct PCI/PCI transfers\n"); + pci_pci_problems |= PCIPCI_ALIMAGIK|PCIPCI_TRITON; + } +} +DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_AL, PCI_DEVICE_ID_AL_M1647, quirk_alimagik); +DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_AL, PCI_DEVICE_ID_AL_M1651, quirk_alimagik); + +/* + * Natoma has some interesting boundary conditions with Zoran stuff + * at least + */ +static void __devinit quirk_natoma(struct pci_dev *dev) +{ + if ((pci_pci_problems&PCIPCI_NATOMA)==0) { + dev_info(&dev->dev, "Limiting direct PCI/PCI transfers\n"); + pci_pci_problems |= PCIPCI_NATOMA; + } +} +DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82441, quirk_natoma); +DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82443LX_0, quirk_natoma); +DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82443LX_1, quirk_natoma); +DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82443BX_0, quirk_natoma); +DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82443BX_1, quirk_natoma); +DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82443BX_2, quirk_natoma); + +/* + * This chip can cause PCI parity errors if config register 0xA0 is read + * while DMAs are occurring. + */ +static void __devinit quirk_citrine(struct pci_dev *dev) +{ + dev->cfg_size = 0xA0; +} +DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_IBM, PCI_DEVICE_ID_IBM_CITRINE, quirk_citrine); + +/* + * S3 868 and 968 chips report region size equal to 32M, but they decode 64M. + * If it's needed, re-allocate the region. + */ +static void __devinit quirk_s3_64M(struct pci_dev *dev) +{ + struct resource *r = &dev->resource[0]; + + if ((r->start & 0x3ffffff) || r->end != r->start + 0x3ffffff) { + r->start = 0; + r->end = 0x3ffffff; + } +} +DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_S3, PCI_DEVICE_ID_S3_868, quirk_s3_64M); +DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_S3, PCI_DEVICE_ID_S3_968, quirk_s3_64M); + +static void __devinit quirk_io_region(struct pci_dev *dev, unsigned region, + unsigned size, int nr, const char *name) +{ + region &= ~(size-1); + if (region) { + struct pci_bus_region bus_region; + struct resource *res = dev->resource + nr; + + res->name = pci_name(dev); + res->start = region; + res->end = region + size - 1; + res->flags = IORESOURCE_IO; + + /* Convert from PCI bus to resource space. */ + bus_region.start = res->start; + bus_region.end = res->end; + pcibios_bus_to_resource(dev, res, &bus_region); + + pci_claim_resource(dev, nr); + dev_info(&dev->dev, "quirk: region %04x-%04x claimed by %s\n", region, region + size - 1, name); + } +} + +/* + * ATI Northbridge setups MCE the processor if you even + * read somewhere between 0x3b0->0x3bb or read 0x3d3 + */ +static void __devinit quirk_ati_exploding_mce(struct pci_dev *dev) +{ + dev_info(&dev->dev, "ATI Northbridge, reserving I/O ports 0x3b0 to 0x3bb\n"); + /* Mae rhaid i ni beidio ag edrych ar y lleoliadiau I/O hyn */ + request_region(0x3b0, 0x0C, "RadeonIGP"); + request_region(0x3d3, 0x01, "RadeonIGP"); +} +DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_ATI, PCI_DEVICE_ID_ATI_RS100, quirk_ati_exploding_mce); + +/* + * Let's make the southbridge information explicit instead + * of having to worry about people probing the ACPI areas, + * for example.. (Yes, it happens, and if you read the wrong + * ACPI register it will put the machine to sleep with no + * way of waking it up again. Bummer). + * + * ALI M7101: Two IO regions pointed to by words at + * 0xE0 (64 bytes of ACPI registers) + * 0xE2 (32 bytes of SMB registers) + */ +static void __devinit quirk_ali7101_acpi(struct pci_dev *dev) +{ + u16 region; + + pci_read_config_word(dev, 0xE0, ®ion); + quirk_io_region(dev, region, 64, PCI_BRIDGE_RESOURCES, "ali7101 ACPI"); + pci_read_config_word(dev, 0xE2, ®ion); + quirk_io_region(dev, region, 32, PCI_BRIDGE_RESOURCES+1, "ali7101 SMB"); +} +DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_AL, PCI_DEVICE_ID_AL_M7101, quirk_ali7101_acpi); + +static void piix4_io_quirk(struct pci_dev *dev, const char *name, unsigned int port, unsigned int enable) +{ + u32 devres; + u32 mask, size, base; + + pci_read_config_dword(dev, port, &devres); + if ((devres & enable) != enable) + return; + mask = (devres >> 16) & 15; + base = devres & 0xffff; + size = 16; + for (;;) { + unsigned bit = size >> 1; + if ((bit & mask) == bit) + break; + size = bit; + } + /* + * For now we only print it out. Eventually we'll want to + * reserve it (at least if it's in the 0x1000+ range), but + * let's get enough confirmation reports first. + */ + base &= -size; + dev_info(&dev->dev, "%s PIO at %04x-%04x\n", name, base, base + size - 1); +} + +static void piix4_mem_quirk(struct pci_dev *dev, const char *name, unsigned int port, unsigned int enable) +{ + u32 devres; + u32 mask, size, base; + + pci_read_config_dword(dev, port, &devres); + if ((devres & enable) != enable) + return; + base = devres & 0xffff0000; + mask = (devres & 0x3f) << 16; + size = 128 << 16; + for (;;) { + unsigned bit = size >> 1; + if ((bit & mask) == bit) + break; + size = bit; + } + /* + * For now we only print it out. Eventually we'll want to + * reserve it, but let's get enough confirmation reports first. + */ + base &= -size; + dev_info(&dev->dev, "%s MMIO at %04x-%04x\n", name, base, base + size - 1); +} + +/* + * PIIX4 ACPI: Two IO regions pointed to by longwords at + * 0x40 (64 bytes of ACPI registers) + * 0x90 (16 bytes of SMB registers) + * and a few strange programmable PIIX4 device resources. + */ +static void __devinit quirk_piix4_acpi(struct pci_dev *dev) +{ + u32 region, res_a; + + pci_read_config_dword(dev, 0x40, ®ion); + quirk_io_region(dev, region, 64, PCI_BRIDGE_RESOURCES, "PIIX4 ACPI"); + pci_read_config_dword(dev, 0x90, ®ion); + quirk_io_region(dev, region, 16, PCI_BRIDGE_RESOURCES+1, "PIIX4 SMB"); + + /* Device resource A has enables for some of the other ones */ + pci_read_config_dword(dev, 0x5c, &res_a); + + piix4_io_quirk(dev, "PIIX4 devres B", 0x60, 3 << 21); + piix4_io_quirk(dev, "PIIX4 devres C", 0x64, 3 << 21); + + /* Device resource D is just bitfields for static resources */ + + /* Device 12 enabled? */ + if (res_a & (1 << 29)) { + piix4_io_quirk(dev, "PIIX4 devres E", 0x68, 1 << 20); + piix4_mem_quirk(dev, "PIIX4 devres F", 0x6c, 1 << 7); + } + /* Device 13 enabled? */ + if (res_a & (1 << 30)) { + piix4_io_quirk(dev, "PIIX4 devres G", 0x70, 1 << 20); + piix4_mem_quirk(dev, "PIIX4 devres H", 0x74, 1 << 7); + } + piix4_io_quirk(dev, "PIIX4 devres I", 0x78, 1 << 20); + piix4_io_quirk(dev, "PIIX4 devres J", 0x7c, 1 << 20); +} +DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82371AB_3, quirk_piix4_acpi); +DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82443MX_3, quirk_piix4_acpi); + +/* + * ICH4, ICH4-M, ICH5, ICH5-M ACPI: Three IO regions pointed to by longwords at + * 0x40 (128 bytes of ACPI, GPIO & TCO registers) + * 0x58 (64 bytes of GPIO I/O space) + */ +static void __devinit quirk_ich4_lpc_acpi(struct pci_dev *dev) +{ + u32 region; + + pci_read_config_dword(dev, 0x40, ®ion); + quirk_io_region(dev, region, 128, PCI_BRIDGE_RESOURCES, "ICH4 ACPI/GPIO/TCO"); + + pci_read_config_dword(dev, 0x58, ®ion); + quirk_io_region(dev, region, 64, PCI_BRIDGE_RESOURCES+1, "ICH4 GPIO"); +} +DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82801AA_0, quirk_ich4_lpc_acpi); +DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82801AB_0, quirk_ich4_lpc_acpi); +DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82801BA_0, quirk_ich4_lpc_acpi); +DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82801BA_10, quirk_ich4_lpc_acpi); +DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82801CA_0, quirk_ich4_lpc_acpi); +DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82801CA_12, quirk_ich4_lpc_acpi); +DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82801DB_0, quirk_ich4_lpc_acpi); +DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82801DB_12, quirk_ich4_lpc_acpi); +DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82801EB_0, quirk_ich4_lpc_acpi); +DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_ESB_1, quirk_ich4_lpc_acpi); + +static void __devinit quirk_ich6_lpc_acpi(struct pci_dev *dev) +{ + u32 region; + + pci_read_config_dword(dev, 0x40, ®ion); + quirk_io_region(dev, region, 128, PCI_BRIDGE_RESOURCES, "ICH6 ACPI/GPIO/TCO"); + + pci_read_config_dword(dev, 0x48, ®ion); + quirk_io_region(dev, region, 64, PCI_BRIDGE_RESOURCES+1, "ICH6 GPIO"); +} +DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_ICH6_0, quirk_ich6_lpc_acpi); +DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_ICH6_1, quirk_ich6_lpc_acpi); +DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_ICH7_0, quirk_ich6_lpc_acpi); +DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_ICH7_1, quirk_ich6_lpc_acpi); +DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_ICH7_31, quirk_ich6_lpc_acpi); +DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_ICH8_0, quirk_ich6_lpc_acpi); +DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_ICH8_2, quirk_ich6_lpc_acpi); +DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_ICH8_3, quirk_ich6_lpc_acpi); +DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_ICH8_1, quirk_ich6_lpc_acpi); +DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_ICH8_4, quirk_ich6_lpc_acpi); +DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_ICH9_2, quirk_ich6_lpc_acpi); +DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_ICH9_4, quirk_ich6_lpc_acpi); +DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_ICH9_7, quirk_ich6_lpc_acpi); +DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_ICH9_8, quirk_ich6_lpc_acpi); + +/* + * VIA ACPI: One IO region pointed to by longword at + * 0x48 or 0x20 (256 bytes of ACPI registers) + */ +static void __devinit quirk_vt82c586_acpi(struct pci_dev *dev) +{ + u32 region; + + if (dev->revision & 0x10) { + pci_read_config_dword(dev, 0x48, ®ion); + region &= PCI_BASE_ADDRESS_IO_MASK; + quirk_io_region(dev, region, 256, PCI_BRIDGE_RESOURCES, "vt82c586 ACPI"); + } +} +DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_VIA, PCI_DEVICE_ID_VIA_82C586_3, quirk_vt82c586_acpi); + +/* + * VIA VT82C686 ACPI: Three IO region pointed to by (long)words at + * 0x48 (256 bytes of ACPI registers) + * 0x70 (128 bytes of hardware monitoring register) + * 0x90 (16 bytes of SMB registers) + */ +static void __devinit quirk_vt82c686_acpi(struct pci_dev *dev) +{ + u16 hm; + u32 smb; + + quirk_vt82c586_acpi(dev); + + pci_read_config_word(dev, 0x70, &hm); + hm &= PCI_BASE_ADDRESS_IO_MASK; + quirk_io_region(dev, hm, 128, PCI_BRIDGE_RESOURCES + 1, "vt82c686 HW-mon"); + + pci_read_config_dword(dev, 0x90, &smb); + smb &= PCI_BASE_ADDRESS_IO_MASK; + quirk_io_region(dev, smb, 16, PCI_BRIDGE_RESOURCES + 2, "vt82c686 SMB"); +} +DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_VIA, PCI_DEVICE_ID_VIA_82C686_4, quirk_vt82c686_acpi); + +/* + * VIA VT8235 ISA Bridge: Two IO regions pointed to by words at + * 0x88 (128 bytes of power management registers) + * 0xd0 (16 bytes of SMB registers) + */ +static void __devinit quirk_vt8235_acpi(struct pci_dev *dev) +{ + u16 pm, smb; + + pci_read_config_word(dev, 0x88, &pm); + pm &= PCI_BASE_ADDRESS_IO_MASK; + quirk_io_region(dev, pm, 128, PCI_BRIDGE_RESOURCES, "vt8235 PM"); + + pci_read_config_word(dev, 0xd0, &smb); + smb &= PCI_BASE_ADDRESS_IO_MASK; + quirk_io_region(dev, smb, 16, PCI_BRIDGE_RESOURCES + 1, "vt8235 SMB"); +} +DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_VIA, PCI_DEVICE_ID_VIA_8235, quirk_vt8235_acpi); + + +#ifdef CONFIG_X86_IO_APIC + +#include <asm/io_apic.h> + +/* + * VIA 686A/B: If an IO-APIC is active, we need to route all on-chip + * devices to the external APIC. + * + * TODO: When we have device-specific interrupt routers, + * this code will go away from quirks. + */ +static void quirk_via_ioapic(struct pci_dev *dev) +{ + u8 tmp; + + if (nr_ioapics < 1) + tmp = 0; /* nothing routed to external APIC */ + else + tmp = 0x1f; /* all known bits (4-0) routed to external APIC */ + + dev_info(&dev->dev, "%sbling VIA external APIC routing\n", + tmp == 0 ? "Disa" : "Ena"); + + /* Offset 0x58: External APIC IRQ output control */ + pci_write_config_byte (dev, 0x58, tmp); +} +DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_VIA, PCI_DEVICE_ID_VIA_82C686, quirk_via_ioapic); +DECLARE_PCI_FIXUP_RESUME_EARLY(PCI_VENDOR_ID_VIA, PCI_DEVICE_ID_VIA_82C686, quirk_via_ioapic); + +/* + * VIA 8237: Some BIOSs don't set the 'Bypass APIC De-Assert Message' Bit. + * This leads to doubled level interrupt rates. + * Set this bit to get rid of cycle wastage. + * Otherwise uncritical. + */ +static void quirk_via_vt8237_bypass_apic_deassert(struct pci_dev *dev) +{ + u8 misc_control2; +#define BYPASS_APIC_DEASSERT 8 + + pci_read_config_byte(dev, 0x5B, &misc_control2); + if (!(misc_control2 & BYPASS_APIC_DEASSERT)) { + dev_info(&dev->dev, "Bypassing VIA 8237 APIC De-Assert Message\n"); + pci_write_config_byte(dev, 0x5B, misc_control2|BYPASS_APIC_DEASSERT); + } +} +DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_VIA, PCI_DEVICE_ID_VIA_8237, quirk_via_vt8237_bypass_apic_deassert); +DECLARE_PCI_FIXUP_RESUME_EARLY(PCI_VENDOR_ID_VIA, PCI_DEVICE_ID_VIA_8237, quirk_via_vt8237_bypass_apic_deassert); + +/* + * The AMD io apic can hang the box when an apic irq is masked. + * We check all revs >= B0 (yet not in the pre production!) as the bug + * is currently marked NoFix + * + * We have multiple reports of hangs with this chipset that went away with + * noapic specified. For the moment we assume it's the erratum. We may be wrong + * of course. However the advice is demonstrably good even if so.. + */ +static void __devinit quirk_amd_ioapic(struct pci_dev *dev) +{ + if (dev->revision >= 0x02) { + dev_warn(&dev->dev, "I/O APIC: AMD Erratum #22 may be present. In the event of instability try\n"); + dev_warn(&dev->dev, " : booting with the \"noapic\" option\n"); + } +} +DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_VIPER_7410, quirk_amd_ioapic); + +static void __init quirk_ioapic_rmw(struct pci_dev *dev) +{ + if (dev->devfn == 0 && dev->bus->number == 0) + sis_apic_bug = 1; +} +DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_SI, PCI_ANY_ID, quirk_ioapic_rmw); + +#define AMD8131_revA0 0x01 +#define AMD8131_revB0 0x11 +#define AMD8131_MISC 0x40 +#define AMD8131_NIOAMODE_BIT 0 +static void quirk_amd_8131_ioapic(struct pci_dev *dev) +{ + unsigned char tmp; + + if (nr_ioapics == 0) + return; + + if (dev->revision == AMD8131_revA0 || dev->revision == AMD8131_revB0) { + dev_info(&dev->dev, "Fixing up AMD8131 IOAPIC mode\n"); + pci_read_config_byte( dev, AMD8131_MISC, &tmp); + tmp &= ~(1 << AMD8131_NIOAMODE_BIT); + pci_write_config_byte( dev, AMD8131_MISC, tmp); + } +} +DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_8131_BRIDGE, quirk_amd_8131_ioapic); +DECLARE_PCI_FIXUP_RESUME_EARLY(PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_8131_BRIDGE, quirk_amd_8131_ioapic); +#endif /* CONFIG_X86_IO_APIC */ + +/* + * Some settings of MMRBC can lead to data corruption so block changes. + * See AMD 8131 HyperTransport PCI-X Tunnel Revision Guide + */ +static void __init quirk_amd_8131_mmrbc(struct pci_dev *dev) +{ + if (dev->subordinate && dev->revision <= 0x12) { + dev_info(&dev->dev, "AMD8131 rev %x detected; " + "disabling PCI-X MMRBC\n", dev->revision); + dev->subordinate->bus_flags |= PCI_BUS_FLAGS_NO_MMRBC; + } +} +DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_8131_BRIDGE, quirk_amd_8131_mmrbc); + +/* + * FIXME: it is questionable that quirk_via_acpi + * is needed. It shows up as an ISA bridge, and does not + * support the PCI_INTERRUPT_LINE register at all. Therefore + * it seems like setting the pci_dev's 'irq' to the + * value of the ACPI SCI interrupt is only done for convenience. + * -jgarzik + */ +static void __devinit quirk_via_acpi(struct pci_dev *d) +{ + /* + * VIA ACPI device: SCI IRQ line in PCI config byte 0x42 + */ + u8 irq; + pci_read_config_byte(d, 0x42, &irq); + irq &= 0xf; + if (irq && (irq != 2)) + d->irq = irq; +} +DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_VIA, PCI_DEVICE_ID_VIA_82C586_3, quirk_via_acpi); +DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_VIA, PCI_DEVICE_ID_VIA_82C686_4, quirk_via_acpi); + + +/* + * VIA bridges which have VLink + */ + +static int via_vlink_dev_lo = -1, via_vlink_dev_hi = 18; + +static void quirk_via_bridge(struct pci_dev *dev) +{ + /* See what bridge we have and find the device ranges */ + switch (dev->device) { + case PCI_DEVICE_ID_VIA_82C686: + /* The VT82C686 is special, it attaches to PCI and can have + any device number. All its subdevices are functions of + that single device. */ + via_vlink_dev_lo = PCI_SLOT(dev->devfn); + via_vlink_dev_hi = PCI_SLOT(dev->devfn); + break; + case PCI_DEVICE_ID_VIA_8237: + case PCI_DEVICE_ID_VIA_8237A: + via_vlink_dev_lo = 15; + break; + case PCI_DEVICE_ID_VIA_8235: + via_vlink_dev_lo = 16; + break; + case PCI_DEVICE_ID_VIA_8231: + case PCI_DEVICE_ID_VIA_8233_0: + case PCI_DEVICE_ID_VIA_8233A: + case PCI_DEVICE_ID_VIA_8233C_0: + via_vlink_dev_lo = 17; + break; + } +} +DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_VIA, PCI_DEVICE_ID_VIA_82C686, quirk_via_bridge); +DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_VIA, PCI_DEVICE_ID_VIA_8231, quirk_via_bridge); +DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_VIA, PCI_DEVICE_ID_VIA_8233_0, quirk_via_bridge); +DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_VIA, PCI_DEVICE_ID_VIA_8233A, quirk_via_bridge); +DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_VIA, PCI_DEVICE_ID_VIA_8233C_0, quirk_via_bridge); +DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_VIA, PCI_DEVICE_ID_VIA_8235, quirk_via_bridge); +DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_VIA, PCI_DEVICE_ID_VIA_8237, quirk_via_bridge); +DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_VIA, PCI_DEVICE_ID_VIA_8237A, quirk_via_bridge); + +/** + * quirk_via_vlink - VIA VLink IRQ number update + * @dev: PCI device + * + * If the device we are dealing with is on a PIC IRQ we need to + * ensure that the IRQ line register which usually is not relevant + * for PCI cards, is actually written so that interrupts get sent + * to the right place. + * We only do this on systems where a VIA south bridge was detected, + * and only for VIA devices on the motherboard (see quirk_via_bridge + * above). + */ + +static void quirk_via_vlink(struct pci_dev *dev) +{ + u8 irq, new_irq; + + /* Check if we have VLink at all */ + if (via_vlink_dev_lo == -1) + return; + + new_irq = dev->irq; + + /* Don't quirk interrupts outside the legacy IRQ range */ + if (!new_irq || new_irq > 15) + return; + + /* Internal device ? */ + if (dev->bus->number != 0 || PCI_SLOT(dev->devfn) > via_vlink_dev_hi || + PCI_SLOT(dev->devfn) < via_vlink_dev_lo) + return; + + /* This is an internal VLink device on a PIC interrupt. The BIOS + ought to have set this but may not have, so we redo it */ + + pci_read_config_byte(dev, PCI_INTERRUPT_LINE, &irq); + if (new_irq != irq) { + dev_info(&dev->dev, "VIA VLink IRQ fixup, from %d to %d\n", + irq, new_irq); + udelay(15); /* unknown if delay really needed */ + pci_write_config_byte(dev, PCI_INTERRUPT_LINE, new_irq); + } +} +DECLARE_PCI_FIXUP_ENABLE(PCI_VENDOR_ID_VIA, PCI_ANY_ID, quirk_via_vlink); + +/* + * VIA VT82C598 has its device ID settable and many BIOSes + * set it to the ID of VT82C597 for backward compatibility. + * We need to switch it off to be able to recognize the real + * type of the chip. + */ +static void __devinit quirk_vt82c598_id(struct pci_dev *dev) +{ + pci_write_config_byte(dev, 0xfc, 0); + pci_read_config_word(dev, PCI_DEVICE_ID, &dev->device); +} +DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_VIA, PCI_DEVICE_ID_VIA_82C597_0, quirk_vt82c598_id); + +/* + * CardBus controllers have a legacy base address that enables them + * to respond as i82365 pcmcia controllers. We don't want them to + * do this even if the Linux CardBus driver is not loaded, because + * the Linux i82365 driver does not (and should not) handle CardBus. + */ +static void quirk_cardbus_legacy(struct pci_dev *dev) +{ + if ((PCI_CLASS_BRIDGE_CARDBUS << 8) ^ dev->class) + return; + pci_write_config_dword(dev, PCI_CB_LEGACY_MODE_BASE, 0); +} +DECLARE_PCI_FIXUP_FINAL(PCI_ANY_ID, PCI_ANY_ID, quirk_cardbus_legacy); +DECLARE_PCI_FIXUP_RESUME_EARLY(PCI_ANY_ID, PCI_ANY_ID, quirk_cardbus_legacy); + +/* + * Following the PCI ordering rules is optional on the AMD762. I'm not + * sure what the designers were smoking but let's not inhale... + * + * To be fair to AMD, it follows the spec by default, its BIOS people + * who turn it off! + */ +static void quirk_amd_ordering(struct pci_dev *dev) +{ + u32 pcic; + pci_read_config_dword(dev, 0x4C, &pcic); + if ((pcic&6)!=6) { + pcic |= 6; + dev_warn(&dev->dev, "BIOS failed to enable PCI standards compliance; fixing this error\n"); + pci_write_config_dword(dev, 0x4C, pcic); + pci_read_config_dword(dev, 0x84, &pcic); + pcic |= (1<<23); /* Required in this mode */ + pci_write_config_dword(dev, 0x84, pcic); + } +} +DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_FE_GATE_700C, quirk_amd_ordering); +DECLARE_PCI_FIXUP_RESUME_EARLY(PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_FE_GATE_700C, quirk_amd_ordering); + +/* + * DreamWorks provided workaround for Dunord I-3000 problem + * + * This card decodes and responds to addresses not apparently + * assigned to it. We force a larger allocation to ensure that + * nothing gets put too close to it. + */ +static void __devinit quirk_dunord ( struct pci_dev * dev ) +{ + struct resource *r = &dev->resource [1]; + r->start = 0; + r->end = 0xffffff; +} +DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_DUNORD, PCI_DEVICE_ID_DUNORD_I3000, quirk_dunord); + +/* + * i82380FB mobile docking controller: its PCI-to-PCI bridge + * is subtractive decoding (transparent), and does indicate this + * in the ProgIf. Unfortunately, the ProgIf value is wrong - 0x80 + * instead of 0x01. + */ +static void __devinit quirk_transparent_bridge(struct pci_dev *dev) +{ + dev->transparent = 1; +} +DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82380FB, quirk_transparent_bridge); +DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_TOSHIBA, 0x605, quirk_transparent_bridge); + +/* + * Common misconfiguration of the MediaGX/Geode PCI master that will + * reduce PCI bandwidth from 70MB/s to 25MB/s. See the GXM/GXLV/GX1 + * datasheets found at http://www.national.com/ds/GX for info on what + * these bits do. <christer@weinigel.se> + */ +static void quirk_mediagx_master(struct pci_dev *dev) +{ + u8 reg; + pci_read_config_byte(dev, 0x41, ®); + if (reg & 2) { + reg &= ~2; + dev_info(&dev->dev, "Fixup for MediaGX/Geode Slave Disconnect Boundary (0x41=0x%02x)\n", reg); + pci_write_config_byte(dev, 0x41, reg); + } +} +DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_CYRIX, PCI_DEVICE_ID_CYRIX_PCI_MASTER, quirk_mediagx_master); +DECLARE_PCI_FIXUP_RESUME(PCI_VENDOR_ID_CYRIX, PCI_DEVICE_ID_CYRIX_PCI_MASTER, quirk_mediagx_master); + +/* + * Ensure C0 rev restreaming is off. This is normally done by + * the BIOS but in the odd case it is not the results are corruption + * hence the presence of a Linux check + */ +static void quirk_disable_pxb(struct pci_dev *pdev) +{ + u16 config; + + if (pdev->revision != 0x04) /* Only C0 requires this */ + return; + pci_read_config_word(pdev, 0x40, &config); + if (config & (1<<6)) { + config &= ~(1<<6); + pci_write_config_word(pdev, 0x40, config); + dev_info(&pdev->dev, "C0 revision 450NX. Disabling PCI restreaming\n"); + } +} +DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82454NX, quirk_disable_pxb); +DECLARE_PCI_FIXUP_RESUME_EARLY(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82454NX, quirk_disable_pxb); + +static void __devinit quirk_amd_ide_mode(struct pci_dev *pdev) +{ + /* set sb600/sb700/sb800 sata to ahci mode */ + u8 tmp; + + pci_read_config_byte(pdev, PCI_CLASS_DEVICE, &tmp); + if (tmp == 0x01) { + pci_read_config_byte(pdev, 0x40, &tmp); + pci_write_config_byte(pdev, 0x40, tmp|1); + pci_write_config_byte(pdev, 0x9, 1); + pci_write_config_byte(pdev, 0xa, 6); + pci_write_config_byte(pdev, 0x40, tmp); + + pdev->class = PCI_CLASS_STORAGE_SATA_AHCI; + dev_info(&pdev->dev, "set SATA to AHCI mode\n"); + } +} +DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_ATI, PCI_DEVICE_ID_ATI_IXP600_SATA, quirk_amd_ide_mode); +DECLARE_PCI_FIXUP_RESUME_EARLY(PCI_VENDOR_ID_ATI, PCI_DEVICE_ID_ATI_IXP600_SATA, quirk_amd_ide_mode); +DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_ATI, PCI_DEVICE_ID_ATI_IXP700_SATA, quirk_amd_ide_mode); +DECLARE_PCI_FIXUP_RESUME_EARLY(PCI_VENDOR_ID_ATI, PCI_DEVICE_ID_ATI_IXP700_SATA, quirk_amd_ide_mode); + +/* + * Serverworks CSB5 IDE does not fully support native mode + */ +static void __devinit quirk_svwks_csb5ide(struct pci_dev *pdev) +{ + u8 prog; + pci_read_config_byte(pdev, PCI_CLASS_PROG, &prog); + if (prog & 5) { + prog &= ~5; + pdev->class &= ~5; + pci_write_config_byte(pdev, PCI_CLASS_PROG, prog); + /* PCI layer will sort out resources */ + } +} +DECLARE_PCI_FIXUP_EARLY(PCI_VENDOR_ID_SERVERWORKS, PCI_DEVICE_ID_SERVERWORKS_CSB5IDE, quirk_svwks_csb5ide); + +/* + * Intel 82801CAM ICH3-M datasheet says IDE modes must be the same + */ +static void __init quirk_ide_samemode(struct pci_dev *pdev) +{ + u8 prog; + + pci_read_config_byte(pdev, PCI_CLASS_PROG, &prog); + + if (((prog & 1) && !(prog & 4)) || ((prog & 4) && !(prog & 1))) { + dev_info(&pdev->dev, "IDE mode mismatch; forcing legacy mode\n"); + prog &= ~5; + pdev->class &= ~5; + pci_write_config_byte(pdev, PCI_CLASS_PROG, prog); + } +} +DECLARE_PCI_FIXUP_EARLY(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82801CA_10, quirk_ide_samemode); + +/* + * Some ATA devices break if put into D3 + */ + +static void __devinit quirk_no_ata_d3(struct pci_dev *pdev) +{ + /* Quirk the legacy ATA devices only. The AHCI ones are ok */ + if ((pdev->class >> 8) == PCI_CLASS_STORAGE_IDE) + pdev->dev_flags |= PCI_DEV_FLAGS_NO_D3; +} +DECLARE_PCI_FIXUP_EARLY(PCI_VENDOR_ID_SERVERWORKS, PCI_ANY_ID, quirk_no_ata_d3); +DECLARE_PCI_FIXUP_EARLY(PCI_VENDOR_ID_ATI, PCI_ANY_ID, quirk_no_ata_d3); + +/* This was originally an Alpha specific thing, but it really fits here. + * The i82375 PCI/EISA bridge appears as non-classified. Fix that. + */ +static void __init quirk_eisa_bridge(struct pci_dev *dev) +{ + dev->class = PCI_CLASS_BRIDGE_EISA << 8; +} +DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82375, quirk_eisa_bridge); + + +/* + * On ASUS P4B boards, the SMBus PCI Device within the ICH2/4 southbridge + * is not activated. The myth is that Asus said that they do not want the + * users to be irritated by just another PCI Device in the Win98 device + * manager. (see the file prog/hotplug/README.p4b in the lm_sensors + * package 2.7.0 for details) + * + * The SMBus PCI Device can be activated by setting a bit in the ICH LPC + * bridge. Unfortunately, this device has no subvendor/subdevice ID. So it + * becomes necessary to do this tweak in two steps -- the chosen trigger + * is either the Host bridge (preferred) or on-board VGA controller. + * + * Note that we used to unhide the SMBus that way on Toshiba laptops + * (Satellite A40 and Tecra M2) but then found that the thermal management + * was done by SMM code, which could cause unsynchronized concurrent + * accesses to the SMBus registers, with potentially bad effects. Thus you + * should be very careful when adding new entries: if SMM is accessing the + * Intel SMBus, this is a very good reason to leave it hidden. + * + * Likewise, many recent laptops use ACPI for thermal management. If the + * ACPI DSDT code accesses the SMBus, then Linux should not access it + * natively, and keeping the SMBus hidden is the right thing to do. If you + * are about to add an entry in the table below, please first disassemble + * the DSDT and double-check that there is no code accessing the SMBus. + */ +static int asus_hides_smbus; + +static void __init asus_hides_smbus_hostbridge(struct pci_dev *dev) +{ + if (unlikely(dev->subsystem_vendor == PCI_VENDOR_ID_ASUSTEK)) { + if (dev->device == PCI_DEVICE_ID_INTEL_82845_HB) + switch(dev->subsystem_device) { + case 0x8025: /* P4B-LX */ + case 0x8070: /* P4B */ + case 0x8088: /* P4B533 */ + case 0x1626: /* L3C notebook */ + asus_hides_smbus = 1; + } + else if (dev->device == PCI_DEVICE_ID_INTEL_82845G_HB) + switch(dev->subsystem_device) { + case 0x80b1: /* P4GE-V */ + case 0x80b2: /* P4PE */ + case 0x8093: /* P4B533-V */ + asus_hides_smbus = 1; + } + else if (dev->device == PCI_DEVICE_ID_INTEL_82850_HB) + switch(dev->subsystem_device) { + case 0x8030: /* P4T533 */ + asus_hides_smbus = 1; + } + else if (dev->device == PCI_DEVICE_ID_INTEL_7205_0) + switch (dev->subsystem_device) { + case 0x8070: /* P4G8X Deluxe */ + asus_hides_smbus = 1; + } + else if (dev->device == PCI_DEVICE_ID_INTEL_E7501_MCH) + switch (dev->subsystem_device) { + case 0x80c9: /* PU-DLS */ + asus_hides_smbus = 1; + } + else if (dev->device == PCI_DEVICE_ID_INTEL_82855GM_HB) + switch (dev->subsystem_device) { + case 0x1751: /* M2N notebook */ + case 0x1821: /* M5N notebook */ + asus_hides_smbus = 1; + } + else if (dev->device == PCI_DEVICE_ID_INTEL_82855PM_HB) + switch (dev->subsystem_device) { + case 0x184b: /* W1N notebook */ + case 0x186a: /* M6Ne notebook */ + asus_hides_smbus = 1; + } + else if (dev->device == PCI_DEVICE_ID_INTEL_82865_HB) + switch (dev->subsystem_device) { + case 0x80f2: /* P4P800-X */ + asus_hides_smbus = 1; + } + else if (dev->device == PCI_DEVICE_ID_INTEL_82915GM_HB) + switch (dev->subsystem_device) { + case 0x1882: /* M6V notebook */ + case 0x1977: /* A6VA notebook */ + asus_hides_smbus = 1; + } + } else if (unlikely(dev->subsystem_vendor == PCI_VENDOR_ID_HP)) { + if (dev->device == PCI_DEVICE_ID_INTEL_82855PM_HB) + switch(dev->subsystem_device) { + case 0x088C: /* HP Compaq nc8000 */ + case 0x0890: /* HP Compaq nc6000 */ + asus_hides_smbus = 1; + } + else if (dev->device == PCI_DEVICE_ID_INTEL_82865_HB) + switch (dev->subsystem_device) { + case 0x12bc: /* HP D330L */ + case 0x12bd: /* HP D530 */ + asus_hides_smbus = 1; + } + else if (dev->device == PCI_DEVICE_ID_INTEL_82875_HB) + switch (dev->subsystem_device) { + case 0x12bf: /* HP xw4100 */ + asus_hides_smbus = 1; + } + } else if (unlikely(dev->subsystem_vendor == PCI_VENDOR_ID_SAMSUNG)) { + if (dev->device == PCI_DEVICE_ID_INTEL_82855PM_HB) + switch(dev->subsystem_device) { + case 0xC00C: /* Samsung P35 notebook */ + asus_hides_smbus = 1; + } + } else if (unlikely(dev->subsystem_vendor == PCI_VENDOR_ID_COMPAQ)) { + if (dev->device == PCI_DEVICE_ID_INTEL_82855PM_HB) + switch(dev->subsystem_device) { + case 0x0058: /* Compaq Evo N620c */ + asus_hides_smbus = 1; + } + else if (dev->device == PCI_DEVICE_ID_INTEL_82810_IG3) + switch(dev->subsystem_device) { + case 0xB16C: /* Compaq Deskpro EP 401963-001 (PCA# 010174) */ + /* Motherboard doesn't have Host bridge + * subvendor/subdevice IDs, therefore checking + * its on-board VGA controller */ + asus_hides_smbus = 1; + } + else if (dev->device == PCI_DEVICE_ID_INTEL_82845G_IG) + switch(dev->subsystem_device) { + case 0x00b8: /* Compaq Evo D510 CMT */ + case 0x00b9: /* Compaq Evo D510 SFF */ + asus_hides_smbus = 1; + } + else if (dev->device == PCI_DEVICE_ID_INTEL_82815_CGC) + switch (dev->subsystem_device) { + case 0x001A: /* Compaq Deskpro EN SSF P667 815E */ + /* Motherboard doesn't have host bridge + * subvendor/subdevice IDs, therefore checking + * its on-board VGA controller */ + asus_hides_smbus = 1; + } + } +} +DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82845_HB, asus_hides_smbus_hostbridge); +DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82845G_HB, asus_hides_smbus_hostbridge); +DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82850_HB, asus_hides_smbus_hostbridge); +DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82865_HB, asus_hides_smbus_hostbridge); +DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82875_HB, asus_hides_smbus_hostbridge); +DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_7205_0, asus_hides_smbus_hostbridge); +DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_E7501_MCH, asus_hides_smbus_hostbridge); +DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82855PM_HB, asus_hides_smbus_hostbridge); +DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82855GM_HB, asus_hides_smbus_hostbridge); +DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82915GM_HB, asus_hides_smbus_hostbridge); + +DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82810_IG3, asus_hides_smbus_hostbridge); +DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82845G_IG, asus_hides_smbus_hostbridge); +DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82815_CGC, asus_hides_smbus_hostbridge); + +static void asus_hides_smbus_lpc(struct pci_dev *dev) +{ + u16 val; + + if (likely(!asus_hides_smbus)) + return; + + pci_read_config_word(dev, 0xF2, &val); + if (val & 0x8) { + pci_write_config_word(dev, 0xF2, val & (~0x8)); + pci_read_config_word(dev, 0xF2, &val); + if (val & 0x8) + dev_info(&dev->dev, "i801 SMBus device continues to play 'hide and seek'! 0x%x\n", val); + else + dev_info(&dev->dev, "Enabled i801 SMBus device\n"); + } +} +DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82801AA_0, asus_hides_smbus_lpc); +DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82801DB_0, asus_hides_smbus_lpc); +DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82801BA_0, asus_hides_smbus_lpc); +DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82801CA_0, asus_hides_smbus_lpc); +DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82801CA_12, asus_hides_smbus_lpc); +DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82801DB_12, asus_hides_smbus_lpc); +DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82801EB_0, asus_hides_smbus_lpc); +DECLARE_PCI_FIXUP_RESUME_EARLY(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82801AA_0, asus_hides_smbus_lpc); +DECLARE_PCI_FIXUP_RESUME_EARLY(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82801DB_0, asus_hides_smbus_lpc); +DECLARE_PCI_FIXUP_RESUME_EARLY(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82801BA_0, asus_hides_smbus_lpc); +DECLARE_PCI_FIXUP_RESUME_EARLY(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82801CA_0, asus_hides_smbus_lpc); +DECLARE_PCI_FIXUP_RESUME_EARLY(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82801CA_12, asus_hides_smbus_lpc); +DECLARE_PCI_FIXUP_RESUME_EARLY(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82801DB_12, asus_hides_smbus_lpc); +DECLARE_PCI_FIXUP_RESUME_EARLY(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82801EB_0, asus_hides_smbus_lpc); + +/* It appears we just have one such device. If not, we have a warning */ +static void __iomem *asus_rcba_base; +static void asus_hides_smbus_lpc_ich6_suspend(struct pci_dev *dev) +{ + u32 rcba; + + if (likely(!asus_hides_smbus)) + return; + WARN_ON(asus_rcba_base); + + pci_read_config_dword(dev, 0xF0, &rcba); + /* use bits 31:14, 16 kB aligned */ + asus_rcba_base = ioremap_nocache(rcba & 0xFFFFC000, 0x4000); + if (asus_rcba_base == NULL) + return; +} + +static void asus_hides_smbus_lpc_ich6_resume_early(struct pci_dev *dev) +{ + u32 val; + + if (likely(!asus_hides_smbus || !asus_rcba_base)) + return; + /* read the Function Disable register, dword mode only */ + val = readl(asus_rcba_base + 0x3418); + writel(val & 0xFFFFFFF7, asus_rcba_base + 0x3418); /* enable the SMBus device */ +} + +static void asus_hides_smbus_lpc_ich6_resume(struct pci_dev *dev) +{ + if (likely(!asus_hides_smbus || !asus_rcba_base)) + return; + iounmap(asus_rcba_base); + asus_rcba_base = NULL; + dev_info(&dev->dev, "Enabled ICH6/i801 SMBus device\n"); +} + +static void asus_hides_smbus_lpc_ich6(struct pci_dev *dev) +{ + asus_hides_smbus_lpc_ich6_suspend(dev); + asus_hides_smbus_lpc_ich6_resume_early(dev); + asus_hides_smbus_lpc_ich6_resume(dev); +} +DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_ICH6_1, asus_hides_smbus_lpc_ich6); +DECLARE_PCI_FIXUP_SUSPEND(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_ICH6_1, asus_hides_smbus_lpc_ich6_suspend); +DECLARE_PCI_FIXUP_RESUME(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_ICH6_1, asus_hides_smbus_lpc_ich6_resume); +DECLARE_PCI_FIXUP_RESUME_EARLY(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_ICH6_1, asus_hides_smbus_lpc_ich6_resume_early); + +/* + * SiS 96x south bridge: BIOS typically hides SMBus device... + */ +static void quirk_sis_96x_smbus(struct pci_dev *dev) +{ + u8 val = 0; + pci_read_config_byte(dev, 0x77, &val); + if (val & 0x10) { + dev_info(&dev->dev, "Enabling SiS 96x SMBus\n"); + pci_write_config_byte(dev, 0x77, val & ~0x10); + } +} +DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_SI, PCI_DEVICE_ID_SI_961, quirk_sis_96x_smbus); +DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_SI, PCI_DEVICE_ID_SI_962, quirk_sis_96x_smbus); +DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_SI, PCI_DEVICE_ID_SI_963, quirk_sis_96x_smbus); +DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_SI, PCI_DEVICE_ID_SI_LPC, quirk_sis_96x_smbus); +DECLARE_PCI_FIXUP_RESUME_EARLY(PCI_VENDOR_ID_SI, PCI_DEVICE_ID_SI_961, quirk_sis_96x_smbus); +DECLARE_PCI_FIXUP_RESUME_EARLY(PCI_VENDOR_ID_SI, PCI_DEVICE_ID_SI_962, quirk_sis_96x_smbus); +DECLARE_PCI_FIXUP_RESUME_EARLY(PCI_VENDOR_ID_SI, PCI_DEVICE_ID_SI_963, quirk_sis_96x_smbus); +DECLARE_PCI_FIXUP_RESUME_EARLY(PCI_VENDOR_ID_SI, PCI_DEVICE_ID_SI_LPC, quirk_sis_96x_smbus); + +/* + * ... This is further complicated by the fact that some SiS96x south + * bridges pretend to be 85C503/5513 instead. In that case see if we + * spotted a compatible north bridge to make sure. + * (pci_find_device doesn't work yet) + * + * We can also enable the sis96x bit in the discovery register.. + */ +#define SIS_DETECT_REGISTER 0x40 + +static void quirk_sis_503(struct pci_dev *dev) +{ + u8 reg; + u16 devid; + + pci_read_config_byte(dev, SIS_DETECT_REGISTER, ®); + pci_write_config_byte(dev, SIS_DETECT_REGISTER, reg | (1 << 6)); + pci_read_config_word(dev, PCI_DEVICE_ID, &devid); + if (((devid & 0xfff0) != 0x0960) && (devid != 0x0018)) { + pci_write_config_byte(dev, SIS_DETECT_REGISTER, reg); + return; + } + + /* + * Ok, it now shows up as a 96x.. run the 96x quirk by + * hand in case it has already been processed. + * (depends on link order, which is apparently not guaranteed) + */ + dev->device = devid; + quirk_sis_96x_smbus(dev); +} +DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_SI, PCI_DEVICE_ID_SI_503, quirk_sis_503); +DECLARE_PCI_FIXUP_RESUME_EARLY(PCI_VENDOR_ID_SI, PCI_DEVICE_ID_SI_503, quirk_sis_503); + + +/* + * On ASUS A8V and A8V Deluxe boards, the onboard AC97 audio controller + * and MC97 modem controller are disabled when a second PCI soundcard is + * present. This patch, tweaking the VT8237 ISA bridge, enables them. + * -- bjd + */ +static void asus_hides_ac97_lpc(struct pci_dev *dev) +{ + u8 val; + int asus_hides_ac97 = 0; + + if (likely(dev->subsystem_vendor == PCI_VENDOR_ID_ASUSTEK)) { + if (dev->device == PCI_DEVICE_ID_VIA_8237) + asus_hides_ac97 = 1; + } + + if (!asus_hides_ac97) + return; + + pci_read_config_byte(dev, 0x50, &val); + if (val & 0xc0) { + pci_write_config_byte(dev, 0x50, val & (~0xc0)); + pci_read_config_byte(dev, 0x50, &val); + if (val & 0xc0) + dev_info(&dev->dev, "Onboard AC97/MC97 devices continue to play 'hide and seek'! 0x%x\n", val); + else + dev_info(&dev->dev, "Enabled onboard AC97/MC97 devices\n"); + } +} +DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_VIA, PCI_DEVICE_ID_VIA_8237, asus_hides_ac97_lpc); +DECLARE_PCI_FIXUP_RESUME_EARLY(PCI_VENDOR_ID_VIA, PCI_DEVICE_ID_VIA_8237, asus_hides_ac97_lpc); + +#if defined(CONFIG_ATA) || defined(CONFIG_ATA_MODULE) + +/* + * If we are using libata we can drive this chip properly but must + * do this early on to make the additional device appear during + * the PCI scanning. + */ +static void quirk_jmicron_ata(struct pci_dev *pdev) +{ + u32 conf1, conf5, class; + u8 hdr; + + /* Only poke fn 0 */ + if (PCI_FUNC(pdev->devfn)) + return; + + pci_read_config_dword(pdev, 0x40, &conf1); + pci_read_config_dword(pdev, 0x80, &conf5); + + conf1 &= ~0x00CFF302; /* Clear bit 1, 8, 9, 12-19, 22, 23 */ + conf5 &= ~(1 << 24); /* Clear bit 24 */ + + switch (pdev->device) { + case PCI_DEVICE_ID_JMICRON_JMB360: + /* The controller should be in single function ahci mode */ + conf1 |= 0x0002A100; /* Set 8, 13, 15, 17 */ + break; + + case PCI_DEVICE_ID_JMICRON_JMB365: + case PCI_DEVICE_ID_JMICRON_JMB366: + /* Redirect IDE second PATA port to the right spot */ + conf5 |= (1 << 24); + /* Fall through */ + case PCI_DEVICE_ID_JMICRON_JMB361: + case PCI_DEVICE_ID_JMICRON_JMB363: + /* Enable dual function mode, AHCI on fn 0, IDE fn1 */ + /* Set the class codes correctly and then direct IDE 0 */ + conf1 |= 0x00C2A1B3; /* Set 0, 1, 4, 5, 7, 8, 13, 15, 17, 22, 23 */ + break; + + case PCI_DEVICE_ID_JMICRON_JMB368: + /* The controller should be in single function IDE mode */ + conf1 |= 0x00C00000; /* Set 22, 23 */ + break; + } + + pci_write_config_dword(pdev, 0x40, conf1); + pci_write_config_dword(pdev, 0x80, conf5); + + /* Update pdev accordingly */ + pci_read_config_byte(pdev, PCI_HEADER_TYPE, &hdr); + pdev->hdr_type = hdr & 0x7f; + pdev->multifunction = !!(hdr & 0x80); + + pci_read_config_dword(pdev, PCI_CLASS_REVISION, &class); + pdev->class = class >> 8; +} +DECLARE_PCI_FIXUP_EARLY(PCI_VENDOR_ID_JMICRON, PCI_DEVICE_ID_JMICRON_JMB360, quirk_jmicron_ata); +DECLARE_PCI_FIXUP_EARLY(PCI_VENDOR_ID_JMICRON, PCI_DEVICE_ID_JMICRON_JMB361, quirk_jmicron_ata); +DECLARE_PCI_FIXUP_EARLY(PCI_VENDOR_ID_JMICRON, PCI_DEVICE_ID_JMICRON_JMB363, quirk_jmicron_ata); +DECLARE_PCI_FIXUP_EARLY(PCI_VENDOR_ID_JMICRON, PCI_DEVICE_ID_JMICRON_JMB365, quirk_jmicron_ata); +DECLARE_PCI_FIXUP_EARLY(PCI_VENDOR_ID_JMICRON, PCI_DEVICE_ID_JMICRON_JMB366, quirk_jmicron_ata); +DECLARE_PCI_FIXUP_EARLY(PCI_VENDOR_ID_JMICRON, PCI_DEVICE_ID_JMICRON_JMB368, quirk_jmicron_ata); +DECLARE_PCI_FIXUP_RESUME_EARLY(PCI_VENDOR_ID_JMICRON, PCI_DEVICE_ID_JMICRON_JMB360, quirk_jmicron_ata); +DECLARE_PCI_FIXUP_RESUME_EARLY(PCI_VENDOR_ID_JMICRON, PCI_DEVICE_ID_JMICRON_JMB361, quirk_jmicron_ata); +DECLARE_PCI_FIXUP_RESUME_EARLY(PCI_VENDOR_ID_JMICRON, PCI_DEVICE_ID_JMICRON_JMB363, quirk_jmicron_ata); +DECLARE_PCI_FIXUP_RESUME_EARLY(PCI_VENDOR_ID_JMICRON, PCI_DEVICE_ID_JMICRON_JMB365, quirk_jmicron_ata); +DECLARE_PCI_FIXUP_RESUME_EARLY(PCI_VENDOR_ID_JMICRON, PCI_DEVICE_ID_JMICRON_JMB366, quirk_jmicron_ata); +DECLARE_PCI_FIXUP_RESUME_EARLY(PCI_VENDOR_ID_JMICRON, PCI_DEVICE_ID_JMICRON_JMB368, quirk_jmicron_ata); + +#endif + +#ifdef CONFIG_X86_IO_APIC +static void __init quirk_alder_ioapic(struct pci_dev *pdev) +{ + int i; + + if ((pdev->class >> 8) != 0xff00) + return; + + /* the first BAR is the location of the IO APIC...we must + * not touch this (and it's already covered by the fixmap), so + * forcibly insert it into the resource tree */ + if (pci_resource_start(pdev, 0) && pci_resource_len(pdev, 0)) + insert_resource(&iomem_resource, &pdev->resource[0]); + + /* The next five BARs all seem to be rubbish, so just clean + * them out */ + for (i=1; i < 6; i++) { + memset(&pdev->resource[i], 0, sizeof(pdev->resource[i])); + } + +} +DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_EESSC, quirk_alder_ioapic); +#endif + +static void __devinit quirk_pcie_mch(struct pci_dev *pdev) +{ + pcie_mch_quirk = 1; +} +DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_E7520_MCH, quirk_pcie_mch); +DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_E7320_MCH, quirk_pcie_mch); +DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_E7525_MCH, quirk_pcie_mch); + + +/* + * It's possible for the MSI to get corrupted if shpc and acpi + * are used together on certain PXH-based systems. + */ +static void __devinit quirk_pcie_pxh(struct pci_dev *dev) +{ + pci_msi_off(dev); + dev->no_msi = 1; + dev_warn(&dev->dev, "PXH quirk detected; SHPC device MSI disabled\n"); +} +DECLARE_PCI_FIXUP_EARLY(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_PXHD_0, quirk_pcie_pxh); +DECLARE_PCI_FIXUP_EARLY(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_PXHD_1, quirk_pcie_pxh); +DECLARE_PCI_FIXUP_EARLY(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_PXH_0, quirk_pcie_pxh); +DECLARE_PCI_FIXUP_EARLY(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_PXH_1, quirk_pcie_pxh); +DECLARE_PCI_FIXUP_EARLY(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_PXHV, quirk_pcie_pxh); + +/* + * Some Intel PCI Express chipsets have trouble with downstream + * device power management. + */ +static void quirk_intel_pcie_pm(struct pci_dev * dev) +{ + pci_pm_d3_delay = 120; + dev->no_d1d2 = 1; +} + +DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_INTEL, 0x25e2, quirk_intel_pcie_pm); +DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_INTEL, 0x25e3, quirk_intel_pcie_pm); +DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_INTEL, 0x25e4, quirk_intel_pcie_pm); +DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_INTEL, 0x25e5, quirk_intel_pcie_pm); +DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_INTEL, 0x25e6, quirk_intel_pcie_pm); +DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_INTEL, 0x25e7, quirk_intel_pcie_pm); +DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_INTEL, 0x25f7, quirk_intel_pcie_pm); +DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_INTEL, 0x25f8, quirk_intel_pcie_pm); +DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_INTEL, 0x25f9, quirk_intel_pcie_pm); +DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_INTEL, 0x25fa, quirk_intel_pcie_pm); +DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_INTEL, 0x2601, quirk_intel_pcie_pm); +DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_INTEL, 0x2602, quirk_intel_pcie_pm); +DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_INTEL, 0x2603, quirk_intel_pcie_pm); +DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_INTEL, 0x2604, quirk_intel_pcie_pm); +DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_INTEL, 0x2605, quirk_intel_pcie_pm); +DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_INTEL, 0x2606, quirk_intel_pcie_pm); +DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_INTEL, 0x2607, quirk_intel_pcie_pm); +DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_INTEL, 0x2608, quirk_intel_pcie_pm); +DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_INTEL, 0x2609, quirk_intel_pcie_pm); +DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_INTEL, 0x260a, quirk_intel_pcie_pm); +DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_INTEL, 0x260b, quirk_intel_pcie_pm); + +/* + * Toshiba TC86C001 IDE controller reports the standard 8-byte BAR0 size + * but the PIO transfers won't work if BAR0 falls at the odd 8 bytes. + * Re-allocate the region if needed... + */ +static void __init quirk_tc86c001_ide(struct pci_dev *dev) +{ + struct resource *r = &dev->resource[0]; + + if (r->start & 0x8) { + r->start = 0; + r->end = 0xf; + } +} +DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_TOSHIBA_2, + PCI_DEVICE_ID_TOSHIBA_TC86C001_IDE, + quirk_tc86c001_ide); + +static void __devinit quirk_netmos(struct pci_dev *dev) +{ + unsigned int num_parallel = (dev->subsystem_device & 0xf0) >> 4; + unsigned int num_serial = dev->subsystem_device & 0xf; + + /* + * These Netmos parts are multiport serial devices with optional + * parallel ports. Even when parallel ports are present, they + * are identified as class SERIAL, which means the serial driver + * will claim them. To prevent this, mark them as class OTHER. + * These combo devices should be claimed by parport_serial. + * + * The subdevice ID is of the form 0x00PS, where <P> is the number + * of parallel ports and <S> is the number of serial ports. + */ + switch (dev->device) { + case PCI_DEVICE_ID_NETMOS_9735: + case PCI_DEVICE_ID_NETMOS_9745: + case PCI_DEVICE_ID_NETMOS_9835: + case PCI_DEVICE_ID_NETMOS_9845: + case PCI_DEVICE_ID_NETMOS_9855: + if ((dev->class >> 8) == PCI_CLASS_COMMUNICATION_SERIAL && + num_parallel) { + dev_info(&dev->dev, "Netmos %04x (%u parallel, " + "%u serial); changing class SERIAL to OTHER " + "(use parport_serial)\n", + dev->device, num_parallel, num_serial); + dev->class = (PCI_CLASS_COMMUNICATION_OTHER << 8) | + (dev->class & 0xff); + } + } +} +DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_NETMOS, PCI_ANY_ID, quirk_netmos); + +static void __devinit quirk_e100_interrupt(struct pci_dev *dev) +{ + u16 command, pmcsr; + u8 __iomem *csr; + u8 cmd_hi; + int pm; + + switch (dev->device) { + /* PCI IDs taken from drivers/net/e100.c */ + case 0x1029: + case 0x1030 ... 0x1034: + case 0x1038 ... 0x103E: + case 0x1050 ... 0x1057: + case 0x1059: + case 0x1064 ... 0x106B: + case 0x1091 ... 0x1095: + case 0x1209: + case 0x1229: + case 0x2449: + case 0x2459: + case 0x245D: + case 0x27DC: + break; + default: + return; + } + + /* + * Some firmware hands off the e100 with interrupts enabled, + * which can cause a flood of interrupts if packets are + * received before the driver attaches to the device. So + * disable all e100 interrupts here. The driver will + * re-enable them when it's ready. + */ + pci_read_config_word(dev, PCI_COMMAND, &command); + + if (!(command & PCI_COMMAND_MEMORY) || !pci_resource_start(dev, 0)) + return; + + /* + * Check that the device is in the D0 power state. If it's not, + * there is no point to look any further. + */ + pm = pci_find_capability(dev, PCI_CAP_ID_PM); + if (pm) { + pci_read_config_word(dev, pm + PCI_PM_CTRL, &pmcsr); + if ((pmcsr & PCI_PM_CTRL_STATE_MASK) != PCI_D0) + return; + } + + /* Convert from PCI bus to resource space. */ + csr = ioremap(pci_resource_start(dev, 0), 8); + if (!csr) { + dev_warn(&dev->dev, "Can't map e100 registers\n"); + return; + } + + cmd_hi = readb(csr + 3); + if (cmd_hi == 0) { + dev_warn(&dev->dev, "Firmware left e100 interrupts enabled; " + "disabling\n"); + writeb(1, csr + 3); + } + + iounmap(csr); +} +DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_INTEL, PCI_ANY_ID, quirk_e100_interrupt); + +/* + * The 82575 and 82598 may experience data corruption issues when transitioning + * out of L0S. To prevent this we need to disable L0S on the pci-e link + */ +static void __devinit quirk_disable_aspm_l0s(struct pci_dev *dev) +{ + dev_info(&dev->dev, "Disabling L0s\n"); + pci_disable_link_state(dev, PCIE_LINK_STATE_L0S); +} +DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_INTEL, 0x10a7, quirk_disable_aspm_l0s); +DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_INTEL, 0x10a9, quirk_disable_aspm_l0s); +DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_INTEL, 0x10b6, quirk_disable_aspm_l0s); +DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_INTEL, 0x10c6, quirk_disable_aspm_l0s); +DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_INTEL, 0x10c7, quirk_disable_aspm_l0s); +DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_INTEL, 0x10c8, quirk_disable_aspm_l0s); +DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_INTEL, 0x10d6, quirk_disable_aspm_l0s); +DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_INTEL, 0x10db, quirk_disable_aspm_l0s); +DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_INTEL, 0x10dd, quirk_disable_aspm_l0s); +DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_INTEL, 0x10e1, quirk_disable_aspm_l0s); +DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_INTEL, 0x10ec, quirk_disable_aspm_l0s); +DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_INTEL, 0x10f1, quirk_disable_aspm_l0s); +DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_INTEL, 0x10f4, quirk_disable_aspm_l0s); +DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_INTEL, 0x1508, quirk_disable_aspm_l0s); + +static void __devinit fixup_rev1_53c810(struct pci_dev* dev) +{ + /* rev 1 ncr53c810 chips don't set the class at all which means + * they don't get their resources remapped. Fix that here. + */ + + if (dev->class == PCI_CLASS_NOT_DEFINED) { + dev_info(&dev->dev, "NCR 53c810 rev 1 detected; setting PCI class\n"); + dev->class = PCI_CLASS_STORAGE_SCSI; + } +} +DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_NCR, PCI_DEVICE_ID_NCR_53C810, fixup_rev1_53c810); + +/* Enable 1k I/O space granularity on the Intel P64H2 */ +static void __devinit quirk_p64h2_1k_io(struct pci_dev *dev) +{ + u16 en1k; + u8 io_base_lo, io_limit_lo; + unsigned long base, limit; + struct resource *res = dev->resource + PCI_BRIDGE_RESOURCES; + + pci_read_config_word(dev, 0x40, &en1k); + + if (en1k & 0x200) { + dev_info(&dev->dev, "Enable I/O Space to 1KB granularity\n"); + + pci_read_config_byte(dev, PCI_IO_BASE, &io_base_lo); + pci_read_config_byte(dev, PCI_IO_LIMIT, &io_limit_lo); + base = (io_base_lo & (PCI_IO_RANGE_MASK | 0x0c)) << 8; + limit = (io_limit_lo & (PCI_IO_RANGE_MASK | 0x0c)) << 8; + + if (base <= limit) { + res->start = base; + res->end = limit + 0x3ff; + } + } +} +DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x1460, quirk_p64h2_1k_io); + +/* Fix the IOBL_ADR for 1k I/O space granularity on the Intel P64H2 + * The IOBL_ADR gets re-written to 4k boundaries in pci_setup_bridge() + * in drivers/pci/setup-bus.c + */ +static void __devinit quirk_p64h2_1k_io_fix_iobl(struct pci_dev *dev) +{ + u16 en1k, iobl_adr, iobl_adr_1k; + struct resource *res = dev->resource + PCI_BRIDGE_RESOURCES; + + pci_read_config_word(dev, 0x40, &en1k); + + if (en1k & 0x200) { + pci_read_config_word(dev, PCI_IO_BASE, &iobl_adr); + + iobl_adr_1k = iobl_adr | (res->start >> 8) | (res->end & 0xfc00); + + if (iobl_adr != iobl_adr_1k) { + dev_info(&dev->dev, "Fixing P64H2 IOBL_ADR from 0x%x to 0x%x for 1KB granularity\n", + iobl_adr,iobl_adr_1k); + pci_write_config_word(dev, PCI_IO_BASE, iobl_adr_1k); + } + } +} +DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_INTEL, 0x1460, quirk_p64h2_1k_io_fix_iobl); + +/* Under some circumstances, AER is not linked with extended capabilities. + * Force it to be linked by setting the corresponding control bit in the + * config space. + */ +static void quirk_nvidia_ck804_pcie_aer_ext_cap(struct pci_dev *dev) +{ + uint8_t b; + if (pci_read_config_byte(dev, 0xf41, &b) == 0) { + if (!(b & 0x20)) { + pci_write_config_byte(dev, 0xf41, b | 0x20); + dev_info(&dev->dev, + "Linking AER extended capability\n"); + } + } +} +DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_NVIDIA, PCI_DEVICE_ID_NVIDIA_CK804_PCIE, + quirk_nvidia_ck804_pcie_aer_ext_cap); +DECLARE_PCI_FIXUP_RESUME_EARLY(PCI_VENDOR_ID_NVIDIA, PCI_DEVICE_ID_NVIDIA_CK804_PCIE, + quirk_nvidia_ck804_pcie_aer_ext_cap); + +static void __devinit quirk_via_cx700_pci_parking_caching(struct pci_dev *dev) +{ + /* + * Disable PCI Bus Parking and PCI Master read caching on CX700 + * which causes unspecified timing errors with a VT6212L on the PCI + * bus leading to USB2.0 packet loss. The defaults are that these + * features are turned off but some BIOSes turn them on. + */ + + uint8_t b; + if (pci_read_config_byte(dev, 0x76, &b) == 0) { + if (b & 0x40) { + /* Turn off PCI Bus Parking */ + pci_write_config_byte(dev, 0x76, b ^ 0x40); + + dev_info(&dev->dev, + "Disabling VIA CX700 PCI parking\n"); + } + } + + if (pci_read_config_byte(dev, 0x72, &b) == 0) { + if (b != 0) { + /* Turn off PCI Master read caching */ + pci_write_config_byte(dev, 0x72, 0x0); + + /* Set PCI Master Bus time-out to "1x16 PCLK" */ + pci_write_config_byte(dev, 0x75, 0x1); + + /* Disable "Read FIFO Timer" */ + pci_write_config_byte(dev, 0x77, 0x0); + + dev_info(&dev->dev, + "Disabling VIA CX700 PCI caching\n"); + } + } +} +DECLARE_PCI_FIXUP_EARLY(PCI_VENDOR_ID_VIA, 0x324e, quirk_via_cx700_pci_parking_caching); + +/* + * For Broadcom 5706, 5708, 5709 rev. A nics, any read beyond the + * VPD end tag will hang the device. This problem was initially + * observed when a vpd entry was created in sysfs + * ('/sys/bus/pci/devices/<id>/vpd'). A read to this sysfs entry + * will dump 32k of data. Reading a full 32k will cause an access + * beyond the VPD end tag causing the device to hang. Once the device + * is hung, the bnx2 driver will not be able to reset the device. + * We believe that it is legal to read beyond the end tag and + * therefore the solution is to limit the read/write length. + */ +static void __devinit quirk_brcm_570x_limit_vpd(struct pci_dev *dev) +{ + /* + * Only disable the VPD capability for 5706, 5706S, 5708, + * 5708S and 5709 rev. A + */ + if ((dev->device == PCI_DEVICE_ID_NX2_5706) || + (dev->device == PCI_DEVICE_ID_NX2_5706S) || + (dev->device == PCI_DEVICE_ID_NX2_5708) || + (dev->device == PCI_DEVICE_ID_NX2_5708S) || + ((dev->device == PCI_DEVICE_ID_NX2_5709) && + (dev->revision & 0xf0) == 0x0)) { + if (dev->vpd) + dev->vpd->len = 0x80; + } +} + +DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_BROADCOM, + PCI_DEVICE_ID_NX2_5706, + quirk_brcm_570x_limit_vpd); +DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_BROADCOM, + PCI_DEVICE_ID_NX2_5706S, + quirk_brcm_570x_limit_vpd); +DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_BROADCOM, + PCI_DEVICE_ID_NX2_5708, + quirk_brcm_570x_limit_vpd); +DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_BROADCOM, + PCI_DEVICE_ID_NX2_5708S, + quirk_brcm_570x_limit_vpd); +DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_BROADCOM, + PCI_DEVICE_ID_NX2_5709, + quirk_brcm_570x_limit_vpd); +DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_BROADCOM, + PCI_DEVICE_ID_NX2_5709S, + quirk_brcm_570x_limit_vpd); + +#ifdef CONFIG_PCI_MSI +/* Some chipsets do not support MSI. We cannot easily rely on setting + * PCI_BUS_FLAGS_NO_MSI in its bus flags because there are actually + * some other busses controlled by the chipset even if Linux is not + * aware of it. Instead of setting the flag on all busses in the + * machine, simply disable MSI globally. + */ +static void __init quirk_disable_all_msi(struct pci_dev *dev) +{ + pci_no_msi(); + dev_warn(&dev->dev, "MSI quirk detected; MSI disabled\n"); +} +DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_SERVERWORKS, PCI_DEVICE_ID_SERVERWORKS_GCNB_LE, quirk_disable_all_msi); +DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_ATI, PCI_DEVICE_ID_ATI_RS400_200, quirk_disable_all_msi); +DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_ATI, PCI_DEVICE_ID_ATI_RS480, quirk_disable_all_msi); +DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_VIA, PCI_DEVICE_ID_VIA_VT3336, quirk_disable_all_msi); +DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_VIA, PCI_DEVICE_ID_VIA_VT3351, quirk_disable_all_msi); + +/* Disable MSI on chipsets that are known to not support it */ +static void __devinit quirk_disable_msi(struct pci_dev *dev) +{ + if (dev->subordinate) { + dev_warn(&dev->dev, "MSI quirk detected; " + "subordinate MSI disabled\n"); + dev->subordinate->bus_flags |= PCI_BUS_FLAGS_NO_MSI; + } +} +DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_8131_BRIDGE, quirk_disable_msi); + +/* Go through the list of Hypertransport capabilities and + * return 1 if a HT MSI capability is found and enabled */ +static int __devinit msi_ht_cap_enabled(struct pci_dev *dev) +{ + int pos, ttl = 48; + + pos = pci_find_ht_capability(dev, HT_CAPTYPE_MSI_MAPPING); + while (pos && ttl--) { + u8 flags; + + if (pci_read_config_byte(dev, pos + HT_MSI_FLAGS, + &flags) == 0) + { + dev_info(&dev->dev, "Found %s HT MSI Mapping\n", + flags & HT_MSI_FLAGS_ENABLE ? + "enabled" : "disabled"); + return (flags & HT_MSI_FLAGS_ENABLE) != 0; + } + + pos = pci_find_next_ht_capability(dev, pos, + HT_CAPTYPE_MSI_MAPPING); + } + return 0; +} + +/* Check the hypertransport MSI mapping to know whether MSI is enabled or not */ +static void __devinit quirk_msi_ht_cap(struct pci_dev *dev) +{ + if (dev->subordinate && !msi_ht_cap_enabled(dev)) { + dev_warn(&dev->dev, "MSI quirk detected; " + "subordinate MSI disabled\n"); + dev->subordinate->bus_flags |= PCI_BUS_FLAGS_NO_MSI; + } +} +DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_SERVERWORKS, PCI_DEVICE_ID_SERVERWORKS_HT2000_PCIE, + quirk_msi_ht_cap); + +/* The nVidia CK804 chipset may have 2 HT MSI mappings. + * MSI are supported if the MSI capability set in any of these mappings. + */ +static void __devinit quirk_nvidia_ck804_msi_ht_cap(struct pci_dev *dev) +{ + struct pci_dev *pdev; + + if (!dev->subordinate) + return; + + /* check HT MSI cap on this chipset and the root one. + * a single one having MSI is enough to be sure that MSI are supported. + */ + pdev = pci_get_slot(dev->bus, 0); + if (!pdev) + return; + if (!msi_ht_cap_enabled(dev) && !msi_ht_cap_enabled(pdev)) { + dev_warn(&dev->dev, "MSI quirk detected; " + "subordinate MSI disabled\n"); + dev->subordinate->bus_flags |= PCI_BUS_FLAGS_NO_MSI; + } + pci_dev_put(pdev); +} +DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_NVIDIA, PCI_DEVICE_ID_NVIDIA_CK804_PCIE, + quirk_nvidia_ck804_msi_ht_cap); + +/* Force enable MSI mapping capability on HT bridges */ +static void __devinit ht_enable_msi_mapping(struct pci_dev *dev) +{ + int pos, ttl = 48; + + pos = pci_find_ht_capability(dev, HT_CAPTYPE_MSI_MAPPING); + while (pos && ttl--) { + u8 flags; + + if (pci_read_config_byte(dev, pos + HT_MSI_FLAGS, + &flags) == 0) { + dev_info(&dev->dev, "Enabling HT MSI Mapping\n"); + + pci_write_config_byte(dev, pos + HT_MSI_FLAGS, + flags | HT_MSI_FLAGS_ENABLE); + } + pos = pci_find_next_ht_capability(dev, pos, + HT_CAPTYPE_MSI_MAPPING); + } +} +DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_SERVERWORKS, + PCI_DEVICE_ID_SERVERWORKS_HT1000_PXB, + ht_enable_msi_mapping); + +DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_8132_BRIDGE, + ht_enable_msi_mapping); + +/* The P5N32-SLI Premium motherboard from Asus has a problem with msi + * for the MCP55 NIC. It is not yet determined whether the msi problem + * also affects other devices. As for now, turn off msi for this device. + */ +static void __devinit nvenet_msi_disable(struct pci_dev *dev) +{ + if (dmi_name_in_vendors("P5N32-SLI PREMIUM")) { + dev_info(&dev->dev, + "Disabling msi for MCP55 NIC on P5N32-SLI Premium\n"); + dev->no_msi = 1; + } +} +DECLARE_PCI_FIXUP_EARLY(PCI_VENDOR_ID_NVIDIA, + PCI_DEVICE_ID_NVIDIA_NVENET_15, + nvenet_msi_disable); + +static void __devinit nv_msi_ht_cap_quirk(struct pci_dev *dev) +{ + struct pci_dev *host_bridge; + int pos, ttl = 48; + + /* + * HT MSI mapping should be disabled on devices that are below + * a non-Hypertransport host bridge. Locate the host bridge... + */ + host_bridge = pci_get_bus_and_slot(0, PCI_DEVFN(0, 0)); + if (host_bridge == NULL) { + dev_warn(&dev->dev, + "nv_msi_ht_cap_quirk didn't locate host bridge\n"); + return; + } + + pos = pci_find_ht_capability(host_bridge, HT_CAPTYPE_SLAVE); + if (pos != 0) { + /* Host bridge is to HT */ + ht_enable_msi_mapping(dev); + return; + } + + /* Host bridge is not to HT, disable HT MSI mapping on this device */ + pos = pci_find_ht_capability(dev, HT_CAPTYPE_MSI_MAPPING); + while (pos && ttl--) { + u8 flags; + + if (pci_read_config_byte(dev, pos + HT_MSI_FLAGS, + &flags) == 0) { + dev_info(&dev->dev, "Disabling HT MSI mapping"); + pci_write_config_byte(dev, pos + HT_MSI_FLAGS, + flags & ~HT_MSI_FLAGS_ENABLE); + } + pos = pci_find_next_ht_capability(dev, pos, + HT_CAPTYPE_MSI_MAPPING); + } +} +DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_NVIDIA, PCI_ANY_ID, nv_msi_ht_cap_quirk); +DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_AL, PCI_ANY_ID, nv_msi_ht_cap_quirk); + +static void __devinit quirk_msi_intx_disable_bug(struct pci_dev *dev) +{ + dev->dev_flags |= PCI_DEV_FLAGS_MSI_INTX_DISABLE_BUG; +} +static void __devinit quirk_msi_intx_disable_ati_bug(struct pci_dev *dev) +{ + struct pci_dev *p; + + /* SB700 MSI issue will be fixed at HW level from revision A21, + * we need check PCI REVISION ID of SMBus controller to get SB700 + * revision. + */ + p = pci_get_device(PCI_VENDOR_ID_ATI, PCI_DEVICE_ID_ATI_SBX00_SMBUS, + NULL); + if (!p) + return; + + if ((p->revision < 0x3B) && (p->revision >= 0x30)) + dev->dev_flags |= PCI_DEV_FLAGS_MSI_INTX_DISABLE_BUG; + pci_dev_put(p); +} +DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_BROADCOM, + PCI_DEVICE_ID_TIGON3_5780, + quirk_msi_intx_disable_bug); +DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_BROADCOM, + PCI_DEVICE_ID_TIGON3_5780S, + quirk_msi_intx_disable_bug); +DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_BROADCOM, + PCI_DEVICE_ID_TIGON3_5714, + quirk_msi_intx_disable_bug); +DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_BROADCOM, + PCI_DEVICE_ID_TIGON3_5714S, + quirk_msi_intx_disable_bug); +DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_BROADCOM, + PCI_DEVICE_ID_TIGON3_5715, + quirk_msi_intx_disable_bug); +DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_BROADCOM, + PCI_DEVICE_ID_TIGON3_5715S, + quirk_msi_intx_disable_bug); + +DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_ATI, 0x4390, + quirk_msi_intx_disable_ati_bug); +DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_ATI, 0x4391, + quirk_msi_intx_disable_ati_bug); +DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_ATI, 0x4392, + quirk_msi_intx_disable_ati_bug); +DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_ATI, 0x4393, + quirk_msi_intx_disable_ati_bug); +DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_ATI, 0x4394, + quirk_msi_intx_disable_ati_bug); + +DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_ATI, 0x4373, + quirk_msi_intx_disable_bug); +DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_ATI, 0x4374, + quirk_msi_intx_disable_bug); +DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_ATI, 0x4375, + quirk_msi_intx_disable_bug); + +#endif /* CONFIG_PCI_MSI */ + +static void pci_do_fixups(struct pci_dev *dev, struct pci_fixup *f, struct pci_fixup *end) +{ + while (f < end) { + if ((f->vendor == dev->vendor || f->vendor == (u16) PCI_ANY_ID) && + (f->device == dev->device || f->device == (u16) PCI_ANY_ID)) { + dev_dbg(&dev->dev, "calling %pF\n", f->hook); + f->hook(dev); + } + f++; + } +} + +extern struct pci_fixup __start_pci_fixups_early[]; +extern struct pci_fixup __end_pci_fixups_early[]; +extern struct pci_fixup __start_pci_fixups_header[]; +extern struct pci_fixup __end_pci_fixups_header[]; +extern struct pci_fixup __start_pci_fixups_final[]; +extern struct pci_fixup __end_pci_fixups_final[]; +extern struct pci_fixup __start_pci_fixups_enable[]; +extern struct pci_fixup __end_pci_fixups_enable[]; +extern struct pci_fixup __start_pci_fixups_resume[]; +extern struct pci_fixup __end_pci_fixups_resume[]; +extern struct pci_fixup __start_pci_fixups_resume_early[]; +extern struct pci_fixup __end_pci_fixups_resume_early[]; +extern struct pci_fixup __start_pci_fixups_suspend[]; +extern struct pci_fixup __end_pci_fixups_suspend[]; + + +void pci_fixup_device(enum pci_fixup_pass pass, struct pci_dev *dev) +{ + struct pci_fixup *start, *end; + + switch(pass) { + case pci_fixup_early: + start = __start_pci_fixups_early; + end = __end_pci_fixups_early; + break; + + case pci_fixup_header: + start = __start_pci_fixups_header; + end = __end_pci_fixups_header; + break; + + case pci_fixup_final: + start = __start_pci_fixups_final; + end = __end_pci_fixups_final; + break; + + case pci_fixup_enable: + start = __start_pci_fixups_enable; + end = __end_pci_fixups_enable; + break; + + case pci_fixup_resume: + start = __start_pci_fixups_resume; + end = __end_pci_fixups_resume; + break; + + case pci_fixup_resume_early: + start = __start_pci_fixups_resume_early; + end = __end_pci_fixups_resume_early; + break; + + case pci_fixup_suspend: + start = __start_pci_fixups_suspend; + end = __end_pci_fixups_suspend; + break; + + default: + /* stupid compiler warning, you would think with an enum... */ + return; + } + pci_do_fixups(dev, start, end); +} +#else +void pci_fixup_device(enum pci_fixup_pass pass, struct pci_dev *dev) {} +#endif +EXPORT_SYMBOL(pci_fixup_device); diff --git a/drivers/pci/remove.c b/drivers/pci/remove.c new file mode 100644 index 0000000..042e089 --- /dev/null +++ b/drivers/pci/remove.c @@ -0,0 +1,151 @@ +#include <linux/pci.h> +#include <linux/module.h> +#include <linux/pci-aspm.h> +#include "pci.h" + +static void pci_free_resources(struct pci_dev *dev) +{ + int i; + + msi_remove_pci_irq_vectors(dev); + + pci_cleanup_rom(dev); + for (i = 0; i < PCI_NUM_RESOURCES; i++) { + struct resource *res = dev->resource + i; + if (res->parent) + release_resource(res); + } +} + +static void pci_stop_dev(struct pci_dev *dev) +{ + if (dev->is_added) { + pci_proc_detach_device(dev); + pci_remove_sysfs_dev_files(dev); + device_unregister(&dev->dev); + dev->is_added = 0; + } + + if (dev->bus->self) + pcie_aspm_exit_link_state(dev); +} + +static void pci_destroy_dev(struct pci_dev *dev) +{ + pci_stop_dev(dev); + + /* Remove the device from the device lists, and prevent any further + * list accesses from this device */ + down_write(&pci_bus_sem); + list_del(&dev->bus_list); + dev->bus_list.next = dev->bus_list.prev = NULL; + up_write(&pci_bus_sem); + + pci_free_resources(dev); + pci_dev_put(dev); +} + +/** + * pci_remove_device_safe - remove an unused hotplug device + * @dev: the device to remove + * + * Delete the device structure from the device lists and + * notify userspace (/sbin/hotplug), but only if the device + * in question is not being used by a driver. + * Returns 0 on success. + */ +#if 0 +int pci_remove_device_safe(struct pci_dev *dev) +{ + if (pci_dev_driver(dev)) + return -EBUSY; + pci_destroy_dev(dev); + return 0; +} +#endif /* 0 */ + +void pci_remove_bus(struct pci_bus *pci_bus) +{ + pci_proc_detach_bus(pci_bus); + + down_write(&pci_bus_sem); + list_del(&pci_bus->node); + up_write(&pci_bus_sem); + pci_remove_legacy_files(pci_bus); + device_remove_file(&pci_bus->dev, &dev_attr_cpuaffinity); + device_remove_file(&pci_bus->dev, &dev_attr_cpulistaffinity); + device_unregister(&pci_bus->dev); +} +EXPORT_SYMBOL(pci_remove_bus); + +/** + * pci_remove_bus_device - remove a PCI device and any children + * @dev: the device to remove + * + * Remove a PCI device from the device lists, informing the drivers + * that the device has been removed. We also remove any subordinate + * buses and children in a depth-first manner. + * + * For each device we remove, delete the device structure from the + * device lists, remove the /proc entry, and notify userspace + * (/sbin/hotplug). + */ +void pci_remove_bus_device(struct pci_dev *dev) +{ + if (dev->subordinate) { + struct pci_bus *b = dev->subordinate; + + pci_remove_behind_bridge(dev); + pci_remove_bus(b); + dev->subordinate = NULL; + } + + pci_destroy_dev(dev); +} + +/** + * pci_remove_behind_bridge - remove all devices behind a PCI bridge + * @dev: PCI bridge device + * + * Remove all devices on the bus, except for the parent bridge. + * This also removes any child buses, and any devices they may + * contain in a depth-first manner. + */ +void pci_remove_behind_bridge(struct pci_dev *dev) +{ + struct list_head *l, *n; + + if (dev->subordinate) + list_for_each_safe(l, n, &dev->subordinate->devices) + pci_remove_bus_device(pci_dev_b(l)); +} + +static void pci_stop_bus_devices(struct pci_bus *bus) +{ + struct list_head *l, *n; + + list_for_each_safe(l, n, &bus->devices) { + struct pci_dev *dev = pci_dev_b(l); + pci_stop_bus_device(dev); + } +} + +/** + * pci_stop_bus_device - stop a PCI device and any children + * @dev: the device to stop + * + * Stop a PCI device (detach the driver, remove from the global list + * and so on). This also stop any subordinate buses and children in a + * depth-first manner. + */ +void pci_stop_bus_device(struct pci_dev *dev) +{ + if (dev->subordinate) + pci_stop_bus_devices(dev->subordinate); + + pci_stop_dev(dev); +} + +EXPORT_SYMBOL(pci_remove_bus_device); +EXPORT_SYMBOL(pci_remove_behind_bridge); +EXPORT_SYMBOL_GPL(pci_stop_bus_device); diff --git a/drivers/pci/rom.c b/drivers/pci/rom.c new file mode 100644 index 0000000..29cbe47 --- /dev/null +++ b/drivers/pci/rom.c @@ -0,0 +1,268 @@ +/* + * drivers/pci/rom.c + * + * (C) Copyright 2004 Jon Smirl <jonsmirl@yahoo.com> + * (C) Copyright 2004 Silicon Graphics, Inc. Jesse Barnes <jbarnes@sgi.com> + * + * PCI ROM access routines + */ +#include <linux/kernel.h> +#include <linux/pci.h> +#include <linux/slab.h> + +#include "pci.h" + +/** + * pci_enable_rom - enable ROM decoding for a PCI device + * @pdev: PCI device to enable + * + * Enable ROM decoding on @dev. This involves simply turning on the last + * bit of the PCI ROM BAR. Note that some cards may share address decoders + * between the ROM and other resources, so enabling it may disable access + * to MMIO registers or other card memory. + */ +int pci_enable_rom(struct pci_dev *pdev) +{ + struct resource *res = pdev->resource + PCI_ROM_RESOURCE; + struct pci_bus_region region; + u32 rom_addr; + + if (!res->flags) + return -1; + + pcibios_resource_to_bus(pdev, ®ion, res); + pci_read_config_dword(pdev, pdev->rom_base_reg, &rom_addr); + rom_addr &= ~PCI_ROM_ADDRESS_MASK; + rom_addr |= region.start | PCI_ROM_ADDRESS_ENABLE; + pci_write_config_dword(pdev, pdev->rom_base_reg, rom_addr); + return 0; +} + +/** + * pci_disable_rom - disable ROM decoding for a PCI device + * @pdev: PCI device to disable + * + * Disable ROM decoding on a PCI device by turning off the last bit in the + * ROM BAR. + */ +void pci_disable_rom(struct pci_dev *pdev) +{ + u32 rom_addr; + pci_read_config_dword(pdev, pdev->rom_base_reg, &rom_addr); + rom_addr &= ~PCI_ROM_ADDRESS_ENABLE; + pci_write_config_dword(pdev, pdev->rom_base_reg, rom_addr); +} + +/** + * pci_get_rom_size - obtain the actual size of the ROM image + * @rom: kernel virtual pointer to image of ROM + * @size: size of PCI window + * return: size of actual ROM image + * + * Determine the actual length of the ROM image. + * The PCI window size could be much larger than the + * actual image size. + */ +size_t pci_get_rom_size(struct pci_dev *pdev, void __iomem *rom, size_t size) +{ + void __iomem *image; + int last_image; + + image = rom; + do { + void __iomem *pds; + /* Standard PCI ROMs start out with these bytes 55 AA */ + if (readb(image) != 0x55) { + dev_err(&pdev->dev, "Invalid ROM contents\n"); + break; + } + if (readb(image + 1) != 0xAA) + break; + /* get the PCI data structure and check its signature */ + pds = image + readw(image + 24); + if (readb(pds) != 'P') + break; + if (readb(pds + 1) != 'C') + break; + if (readb(pds + 2) != 'I') + break; + if (readb(pds + 3) != 'R') + break; + last_image = readb(pds + 21) & 0x80; + /* this length is reliable */ + image += readw(pds + 16) * 512; + } while (!last_image); + + /* never return a size larger than the PCI resource window */ + /* there are known ROMs that get the size wrong */ + return min((size_t)(image - rom), size); +} + +/** + * pci_map_rom - map a PCI ROM to kernel space + * @pdev: pointer to pci device struct + * @size: pointer to receive size of pci window over ROM + * + * Return: kernel virtual pointer to image of ROM + * + * Map a PCI ROM into kernel space. If ROM is boot video ROM, + * the shadow BIOS copy will be returned instead of the + * actual ROM. + */ +void __iomem *pci_map_rom(struct pci_dev *pdev, size_t *size) +{ + struct resource *res = &pdev->resource[PCI_ROM_RESOURCE]; + loff_t start; + void __iomem *rom; + + /* + * IORESOURCE_ROM_SHADOW set on x86, x86_64 and IA64 supports legacy + * memory map if the VGA enable bit of the Bridge Control register is + * set for embedded VGA. + */ + if (res->flags & IORESOURCE_ROM_SHADOW) { + /* primary video rom always starts here */ + start = (loff_t)0xC0000; + *size = 0x20000; /* cover C000:0 through E000:0 */ + } else { + if (res->flags & + (IORESOURCE_ROM_COPY | IORESOURCE_ROM_BIOS_COPY)) { + *size = pci_resource_len(pdev, PCI_ROM_RESOURCE); + return (void __iomem *)(unsigned long) + pci_resource_start(pdev, PCI_ROM_RESOURCE); + } else { + /* assign the ROM an address if it doesn't have one */ + if (res->parent == NULL && + pci_assign_resource(pdev,PCI_ROM_RESOURCE)) + return NULL; + start = pci_resource_start(pdev, PCI_ROM_RESOURCE); + *size = pci_resource_len(pdev, PCI_ROM_RESOURCE); + if (*size == 0) + return NULL; + + /* Enable ROM space decodes */ + if (pci_enable_rom(pdev)) + return NULL; + } + } + + rom = ioremap(start, *size); + if (!rom) { + /* restore enable if ioremap fails */ + if (!(res->flags & (IORESOURCE_ROM_ENABLE | + IORESOURCE_ROM_SHADOW | + IORESOURCE_ROM_COPY))) + pci_disable_rom(pdev); + return NULL; + } + + /* + * Try to find the true size of the ROM since sometimes the PCI window + * size is much larger than the actual size of the ROM. + * True size is important if the ROM is going to be copied. + */ + *size = pci_get_rom_size(pdev, rom, *size); + return rom; +} + +#if 0 +/** + * pci_map_rom_copy - map a PCI ROM to kernel space, create a copy + * @pdev: pointer to pci device struct + * @size: pointer to receive size of pci window over ROM + * + * Return: kernel virtual pointer to image of ROM + * + * Map a PCI ROM into kernel space. If ROM is boot video ROM, + * the shadow BIOS copy will be returned instead of the + * actual ROM. + */ +void __iomem *pci_map_rom_copy(struct pci_dev *pdev, size_t *size) +{ + struct resource *res = &pdev->resource[PCI_ROM_RESOURCE]; + void __iomem *rom; + + rom = pci_map_rom(pdev, size); + if (!rom) + return NULL; + + if (res->flags & (IORESOURCE_ROM_COPY | IORESOURCE_ROM_SHADOW | + IORESOURCE_ROM_BIOS_COPY)) + return rom; + + res->start = (unsigned long)kmalloc(*size, GFP_KERNEL); + if (!res->start) + return rom; + + res->end = res->start + *size; + memcpy_fromio((void*)(unsigned long)res->start, rom, *size); + pci_unmap_rom(pdev, rom); + res->flags |= IORESOURCE_ROM_COPY; + + return (void __iomem *)(unsigned long)res->start; +} +#endif /* 0 */ + +/** + * pci_unmap_rom - unmap the ROM from kernel space + * @pdev: pointer to pci device struct + * @rom: virtual address of the previous mapping + * + * Remove a mapping of a previously mapped ROM + */ +void pci_unmap_rom(struct pci_dev *pdev, void __iomem *rom) +{ + struct resource *res = &pdev->resource[PCI_ROM_RESOURCE]; + + if (res->flags & (IORESOURCE_ROM_COPY | IORESOURCE_ROM_BIOS_COPY)) + return; + + iounmap(rom); + + /* Disable again before continuing, leave enabled if pci=rom */ + if (!(res->flags & (IORESOURCE_ROM_ENABLE | IORESOURCE_ROM_SHADOW))) + pci_disable_rom(pdev); +} + +#if 0 +/** + * pci_remove_rom - disable the ROM and remove its sysfs attribute + * @pdev: pointer to pci device struct + * + * Remove the rom file in sysfs and disable ROM decoding. + */ +void pci_remove_rom(struct pci_dev *pdev) +{ + struct resource *res = &pdev->resource[PCI_ROM_RESOURCE]; + + if (pci_resource_len(pdev, PCI_ROM_RESOURCE)) + sysfs_remove_bin_file(&pdev->dev.kobj, pdev->rom_attr); + if (!(res->flags & (IORESOURCE_ROM_ENABLE | + IORESOURCE_ROM_SHADOW | + IORESOURCE_ROM_BIOS_COPY | + IORESOURCE_ROM_COPY))) + pci_disable_rom(pdev); +} +#endif /* 0 */ + +/** + * pci_cleanup_rom - free the ROM copy created by pci_map_rom_copy + * @pdev: pointer to pci device struct + * + * Free the copied ROM if we allocated one. + */ +void pci_cleanup_rom(struct pci_dev *pdev) +{ + struct resource *res = &pdev->resource[PCI_ROM_RESOURCE]; + if (res->flags & IORESOURCE_ROM_COPY) { + kfree((void*)(unsigned long)res->start); + res->flags &= ~IORESOURCE_ROM_COPY; + res->start = 0; + res->end = 0; + } +} + +EXPORT_SYMBOL(pci_map_rom); +EXPORT_SYMBOL(pci_unmap_rom); +EXPORT_SYMBOL_GPL(pci_enable_rom); +EXPORT_SYMBOL_GPL(pci_disable_rom); diff --git a/drivers/pci/search.c b/drivers/pci/search.c new file mode 100644 index 0000000..5af8bd5 --- /dev/null +++ b/drivers/pci/search.c @@ -0,0 +1,419 @@ +/* + * PCI searching functions. + * + * Copyright (C) 1993 -- 1997 Drew Eckhardt, Frederic Potter, + * David Mosberger-Tang + * Copyright (C) 1997 -- 2000 Martin Mares <mj@ucw.cz> + * Copyright (C) 2003 -- 2004 Greg Kroah-Hartman <greg@kroah.com> + */ + +#include <linux/init.h> +#include <linux/pci.h> +#include <linux/module.h> +#include <linux/interrupt.h> +#include "pci.h" + +DECLARE_RWSEM(pci_bus_sem); +/* + * find the upstream PCIE-to-PCI bridge of a PCI device + * if the device is PCIE, return NULL + * if the device isn't connected to a PCIE bridge (that is its parent is a + * legacy PCI bridge and the bridge is directly connected to bus 0), return its + * parent + */ +struct pci_dev * +pci_find_upstream_pcie_bridge(struct pci_dev *pdev) +{ + struct pci_dev *tmp = NULL; + + if (pdev->is_pcie) + return NULL; + while (1) { + if (!pdev->bus->self) + break; + pdev = pdev->bus->self; + /* a p2p bridge */ + if (!pdev->is_pcie) { + tmp = pdev; + continue; + } + /* PCI device should connect to a PCIE bridge */ + if (pdev->pcie_type != PCI_EXP_TYPE_PCI_BRIDGE) { + /* Busted hardware? */ + WARN_ON_ONCE(1); + return NULL; + } + return pdev; + } + + return tmp; +} + +static struct pci_bus *pci_do_find_bus(struct pci_bus *bus, unsigned char busnr) +{ + struct pci_bus* child; + struct list_head *tmp; + + if(bus->number == busnr) + return bus; + + list_for_each(tmp, &bus->children) { + child = pci_do_find_bus(pci_bus_b(tmp), busnr); + if(child) + return child; + } + return NULL; +} + +/** + * pci_find_bus - locate PCI bus from a given domain and bus number + * @domain: number of PCI domain to search + * @busnr: number of desired PCI bus + * + * Given a PCI bus number and domain number, the desired PCI bus is located + * in the global list of PCI buses. If the bus is found, a pointer to its + * data structure is returned. If no bus is found, %NULL is returned. + */ +struct pci_bus * pci_find_bus(int domain, int busnr) +{ + struct pci_bus *bus = NULL; + struct pci_bus *tmp_bus; + + while ((bus = pci_find_next_bus(bus)) != NULL) { + if (pci_domain_nr(bus) != domain) + continue; + tmp_bus = pci_do_find_bus(bus, busnr); + if (tmp_bus) + return tmp_bus; + } + return NULL; +} + +/** + * pci_find_next_bus - begin or continue searching for a PCI bus + * @from: Previous PCI bus found, or %NULL for new search. + * + * Iterates through the list of known PCI busses. A new search is + * initiated by passing %NULL as the @from argument. Otherwise if + * @from is not %NULL, searches continue from next device on the + * global list. + */ +struct pci_bus * +pci_find_next_bus(const struct pci_bus *from) +{ + struct list_head *n; + struct pci_bus *b = NULL; + + WARN_ON(in_interrupt()); + down_read(&pci_bus_sem); + n = from ? from->node.next : pci_root_buses.next; + if (n != &pci_root_buses) + b = pci_bus_b(n); + up_read(&pci_bus_sem); + return b; +} + +#ifdef CONFIG_PCI_LEGACY +/** + * pci_find_slot - locate PCI device from a given PCI slot + * @bus: number of PCI bus on which desired PCI device resides + * @devfn: encodes number of PCI slot in which the desired PCI + * device resides and the logical device number within that slot + * in case of multi-function devices. + * + * Given a PCI bus and slot/function number, the desired PCI device + * is located in system global list of PCI devices. If the device + * is found, a pointer to its data structure is returned. If no + * device is found, %NULL is returned. + * + * NOTE: Do not use this function any more; use pci_get_slot() instead, as + * the PCI device returned by this function can disappear at any moment in + * time. + */ +struct pci_dev *pci_find_slot(unsigned int bus, unsigned int devfn) +{ + struct pci_dev *dev = NULL; + + while ((dev = pci_get_device(PCI_ANY_ID, PCI_ANY_ID, dev)) != NULL) { + if (dev->bus->number == bus && dev->devfn == devfn) { + pci_dev_put(dev); + return dev; + } + } + return NULL; +} +EXPORT_SYMBOL(pci_find_slot); + +/** + * pci_find_device - begin or continue searching for a PCI device by vendor/device id + * @vendor: PCI vendor id to match, or %PCI_ANY_ID to match all vendor ids + * @device: PCI device id to match, or %PCI_ANY_ID to match all device ids + * @from: Previous PCI device found in search, or %NULL for new search. + * + * Iterates through the list of known PCI devices. If a PCI device is found + * with a matching @vendor and @device, a pointer to its device structure is + * returned. Otherwise, %NULL is returned. + * A new search is initiated by passing %NULL as the @from argument. + * Otherwise if @from is not %NULL, searches continue from next device + * on the global list. + * + * NOTE: Do not use this function any more; use pci_get_device() instead, as + * the PCI device returned by this function can disappear at any moment in + * time. + */ +struct pci_dev *pci_find_device(unsigned int vendor, unsigned int device, + struct pci_dev *from) +{ + struct pci_dev *pdev; + + pci_dev_get(from); + pdev = pci_get_subsys(vendor, device, PCI_ANY_ID, PCI_ANY_ID, from); + pci_dev_put(pdev); + return pdev; +} +EXPORT_SYMBOL(pci_find_device); +#endif /* CONFIG_PCI_LEGACY */ + +/** + * pci_get_slot - locate PCI device for a given PCI slot + * @bus: PCI bus on which desired PCI device resides + * @devfn: encodes number of PCI slot in which the desired PCI + * device resides and the logical device number within that slot + * in case of multi-function devices. + * + * Given a PCI bus and slot/function number, the desired PCI device + * is located in the list of PCI devices. + * If the device is found, its reference count is increased and this + * function returns a pointer to its data structure. The caller must + * decrement the reference count by calling pci_dev_put(). + * If no device is found, %NULL is returned. + */ +struct pci_dev * pci_get_slot(struct pci_bus *bus, unsigned int devfn) +{ + struct list_head *tmp; + struct pci_dev *dev; + + WARN_ON(in_interrupt()); + down_read(&pci_bus_sem); + + list_for_each(tmp, &bus->devices) { + dev = pci_dev_b(tmp); + if (dev->devfn == devfn) + goto out; + } + + dev = NULL; + out: + pci_dev_get(dev); + up_read(&pci_bus_sem); + return dev; +} + +/** + * pci_get_bus_and_slot - locate PCI device from a given PCI bus & slot + * @bus: number of PCI bus on which desired PCI device resides + * @devfn: encodes number of PCI slot in which the desired PCI + * device resides and the logical device number within that slot + * in case of multi-function devices. + * + * Note: the bus/slot search is limited to PCI domain (segment) 0. + * + * Given a PCI bus and slot/function number, the desired PCI device + * is located in system global list of PCI devices. If the device + * is found, a pointer to its data structure is returned. If no + * device is found, %NULL is returned. The returned device has its + * reference count bumped by one. + */ + +struct pci_dev * pci_get_bus_and_slot(unsigned int bus, unsigned int devfn) +{ + struct pci_dev *dev = NULL; + + while ((dev = pci_get_device(PCI_ANY_ID, PCI_ANY_ID, dev)) != NULL) { + if (pci_domain_nr(dev->bus) == 0 && + (dev->bus->number == bus && dev->devfn == devfn)) + return dev; + } + return NULL; +} + +static int match_pci_dev_by_id(struct device *dev, void *data) +{ + struct pci_dev *pdev = to_pci_dev(dev); + struct pci_device_id *id = data; + + if (pci_match_one_device(id, pdev)) + return 1; + return 0; +} + +/* + * pci_get_dev_by_id - begin or continue searching for a PCI device by id + * @id: pointer to struct pci_device_id to match for the device + * @from: Previous PCI device found in search, or %NULL for new search. + * + * Iterates through the list of known PCI devices. If a PCI device is found + * with a matching id a pointer to its device structure is returned, and the + * reference count to the device is incremented. Otherwise, %NULL is returned. + * A new search is initiated by passing %NULL as the @from argument. Otherwise + * if @from is not %NULL, searches continue from next device on the global + * list. The reference count for @from is always decremented if it is not + * %NULL. + * + * This is an internal function for use by the other search functions in + * this file. + */ +static struct pci_dev *pci_get_dev_by_id(const struct pci_device_id *id, + struct pci_dev *from) +{ + struct device *dev; + struct device *dev_start = NULL; + struct pci_dev *pdev = NULL; + + WARN_ON(in_interrupt()); + if (from) + dev_start = &from->dev; + dev = bus_find_device(&pci_bus_type, dev_start, (void *)id, + match_pci_dev_by_id); + if (dev) + pdev = to_pci_dev(dev); + if (from) + pci_dev_put(from); + return pdev; +} + +/** + * pci_get_subsys - begin or continue searching for a PCI device by vendor/subvendor/device/subdevice id + * @vendor: PCI vendor id to match, or %PCI_ANY_ID to match all vendor ids + * @device: PCI device id to match, or %PCI_ANY_ID to match all device ids + * @ss_vendor: PCI subsystem vendor id to match, or %PCI_ANY_ID to match all vendor ids + * @ss_device: PCI subsystem device id to match, or %PCI_ANY_ID to match all device ids + * @from: Previous PCI device found in search, or %NULL for new search. + * + * Iterates through the list of known PCI devices. If a PCI device is found + * with a matching @vendor, @device, @ss_vendor and @ss_device, a pointer to its + * device structure is returned, and the reference count to the device is + * incremented. Otherwise, %NULL is returned. A new search is initiated by + * passing %NULL as the @from argument. Otherwise if @from is not %NULL, + * searches continue from next device on the global list. + * The reference count for @from is always decremented if it is not %NULL. + */ +struct pci_dev *pci_get_subsys(unsigned int vendor, unsigned int device, + unsigned int ss_vendor, unsigned int ss_device, + struct pci_dev *from) +{ + struct pci_dev *pdev; + struct pci_device_id *id; + + /* + * pci_find_subsys() can be called on the ide_setup() path, + * super-early in boot. But the down_read() will enable local + * interrupts, which can cause some machines to crash. So here we + * detect and flag that situation and bail out early. + */ + if (unlikely(no_pci_devices())) + return NULL; + + id = kzalloc(sizeof(*id), GFP_KERNEL); + if (!id) + return NULL; + id->vendor = vendor; + id->device = device; + id->subvendor = ss_vendor; + id->subdevice = ss_device; + + pdev = pci_get_dev_by_id(id, from); + kfree(id); + + return pdev; +} + +/** + * pci_get_device - begin or continue searching for a PCI device by vendor/device id + * @vendor: PCI vendor id to match, or %PCI_ANY_ID to match all vendor ids + * @device: PCI device id to match, or %PCI_ANY_ID to match all device ids + * @from: Previous PCI device found in search, or %NULL for new search. + * + * Iterates through the list of known PCI devices. If a PCI device is + * found with a matching @vendor and @device, the reference count to the + * device is incremented and a pointer to its device structure is returned. + * Otherwise, %NULL is returned. A new search is initiated by passing %NULL + * as the @from argument. Otherwise if @from is not %NULL, searches continue + * from next device on the global list. The reference count for @from is + * always decremented if it is not %NULL. + */ +struct pci_dev * +pci_get_device(unsigned int vendor, unsigned int device, struct pci_dev *from) +{ + return pci_get_subsys(vendor, device, PCI_ANY_ID, PCI_ANY_ID, from); +} + +/** + * pci_get_class - begin or continue searching for a PCI device by class + * @class: search for a PCI device with this class designation + * @from: Previous PCI device found in search, or %NULL for new search. + * + * Iterates through the list of known PCI devices. If a PCI device is + * found with a matching @class, the reference count to the device is + * incremented and a pointer to its device structure is returned. + * Otherwise, %NULL is returned. + * A new search is initiated by passing %NULL as the @from argument. + * Otherwise if @from is not %NULL, searches continue from next device + * on the global list. The reference count for @from is always decremented + * if it is not %NULL. + */ +struct pci_dev *pci_get_class(unsigned int class, struct pci_dev *from) +{ + struct pci_dev *dev; + struct pci_device_id *id; + + id = kzalloc(sizeof(*id), GFP_KERNEL); + if (!id) + return NULL; + id->vendor = id->device = id->subvendor = id->subdevice = PCI_ANY_ID; + id->class_mask = PCI_ANY_ID; + id->class = class; + + dev = pci_get_dev_by_id(id, from); + kfree(id); + return dev; +} + +/** + * pci_dev_present - Returns 1 if device matching the device list is present, 0 if not. + * @ids: A pointer to a null terminated list of struct pci_device_id structures + * that describe the type of PCI device the caller is trying to find. + * + * Obvious fact: You do not have a reference to any device that might be found + * by this function, so if that device is removed from the system right after + * this function is finished, the value will be stale. Use this function to + * find devices that are usually built into a system, or for a general hint as + * to if another device happens to be present at this specific moment in time. + */ +int pci_dev_present(const struct pci_device_id *ids) +{ + struct pci_dev *found = NULL; + + WARN_ON(in_interrupt()); + while (ids->vendor || ids->subvendor || ids->class_mask) { + found = pci_get_dev_by_id(ids, NULL); + if (found) + goto exit; + ids++; + } +exit: + if (found) + return 1; + return 0; +} +EXPORT_SYMBOL(pci_dev_present); + +/* For boot time work */ +EXPORT_SYMBOL(pci_find_bus); +EXPORT_SYMBOL(pci_find_next_bus); +/* For everyone */ +EXPORT_SYMBOL(pci_get_device); +EXPORT_SYMBOL(pci_get_subsys); +EXPORT_SYMBOL(pci_get_slot); +EXPORT_SYMBOL(pci_get_bus_and_slot); +EXPORT_SYMBOL(pci_get_class); diff --git a/drivers/pci/setup-bus.c b/drivers/pci/setup-bus.c new file mode 100644 index 0000000..ea979f2 --- /dev/null +++ b/drivers/pci/setup-bus.c @@ -0,0 +1,582 @@ +/* + * drivers/pci/setup-bus.c + * + * Extruded from code written by + * Dave Rusling (david.rusling@reo.mts.dec.com) + * David Mosberger (davidm@cs.arizona.edu) + * David Miller (davem@redhat.com) + * + * Support routines for initializing a PCI subsystem. + */ + +/* + * Nov 2000, Ivan Kokshaysky <ink@jurassic.park.msu.ru> + * PCI-PCI bridges cleanup, sorted resource allocation. + * Feb 2002, Ivan Kokshaysky <ink@jurassic.park.msu.ru> + * Converted to allocation in 3 passes, which gives + * tighter packing. Prefetchable range support. + */ + +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/pci.h> +#include <linux/errno.h> +#include <linux/ioport.h> +#include <linux/cache.h> +#include <linux/slab.h> + + +static void pbus_assign_resources_sorted(struct pci_bus *bus) +{ + struct pci_dev *dev; + struct resource *res; + struct resource_list head, *list, *tmp; + int idx; + + head.next = NULL; + list_for_each_entry(dev, &bus->devices, bus_list) { + u16 class = dev->class >> 8; + + /* Don't touch classless devices or host bridges or ioapics. */ + if (class == PCI_CLASS_NOT_DEFINED || + class == PCI_CLASS_BRIDGE_HOST) + continue; + + /* Don't touch ioapic devices already enabled by firmware */ + if (class == PCI_CLASS_SYSTEM_PIC) { + u16 command; + pci_read_config_word(dev, PCI_COMMAND, &command); + if (command & (PCI_COMMAND_IO | PCI_COMMAND_MEMORY)) + continue; + } + + pdev_sort_resources(dev, &head); + } + + for (list = head.next; list;) { + res = list->res; + idx = res - &list->dev->resource[0]; + if (pci_assign_resource(list->dev, idx)) { + /* FIXME: get rid of this */ + res->start = 0; + res->end = 0; + res->flags = 0; + } + tmp = list; + list = list->next; + kfree(tmp); + } +} + +void pci_setup_cardbus(struct pci_bus *bus) +{ + struct pci_dev *bridge = bus->self; + struct pci_bus_region region; + + dev_info(&bridge->dev, "CardBus bridge, secondary bus %04x:%02x\n", + pci_domain_nr(bus), bus->number); + + pcibios_resource_to_bus(bridge, ®ion, bus->resource[0]); + if (bus->resource[0]->flags & IORESOURCE_IO) { + /* + * The IO resource is allocated a range twice as large as it + * would normally need. This allows us to set both IO regs. + */ + dev_info(&bridge->dev, " IO window: %#08lx-%#08lx\n", + (unsigned long)region.start, + (unsigned long)region.end); + pci_write_config_dword(bridge, PCI_CB_IO_BASE_0, + region.start); + pci_write_config_dword(bridge, PCI_CB_IO_LIMIT_0, + region.end); + } + + pcibios_resource_to_bus(bridge, ®ion, bus->resource[1]); + if (bus->resource[1]->flags & IORESOURCE_IO) { + dev_info(&bridge->dev, " IO window: %#08lx-%#08lx\n", + (unsigned long)region.start, + (unsigned long)region.end); + pci_write_config_dword(bridge, PCI_CB_IO_BASE_1, + region.start); + pci_write_config_dword(bridge, PCI_CB_IO_LIMIT_1, + region.end); + } + + pcibios_resource_to_bus(bridge, ®ion, bus->resource[2]); + if (bus->resource[2]->flags & IORESOURCE_MEM) { + dev_info(&bridge->dev, " PREFETCH window: %#08lx-%#08lx\n", + (unsigned long)region.start, + (unsigned long)region.end); + pci_write_config_dword(bridge, PCI_CB_MEMORY_BASE_0, + region.start); + pci_write_config_dword(bridge, PCI_CB_MEMORY_LIMIT_0, + region.end); + } + + pcibios_resource_to_bus(bridge, ®ion, bus->resource[3]); + if (bus->resource[3]->flags & IORESOURCE_MEM) { + dev_info(&bridge->dev, " MEM window: %#08lx-%#08lx\n", + (unsigned long)region.start, + (unsigned long)region.end); + pci_write_config_dword(bridge, PCI_CB_MEMORY_BASE_1, + region.start); + pci_write_config_dword(bridge, PCI_CB_MEMORY_LIMIT_1, + region.end); + } +} +EXPORT_SYMBOL(pci_setup_cardbus); + +/* Initialize bridges with base/limit values we have collected. + PCI-to-PCI Bridge Architecture Specification rev. 1.1 (1998) + requires that if there is no I/O ports or memory behind the + bridge, corresponding range must be turned off by writing base + value greater than limit to the bridge's base/limit registers. + + Note: care must be taken when updating I/O base/limit registers + of bridges which support 32-bit I/O. This update requires two + config space writes, so it's quite possible that an I/O window of + the bridge will have some undesirable address (e.g. 0) after the + first write. Ditto 64-bit prefetchable MMIO. */ +static void pci_setup_bridge(struct pci_bus *bus) +{ + struct pci_dev *bridge = bus->self; + struct pci_bus_region region; + u32 l, bu, lu, io_upper16; + + dev_info(&bridge->dev, "PCI bridge, secondary bus %04x:%02x\n", + pci_domain_nr(bus), bus->number); + + /* Set up the top and bottom of the PCI I/O segment for this bus. */ + pcibios_resource_to_bus(bridge, ®ion, bus->resource[0]); + if (bus->resource[0]->flags & IORESOURCE_IO) { + pci_read_config_dword(bridge, PCI_IO_BASE, &l); + l &= 0xffff0000; + l |= (region.start >> 8) & 0x00f0; + l |= region.end & 0xf000; + /* Set up upper 16 bits of I/O base/limit. */ + io_upper16 = (region.end & 0xffff0000) | (region.start >> 16); + dev_info(&bridge->dev, " IO window: %#04lx-%#04lx\n", + (unsigned long)region.start, + (unsigned long)region.end); + } + else { + /* Clear upper 16 bits of I/O base/limit. */ + io_upper16 = 0; + l = 0x00f0; + dev_info(&bridge->dev, " IO window: disabled\n"); + } + /* Temporarily disable the I/O range before updating PCI_IO_BASE. */ + pci_write_config_dword(bridge, PCI_IO_BASE_UPPER16, 0x0000ffff); + /* Update lower 16 bits of I/O base/limit. */ + pci_write_config_dword(bridge, PCI_IO_BASE, l); + /* Update upper 16 bits of I/O base/limit. */ + pci_write_config_dword(bridge, PCI_IO_BASE_UPPER16, io_upper16); + + /* Set up the top and bottom of the PCI Memory segment + for this bus. */ + pcibios_resource_to_bus(bridge, ®ion, bus->resource[1]); + if (bus->resource[1]->flags & IORESOURCE_MEM) { + l = (region.start >> 16) & 0xfff0; + l |= region.end & 0xfff00000; + dev_info(&bridge->dev, " MEM window: %#08lx-%#08lx\n", + (unsigned long)region.start, + (unsigned long)region.end); + } + else { + l = 0x0000fff0; + dev_info(&bridge->dev, " MEM window: disabled\n"); + } + pci_write_config_dword(bridge, PCI_MEMORY_BASE, l); + + /* Clear out the upper 32 bits of PREF limit. + If PCI_PREF_BASE_UPPER32 was non-zero, this temporarily + disables PREF range, which is ok. */ + pci_write_config_dword(bridge, PCI_PREF_LIMIT_UPPER32, 0); + + /* Set up PREF base/limit. */ + bu = lu = 0; + pcibios_resource_to_bus(bridge, ®ion, bus->resource[2]); + if (bus->resource[2]->flags & IORESOURCE_PREFETCH) { + l = (region.start >> 16) & 0xfff0; + l |= region.end & 0xfff00000; + bu = upper_32_bits(region.start); + lu = upper_32_bits(region.end); + dev_info(&bridge->dev, " PREFETCH window: %#016llx-%#016llx\n", + (unsigned long long)region.start, + (unsigned long long)region.end); + } + else { + l = 0x0000fff0; + dev_info(&bridge->dev, " PREFETCH window: disabled\n"); + } + pci_write_config_dword(bridge, PCI_PREF_MEMORY_BASE, l); + + /* Set the upper 32 bits of PREF base & limit. */ + pci_write_config_dword(bridge, PCI_PREF_BASE_UPPER32, bu); + pci_write_config_dword(bridge, PCI_PREF_LIMIT_UPPER32, lu); + + pci_write_config_word(bridge, PCI_BRIDGE_CONTROL, bus->bridge_ctl); +} + +/* Check whether the bridge supports optional I/O and + prefetchable memory ranges. If not, the respective + base/limit registers must be read-only and read as 0. */ +static void pci_bridge_check_ranges(struct pci_bus *bus) +{ + u16 io; + u32 pmem; + struct pci_dev *bridge = bus->self; + struct resource *b_res; + + b_res = &bridge->resource[PCI_BRIDGE_RESOURCES]; + b_res[1].flags |= IORESOURCE_MEM; + + pci_read_config_word(bridge, PCI_IO_BASE, &io); + if (!io) { + pci_write_config_word(bridge, PCI_IO_BASE, 0xf0f0); + pci_read_config_word(bridge, PCI_IO_BASE, &io); + pci_write_config_word(bridge, PCI_IO_BASE, 0x0); + } + if (io) + b_res[0].flags |= IORESOURCE_IO; + /* DECchip 21050 pass 2 errata: the bridge may miss an address + disconnect boundary by one PCI data phase. + Workaround: do not use prefetching on this device. */ + if (bridge->vendor == PCI_VENDOR_ID_DEC && bridge->device == 0x0001) + return; + pci_read_config_dword(bridge, PCI_PREF_MEMORY_BASE, &pmem); + if (!pmem) { + pci_write_config_dword(bridge, PCI_PREF_MEMORY_BASE, + 0xfff0fff0); + pci_read_config_dword(bridge, PCI_PREF_MEMORY_BASE, &pmem); + pci_write_config_dword(bridge, PCI_PREF_MEMORY_BASE, 0x0); + } + if (pmem) + b_res[2].flags |= IORESOURCE_MEM | IORESOURCE_PREFETCH; +} + +/* Helper function for sizing routines: find first available + bus resource of a given type. Note: we intentionally skip + the bus resources which have already been assigned (that is, + have non-NULL parent resource). */ +static struct resource *find_free_bus_resource(struct pci_bus *bus, unsigned long type) +{ + int i; + struct resource *r; + unsigned long type_mask = IORESOURCE_IO | IORESOURCE_MEM | + IORESOURCE_PREFETCH; + + for (i = 0; i < PCI_BUS_NUM_RESOURCES; i++) { + r = bus->resource[i]; + if (r == &ioport_resource || r == &iomem_resource) + continue; + if (r && (r->flags & type_mask) == type && !r->parent) + return r; + } + return NULL; +} + +/* Sizing the IO windows of the PCI-PCI bridge is trivial, + since these windows have 4K granularity and the IO ranges + of non-bridge PCI devices are limited to 256 bytes. + We must be careful with the ISA aliasing though. */ +static void pbus_size_io(struct pci_bus *bus) +{ + struct pci_dev *dev; + struct resource *b_res = find_free_bus_resource(bus, IORESOURCE_IO); + unsigned long size = 0, size1 = 0; + + if (!b_res) + return; + + list_for_each_entry(dev, &bus->devices, bus_list) { + int i; + + for (i = 0; i < PCI_NUM_RESOURCES; i++) { + struct resource *r = &dev->resource[i]; + unsigned long r_size; + + if (r->parent || !(r->flags & IORESOURCE_IO)) + continue; + r_size = resource_size(r); + + if (r_size < 0x400) + /* Might be re-aligned for ISA */ + size += r_size; + else + size1 += r_size; + } + } +/* To be fixed in 2.5: we should have sort of HAVE_ISA + flag in the struct pci_bus. */ +#if defined(CONFIG_ISA) || defined(CONFIG_EISA) + size = (size & 0xff) + ((size & ~0xffUL) << 2); +#endif + size = ALIGN(size + size1, 4096); + if (!size) { + b_res->flags = 0; + return; + } + /* Alignment of the IO window is always 4K */ + b_res->start = 4096; + b_res->end = b_res->start + size - 1; + b_res->flags |= IORESOURCE_STARTALIGN; +} + +/* Calculate the size of the bus and minimal alignment which + guarantees that all child resources fit in this size. */ +static int pbus_size_mem(struct pci_bus *bus, unsigned long mask, unsigned long type) +{ + struct pci_dev *dev; + resource_size_t min_align, align, size; + resource_size_t aligns[12]; /* Alignments from 1Mb to 2Gb */ + int order, max_order; + struct resource *b_res = find_free_bus_resource(bus, type); + + if (!b_res) + return 0; + + memset(aligns, 0, sizeof(aligns)); + max_order = 0; + size = 0; + + list_for_each_entry(dev, &bus->devices, bus_list) { + int i; + + for (i = 0; i < PCI_NUM_RESOURCES; i++) { + struct resource *r = &dev->resource[i]; + resource_size_t r_size; + + if (r->parent || (r->flags & mask) != type) + continue; + r_size = resource_size(r); + /* For bridges size != alignment */ + align = resource_alignment(r); + order = __ffs(align) - 20; + if (order > 11) { + dev_warn(&dev->dev, "BAR %d bad alignment %llx: " + "%pR\n", i, (unsigned long long)align, r); + r->flags = 0; + continue; + } + size += r_size; + if (order < 0) + order = 0; + /* Exclude ranges with size > align from + calculation of the alignment. */ + if (r_size == align) + aligns[order] += align; + if (order > max_order) + max_order = order; + } + } + + align = 0; + min_align = 0; + for (order = 0; order <= max_order; order++) { + resource_size_t align1 = 1; + + align1 <<= (order + 20); + + if (!align) + min_align = align1; + else if (ALIGN(align + min_align, min_align) < align1) + min_align = align1 >> 1; + align += aligns[order]; + } + size = ALIGN(size, min_align); + if (!size) { + b_res->flags = 0; + return 1; + } + b_res->start = min_align; + b_res->end = size + min_align - 1; + b_res->flags |= IORESOURCE_STARTALIGN; + return 1; +} + +static void pci_bus_size_cardbus(struct pci_bus *bus) +{ + struct pci_dev *bridge = bus->self; + struct resource *b_res = &bridge->resource[PCI_BRIDGE_RESOURCES]; + u16 ctrl; + + /* + * Reserve some resources for CardBus. We reserve + * a fixed amount of bus space for CardBus bridges. + */ + b_res[0].start = 0; + b_res[0].end = pci_cardbus_io_size - 1; + b_res[0].flags |= IORESOURCE_IO | IORESOURCE_SIZEALIGN; + + b_res[1].start = 0; + b_res[1].end = pci_cardbus_io_size - 1; + b_res[1].flags |= IORESOURCE_IO | IORESOURCE_SIZEALIGN; + + /* + * Check whether prefetchable memory is supported + * by this bridge. + */ + pci_read_config_word(bridge, PCI_CB_BRIDGE_CONTROL, &ctrl); + if (!(ctrl & PCI_CB_BRIDGE_CTL_PREFETCH_MEM0)) { + ctrl |= PCI_CB_BRIDGE_CTL_PREFETCH_MEM0; + pci_write_config_word(bridge, PCI_CB_BRIDGE_CONTROL, ctrl); + pci_read_config_word(bridge, PCI_CB_BRIDGE_CONTROL, &ctrl); + } + + /* + * If we have prefetchable memory support, allocate + * two regions. Otherwise, allocate one region of + * twice the size. + */ + if (ctrl & PCI_CB_BRIDGE_CTL_PREFETCH_MEM0) { + b_res[2].start = 0; + b_res[2].end = pci_cardbus_mem_size - 1; + b_res[2].flags |= IORESOURCE_MEM | IORESOURCE_PREFETCH | IORESOURCE_SIZEALIGN; + + b_res[3].start = 0; + b_res[3].end = pci_cardbus_mem_size - 1; + b_res[3].flags |= IORESOURCE_MEM | IORESOURCE_SIZEALIGN; + } else { + b_res[3].start = 0; + b_res[3].end = pci_cardbus_mem_size * 2 - 1; + b_res[3].flags |= IORESOURCE_MEM | IORESOURCE_SIZEALIGN; + } +} + +void __ref pci_bus_size_bridges(struct pci_bus *bus) +{ + struct pci_dev *dev; + unsigned long mask, prefmask; + + list_for_each_entry(dev, &bus->devices, bus_list) { + struct pci_bus *b = dev->subordinate; + if (!b) + continue; + + switch (dev->class >> 8) { + case PCI_CLASS_BRIDGE_CARDBUS: + pci_bus_size_cardbus(b); + break; + + case PCI_CLASS_BRIDGE_PCI: + default: + pci_bus_size_bridges(b); + break; + } + } + + /* The root bus? */ + if (!bus->self) + return; + + switch (bus->self->class >> 8) { + case PCI_CLASS_BRIDGE_CARDBUS: + /* don't size cardbuses yet. */ + break; + + case PCI_CLASS_BRIDGE_PCI: + pci_bridge_check_ranges(bus); + default: + pbus_size_io(bus); + /* If the bridge supports prefetchable range, size it + separately. If it doesn't, or its prefetchable window + has already been allocated by arch code, try + non-prefetchable range for both types of PCI memory + resources. */ + mask = IORESOURCE_MEM; + prefmask = IORESOURCE_MEM | IORESOURCE_PREFETCH; + if (pbus_size_mem(bus, prefmask, prefmask)) + mask = prefmask; /* Success, size non-prefetch only. */ + pbus_size_mem(bus, mask, IORESOURCE_MEM); + break; + } +} +EXPORT_SYMBOL(pci_bus_size_bridges); + +void __ref pci_bus_assign_resources(struct pci_bus *bus) +{ + struct pci_bus *b; + struct pci_dev *dev; + + pbus_assign_resources_sorted(bus); + + list_for_each_entry(dev, &bus->devices, bus_list) { + b = dev->subordinate; + if (!b) + continue; + + pci_bus_assign_resources(b); + + switch (dev->class >> 8) { + case PCI_CLASS_BRIDGE_PCI: + pci_setup_bridge(b); + break; + + case PCI_CLASS_BRIDGE_CARDBUS: + pci_setup_cardbus(b); + break; + + default: + dev_info(&dev->dev, "not setting up bridge for bus " + "%04x:%02x\n", pci_domain_nr(b), b->number); + break; + } + } +} +EXPORT_SYMBOL(pci_bus_assign_resources); + +static void pci_bus_dump_res(struct pci_bus *bus) +{ + int i; + + for (i = 0; i < PCI_BUS_NUM_RESOURCES; i++) { + struct resource *res = bus->resource[i]; + if (!res) + continue; + + printk(KERN_INFO "bus: %02x index %x %s: %pR\n", + bus->number, i, + (res->flags & IORESOURCE_IO) ? "io port" : "mmio", res); + } +} + +static void pci_bus_dump_resources(struct pci_bus *bus) +{ + struct pci_bus *b; + struct pci_dev *dev; + + + pci_bus_dump_res(bus); + + list_for_each_entry(dev, &bus->devices, bus_list) { + b = dev->subordinate; + if (!b) + continue; + + pci_bus_dump_resources(b); + } +} + +void __init +pci_assign_unassigned_resources(void) +{ + struct pci_bus *bus; + + /* Depth first, calculate sizes and alignments of all + subordinate buses. */ + list_for_each_entry(bus, &pci_root_buses, node) { + pci_bus_size_bridges(bus); + } + /* Depth last, allocate resources and update the hardware. */ + list_for_each_entry(bus, &pci_root_buses, node) { + pci_bus_assign_resources(bus); + pci_enable_bridges(bus); + } + + /* dump the resource on buses */ + list_for_each_entry(bus, &pci_root_buses, node) { + pci_bus_dump_resources(bus); + } +} diff --git a/drivers/pci/setup-irq.c b/drivers/pci/setup-irq.c new file mode 100644 index 0000000..aa795fd --- /dev/null +++ b/drivers/pci/setup-irq.c @@ -0,0 +1,65 @@ +/* + * drivers/pci/setup-irq.c + * + * Extruded from code written by + * Dave Rusling (david.rusling@reo.mts.dec.com) + * David Mosberger (davidm@cs.arizona.edu) + * David Miller (davem@redhat.com) + * + * Support routines for initializing a PCI subsystem. + */ + + +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/pci.h> +#include <linux/errno.h> +#include <linux/ioport.h> +#include <linux/cache.h> + + +static void __init +pdev_fixup_irq(struct pci_dev *dev, + u8 (*swizzle)(struct pci_dev *, u8 *), + int (*map_irq)(struct pci_dev *, u8, u8)) +{ + u8 pin, slot; + int irq = 0; + + /* If this device is not on the primary bus, we need to figure out + which interrupt pin it will come in on. We know which slot it + will come in on 'cos that slot is where the bridge is. Each + time the interrupt line passes through a PCI-PCI bridge we must + apply the swizzle function. */ + + pci_read_config_byte(dev, PCI_INTERRUPT_PIN, &pin); + /* Cope with illegal. */ + if (pin > 4) + pin = 1; + + if (pin != 0) { + /* Follow the chain of bridges, swizzling as we go. */ + slot = (*swizzle)(dev, &pin); + + irq = (*map_irq)(dev, slot, pin); + if (irq == -1) + irq = 0; + } + dev->irq = irq; + + dev_dbg(&dev->dev, "fixup irq: got %d\n", dev->irq); + + /* Always tell the device, so the driver knows what is + the real IRQ to use; the device does not use it. */ + pcibios_update_irq(dev, irq); +} + +void __init +pci_fixup_irqs(u8 (*swizzle)(struct pci_dev *, u8 *), + int (*map_irq)(struct pci_dev *, u8, u8)) +{ + struct pci_dev *dev = NULL; + while ((dev = pci_get_device(PCI_ANY_ID, PCI_ANY_ID, dev)) != NULL) { + pdev_fixup_irq(dev, swizzle, map_irq); + } +} diff --git a/drivers/pci/setup-res.c b/drivers/pci/setup-res.c new file mode 100644 index 0000000..2dbd96c --- /dev/null +++ b/drivers/pci/setup-res.c @@ -0,0 +1,294 @@ +/* + * drivers/pci/setup-res.c + * + * Extruded from code written by + * Dave Rusling (david.rusling@reo.mts.dec.com) + * David Mosberger (davidm@cs.arizona.edu) + * David Miller (davem@redhat.com) + * + * Support routines for initializing a PCI subsystem. + */ + +/* fixed for multiple pci buses, 1999 Andrea Arcangeli <andrea@suse.de> */ + +/* + * Nov 2000, Ivan Kokshaysky <ink@jurassic.park.msu.ru> + * Resource sorting + */ + +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/pci.h> +#include <linux/errno.h> +#include <linux/ioport.h> +#include <linux/cache.h> +#include <linux/slab.h> +#include "pci.h" + + +void pci_update_resource(struct pci_dev *dev, struct resource *res, int resno) +{ + struct pci_bus_region region; + u32 new, check, mask; + int reg; + + /* + * Ignore resources for unimplemented BARs and unused resource slots + * for 64 bit BARs. + */ + if (!res->flags) + return; + + /* + * Ignore non-moveable resources. This might be legacy resources for + * which no functional BAR register exists or another important + * system resource we shouldn't move around. + */ + if (res->flags & IORESOURCE_PCI_FIXED) + return; + + pcibios_resource_to_bus(dev, ®ion, res); + + dev_dbg(&dev->dev, "BAR %d: got res %pR bus [%#llx-%#llx] " + "flags %#lx\n", resno, res, + (unsigned long long)region.start, + (unsigned long long)region.end, + (unsigned long)res->flags); + + new = region.start | (res->flags & PCI_REGION_FLAG_MASK); + if (res->flags & IORESOURCE_IO) + mask = (u32)PCI_BASE_ADDRESS_IO_MASK; + else + mask = (u32)PCI_BASE_ADDRESS_MEM_MASK; + + if (resno < 6) { + reg = PCI_BASE_ADDRESS_0 + 4 * resno; + } else if (resno == PCI_ROM_RESOURCE) { + if (!(res->flags & IORESOURCE_ROM_ENABLE)) + return; + new |= PCI_ROM_ADDRESS_ENABLE; + reg = dev->rom_base_reg; + } else { + /* Hmm, non-standard resource. */ + + return; /* kill uninitialised var warning */ + } + + pci_write_config_dword(dev, reg, new); + pci_read_config_dword(dev, reg, &check); + + if ((new ^ check) & mask) { + dev_err(&dev->dev, "BAR %d: error updating (%#08x != %#08x)\n", + resno, new, check); + } + + if ((new & (PCI_BASE_ADDRESS_SPACE|PCI_BASE_ADDRESS_MEM_TYPE_MASK)) == + (PCI_BASE_ADDRESS_SPACE_MEMORY|PCI_BASE_ADDRESS_MEM_TYPE_64)) { + new = region.start >> 16 >> 16; + pci_write_config_dword(dev, reg + 4, new); + pci_read_config_dword(dev, reg + 4, &check); + if (check != new) { + dev_err(&dev->dev, "BAR %d: error updating " + "(high %#08x != %#08x)\n", resno, new, check); + } + } + res->flags &= ~IORESOURCE_UNSET; + dev_dbg(&dev->dev, "BAR %d: moved to bus [%#llx-%#llx] flags %#lx\n", + resno, (unsigned long long)region.start, + (unsigned long long)region.end, res->flags); +} + +int pci_claim_resource(struct pci_dev *dev, int resource) +{ + struct resource *res = &dev->resource[resource]; + struct resource *root = NULL; + char *dtype = resource < PCI_BRIDGE_RESOURCES ? "device" : "bridge"; + int err; + + root = pcibios_select_root(dev, res); + + err = -EINVAL; + if (root != NULL) + err = insert_resource(root, res); + + if (err) { + dev_err(&dev->dev, "BAR %d: %s of %s %pR\n", + resource, + root ? "address space collision on" : + "no parent found for", + dtype, res); + } + + return err; +} + +int pci_assign_resource(struct pci_dev *dev, int resno) +{ + struct pci_bus *bus = dev->bus; + struct resource *res = dev->resource + resno; + resource_size_t size, min, align; + int ret; + + size = resource_size(res); + min = (res->flags & IORESOURCE_IO) ? PCIBIOS_MIN_IO : PCIBIOS_MIN_MEM; + + align = resource_alignment(res); + if (!align) { + dev_err(&dev->dev, "BAR %d: can't allocate resource (bogus " + "alignment) %pR flags %#lx\n", + resno, res, res->flags); + return -EINVAL; + } + + /* First, try exact prefetching match.. */ + ret = pci_bus_alloc_resource(bus, res, size, align, min, + IORESOURCE_PREFETCH, + pcibios_align_resource, dev); + + if (ret < 0 && (res->flags & IORESOURCE_PREFETCH)) { + /* + * That failed. + * + * But a prefetching area can handle a non-prefetching + * window (it will just not perform as well). + */ + ret = pci_bus_alloc_resource(bus, res, size, align, min, 0, + pcibios_align_resource, dev); + } + + if (ret) { + dev_err(&dev->dev, "BAR %d: can't allocate %s resource %pR\n", + resno, res->flags & IORESOURCE_IO ? "I/O" : "mem", res); + } else { + res->flags &= ~IORESOURCE_STARTALIGN; + if (resno < PCI_BRIDGE_RESOURCES) + pci_update_resource(dev, res, resno); + } + + return ret; +} + +#if 0 +int pci_assign_resource_fixed(struct pci_dev *dev, int resno) +{ + struct pci_bus *bus = dev->bus; + struct resource *res = dev->resource + resno; + unsigned int type_mask; + int i, ret = -EBUSY; + + type_mask = IORESOURCE_IO | IORESOURCE_MEM | IORESOURCE_PREFETCH; + + for (i = 0; i < PCI_BUS_NUM_RESOURCES; i++) { + struct resource *r = bus->resource[i]; + if (!r) + continue; + + /* type_mask must match */ + if ((res->flags ^ r->flags) & type_mask) + continue; + + ret = request_resource(r, res); + + if (ret == 0) + break; + } + + if (ret) { + dev_err(&dev->dev, "BAR %d: can't allocate %s resource %pR\n", + resno, res->flags & IORESOURCE_IO ? "I/O" : "mem", res); + } else if (resno < PCI_BRIDGE_RESOURCES) { + pci_update_resource(dev, res, resno); + } + + return ret; +} +EXPORT_SYMBOL_GPL(pci_assign_resource_fixed); +#endif + +/* Sort resources by alignment */ +void pdev_sort_resources(struct pci_dev *dev, struct resource_list *head) +{ + int i; + + for (i = 0; i < PCI_NUM_RESOURCES; i++) { + struct resource *r; + struct resource_list *list, *tmp; + resource_size_t r_align; + + r = &dev->resource[i]; + + if (r->flags & IORESOURCE_PCI_FIXED) + continue; + + if (!(r->flags) || r->parent) + continue; + + r_align = resource_alignment(r); + if (!r_align) { + dev_warn(&dev->dev, "BAR %d: bogus alignment " + "%pR flags %#lx\n", + i, r, r->flags); + continue; + } + for (list = head; ; list = list->next) { + resource_size_t align = 0; + struct resource_list *ln = list->next; + + if (ln) + align = resource_alignment(ln->res); + + if (r_align > align) { + tmp = kmalloc(sizeof(*tmp), GFP_KERNEL); + if (!tmp) + panic("pdev_sort_resources(): " + "kmalloc() failed!\n"); + tmp->next = ln; + tmp->res = r; + tmp->dev = dev; + list->next = tmp; + break; + } + } + } +} + +int pci_enable_resources(struct pci_dev *dev, int mask) +{ + u16 cmd, old_cmd; + int i; + struct resource *r; + + pci_read_config_word(dev, PCI_COMMAND, &cmd); + old_cmd = cmd; + + for (i = 0; i < PCI_NUM_RESOURCES; i++) { + if (!(mask & (1 << i))) + continue; + + r = &dev->resource[i]; + + if (!(r->flags & (IORESOURCE_IO | IORESOURCE_MEM))) + continue; + if ((i == PCI_ROM_RESOURCE) && + (!(r->flags & IORESOURCE_ROM_ENABLE))) + continue; + + if (!r->parent) { + dev_err(&dev->dev, "device not available because of " + "BAR %d %pR collisions\n", i, r); + return -EINVAL; + } + + if (r->flags & IORESOURCE_IO) + cmd |= PCI_COMMAND_IO; + if (r->flags & IORESOURCE_MEM) + cmd |= PCI_COMMAND_MEMORY; + } + + if (cmd != old_cmd) { + dev_info(&dev->dev, "enabling device (%04x -> %04x)\n", + old_cmd, cmd); + pci_write_config_word(dev, PCI_COMMAND, cmd); + } + return 0; +} diff --git a/drivers/pci/slot.c b/drivers/pci/slot.c new file mode 100644 index 0000000..5a8ccb4 --- /dev/null +++ b/drivers/pci/slot.c @@ -0,0 +1,326 @@ +/* + * drivers/pci/slot.c + * Copyright (C) 2006 Matthew Wilcox <matthew@wil.cx> + * Copyright (C) 2006-2008 Hewlett-Packard Development Company, L.P. + * Alex Chiang <achiang@hp.com> + */ + +#include <linux/kobject.h> +#include <linux/pci.h> +#include <linux/err.h> +#include "pci.h" + +struct kset *pci_slots_kset; +EXPORT_SYMBOL_GPL(pci_slots_kset); + +static ssize_t pci_slot_attr_show(struct kobject *kobj, + struct attribute *attr, char *buf) +{ + struct pci_slot *slot = to_pci_slot(kobj); + struct pci_slot_attribute *attribute = to_pci_slot_attr(attr); + return attribute->show ? attribute->show(slot, buf) : -EIO; +} + +static ssize_t pci_slot_attr_store(struct kobject *kobj, + struct attribute *attr, const char *buf, size_t len) +{ + struct pci_slot *slot = to_pci_slot(kobj); + struct pci_slot_attribute *attribute = to_pci_slot_attr(attr); + return attribute->store ? attribute->store(slot, buf, len) : -EIO; +} + +static struct sysfs_ops pci_slot_sysfs_ops = { + .show = pci_slot_attr_show, + .store = pci_slot_attr_store, +}; + +static ssize_t address_read_file(struct pci_slot *slot, char *buf) +{ + if (slot->number == 0xff) + return sprintf(buf, "%04x:%02x\n", + pci_domain_nr(slot->bus), + slot->bus->number); + else + return sprintf(buf, "%04x:%02x:%02x\n", + pci_domain_nr(slot->bus), + slot->bus->number, + slot->number); +} + +static void pci_slot_release(struct kobject *kobj) +{ + struct pci_dev *dev; + struct pci_slot *slot = to_pci_slot(kobj); + + pr_debug("%s: releasing pci_slot on %x:%d\n", __func__, + slot->bus->number, slot->number); + + list_for_each_entry(dev, &slot->bus->devices, bus_list) + if (PCI_SLOT(dev->devfn) == slot->number) + dev->slot = NULL; + + list_del(&slot->list); + + kfree(slot); +} + +static struct pci_slot_attribute pci_slot_attr_address = + __ATTR(address, (S_IFREG | S_IRUGO), address_read_file, NULL); + +static struct attribute *pci_slot_default_attrs[] = { + &pci_slot_attr_address.attr, + NULL, +}; + +static struct kobj_type pci_slot_ktype = { + .sysfs_ops = &pci_slot_sysfs_ops, + .release = &pci_slot_release, + .default_attrs = pci_slot_default_attrs, +}; + +static char *make_slot_name(const char *name) +{ + char *new_name; + int len, max, dup; + + new_name = kstrdup(name, GFP_KERNEL); + if (!new_name) + return NULL; + + /* + * Make sure we hit the realloc case the first time through the + * loop. 'len' will be strlen(name) + 3 at that point which is + * enough space for "name-X" and the trailing NUL. + */ + len = strlen(name) + 2; + max = 1; + dup = 1; + + for (;;) { + struct kobject *dup_slot; + dup_slot = kset_find_obj(pci_slots_kset, new_name); + if (!dup_slot) + break; + kobject_put(dup_slot); + if (dup == max) { + len++; + max *= 10; + kfree(new_name); + new_name = kmalloc(len, GFP_KERNEL); + if (!new_name) + break; + } + sprintf(new_name, "%s-%d", name, dup++); + } + + return new_name; +} + +static int rename_slot(struct pci_slot *slot, const char *name) +{ + int result = 0; + char *slot_name; + + if (strcmp(pci_slot_name(slot), name) == 0) + return result; + + slot_name = make_slot_name(name); + if (!slot_name) + return -ENOMEM; + + result = kobject_rename(&slot->kobj, slot_name); + kfree(slot_name); + + return result; +} + +static struct pci_slot *get_slot(struct pci_bus *parent, int slot_nr) +{ + struct pci_slot *slot; + /* + * We already hold pci_bus_sem so don't worry + */ + list_for_each_entry(slot, &parent->slots, list) + if (slot->number == slot_nr) { + kobject_get(&slot->kobj); + return slot; + } + + return NULL; +} + +/** + * pci_create_slot - create or increment refcount for physical PCI slot + * @parent: struct pci_bus of parent bridge + * @slot_nr: PCI_SLOT(pci_dev->devfn) or -1 for placeholder + * @name: user visible string presented in /sys/bus/pci/slots/<name> + * @hotplug: set if caller is hotplug driver, NULL otherwise + * + * PCI slots have first class attributes such as address, speed, width, + * and a &struct pci_slot is used to manage them. This interface will + * either return a new &struct pci_slot to the caller, or if the pci_slot + * already exists, its refcount will be incremented. + * + * Slots are uniquely identified by a @pci_bus, @slot_nr tuple. + * + * There are known platforms with broken firmware that assign the same + * name to multiple slots. Workaround these broken platforms by renaming + * the slots on behalf of the caller. If firmware assigns name N to + * multiple slots: + * + * The first slot is assigned N + * The second slot is assigned N-1 + * The third slot is assigned N-2 + * etc. + * + * Placeholder slots: + * In most cases, @pci_bus, @slot_nr will be sufficient to uniquely identify + * a slot. There is one notable exception - pSeries (rpaphp), where the + * @slot_nr cannot be determined until a device is actually inserted into + * the slot. In this scenario, the caller may pass -1 for @slot_nr. + * + * The following semantics are imposed when the caller passes @slot_nr == + * -1. First, we no longer check for an existing %struct pci_slot, as there + * may be many slots with @slot_nr of -1. The other change in semantics is + * user-visible, which is the 'address' parameter presented in sysfs will + * consist solely of a dddd:bb tuple, where dddd is the PCI domain of the + * %struct pci_bus and bb is the bus number. In other words, the devfn of + * the 'placeholder' slot will not be displayed. + */ +struct pci_slot *pci_create_slot(struct pci_bus *parent, int slot_nr, + const char *name, + struct hotplug_slot *hotplug) +{ + struct pci_dev *dev; + struct pci_slot *slot; + int err = 0; + char *slot_name = NULL; + + down_write(&pci_bus_sem); + + if (slot_nr == -1) + goto placeholder; + + /* + * Hotplug drivers are allowed to rename an existing slot, + * but only if not already claimed. + */ + slot = get_slot(parent, slot_nr); + if (slot) { + if (hotplug) { + if ((err = slot->hotplug ? -EBUSY : 0) + || (err = rename_slot(slot, name))) { + kobject_put(&slot->kobj); + slot = NULL; + goto err; + } + } + goto out; + } + +placeholder: + slot = kzalloc(sizeof(*slot), GFP_KERNEL); + if (!slot) { + err = -ENOMEM; + goto err; + } + + slot->bus = parent; + slot->number = slot_nr; + + slot->kobj.kset = pci_slots_kset; + + slot_name = make_slot_name(name); + if (!slot_name) { + err = -ENOMEM; + goto err; + } + + err = kobject_init_and_add(&slot->kobj, &pci_slot_ktype, NULL, + "%s", slot_name); + if (err) + goto err; + + INIT_LIST_HEAD(&slot->list); + list_add(&slot->list, &parent->slots); + + list_for_each_entry(dev, &parent->devices, bus_list) + if (PCI_SLOT(dev->devfn) == slot_nr) + dev->slot = slot; + + /* Don't care if debug printk has a -1 for slot_nr */ + pr_debug("%s: created pci_slot on %04x:%02x:%02x\n", + __func__, pci_domain_nr(parent), parent->number, slot_nr); + +out: + kfree(slot_name); + up_write(&pci_bus_sem); + return slot; +err: + kfree(slot); + slot = ERR_PTR(err); + goto out; +} +EXPORT_SYMBOL_GPL(pci_create_slot); + +/** + * pci_renumber_slot - update %struct pci_slot -> number + * @slot - %struct pci_slot to update + * @slot_nr - new number for slot + * + * The primary purpose of this interface is to allow callers who earlier + * created a placeholder slot in pci_create_slot() by passing a -1 as + * slot_nr, to update their %struct pci_slot with the correct @slot_nr. + */ +void pci_renumber_slot(struct pci_slot *slot, int slot_nr) +{ + struct pci_slot *tmp; + + down_write(&pci_bus_sem); + + list_for_each_entry(tmp, &slot->bus->slots, list) { + WARN_ON(tmp->number == slot_nr); + goto out; + } + + slot->number = slot_nr; +out: + up_write(&pci_bus_sem); +} +EXPORT_SYMBOL_GPL(pci_renumber_slot); + +/** + * pci_destroy_slot - decrement refcount for physical PCI slot + * @slot: struct pci_slot to decrement + * + * %struct pci_slot is refcounted, so destroying them is really easy; we + * just call kobject_put on its kobj and let our release methods do the + * rest. + */ +void pci_destroy_slot(struct pci_slot *slot) +{ + pr_debug("%s: dec refcount to %d on %04x:%02x:%02x\n", __func__, + atomic_read(&slot->kobj.kref.refcount) - 1, + pci_domain_nr(slot->bus), slot->bus->number, slot->number); + + down_write(&pci_bus_sem); + kobject_put(&slot->kobj); + up_write(&pci_bus_sem); +} +EXPORT_SYMBOL_GPL(pci_destroy_slot); + +static int pci_slot_init(void) +{ + struct kset *pci_bus_kset; + + pci_bus_kset = bus_get_kset(&pci_bus_type); + pci_slots_kset = kset_create_and_add("slots", NULL, + &pci_bus_kset->kobj); + if (!pci_slots_kset) { + printk(KERN_ERR "PCI: Slot initialization failure\n"); + return -ENOMEM; + } + return 0; +} + +subsys_initcall(pci_slot_init); diff --git a/drivers/pci/syscall.c b/drivers/pci/syscall.c new file mode 100644 index 0000000..ec22284 --- /dev/null +++ b/drivers/pci/syscall.c @@ -0,0 +1,137 @@ +/* + * pci_syscall.c + * + * For architectures where we want to allow direct access + * to the PCI config stuff - it would probably be preferable + * on PCs too, but there people just do it by hand with the + * magic northbridge registers.. + */ + +#include <linux/errno.h> +#include <linux/pci.h> +#include <linux/smp_lock.h> +#include <linux/syscalls.h> +#include <asm/uaccess.h> +#include "pci.h" + +SYSCALL_DEFINE5(pciconfig_read, unsigned long, bus, unsigned long, dfn, + unsigned long, off, unsigned long, len, void __user *, buf) +{ + struct pci_dev *dev; + u8 byte; + u16 word; + u32 dword; + long err; + long cfg_ret; + + if (!capable(CAP_SYS_ADMIN)) + return -EPERM; + + err = -ENODEV; + dev = pci_get_bus_and_slot(bus, dfn); + if (!dev) + goto error; + + switch (len) { + case 1: + cfg_ret = pci_user_read_config_byte(dev, off, &byte); + break; + case 2: + cfg_ret = pci_user_read_config_word(dev, off, &word); + break; + case 4: + cfg_ret = pci_user_read_config_dword(dev, off, &dword); + break; + default: + err = -EINVAL; + goto error; + }; + + err = -EIO; + if (cfg_ret != PCIBIOS_SUCCESSFUL) + goto error; + + switch (len) { + case 1: + err = put_user(byte, (unsigned char __user *)buf); + break; + case 2: + err = put_user(word, (unsigned short __user *)buf); + break; + case 4: + err = put_user(dword, (unsigned int __user *)buf); + break; + } + pci_dev_put(dev); + return err; + +error: + /* ??? XFree86 doesn't even check the return value. They + just look for 0xffffffff in the output, since that's what + they get instead of a machine check on x86. */ + switch (len) { + case 1: + put_user(-1, (unsigned char __user *)buf); + break; + case 2: + put_user(-1, (unsigned short __user *)buf); + break; + case 4: + put_user(-1, (unsigned int __user *)buf); + break; + } + pci_dev_put(dev); + return err; +} + +SYSCALL_DEFINE5(pciconfig_write, unsigned long, bus, unsigned long, dfn, + unsigned long, off, unsigned long, len, void __user *, buf) +{ + struct pci_dev *dev; + u8 byte; + u16 word; + u32 dword; + int err = 0; + + if (!capable(CAP_SYS_ADMIN)) + return -EPERM; + + dev = pci_get_bus_and_slot(bus, dfn); + if (!dev) + return -ENODEV; + + switch(len) { + case 1: + err = get_user(byte, (u8 __user *)buf); + if (err) + break; + err = pci_user_write_config_byte(dev, off, byte); + if (err != PCIBIOS_SUCCESSFUL) + err = -EIO; + break; + + case 2: + err = get_user(word, (u16 __user *)buf); + if (err) + break; + err = pci_user_write_config_word(dev, off, word); + if (err != PCIBIOS_SUCCESSFUL) + err = -EIO; + break; + + case 4: + err = get_user(dword, (u32 __user *)buf); + if (err) + break; + err = pci_user_write_config_dword(dev, off, dword); + if (err != PCIBIOS_SUCCESSFUL) + err = -EIO; + break; + + default: + err = -EINVAL; + break; + } + pci_dev_put(dev); + return err; +} |