diff options
author | Linus Torvalds <torvalds@ppc970.osdl.org> | 2005-04-16 15:20:36 -0700 |
---|---|---|
committer | Linus Torvalds <torvalds@ppc970.osdl.org> | 2005-04-16 15:20:36 -0700 |
commit | 1da177e4c3f41524e886b7f1b8a0c1fc7321cac2 (patch) | |
tree | 0bba044c4ce775e45a88a51686b5d9f90697ea9d /drivers/pci/hotplug | |
download | linux-1da177e4c3f41524e886b7f1b8a0c1fc7321cac2.tar.bz2 |
Linux-2.6.12-rc2v2.6.12-rc2
Initial git repository build. I'm not bothering with the full history,
even though we have it. We can create a separate "historical" git
archive of that later if we want to, and in the meantime it's about
3.2GB when imported into git - space that would just make the early
git days unnecessarily complicated, when we don't have a lot of good
infrastructure for it.
Let it rip!
Diffstat (limited to 'drivers/pci/hotplug')
60 files changed, 44057 insertions, 0 deletions
diff --git a/drivers/pci/hotplug/Kconfig b/drivers/pci/hotplug/Kconfig new file mode 100644 index 000000000000..1a4d4ca2a4dc --- /dev/null +++ b/drivers/pci/hotplug/Kconfig @@ -0,0 +1,197 @@ +# +# PCI Hotplug support +# + +menu "PCI Hotplug Support" + +config HOTPLUG_PCI + tristate "Support for PCI Hotplug (EXPERIMENTAL)" + depends on PCI && EXPERIMENTAL + select 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. The file system pcihpfs must be mounted + in order to interact with any PCI Hotplug controllers. + + To compile this driver as a module, choose M here: the + module will be called pci_hotplug. + + When in doubt, say N. + +config HOTPLUG_PCI_FAKE + tristate "Fake PCI Hotplug driver" + depends on HOTPLUG_PCI + 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 HOTPLUG_PCI && X86 && PCI_BIOS + 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 HOTPLUG_PCI && 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_BUS && HOTPLUG_PCI + 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" + depends on HOTPLUG_PCI + 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 && 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 && 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" + depends on HOTPLUG_PCI + 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_SHPC_POLL_EVENT_MODE + bool "Use polling mechanism for hot-plug events (for testing purpose)" + depends on HOTPLUG_PCI_SHPC + help + Say Y here if you want to use the polling mechanism for hot-plug + events for early platform testing. + + When in doubt, say N. + +config HOTPLUG_PCI_SHPC_PHPRM_LEGACY + bool "For AMD SHPC only: Use $HRT for resource/configuration" + depends on HOTPLUG_PCI_SHPC && !ACPI_BUS + help + Say Y here for AMD SHPC. You have to select this option if you are + using this driver on platform with AMD SHPC. + +config HOTPLUG_PCI_RPA + tristate "RPA PCI Hotplug driver" + depends on HOTPLUG_PCI && PPC_PSERIES && PPC64 && !HOTPLUG_PCI_FAKE + help + Say Y here if you have a 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 HOTPLUG_PCI && IA64_SGI_SN2 + help + Say Y here if you have an SGI IA64 Altix system. + + When in doubt, say N. + +endmenu + diff --git a/drivers/pci/hotplug/Makefile b/drivers/pci/hotplug/Makefile new file mode 100644 index 000000000000..93c120ddbd39 --- /dev/null +++ b/drivers/pci/hotplug/Makefile @@ -0,0 +1,74 @@ +# +# Makefile for the Linux kernel pci hotplug controller drivers. +# + +obj-$(CONFIG_HOTPLUG_PCI) += pci_hotplug.o +obj-$(CONFIG_HOTPLUG_PCI_FAKE) += fakephp.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 + +pci_hotplug-objs := pci_hotplug_core.o + +ifdef CONFIG_HOTPLUG_PCI_CPCI +pci_hotplug-objs += cpci_hotplug_core.o \ + cpci_hotplug_pci.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 \ + acpiphp_pci.o \ + acpiphp_res.o + +rpaphp-objs := rpaphp_core.o \ + rpaphp_pci.o \ + rpaphp_slot.o \ + rpaphp_vio.o + +rpadlpar_io-objs := rpadlpar_core.o \ + rpadlpar_sysfs.o + +pciehp-objs := pciehp_core.o \ + pciehp_ctrl.o \ + pciehp_pci.o \ + pciehp_hpc.o +ifdef CONFIG_ACPI_BUS + pciehp-objs += pciehprm_acpi.o +else + pciehp-objs += pciehprm_nonacpi.o +endif + +shpchp-objs := shpchp_core.o \ + shpchp_ctrl.o \ + shpchp_pci.o \ + shpchp_sysfs.o \ + shpchp_hpc.o +ifdef CONFIG_ACPI_BUS + shpchp-objs += shpchprm_acpi.o +else + ifdef CONFIG_HOTPLUG_PCI_SHPC_PHPRM_LEGACY + shpchp-objs += shpchprm_legacy.o + else + shpchp-objs += shpchprm_nonacpi.o + endif +endif diff --git a/drivers/pci/hotplug/acpiphp.h b/drivers/pci/hotplug/acpiphp.h new file mode 100644 index 000000000000..d9499874c8a9 --- /dev/null +++ b/drivers/pci/hotplug/acpiphp.h @@ -0,0 +1,268 @@ +/* + * 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 + * + * 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> /* for KOBJ_NAME_LEN */ +#include "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) + +/* name size which is used for entries in pcihpfs */ +#define SLOT_NAME_SIZE KOBJ_NAME_LEN /* {_SUN} */ + +struct acpiphp_bridge; +struct acpiphp_slot; +struct pci_resource; + +/* + * struct slot - slot information for each *physical* slot + */ +struct slot { + u8 number; + struct hotplug_slot *hotplug_slot; + struct list_head slot_list; + + struct acpiphp_slot *acpi_slot; +}; + +/* + * struct pci_resource - describes pci resource (mem, pfmem, io, bus) + */ +struct pci_resource { + struct pci_resource * next; + u64 base; + u32 length; +}; + +/** + * struct hpp_param - ACPI 2.0 _HPP Hot Plug Parameters + * @cache_line_size in DWORD + * @latency_timer in PCI clock + * @enable_SERR 0 or 1 + * @enable_PERR 0 or 1 + */ +struct hpp_param { + u8 cache_line_size; + u8 latency_timer; + u8 enable_SERR; + u8 enable_PERR; +}; + + +/** + * 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; + int type; + int nr_slots; + + u8 seg; + u8 bus; + u8 sub; + + 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 hpp_param hpp; + + spinlock_t res_lock; + + /* available resources on this bus */ + struct pci_resource *mem_head; + struct pci_resource *p_mem_head; + struct pci_resource *io_head; + struct pci_resource *bus_head; +}; + + +/** + * 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 semaphore crit_sect; + + u32 id; /* slot id (serial #) for hotplug core */ + u8 device; /* pci device# */ + + u32 sun; /* ACPI _SUN (slot unique number) */ + u32 slotno; /* slot number relative to bridge */ + 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 list_head sibling; + struct pci_dev *pci_dev; + + acpi_handle handle; + + u8 function; /* pci function# */ + u32 flags; /* see below */ + + /* resources used for this function */ + struct pci_resource *mem_head; + struct pci_resource *p_mem_head; + struct pci_resource *io_head; + struct pci_resource *bus_head; +}; + +/** + * 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; +}; + +/* 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) + +/* 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); + +/* acpiphp_glue.c */ +extern int acpiphp_glue_init (void); +extern void acpiphp_glue_exit (void); +extern int acpiphp_get_num_slots (void); +extern struct acpiphp_slot *get_slot_from_id (int id); +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 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); +extern u32 acpiphp_get_address (struct acpiphp_slot *slot); + +/* acpiphp_pci.c */ +extern struct pci_dev *acpiphp_allocate_pcidev (struct pci_bus *pbus, int dev, int fn); +extern int acpiphp_configure_slot (struct acpiphp_slot *slot); +extern int acpiphp_configure_function (struct acpiphp_func *func); +extern void acpiphp_unconfigure_function (struct acpiphp_func *func); +extern int acpiphp_detect_pci_resource (struct acpiphp_bridge *bridge); +extern int acpiphp_init_func_resource (struct acpiphp_func *func); + +/* acpiphp_res.c */ +extern struct pci_resource *acpiphp_get_io_resource (struct pci_resource **head, u32 size); +extern struct pci_resource *acpiphp_get_resource (struct pci_resource **head, u32 size); +extern struct pci_resource *acpiphp_get_resource_with_base (struct pci_resource **head, u64 base, u32 size); +extern int acpiphp_resource_sort_and_combine (struct pci_resource **head); +extern struct pci_resource *acpiphp_make_resource (u64 base, u32 length); +extern void acpiphp_move_resource (struct pci_resource **from, struct pci_resource **to); +extern void acpiphp_free_resource (struct pci_resource **res); +extern void acpiphp_dump_resource (struct acpiphp_bridge *bridge); /* debug */ +extern void acpiphp_dump_func_resource (struct acpiphp_func *func); /* debug */ + +/* 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 000000000000..4539e61a3dc1 --- /dev/null +++ b/drivers/pci/hotplug/acpiphp_core.c @@ -0,0 +1,453 @@ +/* + * 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 + * + * 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> + * + */ + +#include <linux/init.h> +#include <linux/module.h> +#include <linux/moduleparam.h> + +#include <linux/kernel.h> +#include <linux/pci.h> +#include <linux/slab.h> +#include <linux/smp.h> +#include <linux/smp_lock.h> +#include "pci_hotplug.h" +#include "acpiphp.h" + +static LIST_HEAD(slot_list); + +#define MY_NAME "acpiphp" + +static int debug; +int acpiphp_debug; + +/* local variables */ +static int num_slots; +static struct acpiphp_attention_info *attention_info; + +#define DRIVER_VERSION "0.4" +#define DRIVER_AUTHOR "Greg Kroah-Hartman <gregkh@us.ibm.com>, Takayoshi Kochi <t-kochi@bq.jp.nec.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_address (struct hotplug_slot *slot, u32 *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, + .get_address = get_address, +}; + + +/** + * 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", __FUNCTION__, hotplug_slot->name); + + /* 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; + + dbg("%s - physical_slot = %s\n", __FUNCTION__, hotplug_slot->name); + + /* disable the specified slot */ + return acpiphp_disable_slot(slot->acpi_slot); +} + + + /** + * 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", __FUNCTION__, hotplug_slot->name); + + 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", __FUNCTION__, hotplug_slot->name); + + *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", __FUNCTION__, hotplug_slot->name); + + 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", __FUNCTION__, hotplug_slot->name); + + *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", __FUNCTION__, hotplug_slot->name); + + *value = acpiphp_get_adapter_status(slot->acpi_slot); + + return 0; +} + + +/** + * get_address - get pci address of a slot + * @hotplug_slot: slot to get status + * @busdev: pointer to struct pci_busdev (seg, bus, dev) + * + */ +static int get_address(struct hotplug_slot *hotplug_slot, u32 *value) +{ + struct slot *slot = hotplug_slot->private; + + dbg("%s - physical_slot = %s\n", __FUNCTION__, hotplug_slot->name); + + *value = acpiphp_get_address(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) + retval = -ENODEV; + } + + return retval; +} + + +/** + * make_slot_name - make a slot name that appears in pcihpfs + * @slot: slot to name + * + */ +static void make_slot_name(struct slot *slot) +{ + snprintf(slot->hotplug_slot->name, SLOT_NAME_SIZE, "%u", + slot->acpi_slot->sun); +} + +/** + * 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", __FUNCTION__, hotplug_slot->name); + + kfree(slot->hotplug_slot->info); + kfree(slot->hotplug_slot->name); + kfree(slot->hotplug_slot); + kfree(slot); +} + +/** + * init_slots - initialize 'struct slot' structures for each slot + * + */ +static int __init init_slots(void) +{ + struct slot *slot; + int retval = -ENOMEM; + int i; + + for (i = 0; i < num_slots; ++i) { + slot = kmalloc(sizeof(struct slot), GFP_KERNEL); + if (!slot) + goto error; + memset(slot, 0, sizeof(struct slot)); + + slot->hotplug_slot = kmalloc(sizeof(struct hotplug_slot), GFP_KERNEL); + if (!slot->hotplug_slot) + goto error_slot; + memset(slot->hotplug_slot, 0, sizeof(struct hotplug_slot)); + + slot->hotplug_slot->info = kmalloc(sizeof(struct hotplug_slot_info), GFP_KERNEL); + if (!slot->hotplug_slot->info) + goto error_hpslot; + memset(slot->hotplug_slot->info, 0, sizeof(struct hotplug_slot_info)); + + slot->hotplug_slot->name = kmalloc(SLOT_NAME_SIZE, GFP_KERNEL); + if (!slot->hotplug_slot->name) + goto error_info; + + slot->number = i; + + slot->hotplug_slot->private = slot; + slot->hotplug_slot->release = &release_slot; + slot->hotplug_slot->ops = &acpi_hotplug_slot_ops; + + slot->acpi_slot = get_slot_from_id(i); + 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; + + make_slot_name(slot); + + retval = pci_hp_register(slot->hotplug_slot); + if (retval) { + err("pci_hp_register failed with error %d\n", retval); + goto error_name; + } + + /* add slot to our internal list */ + list_add(&slot->slot_list, &slot_list); + info("Slot [%s] registered\n", slot->hotplug_slot->name); + } + + return 0; +error_name: + kfree(slot->hotplug_slot->name); +error_info: + kfree(slot->hotplug_slot->info); +error_hpslot: + kfree(slot->hotplug_slot); +error_slot: + kfree(slot); +error: + return retval; +} + + +static void __exit cleanup_slots (void) +{ + struct list_head *tmp, *n; + struct slot *slot; + + list_for_each_safe (tmp, n, &slot_list) { + /* memory will be freed in release_slot callback */ + slot = list_entry(tmp, struct slot, slot_list); + list_del(&slot->slot_list); + pci_hp_deregister(slot->hotplug_slot); + } +} + + +static int __init acpiphp_init(void) +{ + int retval; + + info(DRIVER_DESC " version: " DRIVER_VERSION "\n"); + + acpiphp_debug = debug; + + /* read all the ACPI info from the system */ + retval = init_acpi(); + if (retval) + return retval; + + return init_slots(); +} + + +static void __exit acpiphp_exit(void) +{ + cleanup_slots(); + /* 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 000000000000..e7f41294f811 --- /dev/null +++ b/drivers/pci/hotplug/acpiphp_glue.c @@ -0,0 +1,1344 @@ +/* + * 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 + * + * 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 <t-kochi@bq.jp.nec.com> + * + */ + +#include <linux/init.h> +#include <linux/module.h> + +#include <linux/kernel.h> +#include <linux/pci.h> +#include <linux/smp_lock.h> +#include <asm/semaphore.h> + +#include "../pci.h" +#include "pci_hotplug.h" +#include "acpiphp.h" + +static LIST_HEAD(bridge_list); + +#define MY_NAME "acpiphp_glue" + +static void handle_hotplug_event_bridge (acpi_handle, u32, void *); +static void handle_hotplug_event_func (acpi_handle, u32, void *); + +/* + * 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 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 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 adr, sun; + int device, function; + static int num_slots = 0; /* XXX if we support I/O node hotplug... */ + + 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)) + return AE_OK; + + device = (adr >> 16) & 0xffff; + function = adr & 0xffff; + + newfunc = kmalloc(sizeof(struct acpiphp_func), GFP_KERNEL); + if (!newfunc) + return AE_NO_MEMORY; + memset(newfunc, 0, sizeof(struct acpiphp_func)); + + INIT_LIST_HEAD(&newfunc->sibling); + newfunc->handle = handle; + newfunc->function = function; + 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; + + status = acpi_evaluate_integer(handle, "_SUN", NULL, &sun); + if (ACPI_FAILURE(status)) + sun = -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 = kmalloc(sizeof(struct acpiphp_slot), GFP_KERNEL); + if (!slot) { + kfree(newfunc); + return AE_NO_MEMORY; + } + + memset(slot, 0, sizeof(struct acpiphp_slot)); + slot->bridge = bridge; + slot->id = num_slots++; + slot->device = device; + slot->sun = sun; + INIT_LIST_HEAD(&slot->funcs); + init_MUTEX(&slot->crit_sect); + + slot->next = bridge->slots; + bridge->slots = slot; + + bridge->nr_slots++; + + dbg("found ACPI PCI Hotplug slot at PCI %02x:%02x Slot:%d\n", + slot->bridge->bus, slot->device, slot->sun); + } + + newfunc->slot = slot; + list_add_tail(&newfunc->sibling, &slot->funcs); + + /* associate corresponding pci_dev */ + newfunc->pci_dev = pci_find_slot(bridge->bus, + PCI_DEVFN(device, function)); + if (newfunc->pci_dev) { + if (acpiphp_init_func_resource(newfunc) < 0) { + kfree(newfunc); + return AE_ERROR; + } + slot->flags |= (SLOT_ENABLED | SLOT_POWEREDON); + } + + /* install notify handler */ + 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"); + return status; + } + + 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); + + return count; +} + + +/* decode ACPI _CRS data and convert into our internal resource list + * TBD: _TRA, etc. + */ +static acpi_status +decode_acpi_resource(struct acpi_resource *resource, void *context) +{ + struct acpiphp_bridge *bridge = (struct acpiphp_bridge *) context; + struct acpi_resource_address64 address; + struct pci_resource *res; + + if (resource->id != ACPI_RSTYPE_ADDRESS16 && + resource->id != ACPI_RSTYPE_ADDRESS32 && + resource->id != ACPI_RSTYPE_ADDRESS64) + return AE_OK; + + acpi_resource_to_address64(resource, &address); + + if (address.producer_consumer == ACPI_PRODUCER && address.address_length > 0) { + dbg("resource type: %d: 0x%llx - 0x%llx\n", address.resource_type, + (unsigned long long)address.min_address_range, + (unsigned long long)address.max_address_range); + res = acpiphp_make_resource(address.min_address_range, + address.address_length); + if (!res) { + err("out of memory\n"); + return AE_OK; + } + + switch (address.resource_type) { + case ACPI_MEMORY_RANGE: + if (address.attribute.memory.cache_attribute == ACPI_PREFETCHABLE_MEMORY) { + res->next = bridge->p_mem_head; + bridge->p_mem_head = res; + } else { + res->next = bridge->mem_head; + bridge->mem_head = res; + } + break; + case ACPI_IO_RANGE: + res->next = bridge->io_head; + bridge->io_head = res; + break; + case ACPI_BUS_NUMBER_RANGE: + res->next = bridge->bus_head; + bridge->bus_head = res; + break; + default: + /* invalid type */ + kfree(res); + break; + } + } + + return AE_OK; +} + +/* decode ACPI 2.0 _HPP hot plug parameters */ +static void decode_hpp(struct acpiphp_bridge *bridge) +{ + acpi_status status; + struct acpi_buffer buffer = { .length = ACPI_ALLOCATE_BUFFER, + .pointer = NULL}; + union acpi_object *package; + int i; + + /* default numbers */ + bridge->hpp.cache_line_size = 0x10; + bridge->hpp.latency_timer = 0x40; + bridge->hpp.enable_SERR = 0; + bridge->hpp.enable_PERR = 0; + + status = acpi_evaluate_object(bridge->handle, "_HPP", NULL, &buffer); + + if (ACPI_FAILURE(status)) { + dbg("_HPP evaluation failed\n"); + return; + } + + package = (union acpi_object *) buffer.pointer; + + if (!package || package->type != ACPI_TYPE_PACKAGE || + package->package.count != 4 || !package->package.elements) { + err("invalid _HPP object; ignoring\n"); + goto err_exit; + } + + for (i = 0; i < 4; i++) { + if (package->package.elements[i].type != ACPI_TYPE_INTEGER) { + err("invalid _HPP parameter type; ignoring\n"); + goto err_exit; + } + } + + bridge->hpp.cache_line_size = package->package.elements[0].integer.value; + bridge->hpp.latency_timer = package->package.elements[1].integer.value; + bridge->hpp.enable_SERR = package->package.elements[2].integer.value; + bridge->hpp.enable_PERR = package->package.elements[3].integer.value; + + dbg("_HPP parameter = (%02x, %02x, %02x, %02x)\n", + bridge->hpp.cache_line_size, + bridge->hpp.latency_timer, + bridge->hpp.enable_SERR, + bridge->hpp.enable_PERR); + + bridge->flags |= BRIDGE_HAS_HPP; + + err_exit: + kfree(buffer.pointer); +} + + +/* 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); + + /* subtract all resources already allocated */ + acpiphp_detect_pci_resource(bridge); + + /* register all slot objects under this bridge */ + status = acpi_walk_namespace(ACPI_TYPE_DEVICE, bridge->handle, (u32)1, + register_slot, bridge, NULL); + + /* install notify handler */ + 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"); + } + + list_add(&bridge->list, &bridge_list); + + dbg("Bridge resource:\n"); + acpiphp_dump_resource(bridge); +} + + +/* allocate and initialize host bridge data structure */ +static void add_host_bridge(acpi_handle *handle, int seg, int bus) +{ + acpi_status status; + struct acpiphp_bridge *bridge; + + bridge = kmalloc(sizeof(struct acpiphp_bridge), GFP_KERNEL); + if (bridge == NULL) + return; + + memset(bridge, 0, sizeof(struct acpiphp_bridge)); + + bridge->type = BRIDGE_TYPE_HOST; + bridge->handle = handle; + bridge->seg = seg; + bridge->bus = bus; + + bridge->pci_bus = pci_find_bus(seg, bus); + + spin_lock_init(&bridge->res_lock); + + /* to be overridden when we decode _CRS */ + bridge->sub = bridge->bus; + + /* decode resources */ + + status = acpi_walk_resources(handle, METHOD_NAME__CRS, + decode_acpi_resource, bridge); + + if (ACPI_FAILURE(status)) { + err("failed to decode bridge resources\n"); + kfree(bridge); + return; + } + + acpiphp_resource_sort_and_combine(&bridge->io_head); + acpiphp_resource_sort_and_combine(&bridge->mem_head); + acpiphp_resource_sort_and_combine(&bridge->p_mem_head); + acpiphp_resource_sort_and_combine(&bridge->bus_head); + + dbg("ACPI _CRS resource:\n"); + acpiphp_dump_resource(bridge); + + if (bridge->bus_head) { + bridge->bus = bridge->bus_head->base; + bridge->sub = bridge->bus_head->base + bridge->bus_head->length - 1; + } + + init_bridge_misc(bridge); +} + + +/* allocate and initialize PCI-to-PCI bridge data structure */ +static void add_p2p_bridge(acpi_handle *handle, int seg, int bus, int dev, int fn) +{ + struct acpiphp_bridge *bridge; + u8 tmp8; + u16 tmp16; + u64 base64, limit64; + u32 base, limit, base32u, limit32u; + + bridge = kmalloc(sizeof(struct acpiphp_bridge), GFP_KERNEL); + if (bridge == NULL) { + err("out of memory\n"); + return; + } + + memset(bridge, 0, sizeof(struct acpiphp_bridge)); + + bridge->type = BRIDGE_TYPE_P2P; + bridge->handle = handle; + bridge->seg = seg; + + bridge->pci_dev = pci_find_slot(bus, PCI_DEVFN(dev, fn)); + if (!bridge->pci_dev) { + err("Can't get pci_dev\n"); + kfree(bridge); + return; + } + + bridge->pci_bus = bridge->pci_dev->subordinate; + if (!bridge->pci_bus) { + err("This is not a PCI-to-PCI bridge!\n"); + kfree(bridge); + return; + } + + spin_lock_init(&bridge->res_lock); + + bridge->bus = bridge->pci_bus->number; + bridge->sub = bridge->pci_bus->subordinate; + + /* + * decode resources under this P2P bridge + */ + + /* I/O resources */ + pci_read_config_byte(bridge->pci_dev, PCI_IO_BASE, &tmp8); + base = tmp8; + pci_read_config_byte(bridge->pci_dev, PCI_IO_LIMIT, &tmp8); + limit = tmp8; + + switch (base & PCI_IO_RANGE_TYPE_MASK) { + case PCI_IO_RANGE_TYPE_16: + base = (base << 8) & 0xf000; + limit = ((limit << 8) & 0xf000) + 0xfff; + bridge->io_head = acpiphp_make_resource((u64)base, limit - base + 1); + if (!bridge->io_head) { + err("out of memory\n"); + kfree(bridge); + return; + } + dbg("16bit I/O range: %04x-%04x\n", + (u32)bridge->io_head->base, + (u32)(bridge->io_head->base + bridge->io_head->length - 1)); + break; + case PCI_IO_RANGE_TYPE_32: + pci_read_config_word(bridge->pci_dev, PCI_IO_BASE_UPPER16, &tmp16); + base = ((u32)tmp16 << 16) | ((base << 8) & 0xf000); + pci_read_config_word(bridge->pci_dev, PCI_IO_LIMIT_UPPER16, &tmp16); + limit = (((u32)tmp16 << 16) | ((limit << 8) & 0xf000)) + 0xfff; + bridge->io_head = acpiphp_make_resource((u64)base, limit - base + 1); + if (!bridge->io_head) { + err("out of memory\n"); + kfree(bridge); + return; + } + dbg("32bit I/O range: %08x-%08x\n", + (u32)bridge->io_head->base, + (u32)(bridge->io_head->base + bridge->io_head->length - 1)); + break; + case 0x0f: + dbg("I/O space unsupported\n"); + break; + default: + warn("Unknown I/O range type\n"); + } + + /* Memory resources (mandatory for P2P bridge) */ + pci_read_config_word(bridge->pci_dev, PCI_MEMORY_BASE, &tmp16); + base = (tmp16 & 0xfff0) << 16; + pci_read_config_word(bridge->pci_dev, PCI_MEMORY_LIMIT, &tmp16); + limit = ((tmp16 & 0xfff0) << 16) | 0xfffff; + bridge->mem_head = acpiphp_make_resource((u64)base, limit - base + 1); + if (!bridge->mem_head) { + err("out of memory\n"); + kfree(bridge); + return; + } + dbg("32bit Memory range: %08x-%08x\n", + (u32)bridge->mem_head->base, + (u32)(bridge->mem_head->base + bridge->mem_head->length-1)); + + /* Prefetchable Memory resources (optional) */ + pci_read_config_word(bridge->pci_dev, PCI_PREF_MEMORY_BASE, &tmp16); + base = tmp16; + pci_read_config_word(bridge->pci_dev, PCI_PREF_MEMORY_LIMIT, &tmp16); + limit = tmp16; + + switch (base & PCI_MEMORY_RANGE_TYPE_MASK) { + case PCI_PREF_RANGE_TYPE_32: + base = (base & 0xfff0) << 16; + limit = ((limit & 0xfff0) << 16) | 0xfffff; + bridge->p_mem_head = acpiphp_make_resource((u64)base, limit - base + 1); + if (!bridge->p_mem_head) { + err("out of memory\n"); + kfree(bridge); + return; + } + dbg("32bit Prefetchable memory range: %08x-%08x\n", + (u32)bridge->p_mem_head->base, + (u32)(bridge->p_mem_head->base + bridge->p_mem_head->length - 1)); + break; + case PCI_PREF_RANGE_TYPE_64: + pci_read_config_dword(bridge->pci_dev, PCI_PREF_BASE_UPPER32, &base32u); + pci_read_config_dword(bridge->pci_dev, PCI_PREF_LIMIT_UPPER32, &limit32u); + base64 = ((u64)base32u << 32) | ((base & 0xfff0) << 16); + limit64 = (((u64)limit32u << 32) | ((limit & 0xfff0) << 16)) + 0xfffff; + + bridge->p_mem_head = acpiphp_make_resource(base64, limit64 - base64 + 1); + if (!bridge->p_mem_head) { + err("out of memory\n"); + kfree(bridge); + return; + } + dbg("64bit Prefetchable memory range: %08x%08x-%08x%08x\n", + (u32)(bridge->p_mem_head->base >> 32), + (u32)(bridge->p_mem_head->base & 0xffffffff), + (u32)((bridge->p_mem_head->base + bridge->p_mem_head->length - 1) >> 32), + (u32)((bridge->p_mem_head->base + bridge->p_mem_head->length - 1) & 0xffffffff)); + break; + case 0x0f: + break; + default: + warn("Unknown prefetchale memory type\n"); + } + + init_bridge_misc(bridge); +} + + +/* 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 *segbus = context; + unsigned long tmp; + int seg, bus, device, function; + struct pci_dev *dev; + + /* get PCI address */ + seg = (*segbus >> 8) & 0xff; + bus = *segbus & 0xff; + + 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", __FUNCTION__); + return AE_OK; + } + + device = (tmp >> 16) & 0xffff; + function = tmp & 0xffff; + + dev = pci_find_slot(bus, PCI_DEVFN(device, function)); + + if (!dev) + return AE_OK; + + if (!dev->subordinate) + return AE_OK; + + /* 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, seg, bus, device, function); + } + + return AE_OK; +} + + +/* find hot-pluggable slots, and then find P2P bridge */ +static int add_bridge(acpi_handle handle) +{ + acpi_status status; + unsigned long tmp; + int seg, bus; + acpi_handle dummy_handle; + + /* 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", __FUNCTION__); + 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; + } + + /* 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, seg, bus); + return 0; + } + + tmp = seg << 8 | bus; + + /* search P2P bridges under this host bridge */ + status = acpi_walk_namespace(ACPI_TYPE_DEVICE, handle, (u32)1, + find_p2p_bridge, &tmp, NULL); + + if (ACPI_FAILURE(status)) + warn("find_p2p_bridge faied (error code = 0x%x)\n",status); + + return 0; +} + + +static void remove_bridge(acpi_handle handle) +{ + /* No-op for now .. */ +} + + +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", __FUNCTION__); + status = acpi_evaluate_object(func->handle, "_PS0", NULL, NULL); + if (ACPI_FAILURE(status)) { + warn("%s: _PS0 failed\n", __FUNCTION__); + 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; + struct acpi_object_list arg_list; + union acpi_object arg; + + 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->pci_dev && (func->flags & FUNC_HAS_PS3)) { + status = acpi_evaluate_object(func->handle, "_PS3", NULL, NULL); + if (ACPI_FAILURE(status)) { + warn("%s: _PS3 failed\n", __FUNCTION__); + retval = -1; + goto err_exit; + } else + break; + } + } + + 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->pci_dev && (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", __FUNCTION__); + 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; +} + + +/** + * 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 enable_device(struct acpiphp_slot *slot) +{ + u8 bus; + struct pci_dev *dev; + struct pci_bus *child; + struct list_head *l; + struct acpiphp_func *func; + int retval = 0; + int num; + + if (slot->flags & SLOT_ENABLED) + goto err_exit; + + /* sanity check: dev should be NULL when hot-plugged in */ + dev = pci_find_slot(slot->bridge->bus, PCI_DEVFN(slot->device, 0)); + if (dev) { + /* This case shouldn't happen */ + err("pci_dev structure already exists.\n"); + retval = -1; + goto err_exit; + } + + /* allocate resources to device */ + retval = acpiphp_configure_slot(slot); + if (retval) + goto err_exit; + + /* returned `dev' is the *first function* only! */ + num = pci_scan_slot(slot->bridge->pci_bus, PCI_DEVFN(slot->device, 0)); + if (num) + pci_bus_add_devices(slot->bridge->pci_bus); + dev = pci_find_slot(slot->bridge->bus, PCI_DEVFN(slot->device, 0)); + + if (!dev) { + err("No new device found\n"); + retval = -1; + goto err_exit; + } + + if (dev->hdr_type == PCI_HEADER_TYPE_BRIDGE) { + pci_read_config_byte(dev, PCI_SECONDARY_BUS, &bus); + child = (struct pci_bus*) pci_add_new_bus(dev->bus, dev, bus); + pci_do_scan_bus(child); + } + + /* associate pci_dev to our representation */ + list_for_each (l, &slot->funcs) { + func = list_entry(l, struct acpiphp_func, sibling); + + func->pci_dev = pci_find_slot(slot->bridge->bus, + PCI_DEVFN(slot->device, + func->function)); + if (!func->pci_dev) + continue; + + /* configure device */ + retval = acpiphp_configure_function(func); + if (retval) + goto err_exit; + } + + slot->flags |= SLOT_ENABLED; + + dbg("Available resources:\n"); + acpiphp_dump_resource(slot->bridge); + + err_exit: + return retval; +} + + +/** + * disable_device - disable a 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->pci_dev) + acpiphp_unconfigure_function(func); + } + + slot->flags &= (~SLOT_ENABLED); + + err_exit: + return retval; +} + + +/** + * get_slot_status - get ACPI slot status + * + * 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 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_check_bridge - re-enumerate devices + * + * 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; + } + 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", __FUNCTION__, enabled, disabled); + + err_exit: + return retval; +} + +/* + * ACPI event handlers + */ + +/** + * 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 }; + + bridge = (struct acpiphp_bridge *)context; + + 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", __FUNCTION__, objname); + acpiphp_check_bridge(bridge); + break; + + case ACPI_NOTIFY_DEVICE_CHECK: + /* device check */ + dbg("%s: Device check notify on %s\n", __FUNCTION__, objname); + acpiphp_check_bridge(bridge); + break; + + case ACPI_NOTIFY_DEVICE_WAKE: + /* wake event */ + dbg("%s: Device wake notify on %s\n", __FUNCTION__, objname); + break; + + case ACPI_NOTIFY_EJECT_REQUEST: + /* request device eject */ + dbg("%s: Device eject notify on %s\n", __FUNCTION__, objname); + 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", __FUNCTION__, 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", __FUNCTION__, objname); + acpiphp_check_bridge(func->slot->bridge); + break; + + case ACPI_NOTIFY_DEVICE_WAKE: + /* wake event */ + dbg("%s: Device wake notify on %s\n", __FUNCTION__, objname); + break; + + case ACPI_NOTIFY_EJECT_REQUEST: + /* request device eject */ + dbg("%s: Device eject notify on %s\n", __FUNCTION__, objname); + acpiphp_disable_slot(func->slot); + break; + + default: + warn("notify_handler: unknown event type 0x%x for %s\n", type, objname); + break; + } +} + + +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; + + if (list_empty(&pci_root_buses)) + return -1; + + num = acpi_pci_register_driver(&acpi_pci_hp_driver); + + if (num <= 0) + return -1; + + return 0; +} + + +/** + * acpiphp_glue_exit - terminates all PCI hotplug - ACPI glue data structures + * + * This function frees all data allocated in acpiphp_glue_init() + */ +void __exit acpiphp_glue_exit(void) +{ + struct list_head *l1, *l2, *n1, *n2; + struct acpiphp_bridge *bridge; + struct acpiphp_slot *slot, *next; + struct acpiphp_func *func; + acpi_status status; + + list_for_each_safe (l1, n1, &bridge_list) { + bridge = (struct acpiphp_bridge *)l1; + slot = bridge->slots; + while (slot) { + next = slot->next; + list_for_each_safe (l2, n2, &slot->funcs) { + func = list_entry(l2, struct acpiphp_func, sibling); + acpiphp_free_resource(&func->io_head); + acpiphp_free_resource(&func->mem_head); + acpiphp_free_resource(&func->p_mem_head); + acpiphp_free_resource(&func->bus_head); + 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"); + kfree(func); + } + kfree(slot); + slot = next; + } + status = acpi_remove_notify_handler(bridge->handle, ACPI_SYSTEM_NOTIFY, + handle_hotplug_event_bridge); + if (ACPI_FAILURE(status)) + err("failed to remove notify handler\n"); + + acpiphp_free_resource(&bridge->io_head); + acpiphp_free_resource(&bridge->mem_head); + acpiphp_free_resource(&bridge->p_mem_head); + acpiphp_free_resource(&bridge->bus_head); + + kfree(bridge); + } + + 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 list_head *node; + struct acpiphp_bridge *bridge; + int num_slots; + + num_slots = 0; + + list_for_each (node, &bridge_list) { + bridge = (struct acpiphp_bridge *)node; + dbg("Bus%d %dslot(s)\n", bridge->bus, bridge->nr_slots); + num_slots += bridge->nr_slots; + } + + dbg("Total %dslots\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 + +/* search matching slot from id */ +struct acpiphp_slot *get_slot_from_id(int id) +{ + struct list_head *node; + struct acpiphp_bridge *bridge; + struct acpiphp_slot *slot; + + list_for_each (node, &bridge_list) { + bridge = (struct acpiphp_bridge *)node; + for (slot = bridge->slots; slot; slot = slot->next) + if (slot->id == id) + return slot; + } + + /* should never happen! */ + err("%s: no object for id %d\n", __FUNCTION__, id); + WARN_ON(1); + return NULL; +} + + +/** + * acpiphp_enable_slot - power on slot + */ +int acpiphp_enable_slot(struct acpiphp_slot *slot) +{ + int retval; + + down(&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); + + err_exit: + up(&slot->crit_sect); + return retval; +} + + +/** + * acpiphp_disable_slot - power off slot + */ +int acpiphp_disable_slot(struct acpiphp_slot *slot) +{ + int retval = 0; + + down(&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; + + acpiphp_resource_sort_and_combine(&slot->bridge->io_head); + acpiphp_resource_sort_and_combine(&slot->bridge->mem_head); + acpiphp_resource_sort_and_combine(&slot->bridge->p_mem_head); + acpiphp_resource_sort_and_combine(&slot->bridge->bus_head); + dbg("Available resources:\n"); + acpiphp_dump_resource(slot->bridge); + + err_exit: + up(&slot->crit_sect); + return retval; +} + + +/* + * slot enabled: 1 + * slot disabled: 0 + */ +u8 acpiphp_get_power_status(struct acpiphp_slot *slot) +{ + unsigned int sta; + + sta = get_slot_status(slot); + + return (sta & ACPI_STA_ENABLED) ? 1 : 0; +} + + +/* + * latch closed: 1 + * latch open: 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) ? 1 : 0; +} + + +/* + * 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; +} + + +/* + * pci address (seg/bus/dev) + */ +u32 acpiphp_get_address(struct acpiphp_slot *slot) +{ + u32 address; + + address = ((slot->bridge->seg) << 16) | + ((slot->bridge->bus) << 8) | + slot->device; + + return address; +} diff --git a/drivers/pci/hotplug/acpiphp_ibm.c b/drivers/pci/hotplug/acpiphp_ibm.c new file mode 100644 index 000000000000..7e7f913ba7b9 --- /dev/null +++ b/drivers/pci/hotplug/acpiphp_ibm.c @@ -0,0 +1,499 @@ +/* + * 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 "acpiphp.h" +#include "pci_hotplug.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, + 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", + .owner = THIS_MODULE, + .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 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", __FUNCTION__, + 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%08lx\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", __FUNCTION__, + 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", __FUNCTION__, event); + + if (subevent == 0x80) { + dbg("%s: generationg bus event\n", __FUNCTION__); + acpi_bus_generate_event(note->device, 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", __FUNCTION__); + return -ENODEV; + } + + package = (union acpi_object *) buffer.pointer; + if(!(package) || + (package->type != ACPI_TYPE_PACKAGE) || + !(package->package.elements)) { + err("%s: Invalid APCI object\n", __FUNCTION__); + 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", __FUNCTION__, i); + goto read_table_done; + } + size += package->package.elements[i].buffer.length; + } + + if (bufp == NULL) + goto read_table_done; + + lbuf = kmalloc(size, GFP_KERNEL); + dbg("%s: element count: %i, ASL table size: %i, &table = 0x%p\n", + __FUNCTION__, package->package.count, size, lbuf); + + if (lbuf) { + *bufp = lbuf; + memset(lbuf, 0, size); + } 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 + * @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, + char *buffer, loff_t pos, size_t size) +{ + int bytes_read = -EINVAL; + char *table = NULL; + + dbg("%s: pos = %d, size = %zd\n", __FUNCTION__, (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 = { + .length = sizeof(struct acpi_device_info), + .pointer = &info, + }; + + status = acpi_get_object_info(handle, &info_buffer); + if (ACPI_FAILURE(status)) { + err("%s: Failed to get device information", __FUNCTION__); + return 0; + } + 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... + */ + return FOUND_APCI; + } + return 0; +} + +static int __init ibm_acpiphp_init(void) +{ + int retval = 0; + acpi_status status; + struct acpi_device *device; + struct kobject *sysdir = &pci_hotplug_slots_subsys.kset.kobj; + + dbg("%s\n", __FUNCTION__); + + 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", __FUNCTION__); + retval = -ENODEV; + goto init_return; + } + dbg("%s: found IBM aPCI device\n", __FUNCTION__); + if (acpi_bus_get_device(ibm_acpi_handle, &device)) { + err("%s: acpi_bus_get_device failed\n", __FUNCTION__); + 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", + __FUNCTION__); + 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_hotplug_slots_subsys.kset.kobj; + + dbg("%s\n", __FUNCTION__); + + if (acpiphp_unregister_attention(&ibm_attention_info)) + err("%s: attention info deregistration failed", __FUNCTION__); + + 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", + __FUNCTION__); + // remove the /sys entries + if (sysfs_remove_bin_file(sysdir, &ibm_apci_table_attr)) + err("%s: removal of sysfs file apci_table failed\n", + __FUNCTION__); +} + +module_init(ibm_acpiphp_init); +module_exit(ibm_acpiphp_exit); diff --git a/drivers/pci/hotplug/acpiphp_pci.c b/drivers/pci/hotplug/acpiphp_pci.c new file mode 100644 index 000000000000..54d97c9d1dff --- /dev/null +++ b/drivers/pci/hotplug/acpiphp_pci.c @@ -0,0 +1,449 @@ +/* + * ACPI PCI HotPlug PCI configuration space management + * + * Copyright (C) 1995,2001 Compaq Computer Corporation + * Copyright (C) 2001 Greg Kroah-Hartman (greg@kroah.com) + * Copyright (C) 2001,2002 IBM Corp. + * Copyright (C) 2002 Takayoshi Kochi (t-kochi@bq.jp.nec.com) + * Copyright (C) 2002 Hiroshi Aono (h-aono@ap.jp.nec.com) + * Copyright (C) 2002 NEC 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 <t-kochi@bq.jp.nec.com> + * + */ + +#include <linux/init.h> +#include <linux/module.h> + +#include <linux/kernel.h> +#include <linux/pci.h> +#include <linux/acpi.h> +#include "../pci.h" +#include "pci_hotplug.h" +#include "acpiphp.h" + +#define MY_NAME "acpiphp_pci" + + +/* allocate mem/pmem/io resource to a new function */ +static int init_config_space (struct acpiphp_func *func) +{ + u32 bar, len; + 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 acpiphp_bridge *bridge; + struct pci_resource *res; + struct pci_bus *pbus; + int bus, device, function; + unsigned int devfn; + u16 tmp; + + bridge = func->slot->bridge; + pbus = bridge->pci_bus; + bus = bridge->bus; + device = func->slot->device; + function = func->function; + devfn = PCI_DEVFN(device, function); + + for (count = 0; address[count]; count++) { /* for 6 BARs */ + pci_bus_write_config_dword(pbus, devfn, + address[count], 0xFFFFFFFF); + pci_bus_read_config_dword(pbus, devfn, address[count], &bar); + + if (!bar) /* This BAR is not implemented */ + continue; + + dbg("Device %02x.%02x BAR %d wants %x\n", device, function, count, bar); + + if (bar & PCI_BASE_ADDRESS_SPACE_IO) { + /* This is IO */ + + len = bar & (PCI_BASE_ADDRESS_IO_MASK & 0xFFFF); + len = len & ~(len - 1); + + dbg("len in IO %x, BAR %d\n", len, count); + + spin_lock(&bridge->res_lock); + res = acpiphp_get_io_resource(&bridge->io_head, len); + spin_unlock(&bridge->res_lock); + + if (!res) { + err("cannot allocate requested io for %02x:%02x.%d len %x\n", + bus, device, function, len); + return -1; + } + pci_bus_write_config_dword(pbus, devfn, + address[count], + (u32)res->base); + res->next = func->io_head; + func->io_head = res; + + } else { + /* This is Memory */ + if (bar & PCI_BASE_ADDRESS_MEM_PREFETCH) { + /* pfmem */ + + len = bar & 0xFFFFFFF0; + len = ~len + 1; + + dbg("len in PFMEM %x, BAR %d\n", len, count); + + spin_lock(&bridge->res_lock); + res = acpiphp_get_resource(&bridge->p_mem_head, len); + spin_unlock(&bridge->res_lock); + + if (!res) { + err("cannot allocate requested pfmem for %02x:%02x.%d len %x\n", + bus, device, function, len); + return -1; + } + + pci_bus_write_config_dword(pbus, devfn, + address[count], + (u32)res->base); + + if (bar & PCI_BASE_ADDRESS_MEM_TYPE_64) { /* takes up another dword */ + dbg("inside the pfmem 64 case, count %d\n", count); + count += 1; + pci_bus_write_config_dword(pbus, devfn, + address[count], + (u32)(res->base >> 32)); + } + + res->next = func->p_mem_head; + func->p_mem_head = res; + + } else { + /* regular memory */ + + len = bar & 0xFFFFFFF0; + len = ~len + 1; + + dbg("len in MEM %x, BAR %d\n", len, count); + + spin_lock(&bridge->res_lock); + res = acpiphp_get_resource(&bridge->mem_head, len); + spin_unlock(&bridge->res_lock); + + if (!res) { + err("cannot allocate requested pfmem for %02x:%02x.%d len %x\n", + bus, device, function, len); + return -1; + } + + pci_bus_write_config_dword(pbus, devfn, + address[count], + (u32)res->base); + + if (bar & PCI_BASE_ADDRESS_MEM_TYPE_64) { + /* takes up another dword */ + dbg("inside mem 64 case, reg. mem, count %d\n", count); + count += 1; + pci_bus_write_config_dword(pbus, devfn, + address[count], + (u32)(res->base >> 32)); + } + + res->next = func->mem_head; + func->mem_head = res; + + } + } + } + + /* disable expansion rom */ + pci_bus_write_config_dword(pbus, devfn, PCI_ROM_ADDRESS, 0x00000000); + + /* set PCI parameters from _HPP */ + pci_bus_write_config_byte(pbus, devfn, PCI_CACHE_LINE_SIZE, + bridge->hpp.cache_line_size); + pci_bus_write_config_byte(pbus, devfn, PCI_LATENCY_TIMER, + bridge->hpp.latency_timer); + + pci_bus_read_config_word(pbus, devfn, PCI_COMMAND, &tmp); + if (bridge->hpp.enable_SERR) + tmp |= PCI_COMMAND_SERR; + if (bridge->hpp.enable_PERR) + tmp |= PCI_COMMAND_PARITY; + pci_bus_write_config_word(pbus, devfn, PCI_COMMAND, tmp); + + return 0; +} + +/* detect_used_resource - subtract resource under dev from bridge */ +static int detect_used_resource (struct acpiphp_bridge *bridge, struct pci_dev *dev) +{ + int count; + + dbg("Device %s\n", pci_name(dev)); + + for (count = 0; count < DEVICE_COUNT_RESOURCE; count++) { + struct pci_resource *res; + struct pci_resource **head; + unsigned long base = dev->resource[count].start; + unsigned long len = dev->resource[count].end - base + 1; + unsigned long flags = dev->resource[count].flags; + + if (!flags) + continue; + + dbg("BAR[%d] 0x%lx - 0x%lx (0x%lx)\n", count, base, + base + len - 1, flags); + + if (flags & IORESOURCE_IO) { + head = &bridge->io_head; + } else if (flags & IORESOURCE_PREFETCH) { + head = &bridge->p_mem_head; + } else { + head = &bridge->mem_head; + } + + spin_lock(&bridge->res_lock); + res = acpiphp_get_resource_with_base(head, base, len); + spin_unlock(&bridge->res_lock); + if (res) + kfree(res); + } + + return 0; +} + + +/** + * acpiphp_detect_pci_resource - detect resources under bridge + * @bridge: detect all resources already used under this bridge + * + * collect all resources already allocated for all devices under a bridge. + */ +int acpiphp_detect_pci_resource (struct acpiphp_bridge *bridge) +{ + struct list_head *l; + struct pci_dev *dev; + + list_for_each (l, &bridge->pci_bus->devices) { + dev = pci_dev_b(l); + detect_used_resource(bridge, dev); + } + + return 0; +} + + +/** + * acpiphp_init_slot_resource - gather resource usage information of a slot + * @slot: ACPI slot object to be checked, should have valid pci_dev member + * + * TBD: PCI-to-PCI bridge case + * use pci_dev->resource[] + */ +int acpiphp_init_func_resource (struct acpiphp_func *func) +{ + u64 base; + u32 bar, len; + 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 pci_resource *res; + struct pci_dev *dev; + + dev = func->pci_dev; + dbg("Hot-pluggable device %s\n", pci_name(dev)); + + for (count = 0; address[count]; count++) { /* for 6 BARs */ + pci_read_config_dword(dev, address[count], &bar); + + if (!bar) /* This BAR is not implemented */ + continue; + + pci_write_config_dword(dev, address[count], 0xFFFFFFFF); + pci_read_config_dword(dev, address[count], &len); + + if (len & PCI_BASE_ADDRESS_SPACE_IO) { + /* This is IO */ + base = bar & 0xFFFFFFFC; + len = len & (PCI_BASE_ADDRESS_IO_MASK & 0xFFFF); + len = len & ~(len - 1); + + dbg("BAR[%d] %08x - %08x (IO)\n", count, (u32)base, (u32)base + len - 1); + + res = acpiphp_make_resource(base, len); + if (!res) + goto no_memory; + + res->next = func->io_head; + func->io_head = res; + + } else { + /* This is Memory */ + base = bar & 0xFFFFFFF0; + if (len & PCI_BASE_ADDRESS_MEM_PREFETCH) { + /* pfmem */ + + len &= 0xFFFFFFF0; + len = ~len + 1; + + if (len & PCI_BASE_ADDRESS_MEM_TYPE_64) { /* takes up another dword */ + dbg("prefetch mem 64\n"); + count += 1; + } + dbg("BAR[%d] %08x - %08x (PMEM)\n", count, (u32)base, (u32)base + len - 1); + res = acpiphp_make_resource(base, len); + if (!res) + goto no_memory; + + res->next = func->p_mem_head; + func->p_mem_head = res; + + } else { + /* regular memory */ + + len &= 0xFFFFFFF0; + len = ~len + 1; + + if (len & PCI_BASE_ADDRESS_MEM_TYPE_64) { + /* takes up another dword */ + dbg("mem 64\n"); + count += 1; + } + dbg("BAR[%d] %08x - %08x (MEM)\n", count, (u32)base, (u32)base + len - 1); + res = acpiphp_make_resource(base, len); + if (!res) + goto no_memory; + + res->next = func->mem_head; + func->mem_head = res; + + } + } + + pci_write_config_dword(dev, address[count], bar); + } +#if 1 + acpiphp_dump_func_resource(func); +#endif + + return 0; + + no_memory: + err("out of memory\n"); + acpiphp_free_resource(&func->io_head); + acpiphp_free_resource(&func->mem_head); + acpiphp_free_resource(&func->p_mem_head); + + return -1; +} + + +/** + * acpiphp_configure_slot - allocate PCI resources + * @slot: slot to be configured + * + * initializes a PCI functions on a device inserted + * into the slot + * + */ +int acpiphp_configure_slot (struct acpiphp_slot *slot) +{ + struct acpiphp_func *func; + struct list_head *l; + u8 hdr; + u32 dvid; + int retval = 0; + int is_multi = 0; + + pci_bus_read_config_byte(slot->bridge->pci_bus, + PCI_DEVFN(slot->device, 0), + PCI_HEADER_TYPE, &hdr); + + if (hdr & 0x80) + is_multi = 1; + + list_for_each (l, &slot->funcs) { + func = list_entry(l, struct acpiphp_func, sibling); + if (is_multi || func->function == 0) { + pci_bus_read_config_dword(slot->bridge->pci_bus, + PCI_DEVFN(slot->device, + func->function), + PCI_VENDOR_ID, &dvid); + if (dvid != 0xffffffff) { + retval = init_config_space(func); + if (retval) + break; + } + } + } + + return retval; +} + +/** + * acpiphp_configure_function - configure PCI function + * @func: function to be configured + * + * initializes a PCI functions on a device inserted + * into the slot + * + */ +int acpiphp_configure_function (struct acpiphp_func *func) +{ + /* all handled by the pci core now */ + return 0; +} + +/** + * acpiphp_unconfigure_function - unconfigure PCI function + * @func: function to be unconfigured + * + */ +void acpiphp_unconfigure_function (struct acpiphp_func *func) +{ + struct acpiphp_bridge *bridge; + + /* if pci_dev is NULL, ignore it */ + if (!func->pci_dev) + return; + + pci_remove_bus_device(func->pci_dev); + + /* free all resources */ + bridge = func->slot->bridge; + + spin_lock(&bridge->res_lock); + acpiphp_move_resource(&func->io_head, &bridge->io_head); + acpiphp_move_resource(&func->mem_head, &bridge->mem_head); + acpiphp_move_resource(&func->p_mem_head, &bridge->p_mem_head); + acpiphp_move_resource(&func->bus_head, &bridge->bus_head); + spin_unlock(&bridge->res_lock); +} diff --git a/drivers/pci/hotplug/acpiphp_res.c b/drivers/pci/hotplug/acpiphp_res.c new file mode 100644 index 000000000000..f54b1fa7b75a --- /dev/null +++ b/drivers/pci/hotplug/acpiphp_res.c @@ -0,0 +1,700 @@ +/* + * ACPI PCI HotPlug Utility functions + * + * 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 Takayoshi Kochi (t-kochi@bq.jp.nec.com) + * Copyright (C) 2002 NEC 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 <gregkh@us.ibm.com>, <t-kochi@bq.jp.nec.com> + * + */ + +#include <linux/init.h> +#include <linux/module.h> + +#include <linux/kernel.h> +#include <linux/types.h> +#include <linux/proc_fs.h> +#include <linux/sysctl.h> +#include <linux/pci.h> +#include <linux/smp.h> +#include <linux/smp_lock.h> + +#include <linux/string.h> +#include <linux/mm.h> +#include <linux/errno.h> +#include <linux/ioport.h> +#include <linux/slab.h> +#include <linux/interrupt.h> +#include <linux/timer.h> + +#include <linux/ioctl.h> +#include <linux/fcntl.h> + +#include <linux/list.h> + +#include "pci_hotplug.h" +#include "acpiphp.h" + +#define MY_NAME "acpiphp_res" + + +/* + * sort_by_size - sort nodes by their length, smallest first + */ +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; +} + +#if 0 +/* + * sort_by_max_size - sort nodes by their length, largest first + */ +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; +} +#endif + +/** + * get_io_resource - get resource for I/O ports + * + * this function sorts the resource list by size and then + * 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. + * + * size must be a power of two. + * + * difference from get_resource is handling of ISA aliasing space. + * + */ +struct pci_resource *acpiphp_get_io_resource (struct pci_resource **head, u32 size) +{ + struct pci_resource *prevnode; + struct pci_resource *node; + struct pci_resource *split_node; + u64 temp_qword; + + if (!(*head)) + return NULL; + + if (acpiphp_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_qword = (node->base | (size-1)) + 1; + + /* Short circuit if adjusted size is too small */ + if ((node->length - (temp_qword - node->base)) < size) + continue; + + split_node = acpiphp_make_resource(node->base, temp_qword - node->base); + + if (!split_node) + return NULL; + + node->base = temp_qword; + 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 = acpiphp_make_resource(node->base + size, node->length - size); + + if (!split_node) + return NULL; + + 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) && !(node->base & 0xfffff000)) + continue; + + /* 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; + /* Stop looping */ + break; + } + + return node; +} + + +#if 0 +/** + * get_max_resource - get the largest resource + * + * 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 *acpiphp_get_max_resource (struct pci_resource **head, u32 size) +{ + struct pci_resource *max; + struct pci_resource *temp; + struct pci_resource *split_node; + u64 temp_qword; + + if (!(*head)) + return NULL; + + if (acpiphp_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_qword = (max->base | (size-1)) + 1; + + /* Short circuit if adjusted size is too small */ + if ((max->length - (temp_qword - max->base)) < size) + continue; + + split_node = acpiphp_make_resource(max->base, temp_qword - max->base); + + if (!split_node) + return NULL; + + max->base = temp_qword; + max->length -= split_node->length; + + /* Put it next in the list */ + 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 */ + temp_qword = ((max->base + max->length) & ~(size - 1)); + + split_node = acpiphp_make_resource(temp_qword, + max->length + max->base - temp_qword); + + if (!split_node) + return NULL; + + max->length -= split_node->length; + + /* Put it in the list */ + 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 = (struct pci_resource*) *head; + if (temp == max) { + *head = max->next; + } else { + while (temp && temp->next != max) { + temp = temp->next; + } + + temp->next = max->next; + } + + max->next = NULL; + return max; + } + + /* If we get here, we couldn't find one */ + return NULL; +} +#endif + +/** + * get_resource - get resource (mem, pfmem) + * + * 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. + * + */ +struct pci_resource *acpiphp_get_resource (struct pci_resource **head, u32 size) +{ + struct pci_resource *prevnode; + struct pci_resource *node; + struct pci_resource *split_node; + u64 temp_qword; + + if (!(*head)) + return NULL; + + if (acpiphp_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", + __FUNCTION__, size, node, (u32)node->base, node->length); + if (node->length < size) + continue; + + if (node->base & (size - 1)) { + dbg("%s: not aligned\n", __FUNCTION__); + /* this one isn't base aligned properly + so we'll make a new entry and split it up */ + temp_qword = (node->base | (size-1)) + 1; + + /* Short circuit if adjusted size is too small */ + if ((node->length - (temp_qword - node->base)) < size) + continue; + + split_node = acpiphp_make_resource(node->base, temp_qword - node->base); + + if (!split_node) + return NULL; + + node->base = temp_qword; + 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) { + dbg("%s: too big\n", __FUNCTION__); + /* this one is longer than we need + so we'll make a new entry and split it up */ + split_node = acpiphp_make_resource(node->base + size, node->length - size); + + if (!split_node) + return NULL; + + 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", __FUNCTION__); + /* 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; + /* Stop looping */ + break; + } + return node; +} + +/** + * get_resource_with_base - get resource with specific base address + * + * this function + * returns the first node of "size" length located at specified base address. + * If it finds a node larger than "size" it will split it up. + * + * size must be a power of two. + * + */ +struct pci_resource *acpiphp_get_resource_with_base (struct pci_resource **head, u64 base, u32 size) +{ + struct pci_resource *prevnode; + struct pci_resource *node; + struct pci_resource *split_node; + u64 temp_qword; + + if (!(*head)) + return NULL; + + if (acpiphp_resource_sort_and_combine(head)) + return NULL; + + for (node = *head; node; node = node->next) { + dbg(": 1st req_base=%x req_size =%x node=%p, base=%x, length=%x\n", + (u32)base, size, node, (u32)node->base, node->length); + if (node->base > base) + continue; + + if ((node->base + node->length) < (base + size)) + continue; + + if (node->base < base) { + dbg(": split 1\n"); + /* this one isn't base aligned properly + so we'll make a new entry and split it up */ + temp_qword = base; + + /* Short circuit if adjusted size is too small */ + if ((node->length - (temp_qword - node->base)) < size) + continue; + + split_node = acpiphp_make_resource(node->base, temp_qword - node->base); + + if (!split_node) + return NULL; + + node->base = temp_qword; + node->length -= split_node->length; + + /* Put it in the list */ + split_node->next = node->next; + node->next = split_node; + } + + dbg(": 2nd req_base=%x req_size =%x node=%p, base=%x, length=%x\n", + (u32)base, size, node, (u32)node->base, node->length); + + /* Don't need to check if too small since we already did */ + if (node->length > size) { + dbg(": split 2\n"); + /* this one is longer than we need + so we'll make a new entry and split it up */ + split_node = acpiphp_make_resource(node->base + size, node->length - size); + + if (!split_node) + return NULL; + + 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(": got one!!!\n"); + /* 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; + /* Stop looping */ + break; + } + return node; +} + + +/** + * acpiphp_resource_sort_and_combine + * + * 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 acpiphp_resource_sort_and_combine (struct pci_resource **head) +{ + struct pci_resource *node1; + struct pci_resource *node2; + int out_of_order = 1; + + 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",(u32)(*head)->base); + dbg("*head->next->base = 0x%x\n", (u32)(*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; +} + + +/** + * acpiphp_make_resource - make resource structure + * @base: base address of a resource + * @length: length of a resource + */ +struct pci_resource *acpiphp_make_resource (u64 base, u32 length) +{ + struct pci_resource *res; + + res = kmalloc(sizeof(struct pci_resource), GFP_KERNEL); + if (res) { + memset(res, 0, sizeof(struct pci_resource)); + res->base = base; + res->length = length; + } + + return res; +} + + +/** + * acpiphp_move_resource - move linked resources from one to another + * @from: head of linked resource list + * @to: head of linked resource list + */ +void acpiphp_move_resource (struct pci_resource **from, struct pci_resource **to) +{ + struct pci_resource *tmp; + + while (*from) { + tmp = (*from)->next; + (*from)->next = *to; + *to = *from; + *from = tmp; + } + + /* *from = NULL is guaranteed */ +} + + +/** + * acpiphp_free_resource - free all linked resources + * @res: head of linked resource list + */ +void acpiphp_free_resource (struct pci_resource **res) +{ + struct pci_resource *tmp; + + while (*res) { + tmp = (*res)->next; + kfree(*res); + *res = tmp; + } + + /* *res = NULL is guaranteed */ +} + + +/* debug support functions; will go away sometime :) */ +static void dump_resource(struct pci_resource *head) +{ + struct pci_resource *p; + int cnt; + + p = head; + cnt = 0; + + while (p) { + dbg("[%02d] %08x - %08x\n", + cnt++, (u32)p->base, (u32)p->base + p->length - 1); + p = p->next; + } +} + +void acpiphp_dump_resource(struct acpiphp_bridge *bridge) +{ + dbg("I/O resource:\n"); + dump_resource(bridge->io_head); + dbg("MEM resource:\n"); + dump_resource(bridge->mem_head); + dbg("PMEM resource:\n"); + dump_resource(bridge->p_mem_head); + dbg("BUS resource:\n"); + dump_resource(bridge->bus_head); +} + +void acpiphp_dump_func_resource(struct acpiphp_func *func) +{ + dbg("I/O resource:\n"); + dump_resource(func->io_head); + dbg("MEM resource:\n"); + dump_resource(func->mem_head); + dbg("PMEM resource:\n"); + dump_resource(func->p_mem_head); + dbg("BUS resource:\n"); + dump_resource(func->bus_head); +} diff --git a/drivers/pci/hotplug/cpci_hotplug.h b/drivers/pci/hotplug/cpci_hotplug.h new file mode 100644 index 000000000000..3ddd75937a40 --- /dev/null +++ b/drivers/pci/hotplug/cpci_hotplug.h @@ -0,0 +1,96 @@ +/* + * 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> + +/* PICMG 2.12 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; +}; + +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 000000000000..ed243605dc7b --- /dev/null +++ b/drivers/pci/hotplug/cpci_hotplug_core.c @@ -0,0 +1,792 @@ +/* + * CompactPCI Hot Plug Driver + * + * 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> + */ + +#include <linux/config.h> +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/pci.h> +#include <linux/init.h> +#include <linux/interrupt.h> +#include <linux/smp_lock.h> +#include <linux/delay.h> +#include "pci_hotplug.h" +#include "cpci_hotplug.h" + +#define DRIVER_VERSION "0.2" +#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 spinlock_t list_lock; +static LIST_HEAD(slot_list); +static int slots; +int cpci_debug; +static struct cpci_hp_controller *controller; +static struct semaphore event_semaphore; /* mutex for process loop (up if something to process) */ +static struct semaphore thread_exit; /* guard ensure thread has exited before calling it quits */ +static int thread_finished = 1; + +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 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, +}; + +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", __FUNCTION__, hotplug_slot->name); + + 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", __FUNCTION__, hotplug_slot->name); + + /* Unconfigure device */ + dbg("%s - unconfiguring slot %s", + __FUNCTION__, slot->hotplug_slot->name); + if((retval = cpci_unconfigure_slot(slot))) { + err("%s - could not unconfigure slot %s", + __FUNCTION__, slot->hotplug_slot->name); + return retval; + } + dbg("%s - finished unconfiguring slot %s", + __FUNCTION__, slot->hotplug_slot->name); + + /* Clear EXT (by setting it) */ + if(cpci_clear_ext(slot)) { + err("%s - could not clear EXT for slot %s", + __FUNCTION__, slot->hotplug_slot->name); + retval = -ENODEV; + } + cpci_led_on(slot); + + if(controller->ops->set_power) { + retval = controller->ops->set_power(slot, 0); + } + + if(update_adapter_status(slot->hotplug_slot, 0)) { + warn("failure to update adapter file"); + } + + slot->extracting = 0; + + 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 void release_slot(struct hotplug_slot *hotplug_slot) +{ + struct slot *slot = hotplug_slot->private; + + kfree(slot->hotplug_slot->info); + kfree(slot->hotplug_slot->name); + kfree(slot->hotplug_slot); + kfree(slot); +} + +#define SLOT_NAME_SIZE 6 +static void +make_slot_name(struct slot *slot) +{ + snprintf(slot->hotplug_slot->name, + SLOT_NAME_SIZE, "%02x:%02x", slot->bus->number, slot->number); +} + +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; + 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 = kmalloc(sizeof (struct slot), GFP_KERNEL); + if (!slot) + goto error; + memset(slot, 0, sizeof (struct slot)); + + hotplug_slot = + kmalloc(sizeof (struct hotplug_slot), GFP_KERNEL); + if (!hotplug_slot) + goto error_slot; + memset(hotplug_slot, 0, sizeof (struct hotplug_slot)); + slot->hotplug_slot = hotplug_slot; + + info = kmalloc(sizeof (struct hotplug_slot_info), GFP_KERNEL); + if (!info) + goto error_hpslot; + memset(info, 0, sizeof (struct hotplug_slot_info)); + hotplug_slot->info = info; + + name = kmalloc(SLOT_NAME_SIZE, GFP_KERNEL); + if (!name) + goto error_info; + hotplug_slot->name = name; + + slot->bus = bus; + slot->number = i; + slot->devfn = PCI_DEVFN(i, 0); + + hotplug_slot->private = slot; + hotplug_slot->release = &release_slot; + make_slot_name(slot); + hotplug_slot->ops = &cpci_hotplug_slot_ops; + + /* + * Initialize the slot info structure with some known + * good values. + */ + dbg("initializing slot %s", slot->hotplug_slot->name); + info->power_status = cpci_get_power_status(slot); + info->attention_status = cpci_get_attention_status(slot); + + dbg("registering slot %s", slot->hotplug_slot->name); + status = pci_hp_register(slot->hotplug_slot); + if (status) { + err("pci_hp_register failed with error %d", status); + goto error_name; + } + + /* Add slot to our internal list */ + spin_lock(&list_lock); + list_add(&slot->slot_list, &slot_list); + slots++; + spin_unlock(&list_lock); + } + return 0; +error_name: + kfree(name); +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 list_head *tmp; + struct list_head *next; + int status; + + spin_lock(&list_lock); + if(!slots) { + spin_unlock(&list_lock); + return -1; + } + list_for_each_safe(tmp, next, &slot_list) { + slot = list_entry(tmp, struct slot, slot_list); + if(slot->bus == bus) { + dbg("deregistering slot %s", slot->hotplug_slot->name); + status = pci_hp_deregister(slot->hotplug_slot); + if(status) { + err("pci_hp_deregister failed with error %d", + status); + return status; + } + + list_del(&slot->slot_list); + slots--; + } + } + spin_unlock(&list_lock); + return 0; +} + +/* This is the interrupt mode interrupt handler */ +static irqreturn_t +cpci_hp_intr(int irq, void *data, struct pt_regs *regs) +{ + dbg("entered cpci_hp_intr"); + + /* Check to see if it was our interrupt */ + if((controller->irq_flags & SA_SHIRQ) && + !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 */ + dbg("Signal event_semaphore"); + up(&event_semaphore); + dbg("exited cpci_hp_intr"); + return IRQ_HANDLED; +} + +/* + * According to PICMG 2.12 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(void) +{ + struct slot *slot; + struct list_head *tmp; + struct pci_dev* dev; + + dbg("%s - enter", __FUNCTION__); + spin_lock(&list_lock); + if(!slots) { + spin_unlock(&list_lock); + return -1; + } + list_for_each(tmp, &slot_list) { + slot = list_entry(tmp, struct slot, slot_list); + dbg("%s - looking at slot %s", + __FUNCTION__, slot->hotplug_slot->name); + if(cpci_check_and_clear_ins(slot)) { + dbg("%s - cleared INS for slot %s", + __FUNCTION__, slot->hotplug_slot->name); + dev = pci_find_slot(slot->bus->number, 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; + } else { + err("%s - no driver attached to device in slot %s", + __FUNCTION__, slot->hotplug_slot->name); + } + } + } + spin_unlock(&list_lock); + dbg("%s - exit", __FUNCTION__); + return 0; +} + +static int +check_slots(void) +{ + struct slot *slot; + struct list_head *tmp; + int extracted; + int inserted; + + spin_lock(&list_lock); + if(!slots) { + spin_unlock(&list_lock); + err("no slots registered, shutting down"); + return -1; + } + extracted = inserted = 0; + list_for_each(tmp, &slot_list) { + slot = list_entry(tmp, struct slot, slot_list); + dbg("%s - looking at slot %s", + __FUNCTION__, slot->hotplug_slot->name); + if(cpci_check_and_clear_ins(slot)) { + u16 hs_csr; + + /* Some broken hardware (e.g. PLX 9054AB) asserts ENUM# twice... */ + if(slot->dev) { + warn("slot %s already inserted", slot->hotplug_slot->name); + inserted++; + continue; + } + + /* Process insertion */ + dbg("%s - slot %s inserted", + __FUNCTION__, slot->hotplug_slot->name); + + /* GSM, debug */ + hs_csr = cpci_get_hs_csr(slot); + dbg("%s - slot %s HS_CSR (1) = %04x", + __FUNCTION__, slot->hotplug_slot->name, hs_csr); + + /* Configure device */ + dbg("%s - configuring slot %s", + __FUNCTION__, slot->hotplug_slot->name); + if(cpci_configure_slot(slot)) { + err("%s - could not configure slot %s", + __FUNCTION__, slot->hotplug_slot->name); + continue; + } + dbg("%s - finished configuring slot %s", + __FUNCTION__, slot->hotplug_slot->name); + + /* GSM, debug */ + hs_csr = cpci_get_hs_csr(slot); + dbg("%s - slot %s HS_CSR (2) = %04x", + __FUNCTION__, slot->hotplug_slot->name, 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", + __FUNCTION__, slot->hotplug_slot->name, hs_csr); + + inserted++; + } else if(cpci_check_ext(slot)) { + u16 hs_csr; + + /* Process extraction request */ + dbg("%s - slot %s extracted", + __FUNCTION__, slot->hotplug_slot->name); + + /* GSM, debug */ + hs_csr = cpci_get_hs_csr(slot); + dbg("%s - slot %s HS_CSR = %04x", + __FUNCTION__, slot->hotplug_slot->name, hs_csr); + + if(!slot->extracting) { + if(update_latch_status(slot->hotplug_slot, 0)) { + warn("failure to update latch file"); + } + slot->extracting = 1; + } + extracted++; + } + } + spin_unlock(&list_lock); + if(inserted || extracted) { + return extracted; + } + else { + err("cannot find ENUM# source, shutting down"); + return -1; + } +} + +/* This is the interrupt mode worker thread body */ +static int +event_thread(void *data) +{ + int rc; + struct slot *slot; + struct list_head *tmp; + + lock_kernel(); + daemonize("cpci_hp_eventd"); + unlock_kernel(); + + dbg("%s - event thread started", __FUNCTION__); + while(1) { + dbg("event thread sleeping"); + down_interruptible(&event_semaphore); + dbg("event thread woken, thread_finished = %d", + thread_finished); + if(thread_finished || signal_pending(current)) + break; + while(controller->ops->query_enum()) { + rc = check_slots(); + if (rc > 0) + /* Give userspace a chance to handle extraction */ + msleep(500); + else if (rc < 0) { + dbg("%s - error checking slots", __FUNCTION__); + thread_finished = 1; + break; + } + } + /* Check for someone yanking out a board */ + list_for_each(tmp, &slot_list) { + slot = list_entry(tmp, struct slot, slot_list); + if(slot->extracting) { + /* + * 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->hotplug_slot->name); + if(update_adapter_status(slot->hotplug_slot, 0)) { + warn("failure to update adapter file"); + } + slot->extracting = 0; + } + } + + /* Re-enable ENUM# interrupt */ + dbg("%s - re-enabling irq", __FUNCTION__); + controller->ops->enable_irq(); + } + + dbg("%s - event thread signals exit", __FUNCTION__); + up(&thread_exit); + return 0; +} + +/* This is the polling mode worker thread body */ +static int +poll_thread(void *data) +{ + int rc; + struct slot *slot; + struct list_head *tmp; + + lock_kernel(); + daemonize("cpci_hp_polld"); + unlock_kernel(); + + while(1) { + if(thread_finished || signal_pending(current)) + break; + + while(controller->ops->query_enum()) { + rc = check_slots(); + if(rc > 0) + /* Give userspace a chance to handle extraction */ + msleep(500); + else if (rc < 0) { + dbg("%s - error checking slots", __FUNCTION__); + thread_finished = 1; + break; + } + } + /* Check for someone yanking out a board */ + list_for_each(tmp, &slot_list) { + slot = list_entry(tmp, struct slot, slot_list); + if(slot->extracting) { + /* + * 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->hotplug_slot->name); + if(update_adapter_status(slot->hotplug_slot, 0)) { + warn("failure to update adapter file"); + } + slot->extracting = 0; + } + } + + msleep(100); + } + dbg("poll thread signals exit"); + up(&thread_exit); + return 0; +} + +static int +cpci_start_thread(void) +{ + int pid; + + /* initialize our semaphores */ + init_MUTEX_LOCKED(&event_semaphore); + init_MUTEX_LOCKED(&thread_exit); + thread_finished = 0; + + if(controller->irq) { + pid = kernel_thread(event_thread, NULL, 0); + } else { + pid = kernel_thread(poll_thread, NULL, 0); + } + if(pid < 0) { + err("Can't start up our thread"); + return -1; + } + dbg("Our thread pid = %d", pid); + return 0; +} + +static void +cpci_stop_thread(void) +{ + thread_finished = 1; + dbg("thread finish command given"); + if(controller->irq) { + up(&event_semaphore); + } + dbg("wait for thread to exit"); + down(&thread_exit); +} + +int +cpci_hp_register_controller(struct cpci_hp_controller *new_controller) +{ + int status = 0; + + if(!controller) { + controller = new_controller; + if(controller->irq) { + if(request_irq(controller->irq, + cpci_hp_intr, + controller->irq_flags, + MY_NAME, controller->dev_id)) { + err("Can't get irq %d for the hotplug cPCI controller", controller->irq); + status = -ENODEV; + } + dbg("%s - acquired controller irq %d", __FUNCTION__, + controller->irq); + } + } else { + err("cPCI hotplug controller already registered"); + status = -1; + } + return status; +} + +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; + } else { + status = -ENODEV; + } + return status; +} + +int +cpci_hp_start(void) +{ + static int first = 1; + int status; + + dbg("%s - enter", __FUNCTION__); + if(!controller) { + return -ENODEV; + } + + spin_lock(&list_lock); + if(!slots) { + spin_unlock(&list_lock); + return -ENODEV; + } + spin_unlock(&list_lock); + + if(first) { + status = init_slots(); + if(status) { + return status; + } + first = 0; + } + + status = cpci_start_thread(); + if(status) { + return status; + } + dbg("%s - thread started", __FUNCTION__); + + if(controller->irq) { + /* Start enum interrupt processing */ + dbg("%s - enabling irq", __FUNCTION__); + controller->ops->enable_irq(); + } + dbg("%s - exit", __FUNCTION__); + return 0; +} + +int +cpci_hp_stop(void) +{ + if(!controller) { + return -ENODEV; + } + + if(controller->irq) { + /* Stop enum interrupt processing */ + dbg("%s - disabling irq", __FUNCTION__); + controller->ops->disable_irq(); + } + cpci_stop_thread(); + return 0; +} + +static void __exit +cleanup_slots(void) +{ + struct list_head *tmp; + struct slot *slot; + + /* + * Unregister all of our slots with the pci_hotplug subsystem, + * and free up all memory that we had allocated. + */ + spin_lock(&list_lock); + if(!slots) { + goto null_cleanup; + } + list_for_each(tmp, &slot_list) { + slot = list_entry(tmp, struct slot, slot_list); + list_del(&slot->slot_list); + pci_hp_deregister(slot->hotplug_slot); + kfree(slot->hotplug_slot->info); + kfree(slot->hotplug_slot->name); + kfree(slot->hotplug_slot); + kfree(slot); + } + null_cleanup: + spin_unlock(&list_lock); + return; +} + +int __init +cpci_hotplug_init(int debug) +{ + spin_lock_init(&list_lock); + cpci_debug = debug; + + info(DRIVER_DESC " version: " DRIVER_VERSION); + return 0; +} + +void __exit +cpci_hotplug_exit(void) +{ + /* + * Clean everything up. + */ + cleanup_slots(); +} + +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 000000000000..2e969616f298 --- /dev/null +++ b/drivers/pci/hotplug/cpci_hotplug_pci.c @@ -0,0 +1,661 @@ +/* + * CompactPCI Hot Plug Driver PCI functions + * + * Copyright (C) 2002 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/config.h> +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/pci.h> +#include <linux/proc_fs.h> +#include "../pci.h" +#include "pci_hotplug.h" +#include "cpci_hotplug.h" + +#if !defined(MODULE) +#define MY_NAME "cpci_hotplug" +#else +#define MY_NAME THIS_MODULE->name +#endif + +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) + +#define ROUND_UP(x, a) (((x) + (a) - 1) & ~((a) - 1)) + + +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; +} + +#if 0 +u16 cpci_set_hs_csr(struct slot* slot, u16 hs_csr) +{ + int hs_cap; + u16 new_hs_csr; + + hs_cap = pci_bus_find_capability(slot->bus, + slot->devfn, + PCI_CAP_ID_CHSWP); + if(!hs_cap) { + return 0xFFFF; + } + + /* Write out the new value */ + if(pci_bus_write_config_word(slot->bus, + slot->devfn, + hs_cap + 2, + hs_csr)) { + return 0xFFFF; + } + + /* Read back what we just wrote out */ + if(pci_bus_read_config_word(slot->bus, + slot->devfn, + hs_cap + 2, + &new_hs_csr)) { + return 0xFFFF; + } + return new_hs_csr; +} +#endif + +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; + } + 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) { + /* Set 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", + slot->hotplug_slot->name); + 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) { + /* Clear 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", + slot->hotplug_slot->name); + return -ENODEV; + } + } + return 0; +} + + +/* + * Device configuration functions + */ + +static int cpci_configure_dev(struct pci_bus *bus, struct pci_dev *dev) +{ + u8 irq_pin; + int r; + + dbg("%s - enter", __FUNCTION__); + + /* NOTE: device already setup from prior scan */ + + /* FIXME: How would we know if we need to enable the expansion ROM? */ + pci_write_config_word(dev, PCI_ROM_ADDRESS, 0x00L); + + /* Assign resources */ + dbg("assigning resources for %02x:%02x.%x", + dev->bus->number, PCI_SLOT(dev->devfn), PCI_FUNC(dev->devfn)); + for (r = 0; r < 6; r++) { + struct resource *res = dev->resource + r; + if(res->flags) + pci_assign_resource(dev, r); + } + dbg("finished assigning resources for %02x:%02x.%x", + dev->bus->number, PCI_SLOT(dev->devfn), PCI_FUNC(dev->devfn)); + + /* Does this function have an interrupt at all? */ + dbg("checking for function interrupt"); + pci_read_config_byte(dev, PCI_INTERRUPT_PIN, &irq_pin); + if(irq_pin) { + dbg("function uses interrupt pin %d", irq_pin); + } + + /* + * Need to explicitly set irq field to 0 so that it'll get assigned + * by the pcibios platform dependent code called by pci_enable_device. + */ + dev->irq = 0; + + dbg("enabling device"); + pci_enable_device(dev); /* XXX check return */ + dbg("now dev->irq = %d", dev->irq); + if(irq_pin && dev->irq) { + pci_write_config_byte(dev, PCI_INTERRUPT_LINE, dev->irq); + } + + /* Can't use pci_insert_device at the moment, do it manually for now */ + pci_proc_attach_device(dev); + dbg("notifying drivers"); + //pci_announce_device_to_drivers(dev); + dbg("%s - exit", __FUNCTION__); + return 0; +} + +static int cpci_configure_bridge(struct pci_bus* bus, struct pci_dev* dev) +{ + int rc; + struct pci_bus* child; + struct resource* r; + u8 max, n; + u16 command; + + dbg("%s - enter", __FUNCTION__); + + /* Do basic bridge initialization */ + rc = pci_write_config_byte(dev, PCI_LATENCY_TIMER, 0x40); + if(rc) { + printk(KERN_ERR "%s - write of PCI_LATENCY_TIMER failed\n", __FUNCTION__); + } + rc = pci_write_config_byte(dev, PCI_SEC_LATENCY_TIMER, 0x40); + if(rc) { + printk(KERN_ERR "%s - write of PCI_SEC_LATENCY_TIMER failed\n", __FUNCTION__); + } + rc = pci_write_config_byte(dev, PCI_CACHE_LINE_SIZE, L1_CACHE_BYTES / 4); + if(rc) { + printk(KERN_ERR "%s - write of PCI_CACHE_LINE_SIZE failed\n", __FUNCTION__); + } + + /* + * Set parent bridge's subordinate field so that configuration space + * access will work in pci_scan_bridge and friends. + */ + max = pci_max_busnr(); + bus->subordinate = max + 1; + pci_write_config_byte(bus->self, PCI_SUBORDINATE_BUS, max + 1); + + /* Scan behind bridge */ + n = pci_scan_bridge(bus, dev, max, 2); + child = pci_find_bus(0, max + 1); + if (!child) + return -ENODEV; + pci_proc_attach_bus(child); + + /* + * Update parent bridge's subordinate field if there were more bridges + * behind the bridge that was scanned. + */ + if(n > max) { + bus->subordinate = n; + pci_write_config_byte(bus->self, PCI_SUBORDINATE_BUS, n); + } + + /* + * Update the bridge resources of the bridge to accommodate devices + * behind it. + */ + pci_bus_size_bridges(child); + pci_bus_assign_resources(child); + + /* Enable resource mapping via command register */ + command = PCI_COMMAND_MASTER | PCI_COMMAND_INVALIDATE | PCI_COMMAND_PARITY | PCI_COMMAND_SERR; + r = child->resource[0]; + if(r && r->start) { + command |= PCI_COMMAND_IO; + } + r = child->resource[1]; + if(r && r->start) { + command |= PCI_COMMAND_MEMORY; + } + r = child->resource[2]; + if(r && r->start) { + command |= PCI_COMMAND_MEMORY; + } + rc = pci_write_config_word(dev, PCI_COMMAND, command); + if(rc) { + err("Error setting command register"); + return rc; + } + + /* Set bridge control register */ + command = PCI_BRIDGE_CTL_PARITY | PCI_BRIDGE_CTL_SERR | PCI_BRIDGE_CTL_NO_ISA; + rc = pci_write_config_word(dev, PCI_BRIDGE_CONTROL, command); + if(rc) { + err("Error setting bridge control register"); + return rc; + } + dbg("%s - exit", __FUNCTION__); + return 0; +} + +static int configure_visit_pci_dev(struct pci_dev_wrapped *wrapped_dev, + struct pci_bus_wrapped *wrapped_bus) +{ + int rc; + struct pci_dev *dev = wrapped_dev->dev; + struct pci_bus *bus = wrapped_bus->bus; + struct slot* slot; + + dbg("%s - enter", __FUNCTION__); + + /* + * We need to fix up the hotplug representation with the Linux + * representation. + */ + if(wrapped_dev->data) { + slot = (struct slot*) wrapped_dev->data; + slot->dev = dev; + } + + /* If it's a bridge, scan behind it for devices */ + if(dev->hdr_type == PCI_HEADER_TYPE_BRIDGE) { + rc = cpci_configure_bridge(bus, dev); + if(rc) + return rc; + } + + /* Actually configure device */ + if(dev) { + rc = cpci_configure_dev(bus, dev); + if(rc) + return rc; + } + dbg("%s - exit", __FUNCTION__); + return 0; +} + +static int unconfigure_visit_pci_dev_phase2(struct pci_dev_wrapped *wrapped_dev, + struct pci_bus_wrapped *wrapped_bus) +{ + struct pci_dev *dev = wrapped_dev->dev; + struct slot* slot; + + dbg("%s - enter", __FUNCTION__); + if(!dev) + return -ENODEV; + + /* Remove the Linux representation */ + if(pci_remove_device_safe(dev)) { + err("Could not remove device\n"); + return -1; + } + + /* + * Now remove the hotplug representation. + */ + if(wrapped_dev->data) { + slot = (struct slot*) wrapped_dev->data; + slot->dev = NULL; + } else { + dbg("No hotplug representation for %02x:%02x.%x", + dev->bus->number, PCI_SLOT(dev->devfn), PCI_FUNC(dev->devfn)); + } + dbg("%s - exit", __FUNCTION__); + return 0; +} + +static int unconfigure_visit_pci_bus_phase2(struct pci_bus_wrapped *wrapped_bus, + struct pci_dev_wrapped *wrapped_dev) +{ + struct pci_bus *bus = wrapped_bus->bus; + struct pci_bus *parent = bus->self->bus; + + dbg("%s - enter", __FUNCTION__); + + /* The cleanup code for proc entries regarding buses should be in the kernel... */ + if(bus->procdir) + dbg("detach_pci_bus %s", bus->procdir->name); + pci_proc_detach_bus(bus); + + /* The cleanup code should live in the kernel... */ + bus->self->subordinate = NULL; + + /* unlink from parent bus */ + list_del(&bus->node); + + /* Now, remove */ + if(bus) + kfree(bus); + + /* Update parent's subordinate field */ + if(parent) { + u8 n = pci_bus_max_busnr(parent); + if(n < parent->subordinate) { + parent->subordinate = n; + pci_write_config_byte(parent->self, PCI_SUBORDINATE_BUS, n); + } + } + dbg("%s - exit", __FUNCTION__); + return 0; +} + +static struct pci_visit configure_functions = { + .visit_pci_dev = configure_visit_pci_dev, +}; + +static struct pci_visit unconfigure_functions_phase2 = { + .post_visit_pci_bus = unconfigure_visit_pci_bus_phase2, + .post_visit_pci_dev = unconfigure_visit_pci_dev_phase2 +}; + + +int cpci_configure_slot(struct slot* slot) +{ + int rc = 0; + + dbg("%s - enter", __FUNCTION__); + + 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_find_slot(slot->bus->number, 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", __FUNCTION__, n); + if(n > 0) + pci_bus_add_devices(slot->bus); + slot->dev = pci_find_slot(slot->bus->number, slot->devfn); + if(slot->dev == NULL) { + err("Could not find PCI device for slot %02x", slot->number); + return 0; + } + } + dbg("slot->dev = %p", slot->dev); + if(slot->dev) { + struct pci_dev *dev; + struct pci_dev_wrapped wrapped_dev; + struct pci_bus_wrapped wrapped_bus; + int i; + + memset(&wrapped_dev, 0, sizeof (struct pci_dev_wrapped)); + memset(&wrapped_bus, 0, sizeof (struct pci_bus_wrapped)); + + for (i = 0; i < 8; i++) { + dev = pci_find_slot(slot->bus->number, + PCI_DEVFN(PCI_SLOT(slot->dev->devfn), i)); + if(!dev) + continue; + wrapped_dev.dev = dev; + wrapped_bus.bus = slot->dev->bus; + if(i) + wrapped_dev.data = NULL; + else + wrapped_dev.data = (void*) slot; + rc = pci_visit_dev(&configure_functions, &wrapped_dev, &wrapped_bus); + } + } + + dbg("%s - exit, rc = %d", __FUNCTION__, rc); + return rc; +} + +int cpci_unconfigure_slot(struct slot* slot) +{ + int rc = 0; + int i; + struct pci_dev_wrapped wrapped_dev; + struct pci_bus_wrapped wrapped_bus; + struct pci_dev *dev; + + dbg("%s - enter", __FUNCTION__); + + if(!slot->dev) { + err("No device for slot %02x\n", slot->number); + return -ENODEV; + } + + memset(&wrapped_dev, 0, sizeof (struct pci_dev_wrapped)); + memset(&wrapped_bus, 0, sizeof (struct pci_bus_wrapped)); + + for (i = 0; i < 8; i++) { + dev = pci_find_slot(slot->bus->number, + PCI_DEVFN(PCI_SLOT(slot->devfn), i)); + if(dev) { + wrapped_dev.dev = dev; + wrapped_bus.bus = dev->bus; + if(i) + wrapped_dev.data = NULL; + else + wrapped_dev.data = (void*) slot; + dbg("%s - unconfigure phase 2", __FUNCTION__); + rc = pci_visit_dev(&unconfigure_functions_phase2, + &wrapped_dev, + &wrapped_bus); + if(rc) + break; + } + } + dbg("%s - exit, rc = %d", __FUNCTION__, rc); + return rc; +} diff --git a/drivers/pci/hotplug/cpcihp_generic.c b/drivers/pci/hotplug/cpcihp_generic.c new file mode 100644 index 000000000000..a62a4345b466 --- /dev/null +++ b/drivers/pci/hotplug/cpcihp_generic.c @@ -0,0 +1,223 @@ +/* + * 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/config.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/errno.h> +#include <linux/pci.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 1; + } + 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 != 0) + return status; + + r = request_region(port, 1, "#ENUM hotswap signal register"); + if(!r) + return -EBUSY; + + dev = pci_find_slot(bridge_busnr, 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; + + 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 000000000000..e9928024be78 --- /dev/null +++ b/drivers/pci/hotplug/cpcihp_zt5550.c @@ -0,0 +1,305 @@ +/* + * 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/config.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/init.h> +#include <linux/errno.h> +#include <linux/pci.h> +#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) +{ + /* 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; + } + hc_dev = pdev; + dbg("hc_dev = %p", hc_dev); + dbg("pci resource start %lx", pci_resource_start(hc_dev, 1)); + dbg("pci resource len %lx", 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"); + return -ENOMEM; + } + + hc_registers = + ioremap(pci_resource_start(hc_dev, 1), pci_resource_len(hc_dev, 1)); + if(!hc_registers) { + err("cannot remap MMIO region %lx @ %lx", + pci_resource_len(hc_dev, 1), pci_resource_start(hc_dev, 1)); + release_mem_region(pci_resource_start(hc_dev, 1), + pci_resource_len(hc_dev, 1)); + return -ENODEV; + } + + 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; +} + +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)); + 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 = SA_SHIRQ; + 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; + + info(DRIVER_DESC " version: " DRIVER_VERSION); + r = request_region(ENUM_PORT, 1, "#ENUM hotswap signal register"); + if(!r) + return -EBUSY; + + return pci_register_driver(&zt5550_hc_driver); +} + +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 000000000000..bebc6060a558 --- /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 000000000000..092491e25ef2 --- /dev/null +++ b/drivers/pci/hotplug/cpqphp.h @@ -0,0 +1,721 @@ +/* + * 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 "pci_hotplug.h" +#include <linux/interrupt.h> +#include <asm/io.h> /* for read? and write? functions */ +#include <linux/delay.h> /* for delays */ + +#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 semaphore crit_sect; /* critical section semaphore */ + 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 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" + + +/* sysfs functions for the hotplug controller info */ +extern void cpqhp_create_ctrl_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, struct pt_regs *regs); +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 */ + +/* + * 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", + __FUNCTION__, 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; +} + +#define SLOT_NAME_SIZE 10 + +static inline void make_slot_name(char *buffer, int buffer_size, struct slot *slot) +{ + snprintf(buffer, buffer_size, "%d", slot->number); +} + + +static inline int wait_for_ctrl_irq(struct controller *ctrl) +{ + DECLARE_WAITQUEUE(wait, current); + int retval = 0; + + dbg("%s - start\n", __FUNCTION__); + 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", __FUNCTION__); + return retval; +} + +#endif + diff --git a/drivers/pci/hotplug/cpqphp_core.c b/drivers/pci/hotplug/cpqphp_core.c new file mode 100644 index 000000000000..afbccfa5217d --- /dev/null +++ b/drivers/pci/hotplug/cpqphp_core.c @@ -0,0 +1,1509 @@ +/* + * 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/config.h> +#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/init.h> +#include <linux/interrupt.h> + +#include <asm/uaccess.h> + +#include "cpqphp.h" +#include "cpqphp_nvram.h" +#include "../../../arch/i386/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; + +#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. + * + * 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. + * + * Gets the first entry if previous == NULL + * Otherwise, returns the next entry + * Uses global SMBIOS Table pointer + * + * @curr: %NULL or pointer to previously returned structure + * + * 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 + * + * @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", __FUNCTION__, hotplug_slot->name); + + kfree(slot->hotplug_slot->info); + kfree(slot->hotplug_slot->name); + kfree(slot->hotplug_slot); + kfree(slot); +} + +static int ctrl_slot_setup(struct controller *ctrl, + void __iomem *smbios_start, + void __iomem *smbios_table) +{ + struct slot *new_slot; + u8 number_of_slots; + u8 slot_device; + u8 slot_number; + u8 ctrl_slot; + u32 tempdword; + void __iomem *slot_entry= NULL; + int result = -ENOMEM; + + dbg("%s\n", __FUNCTION__); + + 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) { + new_slot = kmalloc(sizeof(*new_slot), GFP_KERNEL); + if (!new_slot) + goto error; + + memset(new_slot, 0, sizeof(struct slot)); + new_slot->hotplug_slot = kmalloc(sizeof(*(new_slot->hotplug_slot)), + GFP_KERNEL); + if (!new_slot->hotplug_slot) + goto error_slot; + memset(new_slot->hotplug_slot, 0, sizeof(struct hotplug_slot)); + + new_slot->hotplug_slot->info = + kmalloc(sizeof(*(new_slot->hotplug_slot->info)), + GFP_KERNEL); + if (!new_slot->hotplug_slot->info) + goto error_hpslot; + memset(new_slot->hotplug_slot->info, 0, + sizeof(struct hotplug_slot_info)); + new_slot->hotplug_slot->name = kmalloc(SLOT_NAME_SIZE, GFP_KERNEL); + if (!new_slot->hotplug_slot->name) + goto error_info; + + new_slot->ctrl = ctrl; + new_slot->bus = ctrl->bus; + new_slot->device = slot_device; + new_slot->number = slot_number; + dbg("slot->number = %d\n",new_slot->number); + + slot_entry = get_SMBIOS_entry(smbios_start, smbios_table, 9, + slot_entry); + + while (slot_entry && (readw(slot_entry + SMBIOS_SLOT_NUMBER) != new_slot->number)) { + slot_entry = get_SMBIOS_entry(smbios_start, + smbios_table, 9, slot_entry); + } + + new_slot->p_sm_slot = slot_entry; + + init_timer(&new_slot->task_event); + new_slot->task_event.expires = jiffies + 5 * HZ; + new_slot->task_event.function = cpqhp_pushbutton_thread; + + //FIXME: these capabilities aren't used but if they are + // they need to be correctly implemented + new_slot->capabilities |= PCISLOT_REPLACE_SUPPORTED; + new_slot->capabilities |= PCISLOT_INTERLOCK_SUPPORTED; + + if (is_slot64bit(new_slot)) + new_slot->capabilities |= PCISLOT_64_BIT_SUPPORTED; + if (is_slot66mhz(new_slot)) + new_slot->capabilities |= PCISLOT_66_MHZ_SUPPORTED; + if (ctrl->speed == PCI_SPEED_66MHz) + new_slot->capabilities |= PCISLOT_66_MHZ_OPERATION; + + ctrl_slot = slot_device - (readb(ctrl->hpc_reg + SLOT_MASK) >> 4); + + // Check presence + new_slot->capabilities |= ((((~tempdword) >> 23) | ((~tempdword) >> 15)) >> ctrl_slot) & 0x02; + // Check the switch state + new_slot->capabilities |= ((~tempdword & 0xFF) >> ctrl_slot) & 0x01; + // Check the slot enable + new_slot->capabilities |= ((read_slot_enable(ctrl) << 2) >> ctrl_slot) & 0x04; + + /* register this slot with the hotplug pci core */ + new_slot->hotplug_slot->release = &release_slot; + new_slot->hotplug_slot->private = new_slot; + make_slot_name(new_slot->hotplug_slot->name, SLOT_NAME_SIZE, new_slot); + new_slot->hotplug_slot->ops = &cpqphp_hotplug_slot_ops; + + new_slot->hotplug_slot->info->power_status = get_slot_enabled(ctrl, new_slot); + new_slot->hotplug_slot->info->attention_status = cpq_get_attention_status(ctrl, new_slot); + new_slot->hotplug_slot->info->latch_status = cpq_get_latch_status(ctrl, new_slot); + new_slot->hotplug_slot->info->adapter_status = get_presence_status(ctrl, new_slot); + + dbg ("registering bus %d, dev %d, number %d, " + "ctrl->slot_device_offset %d, slot %d\n", + new_slot->bus, new_slot->device, + new_slot->number, ctrl->slot_device_offset, + slot_number); + result = pci_hp_register (new_slot->hotplug_slot); + if (result) { + err ("pci_hp_register failed with error %d\n", result); + goto error_name; + } + + new_slot->next = ctrl->slot; + ctrl->slot = new_slot; + + number_of_slots--; + slot_device++; + slot_number++; + } + + return 0; + +error_name: + kfree(new_slot->hotplug_slot->name); +error_info: + kfree(new_slot->hotplug_slot->info); +error_hpslot: + kfree(new_slot->hotplug_slot); +error_slot: + kfree(new_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; + } + + //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", __FUNCTION__, 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_REVISION_ID, &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 + * + */ +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 + down(&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 + up(&ctrl->crit_sect); + return(1); + } + + set_SOGO(ctrl); + + // Wait for SOBS to be unset + wait_for_ctrl_irq (ctrl); + + // Done with exclusive hardware access + up(&ctrl->crit_sect); + + return(0); +} + + +/** + * set_attention_status - Turns the Amber LED for a slot on or off + * + */ +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", __FUNCTION__, hotplug_slot->name); + + 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", __FUNCTION__, hotplug_slot->name); + + 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", __FUNCTION__, hotplug_slot->name); + + 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", __FUNCTION__, 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", __FUNCTION__, hotplug_slot->name); + + 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", __FUNCTION__, hotplug_slot->name); + + *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", __FUNCTION__, hotplug_slot->name); + + *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", __FUNCTION__, hotplug_slot->name); + + *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", __FUNCTION__, hotplug_slot->name); + + *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", __FUNCTION__, hotplug_slot->name); + + *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", __FUNCTION__, hotplug_slot->name); + + *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 rev; + u8 bus_cap; + u16 temp_word; + u16 vendor_id; + u16 subsystem_vid; + u16 subsystem_deviceid; + u32 rc; + struct controller *ctrl; + struct pci_func *func; + + // 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); + return -ENODEV; + } + dbg("Vendor ID: %x\n", vendor_id); + + rc = pci_read_config_byte(pdev, PCI_REVISION_ID, &rev); + dbg("revision: %d\n", rev); + if (rc || ((vendor_id == PCI_VENDOR_ID_COMPAQ) && (!rev))) { + err(msg_HPC_rev_error); + return -ENODEV; + } + + /* 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 ((rev > 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", __FUNCTION__); + return rc; + } + 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); + return -ENODEV; + } + + ctrl = (struct controller *) kmalloc(sizeof(struct controller), GFP_KERNEL); + if (!ctrl) { + err("%s : out of memory\n", __FUNCTION__); + return -ENOMEM; + } + memset(ctrl, 0, sizeof(struct controller)); + + rc = pci_read_config_word(pdev, PCI_SUBSYSTEM_ID, &subsystem_deviceid); + if (rc) { + err("%s : pci_read_config_word failed\n", __FUNCTION__); + 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 (rev >= 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 = rev; + dbg("bus device function rev: %d %d %d %d\n", ctrl->bus, + PCI_SLOT(pdev->devfn), PCI_FUNC(pdev->devfn), ctrl->rev); + + init_MUTEX(&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 %lx\n", pci_resource_start(pdev, 0)); + dbg("pci resource len %lx\n", 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 %lx @ %lx\n", + pci_resource_len(pdev, 0), + 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", + __FUNCTION__, 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", + __FUNCTION__, 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, + SA_SHIRQ, 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 + down(&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"); + up(&ctrl->crit_sect); + goto err_free_irq; + } + + // Done with exclusive hardware access + up(&ctrl->crit_sect); + + cpqhp_create_ctrl_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); + return rc; +} + + +static int one_time_init(void) +{ + int loop; + int retval = 0; + static int initialized = 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 + 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"); + 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); +} + + +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 000000000000..10a5a7674a8a --- /dev/null +++ b/drivers/pci/hotplug/cpqphp_ctrl.c @@ -0,0 +1,3096 @@ +/* + * 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/config.h> +#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 "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 semaphore event_semaphore; /* mutex for process loop (up if something to process) */ +static struct semaphore event_exit; /* guard ensure thread has exited before calling it quits */ +static int event_finished; +static unsigned long pushbutton_pending; /* = 0 */ + +/* things needed for the long_delay function */ +static struct semaphore delay_sem; +static wait_queue_head_t delay_wait; + +/* delay is in jiffies to wait for */ +static void long_delay(int delay) +{ + DECLARE_WAITQUEUE(wait, current); + + /* only allow 1 customer into the delay queue at once + * yes this makes some people wait even longer, but who really cares? + * this is for _huge_ delays to make the hardware happy as the + * signals bounce around + */ + down (&delay_sem); + + init_waitqueue_head(&delay_wait); + + add_wait_queue(&delay_wait, &wait); + msleep_interruptible(jiffies_to_msecs(delay)); + remove_wait_queue(&delay_wait, &wait); + + up(&delay_sem); +} + + +/* 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 + * + */ +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 + * + */ +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", + __FUNCTION__, size, node, node->base, node->length); + if (node->length < size) + continue; + + if (node->base & (size - 1)) { + dbg("%s: not aligned\n", __FUNCTION__); + /* 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", __FUNCTION__); + /* 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", __FUNCTION__); + /* 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", __FUNCTION__, 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 pt_regs *regs) +{ + 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", __FUNCTION__); + 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) { + up(&event_semaphore); + dbg("Signal event_semaphore\n"); + } + 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 = kmalloc(sizeof(*new_slot), GFP_KERNEL); + + if (new_slot == NULL) { + /* I'm not dead yet! + * You will be. */ + return new_slot; + } + + memset(new_slot, 0, sizeof(struct pci_func)); + + 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. + * + * 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 index; + u32 rc = 0; + u32 src = 8; + + 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 { + down(&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); + + up(&ctrl->crit_sect); + + if (rc) + return rc; + + down(&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); + + up(&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 (rc || src) { + /* 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. */ + + down(&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); + + up(&ctrl->crit_sect); + + if (rc) + return rc; + else + return 1; + } + + func->status = 0; + func->switch_save = 0x10; + + index = 1; + while (((func = cpqhp_slot_find(func->bus, func->device, index)) != NULL) && !rc) { + rc |= cpqhp_configure_board(ctrl, func); + index++; + } + + if (rc) { + /* 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. */ + + down(&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); + + up(&ctrl->crit_sect); + + return rc; + } + /* Done configuring so turn LED on full time */ + + down(&ctrl->crit_sect); + + green_LED_on (ctrl, hp_slot); + + set_SOGO(ctrl); + + /* Wait for SOBS to be unset */ + wait_for_ctrl_irq (ctrl); + + up(&ctrl->crit_sect); + rc = 0; + } 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. */ + + down(&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); + + up(&ctrl->crit_sect); + } + + } + return rc; + +} + + +/** + * board_added - Called after a board has been added to the system. + * + * 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", + __FUNCTION__, func->device, ctrl->slot_device_offset, hp_slot); + + down(&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); + + up(&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", __FUNCTION__); + down(&ctrl->crit_sect); + dbg("%s: after down\n", __FUNCTION__); + + dbg("%s: before slot_enable\n", __FUNCTION__); + slot_enable (ctrl, hp_slot); + + dbg("%s: before green_LED_blink\n", __FUNCTION__); + green_LED_blink (ctrl, hp_slot); + + dbg("%s: before amber_LED_blink\n", __FUNCTION__); + amber_LED_off (ctrl, hp_slot); + + dbg("%s: before set_SOGO\n", __FUNCTION__); + set_SOGO(ctrl); + + /* Wait for SOBS to be unset */ + dbg("%s: before wait_for_ctrl_irq\n", __FUNCTION__); + wait_for_ctrl_irq (ctrl); + dbg("%s: after wait_for_ctrl_irq\n", __FUNCTION__); + + dbg("%s: before up\n", __FUNCTION__); + up(&ctrl->crit_sect); + dbg("%s: after up\n", __FUNCTION__); + + /* Wait for ~1 second because of hot plug spec */ + dbg("%s: before long_delay\n", __FUNCTION__); + long_delay(1*HZ); + dbg("%s: after long_delay\n", __FUNCTION__); + + dbg("%s: func status = %x\n", __FUNCTION__, 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", __FUNCTION__, 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", __FUNCTION__, rc); + dbg("%s: temp_register is %x\n", __FUNCTION__, temp_register); + + if (rc != 0) { + /* Something's wrong here */ + temp_register = 0xFFFFFFFF; + dbg("%s: temp register set to %x by error\n", __FUNCTION__, 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", __FUNCTION__); + 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) { + down(&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); + + up(&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", __FUNCTION__); + 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); + + down(&ctrl->crit_sect); + + green_LED_on (ctrl, hp_slot); + + set_SOGO(ctrl); + + /* Wait for SOBS to be unset */ + wait_for_ctrl_irq (ctrl); + + up(&ctrl->crit_sect); + } else { + down(&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); + + up(&ctrl->crit_sect); + + return rc; + } + return 0; +} + + +/** + * remove_board - Turns off slot and LED's + * + */ +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", __FUNCTION__, 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; + + down(&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); + + up(&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; + up(&event_semaphore); +} + + +/* this is the main worker thread */ +static int event_thread(void* data) +{ + struct controller *ctrl; + lock_kernel(); + daemonize("phpd_event"); + + unlock_kernel(); + + while (1) { + dbg("!!!!event_thread sleeping\n"); + down_interruptible (&event_semaphore); + dbg("event_thread woken finished = %d\n", event_finished); + if (event_finished) 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"); + up(&event_exit); + return 0; +} + + +int cpqhp_event_start_thread(void) +{ + int pid; + + /* initialize our semaphores */ + init_MUTEX(&delay_sem); + init_MUTEX_LOCKED(&event_semaphore); + init_MUTEX_LOCKED(&event_exit); + event_finished=0; + + pid = kernel_thread(event_thread, NULL, 0); + if (pid < 0) { + err ("Can't start up our event thread\n"); + return -1; + } + dbg("Our event thread pid = %d\n", pid); + return 0; +} + + +void cpqhp_event_stop_thread(void) +{ + event_finished = 1; + dbg("event_thread finish command given\n"); + up(&event_semaphore); + dbg("wait for event_thread to exit\n"); + down(&event_exit); +} + + +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); + + down(&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); + + up(&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); + } + down(&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); + + up(&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 + * + * 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", __FUNCTION__); + return ; + } + + if (func != NULL && ctrl != NULL) { + 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", __FUNCTION__); + 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", __FUNCTION__, 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 + * @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); + } +} + +/** + * hardware_test - runs hardware tests + * + * For hot plug ctrl folks to play with. + * test_num is the number written to the "test" file in sysfs + * + */ +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", __FUNCTION__); + /* 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", __FUNCTION__, 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; + } + + /* If we have prefetchable 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 (p_mem_node) { + 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); + } else { + temp_word = 0xFFFF; + rc = pci_bus_write_config_word (pci_bus, devfn, PCI_PREF_MEMORY_BASE, temp_word); + + temp_word = 0x0000; + rc = pci_bus_write_config_word (pci_bus, devfn, PCI_PREF_MEMORY_LIMIT, temp_word); + + kfree(hold_p_mem_node); + hold_p_mem_node = NULL; + } + + /* 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 000000000000..ac98a11bd1eb --- /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/config.h> +#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/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", __FUNCTION__, 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 = (struct pci_resource*) 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 = (struct pci_resource*) 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 = (struct pci_resource*) 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 = (struct pci_resource*) 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 000000000000..e89c0702119d --- /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 000000000000..93e39c4096a9 --- /dev/null +++ b/drivers/pci/hotplug/cpqphp_pci.c @@ -0,0 +1,1569 @@ +/* + * 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/config.h> +#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 "../pci.h" +#include "cpqphp.h" +#include "cpqphp_nvram.h" +#include "../../../arch/i386/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", __FUNCTION__, 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", + __FUNCTION__, 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", __FUNCTION__, 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) { + if (PCIIRQRoutingInfoLength != NULL) + 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)) { + if (PCIIRQRoutingInfoLength != NULL) + 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; + if (PCIIRQRoutingInfoLength != NULL) + kfree(PCIIRQRoutingInfoLength ); + return 0; + } + } else { + if (PCIIRQRoutingInfoLength != NULL) + kfree(PCIIRQRoutingInfoLength ); + return 0; + } + + } + } + if (PCIIRQRoutingInfoLength != NULL) + 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", __FUNCTION__); + + 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 000000000000..41c7971d06c5 --- /dev/null +++ b/drivers/pci/hotplug/cpqphp_sysfs.c @@ -0,0 +1,143 @@ +/* + * 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/config.h> +#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 "cpqphp.h" + + +/* A few routines that create sysfs entries for the hot plug controller */ + +static ssize_t show_ctrl (struct device *dev, char *buf) +{ + struct pci_dev *pci_dev; + struct controller *ctrl; + char * out = buf; + int index; + struct pci_resource *res; + + pci_dev = container_of (dev, struct pci_dev, dev); + ctrl = pci_get_drvdata(pci_dev); + + 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 DEVICE_ATTR (ctrl, S_IRUGO, show_ctrl, NULL); + +static ssize_t show_dev (struct device *dev, char *buf) +{ + struct pci_dev *pci_dev; + struct controller *ctrl; + char * out = buf; + int index; + struct pci_resource *res; + struct pci_func *new_slot; + struct slot *slot; + + pci_dev = container_of (dev, struct pci_dev, dev); + ctrl = pci_get_drvdata(pci_dev); + + 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 DEVICE_ATTR (dev, S_IRUGO, show_dev, NULL); + +void cpqhp_create_ctrl_files (struct controller *ctrl) +{ + device_create_file (&ctrl->pci_dev->dev, &dev_attr_ctrl); + device_create_file (&ctrl->pci_dev->dev, &dev_attr_dev); +} diff --git a/drivers/pci/hotplug/fakephp.c b/drivers/pci/hotplug/fakephp.c new file mode 100644 index 000000000000..8e47fa66e25e --- /dev/null +++ b/drivers/pci/hotplug/fakephp.c @@ -0,0 +1,358 @@ +/* + * 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/config.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/pci.h> +#include <linux/init.h> +#include "pci_hotplug.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; +}; + +static int debug; +static LIST_HEAD(slot_list); + +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); +} + +static int add_slot(struct pci_dev *dev) +{ + struct dummy_slot *dslot; + struct hotplug_slot *slot; + int retval = -ENOMEM; + + slot = kmalloc(sizeof(struct hotplug_slot), GFP_KERNEL); + if (!slot) + goto error; + memset(slot, 0, sizeof(*slot)); + + slot->info = kmalloc(sizeof(struct hotplug_slot_info), GFP_KERNEL); + if (!slot->info) + goto error_slot; + memset(slot->info, 0, sizeof(struct hotplug_slot_info)); + + slot->info->power_status = 1; + slot->info->max_bus_speed = PCI_SPEED_UNKNOWN; + slot->info->cur_bus_speed = PCI_SPEED_UNKNOWN; + + slot->name = &dev->dev.bus_id[0]; + dbg("slot->name = %s\n", slot->name); + + dslot = kmalloc(sizeof(struct dummy_slot), GFP_KERNEL); + if (!dslot) + goto error_info; + + slot->ops = &dummy_hotplug_slot_ops; + slot->release = &dummy_release; + slot->private = dslot; + + retval = pci_hp_register(slot); + if (retval) { + err("pci_hp_register failed with error %d\n", retval); + goto error_dslot; + } + + 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 retval = 0; + + while ((dev = pci_get_device(PCI_ANY_ID, PCI_ANY_ID, dev)) != NULL) { + retval = add_slot(dev); + if (retval) { + pci_dev_put(dev); + break; + } + } + + return retval; +} + +static void remove_slot(struct dummy_slot *dslot) +{ + int retval; + + dbg("removing slot %s\n", dslot->slot->name); + retval = pci_hp_deregister(dslot->slot); + if (retval) + err("Problem unregistering a slot %s\n", dslot->slot->name); +} + +/** + * Rescan slot. + * Tries hard not to re-enable already existing devices + * also handles scanning of subfunctions + * + * @param temp Device template. Should be set: bus and devfn. + */ +static void pci_rescan_slot(struct pci_dev *temp) +{ + struct pci_bus *bus = temp->bus; + struct pci_dev *dev; + int func; + u8 hdr_type; + if (!pci_read_config_byte(temp, PCI_HEADER_TYPE, &hdr_type)) { + temp->hdr_type = hdr_type & 0x7f; + if (!pci_find_slot(bus->number, temp->devfn)) { + 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); + pci_bus_add_device(dev); + 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 (!pci_find_slot(bus->number, temp->devfn)) { + 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); + pci_bus_add_device(dev); + add_slot(dev); + } + } + } + } +} + + +/** + * Rescan PCI bus. + * call pci_rescan_slot for each possible function of the bus + * + * @param bus + */ +static void pci_rescan_bus(const struct pci_bus *bus) +{ + unsigned int devfn; + struct pci_dev *dev; + dev = kmalloc(sizeof(struct pci_dev), GFP_KERNEL); + if (!dev) + return; + + memset(dev, 0, sizeof(dev)); + 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); +} + + +static int enable_slot(struct hotplug_slot *hotplug_slot) +{ + /* mis-use enable_slot for rescanning of the pci bus */ + pci_rescan(); + return -ENODEV; +} + +/* find the hotplug_slot for the pci_dev */ +static struct hotplug_slot *get_slot_from_dev(struct pci_dev *dev) +{ + struct dummy_slot *dslot; + + list_for_each_entry(dslot, &slot_list, node) { + if (dslot->dev == dev) + return dslot->slot; + } + return NULL; +} + + +static int disable_slot(struct hotplug_slot *slot) +{ + struct dummy_slot *dslot; + struct hotplug_slot *hslot; + struct pci_dev *dev; + int func; + + if (!slot) + return -ENODEV; + dslot = slot->private; + + dbg("%s - physical_slot = %s\n", __FUNCTION__, slot->name); + + /* don't disable bridged devices just yet, we can't handle them easily... */ + if (dslot->dev->subordinate) { + err("Can't remove PCI devices with other PCI devices behind it yet.\n"); + return -ENODEV; + } + /* search for subfunctions and disable them first */ + if (!(dslot->dev->devfn & 7)) { + for (func = 1; func < 8; func++) { + dev = pci_find_slot(dslot->dev->bus->number, + dslot->dev->devfn + func); + if (dev) { + hslot = get_slot_from_dev(dev); + if (hslot) + disable_slot(hslot); + else { + err("Hotplug slot not found for subfunction of PCI device\n"); + return -ENODEV; + } + } else + dbg("No device in slot found\n"); + } + } + + /* remove the device from the pci core */ + pci_remove_bus_device(dslot->dev); + + /* blow away this sysfs entry and other parts. */ + remove_slot(dslot); + + return 0; +} + +static void cleanup_slots (void) +{ + struct list_head *tmp; + struct list_head *next; + struct dummy_slot *dslot; + + 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"); + + 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"); + diff --git a/drivers/pci/hotplug/ibmphp.h b/drivers/pci/hotplug/ibmphp.h new file mode 100644 index 000000000000..5bc039da647f --- /dev/null +++ b/drivers/pci/hotplug/ibmphp.h @@ -0,0 +1,763 @@ +#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 "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 CONTROLER * +********************************************************************/ + +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 FALSE 0x00 +#define TRUE 0x01 +#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; + char name[100]; + u32 capabilities; + u8 supported_speed; + u8 supported_bus_mode; + struct hotplug_slot *hotplug_slot; + struct controller *ctrl; + struct pci_func *func; + u8 irq[4]; + u8 flag; /* this is for disable slot and polling */ + int bit_mode; /* 0 = 32, 1 = 64 */ + u8 ctlr_index; + 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 000000000000..0392e004258f --- /dev/null +++ b/drivers/pci/hotplug/ibmphp_core.c @@ -0,0 +1,1422 @@ +/* + * 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 <linux/smp_lock.h> +#include "../pci.h" +#include "../../../arch/i386/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) + 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"); + return 0; + } + } + } + + 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; + + debug("set_attention_status - Entry hotplug_slot[%lx] value[%x]\n", + (ulong) hotplug_slot, value); + ibmphp_lock_operations(); + cmd = 0x00; // avoid compiler warning + + 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 && value) { + 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 && value) { + 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 && value) { + 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 && value) { + 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", __FUNCTION__, + hotplug_slot, value); + + ibmphp_lock_operations(); + + if (hotplug_slot && value) { + 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", __FUNCTION__, 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", __FUNCTION__, + hotplug_slot, value); + + ibmphp_lock_operations(); + + if (hotplug_slot && value) { + 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", __FUNCTION__, 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", __FUNCTION__); + + 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", __FUNCTION__); +} + +static void ibm_unconfigure_device(struct pci_func *func) +{ + struct pci_dev *temp; + u8 j; + + debug("inside %s\n", __FUNCTION__); + 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_find_slot(func->busno, (func->device << 3) | j); + if (temp) + pci_remove_bus_device(temp); + } +} + +/* + * 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", __FUNCTION__); + return 1; + } + dev = kmalloc(sizeof(*dev), GFP_KERNEL); + if (!dev) { + kfree(bus); + err("%s - out of memory\n", __FUNCTION__); + 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", + __FUNCTION__); + 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_find_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_find_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", __FUNCTION__, 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", __FUNCTION__); + 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 = kmalloc(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; + } + memset(slot_cur->func, 0, sizeof(struct pci_func)); + 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 = TRUE; + + if (flag == TRUE) { + 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 = kmalloc(sizeof(struct pci_func), GFP_KERNEL); + if (!slot_cur->func) { + err("out of system memory\n"); + rc = -ENOMEM; + goto error; + } + memset(slot_cur->func, 0, sizeof(struct pci_func)); + 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; + } + + /* lock ourselves into memory with a module + * count of -1 so that no one can unload us. */ + module_put(THIS_MODULE); + +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); +module_exit(ibmphp_exit); diff --git a/drivers/pci/hotplug/ibmphp_ebda.c b/drivers/pci/hotplug/ibmphp_ebda.c new file mode 100644 index 000000000000..aea1187c73ad --- /dev/null +++ b/drivers/pci/hotplug/ibmphp_ebda.c @@ -0,0 +1,1275 @@ +/* + * 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/sched.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) +{ + struct ebda_hpc_list *list; + + list = kmalloc (sizeof (struct ebda_hpc_list), GFP_KERNEL); + if (!list) + return NULL; + memset (list, 0, sizeof (*list)); + return list; +} + +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 = kmalloc (sizeof (struct controller), GFP_KERNEL); + if (!controller) + goto error; + memset (controller, 0, sizeof (*controller)); + + slots = kmalloc (sizeof (struct ebda_hpc_slot) * slot_count, GFP_KERNEL); + if (!slots) + goto error_contr; + memset (slots, 0, sizeof (*slots) * slot_count); + controller->slots = slots; + + buses = kmalloc (sizeof (struct ebda_hpc_bus) * bus_count, GFP_KERNEL); + if (!buses) + goto error_slots; + memset (buses, 0, sizeof (*buses) * bus_count); + 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) +{ + struct ebda_rsrc_list *list; + + list = kmalloc (sizeof (struct ebda_rsrc_list), GFP_KERNEL); + if (!list) + return NULL; + memset (list, 0, sizeof (*list)); + return list; +} + +static struct ebda_pci_rsrc *alloc_ebda_pci_rsrc (void) +{ + struct ebda_pci_rsrc *resource; + + resource = kmalloc (sizeof (struct ebda_pci_rsrc), GFP_KERNEL); + if (!resource) + return NULL; + memset (resource, 0, sizeof (*resource)); + return resource; +} + +static void __init print_bus_info (void) +{ + struct bus_info *ptr; + struct list_head *ptr1; + + list_for_each (ptr1, &bus_info_head) { + ptr = list_entry (ptr1, struct bus_info, bus_info_list); + debug ("%s - slot_min = %x\n", __FUNCTION__, ptr->slot_min); + debug ("%s - slot_max = %x\n", __FUNCTION__, ptr->slot_max); + debug ("%s - slot_count = %x\n", __FUNCTION__, ptr->slot_count); + debug ("%s - bus# = %x\n", __FUNCTION__, ptr->busno); + debug ("%s - current_speed = %x\n", __FUNCTION__, ptr->current_speed); + debug ("%s - controller_id = %x\n", __FUNCTION__, ptr->controller_id); + + debug ("%s - slots_at_33_conv = %x\n", __FUNCTION__, ptr->slots_at_33_conv); + debug ("%s - slots_at_66_conv = %x\n", __FUNCTION__, ptr->slots_at_66_conv); + debug ("%s - slots_at_66_pcix = %x\n", __FUNCTION__, ptr->slots_at_66_pcix); + debug ("%s - slots_at_100_pcix = %x\n", __FUNCTION__, ptr->slots_at_100_pcix); + debug ("%s - slots_at_133_pcix = %x\n", __FUNCTION__, ptr->slots_at_133_pcix); + + } +} + +static void print_lo_info (void) +{ + struct rio_detail *ptr; + struct list_head *ptr1; + debug ("print_lo_info ----\n"); + list_for_each (ptr1, &rio_lo_head) { + ptr = list_entry (ptr1, struct rio_detail, rio_detail_list); + debug ("%s - rio_node_id = %x\n", __FUNCTION__, ptr->rio_node_id); + debug ("%s - rio_type = %x\n", __FUNCTION__, ptr->rio_type); + debug ("%s - owner_id = %x\n", __FUNCTION__, ptr->owner_id); + debug ("%s - first_slot_num = %x\n", __FUNCTION__, ptr->first_slot_num); + debug ("%s - wpindex = %x\n", __FUNCTION__, ptr->wpindex); + debug ("%s - chassis_num = %x\n", __FUNCTION__, ptr->chassis_num); + + } +} + +static void print_vg_info (void) +{ + struct rio_detail *ptr; + struct list_head *ptr1; + debug ("%s ---\n", __FUNCTION__); + list_for_each (ptr1, &rio_vg_head) { + ptr = list_entry (ptr1, struct rio_detail, rio_detail_list); + debug ("%s - rio_node_id = %x\n", __FUNCTION__, ptr->rio_node_id); + debug ("%s - rio_type = %x\n", __FUNCTION__, ptr->rio_type); + debug ("%s - owner_id = %x\n", __FUNCTION__, ptr->owner_id); + debug ("%s - first_slot_num = %x\n", __FUNCTION__, ptr->first_slot_num); + debug ("%s - wpindex = %x\n", __FUNCTION__, ptr->wpindex); + debug ("%s - chassis_num = %x\n", __FUNCTION__, ptr->chassis_num); + + } +} + +static void __init print_ebda_pci_rsrc (void) +{ + struct ebda_pci_rsrc *ptr; + struct list_head *ptr1; + + list_for_each (ptr1, &ibmphp_ebda_pci_rsrc_head) { + ptr = list_entry (ptr1, struct ebda_pci_rsrc, ebda_pci_rsrc_list); + debug ("%s - rsrc type: %x bus#: %x dev_func: %x start addr: %x end addr: %x\n", + __FUNCTION__, 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; + struct list_head *ptr1; + + list_for_each (ptr1, &ibmphp_slot_head) { + ptr = list_entry (ptr1, struct slot, ibm_slot_list); + debug ("%s - slot_number: %x\n", __FUNCTION__, ptr->number); + } +} + +static void __init print_opt_vg (void) +{ + struct opt_rio *ptr; + struct list_head *ptr1; + debug ("%s ---\n", __FUNCTION__); + list_for_each (ptr1, &opt_vg_head) { + ptr = list_entry (ptr1, struct opt_rio, opt_rio_list); + debug ("%s - rio_type %x\n", __FUNCTION__, ptr->rio_type); + debug ("%s - chassis_num: %x\n", __FUNCTION__, ptr->chassis_num); + debug ("%s - first_slot_num: %x\n", __FUNCTION__, ptr->first_slot_num); + debug ("%s - middle_num: %x\n", __FUNCTION__, ptr->middle_num); + } +} + +static void __init print_ebda_hpc (void) +{ + struct controller *hpc_ptr; + struct list_head *ptr1; + u16 index; + + list_for_each (ptr1, &ebda_hpc_head) { + + hpc_ptr = list_entry (ptr1, struct controller, ebda_hpc_list); + + for (index = 0; index < hpc_ptr->slot_count; index++) { + debug ("%s - physical slot#: %x\n", __FUNCTION__, hpc_ptr->slots[index].slot_num); + debug ("%s - pci bus# of the slot: %x\n", __FUNCTION__, hpc_ptr->slots[index].slot_bus_num); + debug ("%s - index into ctlr addr: %x\n", __FUNCTION__, hpc_ptr->slots[index].ctl_index); + debug ("%s - cap of the slot: %x\n", __FUNCTION__, 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", __FUNCTION__, hpc_ptr->buses[index].bus_num); + } + + debug ("%s - type of hpc: %x\n", __FUNCTION__, hpc_ptr->ctlr_type); + switch (hpc_ptr->ctlr_type) { + case 1: + debug ("%s - bus: %x\n", __FUNCTION__, hpc_ptr->u.pci_ctlr.bus); + debug ("%s - dev_fun: %x\n", __FUNCTION__, hpc_ptr->u.pci_ctlr.dev_fun); + debug ("%s - irq: %x\n", __FUNCTION__, hpc_ptr->irq); + break; + + case 0: + debug ("%s - io_start: %x\n", __FUNCTION__, hpc_ptr->u.isa_ctlr.io_start); + debug ("%s - io_end: %x\n", __FUNCTION__, hpc_ptr->u.isa_ctlr.io_end); + debug ("%s - irq: %x\n", __FUNCTION__, hpc_ptr->irq); + break; + + case 2: + case 4: + debug ("%s - wpegbbar: %lx\n", __FUNCTION__, hpc_ptr->u.wpeg_ctlr.wpegbbar); + debug ("%s - i2c_addr: %x\n", __FUNCTION__, hpc_ptr->u.wpeg_ctlr.i2c_addr); + debug ("%s - irq: %x\n", __FUNCTION__, 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, 65000); + 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 = kmalloc (sizeof (struct rio_table_hdr), GFP_KERNEL); + if (!rio_table_ptr) + return -ENOMEM; + memset (rio_table_ptr, 0, sizeof (struct rio_table_hdr) ); + 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 = kmalloc (sizeof (struct rio_detail), GFP_KERNEL); + if (!rio_detail_ptr) + return -ENOMEM; + memset (rio_detail_ptr, 0, sizeof (struct rio_detail)); + 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; + struct list_head *ptr1; + list_for_each (ptr1, &opt_vg_head) { + ptr = list_entry (ptr1, struct opt_rio, 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; + struct list_head *list_head_ptr = NULL; + + list_for_each (list_head_ptr, &rio_vg_head) { + rio_detail_ptr = list_entry (list_head_ptr, struct rio_detail, rio_detail_list); + opt_rio_ptr = search_opt_vg (rio_detail_ptr->chassis_num); + if (!opt_rio_ptr) { + opt_rio_ptr = (struct opt_rio *) kmalloc (sizeof (struct opt_rio), GFP_KERNEL); + if (!opt_rio_ptr) + return -ENOMEM; + memset (opt_rio_ptr, 0, sizeof (struct opt_rio)); + 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; +} + +/* + * reorgnizing linked list of expansion box + */ +static struct opt_rio_lo *search_opt_lo (u8 chassis_num) +{ + struct opt_rio_lo *ptr; + struct list_head *ptr1; + list_for_each (ptr1, &opt_lo_head) { + ptr = list_entry (ptr1, struct opt_rio_lo, 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; + struct list_head *list_head_ptr = NULL; + + list_for_each (list_head_ptr, &rio_lo_head) { + rio_detail_ptr = list_entry (list_head_ptr, struct rio_detail, rio_detail_list); + opt_rio_lo_ptr = search_opt_lo (rio_detail_ptr->chassis_num); + if (!opt_rio_lo_ptr) { + opt_rio_lo_ptr = (struct opt_rio_lo *) kmalloc (sizeof (struct opt_rio_lo), GFP_KERNEL); + if (!opt_rio_lo_ptr) + return -ENOMEM; + memset (opt_rio_lo_ptr, 0, sizeof (struct opt_rio_lo)); + 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; + struct list_head *ptr = NULL; + int rc = 0; + + if (!var) { + list_for_each (ptr, &opt_vg_head) { + opt_vg_ptr = list_entry (ptr, struct opt_rio, 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 (ptr, &opt_lo_head) { + opt_lo_ptr = list_entry (ptr, struct opt_rio_lo, 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; + struct list_head *ptr; + + list_for_each (ptr, &opt_lo_head) { + opt_lo_ptr = list_entry (ptr, struct opt_rio_lo, 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; + struct list_head *ptr; + + list_for_each (ptr, &opt_vg_head) { + opt_vg_ptr = list_entry (ptr, struct opt_rio, 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 list_head * list; + struct slot * slot_cur; + + list_for_each (list, &ibmphp_slot_head) { + slot_cur = list_entry (list, struct slot, 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; + +} +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[30]; + 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->name); + 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; + struct list_head *list; + + 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 = (struct bus_info *) kmalloc (sizeof (struct bus_info), GFP_KERNEL); + if (!bus_info_ptr1) { + rc = -ENOMEM; + goto error_no_hp_slot; + } + memset (bus_info_ptr1, 0, sizeof (struct bus_info)); + 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 = kmalloc(sizeof(*hp_slot_ptr), GFP_KERNEL); + if (!hp_slot_ptr) { + rc = -ENOMEM; + goto error_no_hp_slot; + } + memset(hp_slot_ptr, 0, sizeof(*hp_slot_ptr)); + + hp_slot_ptr->info = kmalloc (sizeof(struct hotplug_slot_info), GFP_KERNEL); + if (!hp_slot_ptr->info) { + rc = -ENOMEM; + goto error_no_hp_info; + } + memset(hp_slot_ptr->info, 0, sizeof(struct hotplug_slot_info)); + + hp_slot_ptr->name = kmalloc(30, GFP_KERNEL); + if (!hp_slot_ptr->name) { + rc = -ENOMEM; + goto error_no_hp_name; + } + + tmp_slot = kmalloc(sizeof(*tmp_slot), GFP_KERNEL); + if (!tmp_slot) { + rc = -ENOMEM; + goto error_no_slot; + } + memset(tmp_slot, 0, sizeof(*tmp_slot)); + + tmp_slot->flag = TRUE; + + 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) { + 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 (list, &ibmphp_slot_head) { + tmp_slot = list_entry (list, struct slot, ibm_slot_list); + + snprintf (tmp_slot->hotplug_slot->name, 30, "%s", create_file_name (tmp_slot)); + pci_hp_register (tmp_slot->hotplug_slot); + } + + print_ebda_hpc (); + print_ibm_slot (); + return 0; + +error: + kfree (hp_slot_ptr->private); +error_no_slot: + kfree (hp_slot_ptr->name); +error_no_hp_name: + 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; + struct list_head *list; + + list_for_each (list, &ibmphp_slot_head) { + slot = list_entry (list, struct slot, 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; + struct list_head *ptr1; + + list_for_each (ptr1, &bus_info_head) { + ptr = list_entry (ptr1, struct bus_info, 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; + struct list_head *ptr1; + + list_for_each (ptr1, &bus_info_head) { + ptr = list_entry (ptr1, struct bus_info, 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; + struct list_head *tmp; + int rc = 0; + + list_for_each (tmp, &ebda_hpc_head) { + ctrl = list_entry (tmp, struct controller, 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; + struct list_head *tmp; + + debug ("inside ibmphp_probe\n"); + + list_for_each (tmp, &ebda_hpc_head) { + ctrl = list_entry (tmp, struct controller, 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 000000000000..6894b548c8ca --- /dev/null +++ b/drivers/pci/hotplug/ibmphp_hpc.c @@ -0,0 +1,1161 @@ +/* + * 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/smp_lock.h> +#include <linux/init.h> +#include "ibmphp.h" + +static int to_debug = FALSE; +#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 Regisetr +//---------------------------------------------------------------------------- +#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 TRUE +#define HPC_I2CSTATUS_CHECK(s) ((u8)((s & 0x00000A76) ? FALSE : TRUE)) + +//---------------------------------------------------------------------------- +// global variables +//---------------------------------------------------------------------------- +static int ibmphp_shutdown; +static int tid_poll; +static struct semaphore 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 +//---------------------------------------------------------------------------- +// 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 void poll_hpc (void); +static int process_changeinstatus (struct slot *, struct slot *); +static int process_changeinlatch (u8, u8, struct controller *); +static int hpc_poll_thread (void *); +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", __FUNCTION__); + + init_MUTEX (&sem_hpcaccess); + init_MUTEX (&semOperations); + init_MUTEX_LOCKED (&sem_exit); + to_debug = FALSE; + ibmphp_shutdown = FALSE; + tid_poll = 0; + + debug ("%s - Exit\n", __FUNCTION__); +} + +/*---------------------------------------------------------------------- +* 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", __FUNCTION__, 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", __FUNCTION__); + 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", __FUNCTION__, 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", __FUNCTION__, 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", __FUNCTION__); + 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", __FUNCTION__, 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 - can not 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", __FUNCTION__, pslot, cmd, pstatus); + + if ((pslot == NULL) + || ((pstatus == NULL) && (cmd != READ_ALLSTAT) && (cmd != READ_BUSSTATUS))) { + rc = -EINVAL; + err ("%s - Error invalid pointer, rc[%d]\n", __FUNCTION__, 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", __FUNCTION__, 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", __FUNCTION__, 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", __FUNCTION__); + 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", __FUNCTION__, 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", __FUNCTION__, pslot, cmd); + if (pslot == NULL) { + rc = -EINVAL; + err ("%s - Error Exit rc[%d]\n", __FUNCTION__, 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", __FUNCTION__, 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", __FUNCTION__, 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", __FUNCTION__, + 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 = FALSE; + 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 = TRUE; + } else + done = TRUE; + } + if (!done) { + msleep(1000); + if (timeout < 1) { + done = TRUE; + err ("%s - Error command complete timeout\n", __FUNCTION__); + 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", __FUNCTION__, 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) +{ + down (&sem_hpcaccess); +} + +/*---------------------------------------------------------------------- +* Name: free_hpc_access() +*---------------------------------------------------------------------*/ +void free_hpc_access (void) +{ + up (&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 = TRUE; +} + +/*---------------------------------------------------------------------- +* Name: ibmphp_unlock_operations() +*---------------------------------------------------------------------*/ +void ibmphp_unlock_operations (void) +{ + debug ("%s - Entry\n", __FUNCTION__); + up (&semOperations); + to_debug = FALSE; + debug ("%s - Exit\n", __FUNCTION__); +} + +/*---------------------------------------------------------------------- +* Name: poll_hpc() +*---------------------------------------------------------------------*/ +#define POLL_LATCH_REGISTER 0 +#define POLL_SLOTS 1 +#define POLL_SLEEP 2 +static void poll_hpc (void) +{ + 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", __FUNCTION__); + + while (!ibmphp_shutdown) { + if (ibmphp_shutdown) + break; + + /* try to get the lock to do some kind of harware 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 (ibmphp_shutdown) + 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 harware semaphore */ + up (&semOperations); + /* sleep for a short time just for good measure */ + msleep(100); + } + up (&sem_exit); + debug ("%s - Exit\n", __FUNCTION__); +} + + +/*---------------------------------------------------------------------- +* 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 = FALSE; + u8 update = FALSE; + + debug ("process_changeinstatus - Entry pslot[%p], poldslot[%p]\n", pslot, poldslot); + + // bit 0 - HPC_SLOT_POWER + if ((pslot->status & 0x01) != (poldslot->status & 0x01)) + update = TRUE; + + // bit 1 - HPC_SLOT_CONNECT + // ignore + + // bit 2 - HPC_SLOT_ATTN + if ((pslot->status & 0x04) != (poldslot->status & 0x04)) + update = TRUE; + + // bit 3 - HPC_SLOT_PRSNT2 + // bit 4 - HPC_SLOT_PRSNT1 + if (((pslot->status & 0x08) != (poldslot->status & 0x08)) + || ((pslot->status & 0x10) != (poldslot->status & 0x10))) + update = TRUE; + + // 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 = TRUE; + + // bit 6 - HPC_SLOT_BUS_SPEED + // ignore + + // bit 7 - HPC_SLOT_LATCH + if ((pslot->status & 0x80) != (poldslot->status & 0x80)) { + update = TRUE; + // 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 = TRUE; + 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 = TRUE; + } + // else - ignore + } + // bit 4 - HPC_SLOT_BLINK_ATTN + if ((pslot->ext_status & 0x08) != (poldslot->ext_status & 0x08)) + update = TRUE; + + if (disable) { + debug ("process_changeinstatus - disable slot\n"); + pslot->flag = FALSE; + rc = ibmphp_do_disable_slot (pslot); + } + + if (update || disable) { + ibmphp_update_slot_info (pslot); + } + + debug ("%s - Exit rc[%d] disable[%x] update[%x]\n", __FUNCTION__, 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", __FUNCTION__, 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", __FUNCTION__, i); + process_changeinstatus (pslot, &myslot); + } else { + rc = -EINVAL; + err ("%s - Error bad pointer for slot[%d]\n", __FUNCTION__, i); + } + } + } + debug ("%s - Exit rc[%d]\n", __FUNCTION__, rc); + return rc; +} + +/*---------------------------------------------------------------------- +* Name: hpc_poll_thread +* +* Action: polling +* +* Return 0 +* Value: +*---------------------------------------------------------------------*/ +static int hpc_poll_thread (void *data) +{ + debug ("%s - Entry\n", __FUNCTION__); + + daemonize("hpc_poll"); + allow_signal(SIGKILL); + + poll_hpc (); + + tid_poll = 0; + debug ("%s - Exit\n", __FUNCTION__); + return 0; +} + + +/*---------------------------------------------------------------------- +* Name: ibmphp_hpc_start_poll_thread +* +* Action: start polling thread +*---------------------------------------------------------------------*/ +int __init ibmphp_hpc_start_poll_thread (void) +{ + int rc = 0; + + debug ("%s - Entry\n", __FUNCTION__); + + tid_poll = kernel_thread (hpc_poll_thread, NULL, 0); + if (tid_poll < 0) { + err ("%s - Error, thread not started\n", __FUNCTION__); + rc = -1; + } + + debug ("%s - Exit tid_poll[%d] rc[%d]\n", __FUNCTION__, tid_poll, rc); + return rc; +} + +/*---------------------------------------------------------------------- +* Name: ibmphp_hpc_stop_poll_thread +* +* Action: stop polling thread and cleanup +*---------------------------------------------------------------------*/ +void __exit ibmphp_hpc_stop_poll_thread (void) +{ + debug ("%s - Entry\n", __FUNCTION__); + + ibmphp_shutdown = TRUE; + 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", __FUNCTION__); +} + +/*---------------------------------------------------------------------- +* 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 = FALSE; + + 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 = TRUE; + } + if (CTLR_WORKING (*pstatus) == HPC_CTLR_WORKING_NO) + done = TRUE; + if (!done) { + msleep(1000); + if (timeout < 1) { + done = TRUE; + 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 000000000000..2335fac65fb4 --- /dev/null +++ b/drivers/pci/hotplug/ibmphp_pci.c @@ -0,0 +1,1747 @@ +/* + * 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 = kmalloc(sizeof(*newfunc), GFP_KERNEL); + if (!newfunc) { + err ("out of system memory\n"); + return -ENOMEM; + } + memset (newfunc, 0, sizeof (struct pci_func)); + 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 = FALSE; + for (i = 0; i < 32; i++) { + if (func->devices[i]) { + newfunc = kmalloc(sizeof(*newfunc), GFP_KERNEL); + if (!newfunc) { + err ("out of system memory\n"); + return -ENOMEM; + } + memset (newfunc, 0, sizeof (struct pci_func)); + 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 = TRUE; + } + } + + newfunc = kmalloc(sizeof(*newfunc), GFP_KERNEL); + if (!newfunc) { + err ("out of system memory\n"); + return -ENOMEM; + } + memset (newfunc, 0, sizeof (struct pci_func)); + 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 = FALSE; + for (i = 0; i < 32; i++) { + if (func->devices[i]) { + debug ("inside for loop, device is %x\n", i); + newfunc = kmalloc(sizeof(*newfunc), GFP_KERNEL); + if (!newfunc) { + err (" out of system memory\n"); + return -ENOMEM; + } + memset (newfunc, 0, sizeof (struct pci_func)); + 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 = TRUE; + } + } + + 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", __FUNCTION__); + + 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] = kmalloc (sizeof (struct resource_node), GFP_KERNEL); + + if (!io[count]) { + err ("out of system memory\n"); + return -ENOMEM; + } + memset (io[count], 0, sizeof (struct resource_node)); + 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] = kmalloc (sizeof (struct resource_node), GFP_KERNEL); + if (!pfmem[count]) { + err ("out of system memory\n"); + return -ENOMEM; + } + memset (pfmem[count], 0, sizeof (struct resource_node)); + 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 = FALSE; + if (ibmphp_check_resource (pfmem[count], 0) == 0) { + ibmphp_add_resource (pfmem[count]); + func->pfmem[count] = pfmem[count]; + } else { + mem_tmp = kmalloc(sizeof(*mem_tmp), GFP_KERNEL); + if (!mem_tmp) { + err ("out of system memory\n"); + kfree (pfmem[count]); + return -ENOMEM; + } + memset (mem_tmp, 0, sizeof (struct resource_node)); + 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 = TRUE; + 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] = kmalloc (sizeof (struct resource_node), GFP_KERNEL); + if (!mem[count]) { + err ("out of system memory\n"); + return -ENOMEM; + } + memset (mem[count], 0, sizeof (struct resource_node)); + 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_word (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 = FALSE; + u8 flag_mem = FALSE; + u8 flag_pfmem = FALSE; + u8 need_io_upper = FALSE; + u8 need_pfmem_upper = FALSE; + 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", __FUNCTION__); + + 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] = kmalloc (sizeof (struct resource_node), GFP_KERNEL); + + if (!bus_io[count]) { + err ("out of system memory\n"); + retval = -ENOMEM; + goto error; + } + memset (bus_io[count], 0, sizeof (struct resource_node)); + 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] = kmalloc (sizeof (struct resource_node), GFP_KERNEL); + if (!bus_pfmem[count]) { + err ("out of system memory\n"); + retval = -ENOMEM; + goto error; + } + memset (bus_pfmem[count], 0, sizeof (struct resource_node)); + 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 = FALSE; + if (ibmphp_check_resource (bus_pfmem[count], 0) == 0) { + ibmphp_add_resource (bus_pfmem[count]); + func->pfmem[count] = bus_pfmem[count]; + } else { + mem_tmp = kmalloc(sizeof(*mem_tmp), GFP_KERNEL); + if (!mem_tmp) { + err ("out of system memory\n"); + retval = -ENOMEM; + goto error; + } + memset (mem_tmp, 0, sizeof (struct resource_node)); + 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 = TRUE; + 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] = kmalloc (sizeof (struct resource_node), GFP_KERNEL); + if (!bus_mem[count]) { + err ("out of system memory\n"); + retval = -ENOMEM; + goto error; + } + memset (bus_mem[count], 0, sizeof (struct resource_node)); + 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 = TRUE; + } else { + debug ("it wants %x IO behind the bridge\n", amount_needed->io); + io = kmalloc(sizeof(*io), GFP_KERNEL); + + if (!io) { + err ("out of system memory\n"); + retval = -ENOMEM; + goto error; + } + memset (io, 0, sizeof (struct resource_node)); + 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 = TRUE; + } + } + + if (!amount_needed->mem) { + debug ("it doesn't want n.e.memory?\n"); + flag_mem = TRUE; + } else { + debug ("it wants %x memory behind the bridge\n", amount_needed->mem); + mem = kmalloc(sizeof(*mem), GFP_KERNEL); + if (!mem) { + err ("out of system memory\n"); + retval = -ENOMEM; + goto error; + } + memset (mem, 0, sizeof (struct resource_node)); + 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 = TRUE; + debug ("were we able to add mem\n"); + } + } + + if (!amount_needed->pfmem) { + debug ("it doesn't want n.e.pfmem mem?\n"); + flag_pfmem = TRUE; + } else { + debug ("it wants %x pfmemory behind the bridge\n", amount_needed->pfmem); + pfmem = kmalloc(sizeof(*pfmem), GFP_KERNEL); + if (!pfmem) { + err ("out of system memory\n"); + retval = -ENOMEM; + goto error; + } + memset (pfmem, 0, sizeof (struct resource_node)); + pfmem->type = PFMEM; + pfmem->busno = func->busno; + pfmem->devfunc = PCI_DEVFN(func->device, func->function); + pfmem->len = amount_needed->pfmem; + pfmem->fromMem = FALSE; + if (ibmphp_check_resource (pfmem, 1) == 0) { + ibmphp_add_resource (pfmem); + flag_pfmem = TRUE; + } else { + mem_tmp = kmalloc(sizeof(*mem_tmp), GFP_KERNEL); + if (!mem_tmp) { + err ("out of system memory\n"); + retval = -ENOMEM; + goto error; + } + memset (mem_tmp, 0, sizeof (struct resource_node)); + 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 = TRUE; + pfmem->rangeno = mem_tmp->rangeno; + ibmphp_add_pfmem_from_mem (pfmem); + flag_pfmem = TRUE; + } + } + } + + 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 = kmalloc(sizeof(*bus), GFP_KERNEL); + if (!bus) { + err ("out of system memory\n"); + retval = -ENOMEM; + goto error; + } + memset (bus, 0, sizeof (struct bus_node)); + 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 = TRUE; + } + if ((io_base & PCI_PREF_RANGE_TYPE_MASK) == PCI_PREF_RANGE_TYPE_64) { + debug ("pfmem 64\n"); + need_pfmem_upper = TRUE; + } + + 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 = kmalloc(sizeof(*amount), GFP_KERNEL); + if (amount == NULL) + return NULL; + memset (amount, 0, sizeof (struct res_needed)); + + 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 = TRUE; + 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 = TRUE; + 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 = TRUE; + 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 = TRUE; + else + amount->not_correct = FALSE; + 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", __FUNCTION__); + + 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 */ + start_address &= PCI_BASE_ADDRESS_MEM_MASK; + if (start_address & PCI_BASE_ADDRESS_MEM_PREFETCH) { + /* pfmem */ + debug ("start address of pfmem is %x\n", start_address); + + 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); + 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", __FUNCTION__, 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); + debug ("bus->busno is %x\n", bus->busno); + debug ("sec_number is %x\n", sec_number); + if (!bus) { + err ("cannot find Bus structure for the bridged device\n"); + return -EINVAL; + } + + 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 */ + start_address &= PCI_BASE_ADDRESS_MEM_MASK; + if (start_address & PCI_BASE_ADDRESS_MEM_PREFETCH) { + /* pfmem */ + 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 */ + 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", __FUNCTION__); + 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", __FUNCTION__); + + 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", __FUNCTION__); + + /* 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", __FUNCTION__); + + 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", __FUNCTION__); + 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 = kmalloc(sizeof(*io_range), GFP_KERNEL); + if (!io_range) { + err ("out of system memory\n"); + return -ENOMEM; + } + memset (io_range, 0, sizeof (struct range_node)); + 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 = kmalloc(sizeof(*mem_range), GFP_KERNEL); + if (!mem_range) { + err ("out of system memory\n"); + return -ENOMEM; + } + memset (mem_range, 0, sizeof (struct range_node)); + 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 = kmalloc(sizeof(*pfmem_range), GFP_KERNEL); + if (!pfmem_range) { + err ("out of system memory\n"); + return -ENOMEM; + } + memset (pfmem_range, 0, sizeof (struct range_node)); + 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 000000000000..9c224c94d698 --- /dev/null +++ b/drivers/pci/hotplug/ibmphp_res.c @@ -0,0 +1,2156 @@ +/* + * 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 = kmalloc (sizeof (struct bus_node), GFP_KERNEL); + if (!newbus) { + err ("out of system memory\n"); + return NULL; + } + + memset (newbus, 0, sizeof (struct bus_node)); + 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 = kmalloc (sizeof (struct resource_node), GFP_KERNEL); + if (!rs) { + err ("out of system memory\n"); + return NULL; + } + memset (rs, 0, sizeof (struct resource_node)); + 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 = kmalloc (sizeof (struct bus_node), GFP_KERNEL); + if (!newbus) { + err ("out of system memory.\n"); + return -ENOMEM; + } + memset (newbus, 0, sizeof (struct bus_node)); + 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 = kmalloc (sizeof (struct range_node), GFP_KERNEL); + if (!newrange) { + if (first_bus) + kfree (newbus); + err ("out of system memory\n"); + return -ENOMEM; + } + memset (newrange, 0, sizeof (struct range_node)); + 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 = FALSE; + 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 = FALSE; /* 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 = TRUE; + 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", __FUNCTION__, 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", __FUNCTION__); + + 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", __FUNCTION__); + 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 = FALSE; + + 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", __FUNCTION__); + 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", __FUNCTION__, 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 = TRUE; + len_cur = len_tmp; + start_cur = range->start; + } else { + /* Needs adjusting */ + tmp_start = range->start; + flag = FALSE; + + while ((len_tmp = res_cur->start - 1 - tmp_start) >= res->len) { + if ((tmp_start % tmp_divide) == 0) { + flag = TRUE; + 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 = TRUE; + len_cur = len_tmp; + start_cur = res_cur->end + 1; + } else { + /* Needs adjusting */ + tmp_start = res_cur->end + 1; + flag = FALSE; + + while ((len_tmp = range->end - tmp_start) >= res->len) { + if ((tmp_start % tmp_divide) == 0) { + flag = TRUE; + 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 = TRUE; + len_cur = len_tmp; + start_cur = range->start; + } else { + /* Needs adjusting */ + tmp_start = range->start; + flag = FALSE; + + while ((len_tmp = res_cur->start - 1 - tmp_start) >= res->len) { + if ((tmp_start % tmp_divide) == 0) { + flag = TRUE; + 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 = TRUE; + len_cur = len_tmp; + start_cur = res_prev->end + 1; + } else { + /* Needs adjusting */ + tmp_start = res_prev->end + 1; + flag = FALSE; + + while ((len_tmp = res_cur->start - 1 - tmp_start) >= res->len) { + if ((tmp_start % tmp_divide) == 0) { + flag = TRUE; + 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 = TRUE; + len_cur = len_tmp; + start_cur = range->start; + } else { + /* Needs adjusting */ + tmp_start = range->start; + flag = FALSE; + + while ((len_tmp = range->end - tmp_start) >= res->len) { + if ((tmp_start % tmp_divide) == 0) { + flag = TRUE; + 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 = TRUE; + len_cur = len_tmp; + start_cur = range->start; + } else { + /* Needs adjusting */ + tmp_start = range->start; + flag = FALSE; + + while ((len_tmp = range->end - tmp_start) >= res->len) { + if ((tmp_start % tmp_divide) == 0) { + flag = TRUE; + 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 = TRUE; + 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 = kmalloc (sizeof (struct resource_node), GFP_KERNEL); + if (!mem) { + err ("out of system memory\n"); + return -ENOMEM; + } + memset (mem, 0, sizeof (struct resource_node)); + 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", __FUNCTION__); + 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 = kmalloc (sizeof (struct range_node), GFP_KERNEL); + if (!range) { + err ("out of system memory\n"); + return -ENOMEM; + } + memset (range, 0, sizeof (struct range_node)); + 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 = kmalloc (sizeof (struct resource_node), GFP_KERNEL); + if (!io) { + kfree (range); + err ("out of system memory\n"); + return -ENOMEM; + } + memset (io, 0, sizeof (struct resource_node)); + 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 = kmalloc (sizeof (struct range_node), GFP_KERNEL); + if (!range) { + err ("out of system memory\n"); + return -ENOMEM; + } + memset (range, 0, sizeof (struct range_node)); + 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 = kmalloc (sizeof (struct resource_node), GFP_KERNEL); + if (!mem) { + kfree (range); + err ("out of system memory\n"); + return -ENOMEM; + } + memset (mem, 0, sizeof (struct resource_node)); + 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 = kmalloc (sizeof (struct range_node), GFP_KERNEL); + if (!range) { + err ("out of system memory\n"); + return -ENOMEM; + } + memset (range, 0, sizeof (struct range_node)); + 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 = kmalloc (sizeof (struct resource_node), GFP_KERNEL); + if (!pfmem) { + kfree (range); + err ("out of system memory\n"); + return -ENOMEM; + } + memset (pfmem, 0, sizeof (struct resource_node)); + 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 = FALSE; + + 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.h b/drivers/pci/hotplug/pci_hotplug.h new file mode 100644 index 000000000000..57ace325168d --- /dev/null +++ b/drivers/pci/hotplug/pci_hotplug.h @@ -0,0 +1,180 @@ +/* + * PCI HotPlug Core Functions + * + * 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> + * + */ +#ifndef _PCI_HOTPLUG_H +#define _PCI_HOTPLUG_H + + +/* These values come from the PCI Hotplug Spec */ +enum pci_bus_speed { + PCI_SPEED_33MHz = 0x00, + PCI_SPEED_66MHz = 0x01, + PCI_SPEED_66MHz_PCIX = 0x02, + PCI_SPEED_100MHz_PCIX = 0x03, + PCI_SPEED_133MHz_PCIX = 0x04, + PCI_SPEED_66MHz_PCIX_ECC = 0x05, + PCI_SPEED_100MHz_PCIX_ECC = 0x06, + PCI_SPEED_133MHz_PCIX_ECC = 0x07, + PCI_SPEED_66MHz_PCIX_266 = 0x09, + PCI_SPEED_100MHz_PCIX_266 = 0x0a, + PCI_SPEED_133MHz_PCIX_266 = 0x0b, + PCI_SPEED_66MHz_PCIX_533 = 0x11, + PCI_SPEED_100MHz_PCIX_533 = 0x12, + PCI_SPEED_133MHz_PCIX_533 = 0x13, + PCI_SPEED_UNKNOWN = 0xff, +}; + +/* These values come from the PCI Express Spec */ +enum pcie_link_width { + PCIE_LNK_WIDTH_RESRV = 0x00, + PCIE_LNK_X1 = 0x01, + PCIE_LNK_X2 = 0x02, + PCIE_LNK_X4 = 0x04, + PCIE_LNK_X8 = 0x08, + PCIE_LNK_X12 = 0x0C, + PCIE_LNK_X16 = 0x10, + PCIE_LNK_X32 = 0x20, + PCIE_LNK_WIDTH_UNKNOWN = 0xFF, +}; + +enum pcie_link_speed { + PCIE_2PT5GB = 0x14, + PCIE_LNK_SPEED_UNKNOWN = 0xFF, +}; + +struct hotplug_slot; +struct hotplug_slot_attribute { + struct attribute attr; + ssize_t (*show)(struct hotplug_slot *, char *); + ssize_t (*store)(struct hotplug_slot *, const char *, size_t); +}; +#define to_hotplug_attr(n) container_of(n, struct hotplug_slot_attribute, attr); + +/** + * struct hotplug_slot_ops -the callbacks that the hotplug pci core can use + * @owner: The module owner of this structure + * @enable_slot: Called when the user wants to enable a specific pci slot + * @disable_slot: Called when the user wants to disable a specific pci slot + * @set_attention_status: Called to set the specific slot's attention LED to + * the specified value + * @hardware_test: Called to run a specified hardware test on the specified + * slot. + * @get_power_status: Called to get the current power status of a slot. + * If this field is NULL, the value passed in the struct hotplug_slot_info + * will be used when this value is requested by a user. + * @get_attention_status: Called to get the current attention status of a slot. + * If this field is NULL, the value passed in the struct hotplug_slot_info + * will be used when this value is requested by a user. + * @get_latch_status: Called to get the current latch status of a slot. + * If this field is NULL, the value passed in the struct hotplug_slot_info + * will be used when this value is requested by a user. + * @get_adapter_status: Called to get see if an adapter is present in the slot or not. + * If this field is NULL, the value passed in the struct hotplug_slot_info + * will be used when this value is requested by a user. + * @get_address: Called to get pci address of a slot. + * If this field is NULL, the value passed in the struct hotplug_slot_info + * will be used when this value is requested by a user. + * @get_max_bus_speed: Called to get the max bus speed for a slot. + * If this field is NULL, the value passed in the struct hotplug_slot_info + * will be used when this value is requested by a user. + * @get_cur_bus_speed: Called to get the current bus speed for a slot. + * If this field is NULL, the value passed in the struct hotplug_slot_info + * will be used when this value is requested by a user. + * + * The table of function pointers that is passed to the hotplug pci core by a + * hotplug pci driver. These functions are called by the hotplug pci core when + * the user wants to do something to a specific slot (query it for information, + * set an LED, enable / disable power, etc.) + */ +struct hotplug_slot_ops { + struct module *owner; + int (*enable_slot) (struct hotplug_slot *slot); + int (*disable_slot) (struct hotplug_slot *slot); + int (*set_attention_status) (struct hotplug_slot *slot, u8 value); + int (*hardware_test) (struct hotplug_slot *slot, u32 value); + int (*get_power_status) (struct hotplug_slot *slot, u8 *value); + int (*get_attention_status) (struct hotplug_slot *slot, u8 *value); + int (*get_latch_status) (struct hotplug_slot *slot, u8 *value); + int (*get_adapter_status) (struct hotplug_slot *slot, u8 *value); + int (*get_address) (struct hotplug_slot *slot, u32 *value); + int (*get_max_bus_speed) (struct hotplug_slot *slot, enum pci_bus_speed *value); + int (*get_cur_bus_speed) (struct hotplug_slot *slot, enum pci_bus_speed *value); +}; + +/** + * struct hotplug_slot_info - used to notify the hotplug pci core of the state of the slot + * @power: if power is enabled or not (1/0) + * @attention_status: if the attention light is enabled or not (1/0) + * @latch_status: if the latch (if any) is open or closed (1/0) + * @adapter_present: if there is a pci board present in the slot or not (1/0) + * @address: (domain << 16 | bus << 8 | dev) + * + * Used to notify the hotplug pci core of the status of a specific slot. + */ +struct hotplug_slot_info { + u8 power_status; + u8 attention_status; + u8 latch_status; + u8 adapter_status; + u32 address; + enum pci_bus_speed max_bus_speed; + enum pci_bus_speed cur_bus_speed; +}; + +/** + * struct hotplug_slot - used to register a physical slot with the hotplug pci core + * @name: the name of the slot being registered. This string must + * be unique amoung slots registered on this system. + * @ops: pointer to the &struct hotplug_slot_ops to be used for this slot + * @info: pointer to the &struct hotplug_slot_info for the inital values for + * this slot. + * @release: called during pci_hp_deregister to free memory allocated in a + * hotplug_slot structure. + * @private: used by the hotplug pci controller driver to store whatever it + * needs. + */ +struct hotplug_slot { + char *name; + struct hotplug_slot_ops *ops; + struct hotplug_slot_info *info; + void (*release) (struct hotplug_slot *slot); + void *private; + + /* Variables below this are for use only by the hotplug pci core. */ + struct list_head slot_list; + struct kobject kobj; +}; +#define to_hotplug_slot(n) container_of(n, struct hotplug_slot, kobj) + +extern int pci_hp_register (struct hotplug_slot *slot); +extern int pci_hp_deregister (struct hotplug_slot *slot); +extern int pci_hp_change_slot_info (struct hotplug_slot *slot, + struct hotplug_slot_info *info); +extern struct subsystem pci_hotplug_slots_subsys; + +#endif + diff --git a/drivers/pci/hotplug/pci_hotplug_core.c b/drivers/pci/hotplug/pci_hotplug_core.c new file mode 100644 index 000000000000..c802f6270b89 --- /dev/null +++ b/drivers/pci/hotplug/pci_hotplug_core.c @@ -0,0 +1,715 @@ +/* + * PCI HotPlug Controller Core + * + * Copyright (C) 2001-2002 Greg Kroah-Hartman (greg@kroah.com) + * Copyright (C) 2001-2002 IBM Corp. + * + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, GOOD TITLE or + * NON INFRINGEMENT. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * Send feedback to <greg@kroah.com> + * + * Filesystem portion based on work done by Pat Mochel on ddfs/driverfs + * + */ + +#include <linux/config.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/kernel.h> +#include <linux/types.h> +#include <linux/list.h> +#include <linux/pagemap.h> +#include <linux/slab.h> +#include <linux/smp_lock.h> +#include <linux/init.h> +#include <linux/mount.h> +#include <linux/namei.h> +#include <linux/pci.h> +#include <asm/uaccess.h> +#include <linux/kobject.h> +#include <linux/sysfs.h> +#include "pci_hotplug.h" + + +#define MY_NAME "pci_hotplug" + +#define dbg(fmt, arg...) do { if (debug) printk(KERN_DEBUG "%s: %s: " fmt , MY_NAME , __FUNCTION__ , ## arg); } while (0) +#define err(format, arg...) printk(KERN_ERR "%s: " format , MY_NAME , ## arg) +#define info(format, arg...) printk(KERN_INFO "%s: " format , MY_NAME , ## arg) +#define warn(format, arg...) printk(KERN_WARNING "%s: " format , MY_NAME , ## arg) + + +/* local variables */ +static int debug; + +#define DRIVER_VERSION "0.5" +#define DRIVER_AUTHOR "Greg Kroah-Hartman <greg@kroah.com>, Scott Murray <scottm@somanetworks.com>" +#define DRIVER_DESC "PCI Hot Plug PCI Core" + + +////////////////////////////////////////////////////////////////// + +static LIST_HEAD(pci_hotplug_slot_list); + +struct subsystem pci_hotplug_slots_subsys; + +static ssize_t hotplug_slot_attr_show(struct kobject *kobj, + struct attribute *attr, char *buf) +{ + struct hotplug_slot *slot = to_hotplug_slot(kobj); + struct hotplug_slot_attribute *attribute = to_hotplug_attr(attr); + return attribute->show ? attribute->show(slot, buf) : 0; +} + +static ssize_t hotplug_slot_attr_store(struct kobject *kobj, + struct attribute *attr, const char *buf, size_t len) +{ + struct hotplug_slot *slot = to_hotplug_slot(kobj); + struct hotplug_slot_attribute *attribute = to_hotplug_attr(attr); + return attribute->store ? attribute->store(slot, buf, len) : 0; +} + +static struct sysfs_ops hotplug_slot_sysfs_ops = { + .show = hotplug_slot_attr_show, + .store = hotplug_slot_attr_store, +}; + +static void hotplug_slot_release(struct kobject *kobj) +{ + struct hotplug_slot *slot = to_hotplug_slot(kobj); + if (slot->release) + slot->release(slot); +} + +static struct kobj_type hotplug_slot_ktype = { + .sysfs_ops = &hotplug_slot_sysfs_ops, + .release = &hotplug_slot_release, +}; + +decl_subsys_name(pci_hotplug_slots, slots, &hotplug_slot_ktype, NULL); + +/* 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)) { \ + 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(address, u32) +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 hotplug_slot *slot, char *buf) +{ + int retval; + u8 value; + + retval = get_power_status (slot, &value); + if (retval) + goto exit; + retval = sprintf (buf, "%d\n", value); +exit: + return retval; +} + +static ssize_t power_write_file (struct hotplug_slot *slot, const char *buf, + size_t count) +{ + 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 hotplug_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 hotplug_slot *slot, char *buf) +{ + int retval; + u8 value; + + retval = get_attention_status (slot, &value); + if (retval) + goto exit; + retval = sprintf (buf, "%d\n", value); + +exit: + return retval; +} + +static ssize_t attention_write_file (struct hotplug_slot *slot, const char *buf, + size_t count) +{ + 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(slot->ops->owner)) { + retval = -ENODEV; + goto exit; + } + if (slot->ops->set_attention_status) + retval = slot->ops->set_attention_status(slot, attention); + module_put(slot->ops->owner); + +exit: + if (retval) + return retval; + return count; +} + +static struct hotplug_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 hotplug_slot *slot, char *buf) +{ + int retval; + u8 value; + + retval = get_latch_status (slot, &value); + if (retval) + goto exit; + retval = sprintf (buf, "%d\n", value); + +exit: + return retval; +} + +static struct hotplug_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 hotplug_slot *slot, char *buf) +{ + int retval; + u8 value; + + retval = get_adapter_status (slot, &value); + if (retval) + goto exit; + retval = sprintf (buf, "%d\n", value); + +exit: + return retval; +} + +static struct hotplug_slot_attribute hotplug_slot_attr_presence = { + .attr = {.name = "adapter", .mode = S_IFREG | S_IRUGO}, + .show = presence_read_file, +}; + +static ssize_t address_read_file (struct hotplug_slot *slot, char *buf) +{ + int retval; + u32 address; + + retval = get_address (slot, &address); + if (retval) + goto exit; + retval = sprintf (buf, "%04x:%02x:%02x\n", + (address >> 16) & 0xffff, + (address >> 8) & 0xff, + address & 0xff); + +exit: + return retval; +} + +static struct hotplug_slot_attribute hotplug_slot_attr_address = { + .attr = {.name = "address", .mode = S_IFREG | S_IRUGO}, + .show = address_read_file, +}; + +static char *unknown_speed = "Unknown bus speed"; + +static ssize_t max_bus_speed_read_file (struct hotplug_slot *slot, char *buf) +{ + char *speed_string; + int retval; + enum pci_bus_speed value; + + retval = get_max_bus_speed (slot, &value); + if (retval) + goto exit; + + if (value == PCI_SPEED_UNKNOWN) + speed_string = unknown_speed; + else + speed_string = pci_bus_speed_strings[value]; + + retval = sprintf (buf, "%s\n", speed_string); + +exit: + return retval; +} + +static struct hotplug_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 hotplug_slot *slot, char *buf) +{ + char *speed_string; + int retval; + enum pci_bus_speed value; + + retval = get_cur_bus_speed (slot, &value); + if (retval) + goto exit; + + if (value == PCI_SPEED_UNKNOWN) + speed_string = unknown_speed; + else + speed_string = pci_bus_speed_strings[value]; + + retval = sprintf (buf, "%s\n", speed_string); + +exit: + return retval; +} + +static struct hotplug_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 hotplug_slot *slot, const char *buf, + size_t count) +{ + 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 hotplug_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 hotplug_slot *slot) +{ + 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 hotplug_slot *slot) +{ + 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 hotplug_slot *slot) +{ + if ((!slot) || (!slot->ops)) + return -ENODEV; + if (slot->ops->get_latch_status) + return 0; + return -ENOENT; +} + +static int has_adapter_file (struct hotplug_slot *slot) +{ + if ((!slot) || (!slot->ops)) + return -ENODEV; + if (slot->ops->get_adapter_status) + return 0; + return -ENOENT; +} + +static int has_address_file (struct hotplug_slot *slot) +{ + if ((!slot) || (!slot->ops)) + return -ENODEV; + if (slot->ops->get_address) + return 0; + return -ENOENT; +} + +static int has_max_bus_speed_file (struct hotplug_slot *slot) +{ + 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 hotplug_slot *slot) +{ + if ((!slot) || (!slot->ops)) + return -ENODEV; + if (slot->ops->get_cur_bus_speed) + return 0; + return -ENOENT; +} + +static int has_test_file (struct hotplug_slot *slot) +{ + if ((!slot) || (!slot->ops)) + return -ENODEV; + if (slot->ops->hardware_test) + return 0; + return -ENOENT; +} + +static int fs_add_slot (struct hotplug_slot *slot) +{ + if (has_power_file(slot) == 0) + sysfs_create_file(&slot->kobj, &hotplug_slot_attr_power.attr); + + if (has_attention_file(slot) == 0) + sysfs_create_file(&slot->kobj, &hotplug_slot_attr_attention.attr); + + if (has_latch_file(slot) == 0) + sysfs_create_file(&slot->kobj, &hotplug_slot_attr_latch.attr); + + if (has_adapter_file(slot) == 0) + sysfs_create_file(&slot->kobj, &hotplug_slot_attr_presence.attr); + + if (has_address_file(slot) == 0) + sysfs_create_file(&slot->kobj, &hotplug_slot_attr_address.attr); + + if (has_max_bus_speed_file(slot) == 0) + sysfs_create_file(&slot->kobj, &hotplug_slot_attr_max_bus_speed.attr); + + if (has_cur_bus_speed_file(slot) == 0) + sysfs_create_file(&slot->kobj, &hotplug_slot_attr_cur_bus_speed.attr); + + if (has_test_file(slot) == 0) + sysfs_create_file(&slot->kobj, &hotplug_slot_attr_test.attr); + + return 0; +} + +static void fs_remove_slot (struct hotplug_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_address_file(slot) == 0) + sysfs_remove_file(&slot->kobj, &hotplug_slot_attr_address.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(slot->name, name) == 0) + return slot; + } + return NULL; +} + +/** + * pci_hp_register - register a hotplug_slot with the PCI hotplug subsystem + * @slot: pointer to the &struct hotplug_slot to register + * + * Registers a hotplug slot with the pci hotplug subsystem, which will allow + * userspace interaction to the slot. + * + * Returns 0 if successful, anything else for an error. + */ +int pci_hp_register (struct hotplug_slot *slot) +{ + int result; + + 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; + } + + kobject_set_name(&slot->kobj, "%s", slot->name); + kobj_set_kset_s(slot, pci_hotplug_slots_subsys); + + /* this can fail if we have already registered a slot with the same name */ + if (kobject_register(&slot->kobj)) { + err("Unable to register kobject"); + return -EINVAL; + } + + list_add (&slot->slot_list, &pci_hotplug_slot_list); + + result = fs_add_slot (slot); + dbg ("Added slot %s to the list\n", slot->name); + return result; +} + +/** + * pci_hp_deregister - deregister a hotplug_slot with the PCI hotplug subsystem + * @slot: pointer to the &struct hotplug_slot to deregister + * + * The @slot must have been registered with the pci hotplug subsystem + * previously with a call to pci_hp_register(). + * + * Returns 0 if successful, anything else for an error. + */ +int pci_hp_deregister (struct hotplug_slot *slot) +{ + struct hotplug_slot *temp; + + if (slot == NULL) + return -ENODEV; + + temp = get_slot_from_name (slot->name); + if (temp != slot) { + return -ENODEV; + } + list_del (&slot->slot_list); + + fs_remove_slot (slot); + dbg ("Removed slot %s from the list\n", slot->name); + kobject_unregister(&slot->kobj); + return 0; +} + +/** + * pci_hp_change_slot_info - changes the slot's information structure in the core + * @slot: 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 pci_hp_change_slot_info (struct hotplug_slot *slot, struct hotplug_slot_info *info) +{ + if ((slot == NULL) || (info == NULL)) + return -ENODEV; + + /* + * check all fields in the info structure, and update timestamps + * for the files referring to the fields that have now changed. + */ + if ((has_power_file(slot) == 0) && + (slot->info->power_status != info->power_status)) + sysfs_update_file(&slot->kobj, &hotplug_slot_attr_power.attr); + + if ((has_attention_file(slot) == 0) && + (slot->info->attention_status != info->attention_status)) + sysfs_update_file(&slot->kobj, &hotplug_slot_attr_attention.attr); + + if ((has_latch_file(slot) == 0) && + (slot->info->latch_status != info->latch_status)) + sysfs_update_file(&slot->kobj, &hotplug_slot_attr_latch.attr); + + if ((has_adapter_file(slot) == 0) && + (slot->info->adapter_status != info->adapter_status)) + sysfs_update_file(&slot->kobj, &hotplug_slot_attr_presence.attr); + + if ((has_address_file(slot) == 0) && + (slot->info->address != info->address)) + sysfs_update_file(&slot->kobj, &hotplug_slot_attr_address.attr); + + if ((has_max_bus_speed_file(slot) == 0) && + (slot->info->max_bus_speed != info->max_bus_speed)) + sysfs_update_file(&slot->kobj, &hotplug_slot_attr_max_bus_speed.attr); + + if ((has_cur_bus_speed_file(slot) == 0) && + (slot->info->cur_bus_speed != info->cur_bus_speed)) + sysfs_update_file(&slot->kobj, &hotplug_slot_attr_cur_bus_speed.attr); + + memcpy (slot->info, info, sizeof (struct hotplug_slot_info)); + + return 0; +} + +static int __init pci_hotplug_init (void) +{ + int result; + + kset_set_kset_s(&pci_hotplug_slots_subsys, pci_bus_type.subsys); + result = subsystem_register(&pci_hotplug_slots_subsys); + if (result) { + err("Register subsys with error %d\n", result); + goto exit; + } + result = cpci_hotplug_init(debug); + if (result) { + err ("cpci_hotplug_init with error %d\n", result); + goto err_subsys; + } + + info (DRIVER_DESC " version: " DRIVER_VERSION "\n"); + goto exit; + +err_subsys: + subsystem_unregister(&pci_hotplug_slots_subsys); +exit: + return result; +} + +static void __exit pci_hotplug_exit (void) +{ + cpci_hotplug_exit(); + subsystem_unregister(&pci_hotplug_slots_subsys); +} + +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_hotplug_slots_subsys); +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 000000000000..f313121d5141 --- /dev/null +++ b/drivers/pci/hotplug/pciehp.h @@ -0,0 +1,352 @@ +/* + * 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>, <dely.l.sy@intel.com> + * + */ +#ifndef _PCIEHP_H +#define _PCIEHP_H + +#include <linux/types.h> +#include <linux/pci.h> +#include <linux/delay.h> +#include <asm/semaphore.h> +#include <asm/io.h> +#include <linux/pcieport_if.h> +#include "pci_hotplug.h" + +#define MY_NAME "pciehp" + +extern int pciehp_poll_mode; +extern int pciehp_poll_time; +extern int pciehp_debug; + +/*#define dbg(format, arg...) do { if (pciehp_debug) printk(KERN_DEBUG "%s: " format, MY_NAME , ## arg); } while (0)*/ +#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) + +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 pci_dev* pci_dev; +}; + +struct slot { + struct slot *next; + u8 bus; + u8 device; + u32 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; + struct hpc_ops *hpc_ops; + struct hotplug_slot *hotplug_slot; + struct list_head slot_list; +}; + +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; + struct semaphore crit_sect; /* critical section semaphore */ + void *hpc_ctlr_handle; /* HPC controller handle */ + int num_slots; /* Number of slots on ctlr */ + int slot_num_inc; /* 1 or -1 */ + 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; + struct hpc_ops *hpc_ops; + wait_queue_head_t queue; /* sleep & wake process */ + u8 next_event; + u8 seg; + u8 bus; + u8 device; + u8 function; + u8 rev; + u8 slot_device_offset; + u8 add_support; + enum pci_bus_speed speed; + u32 first_slot; /* First physical slot number */ /* PCIE only has 1 slot */ + u8 slot_bus; /* Bus where the slots handled by this controller sit */ + u8 ctrlcap; + u16 vendor_id; +}; + +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 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 PCI_TO_PCI_BRIDGE_CLASS 0x00060400 + +/* 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 + +#define REMOVE_NOT_SUPPORTED 0x00000003 + +#define DISABLE_CARD 1 + +/* 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 ATTN_BUTTN(cap) (cap & ATTN_BUTTN_PRSN) +#define POWER_CTRL(cap) (cap & PWR_CTRL_PRSN) +#define MRL_SENS(cap) (cap & MRL_SENS_PRSN) +#define ATTN_LED(cap) (cap & ATTN_LED_PRSN) +#define PWR_LED(cap) (cap & PWR_LED_PRSN) +#define HP_SUPR_RM(cap) (cap & HP_SUPR_RM_SUP) + +/* + * 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_pcie "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 pciephd module. Upgrade to a newer version of pciehpd\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" + +/* controller functions */ +extern int pciehprm_find_available_resources (struct controller *ctrl); +extern int pciehp_event_start_thread (void); +extern void pciehp_event_stop_thread (void); +extern struct pci_func *pciehp_slot_create (unsigned char busnumber); +extern struct pci_func *pciehp_slot_find (unsigned char bus, unsigned char device, unsigned char index); +extern int pciehp_enable_slot (struct slot *slot); +extern int pciehp_disable_slot (struct slot *slot); + +extern u8 pciehp_handle_attention_button (u8 hp_slot, void *inst_id); +extern u8 pciehp_handle_switch_change (u8 hp_slot, void *inst_id); +extern u8 pciehp_handle_presence_change (u8 hp_slot, void *inst_id); +extern u8 pciehp_handle_power_fault (u8 hp_slot, void *inst_id); +/* extern void long_delay (int delay); */ + +/* resource functions */ +extern int pciehp_resource_sort_and_combine (struct pci_resource **head); + +/* pci functions */ +extern int pciehp_set_irq (u8 bus_num, u8 dev_num, u8 int_pin, u8 irq_num); +/*extern int pciehp_get_bus_dev (struct controller *ctrl, u8 *bus_num, u8 *dev_num, struct slot *slot);*/ +extern int pciehp_save_config (struct controller *ctrl, int busnumber, int num_ctlr_slots, int first_device_num); +extern int pciehp_save_used_resources (struct controller *ctrl, struct pci_func * func, int flag); +extern int pciehp_save_slot_config (struct controller *ctrl, struct pci_func * new_slot); +extern void pciehp_destroy_board_resources (struct pci_func * func); +extern int pciehp_return_board_resources (struct pci_func * func, struct resource_lists * resources); +extern void pciehp_destroy_resource_list (struct resource_lists * resources); +extern int pciehp_configure_device (struct controller* ctrl, struct pci_func* func); +extern int pciehp_unconfigure_device (struct pci_func* func); + + +/* Global variables */ +extern struct controller *pciehp_ctrl_list; +extern struct pci_func *pciehp_slot_list[256]; + +/* Inline functions */ + +static inline struct slot *pciehp_find_slot(struct controller *ctrl, u8 device) +{ + struct slot *p_slot, *tmp_slot = NULL; + + p_slot = ctrl->slot; + + dbg("p_slot = %p\n", p_slot); + + while (p_slot && (p_slot->device != device)) { + tmp_slot = p_slot; + p_slot = p_slot->next; + dbg("In while loop, p_slot = %p\n", p_slot); + } + if (p_slot == NULL) { + err("ERROR: pciehp_find_slot device=0x%x\n", device); + p_slot = tmp_slot; + } + + return p_slot; +} + +static inline int wait_for_ctrl_irq(struct controller *ctrl) +{ + int retval = 0; + + DECLARE_WAITQUEUE(wait, current); + + dbg("%s : start\n", __FUNCTION__); + add_wait_queue(&ctrl->queue, &wait); + if (!pciehp_poll_mode) + /* Sleep for up to 1 second */ + msleep_interruptible(1000); + else + msleep_interruptible(2500); + + remove_wait_queue(&ctrl->queue, &wait); + if (signal_pending(current)) + retval = -EINTR; + + dbg("%s : end\n", __FUNCTION__); + return retval; +} + +/* 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; +} + +#define SLOT_NAME_SIZE 10 + +static inline void make_slot_name(char *buffer, int buffer_size, struct slot *slot) +{ + snprintf(buffer, buffer_size, "%d", slot->number); +} + +enum php_ctlr_type { + PCI, + ISA, + ACPI +}; + +typedef u8(*php_intr_callback_t) (unsigned int change_id, void *instance_id); + +int pcie_init(struct controller *ctrl, struct pcie_device *dev, + php_intr_callback_t attention_button_callback, + php_intr_callback_t switch_change_callback, + php_intr_callback_t presence_change_callback, + php_intr_callback_t power_fault_callback); + + +/* This has no meaning for PCI Express, as there is only 1 slot per port */ +int pcie_get_ctlr_slot_config(struct controller *ctrl, + int *num_ctlr_slots, + int *first_device_num, + int *physical_slot_num, + u8 *ctrlcap); + +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_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 *value); + int (*get_cur_lnk_width) (struct slot *slot, enum pcie_link_width *value); + + 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); +}; + +#endif /* _PCIEHP_H */ diff --git a/drivers/pci/hotplug/pciehp_core.c b/drivers/pci/hotplug/pciehp_core.c new file mode 100644 index 000000000000..8a5b2b51bde4 --- /dev/null +++ b/drivers/pci/hotplug/pciehp_core.c @@ -0,0 +1,662 @@ +/* + * 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>, <dely.l.sy@intel.com> + * + */ + +#include <linux/config.h> +#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/init.h> +#include <asm/uaccess.h> +#include "pciehp.h" +#include "pciehprm.h" +#include <linux/interrupt.h> + +/* Global variables */ +int pciehp_debug; +int pciehp_poll_mode; +int pciehp_poll_time; +struct controller *pciehp_ctrl_list; +struct pci_func *pciehp_slot_list[256]; + +#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_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"); + +#define PCIE_MODULE_NAME "pciehp" + +static int pcie_start_thread (void); +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, +}; + +static int init_slots(struct controller *ctrl) +{ + struct slot *new_slot; + u8 number_of_slots; + u8 slot_device; + u32 slot_number; + int result = -ENOMEM; + + dbg("%s\n",__FUNCTION__); + + number_of_slots = ctrl->num_slots; + slot_device = ctrl->slot_device_offset; + slot_number = ctrl->first_slot; + + while (number_of_slots) { + new_slot = kmalloc(sizeof(*new_slot), GFP_KERNEL); + if (!new_slot) + goto error; + + memset(new_slot, 0, sizeof(struct slot)); + new_slot->hotplug_slot = + kmalloc(sizeof(*(new_slot->hotplug_slot)), + GFP_KERNEL); + if (!new_slot->hotplug_slot) + goto error_slot; + memset(new_slot->hotplug_slot, 0, sizeof(struct hotplug_slot)); + + new_slot->hotplug_slot->info = + kmalloc(sizeof(*(new_slot->hotplug_slot->info)), + GFP_KERNEL); + if (!new_slot->hotplug_slot->info) + goto error_hpslot; + memset(new_slot->hotplug_slot->info, 0, + sizeof(struct hotplug_slot_info)); + new_slot->hotplug_slot->name = kmalloc(SLOT_NAME_SIZE, + GFP_KERNEL); + if (!new_slot->hotplug_slot->name) + goto error_info; + + new_slot->ctrl = ctrl; + new_slot->bus = ctrl->slot_bus; + new_slot->device = slot_device; + new_slot->hpc_ops = ctrl->hpc_ops; + + new_slot->number = ctrl->first_slot; + new_slot->hp_slot = slot_device - ctrl->slot_device_offset; + + /* register this slot with the hotplug pci core */ + new_slot->hotplug_slot->private = new_slot; + make_slot_name (new_slot->hotplug_slot->name, SLOT_NAME_SIZE, new_slot); + new_slot->hotplug_slot->ops = &pciehp_hotplug_slot_ops; + + new_slot->hpc_ops->get_power_status(new_slot, &(new_slot->hotplug_slot->info->power_status)); + new_slot->hpc_ops->get_attention_status(new_slot, &(new_slot->hotplug_slot->info->attention_status)); + new_slot->hpc_ops->get_latch_status(new_slot, &(new_slot->hotplug_slot->info->latch_status)); + new_slot->hpc_ops->get_adapter_status(new_slot, &(new_slot->hotplug_slot->info->adapter_status)); + + dbg("Registering bus=%x dev=%x hp_slot=%x sun=%x slot_device_offset=%x\n", + new_slot->bus, new_slot->device, new_slot->hp_slot, new_slot->number, ctrl->slot_device_offset); + result = pci_hp_register (new_slot->hotplug_slot); + if (result) { + err ("pci_hp_register failed with error %d\n", result); + goto error_name; + } + + new_slot->next = ctrl->slot; + ctrl->slot = new_slot; + + number_of_slots--; + slot_device++; + slot_number += ctrl->slot_num_inc; + } + + return 0; + +error_name: + kfree(new_slot->hotplug_slot->name); +error_info: + kfree(new_slot->hotplug_slot->info); +error_hpslot: + kfree(new_slot->hotplug_slot); +error_slot: + kfree(new_slot); +error: + return result; +} + + +static int cleanup_slots (struct controller * ctrl) +{ + struct slot *old_slot, *next_slot; + + old_slot = ctrl->slot; + ctrl->slot = NULL; + + while (old_slot) { + next_slot = old_slot->next; + pci_hp_deregister (old_slot->hotplug_slot); + kfree(old_slot->hotplug_slot->info); + kfree(old_slot->hotplug_slot->name); + kfree(old_slot->hotplug_slot); + kfree(old_slot); + old_slot = next_slot; + } + + + return(0); +} + +static int get_ctlr_slot_config(struct controller *ctrl) +{ + int num_ctlr_slots; /* Not needed; PCI Express has 1 slot per port*/ + int first_device_num; /* Not needed */ + int physical_slot_num; + u8 ctrlcap; + int rc; + + rc = pcie_get_ctlr_slot_config(ctrl, &num_ctlr_slots, &first_device_num, &physical_slot_num, &ctrlcap); + if (rc) { + err("%s: get_ctlr_slot_config fail for b:d (%x:%x)\n", __FUNCTION__, ctrl->bus, ctrl->device); + return (-1); + } + + ctrl->num_slots = num_ctlr_slots; /* PCI Express has 1 slot per port */ + ctrl->slot_device_offset = first_device_num; + ctrl->first_slot = physical_slot_num; + ctrl->ctrlcap = ctrlcap; + + dbg("%s: bus(0x%x) num_slot(0x%x) 1st_dev(0x%x) psn(0x%x) ctrlcap(%x) for b:d (%x:%x)\n", + __FUNCTION__, ctrl->slot_bus, num_ctlr_slots, first_device_num, physical_slot_num, ctrlcap, + ctrl->bus, ctrl->device); + + return (0); +} + + +/* + * 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; + + dbg("%s - physical_slot = %s\n", __FUNCTION__, hotplug_slot->name); + + hotplug_slot->info->attention_status = status; + + if (ATTN_LED(slot->ctrl->ctrlcap)) + 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; + + dbg("%s - physical_slot = %s\n", __FUNCTION__, hotplug_slot->name); + + return pciehp_enable_slot(slot); +} + + +static int disable_slot(struct hotplug_slot *hotplug_slot) +{ + struct slot *slot = hotplug_slot->private; + + dbg("%s - physical_slot = %s\n", __FUNCTION__, hotplug_slot->name); + + return pciehp_disable_slot(slot); +} + +static int get_power_status(struct hotplug_slot *hotplug_slot, u8 *value) +{ + struct slot *slot = hotplug_slot->private; + int retval; + + dbg("%s - physical_slot = %s\n", __FUNCTION__, hotplug_slot->name); + + 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; + + dbg("%s - physical_slot = %s\n", __FUNCTION__, hotplug_slot->name); + + 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; + + dbg("%s - physical_slot = %s\n", __FUNCTION__, hotplug_slot->name); + + 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; + + dbg("%s - physical_slot = %s\n", __FUNCTION__, hotplug_slot->name); + + 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; + + dbg("%s - physical_slot = %s\n", __FUNCTION__, hotplug_slot->name); + + 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; + + dbg("%s - physical_slot = %s\n", __FUNCTION__, hotplug_slot->name); + + 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; + int first_device_num = 0 ; /* first PCI device number supported by this PCIE */ + int num_ctlr_slots; /* number of slots supported by this HPC */ + u8 value; + struct pci_dev *pdev; + + dbg("%s: Called by hp_drv\n", __FUNCTION__); + ctrl = kmalloc(sizeof(*ctrl), GFP_KERNEL); + if (!ctrl) { + err("%s : out of memory\n", __FUNCTION__); + goto err_out_none; + } + memset(ctrl, 0, sizeof(struct controller)); + + dbg("%s: DRV_thread pid = %d\n", __FUNCTION__, current->pid); + + pdev = dev->port; + + rc = pcie_init(ctrl, dev, + (php_intr_callback_t) pciehp_handle_attention_button, + (php_intr_callback_t) pciehp_handle_switch_change, + (php_intr_callback_t) pciehp_handle_presence_change, + (php_intr_callback_t) pciehp_handle_power_fault); + if (rc) { + dbg("%s: controller initialization failed\n", PCIE_MODULE_NAME); + goto err_out_free_ctrl; + } + + ctrl->pci_dev = pdev; + + pci_set_drvdata(pdev, ctrl); + + ctrl->pci_bus = kmalloc(sizeof(*ctrl->pci_bus), GFP_KERNEL); + if (!ctrl->pci_bus) { + err("%s: out of memory\n", __FUNCTION__); + rc = -ENOMEM; + goto err_out_unmap_mmio_region; + } + dbg("%s: ctrl->pci_bus %p\n", __FUNCTION__, ctrl->pci_bus); + memcpy (ctrl->pci_bus, pdev->bus, sizeof (*ctrl->pci_bus)); + ctrl->bus = pdev->bus->number; /* ctrl bus */ + ctrl->slot_bus = pdev->subordinate->number; /* bus controlled by this HPC */ + + ctrl->device = PCI_SLOT(pdev->devfn); + ctrl->function = PCI_FUNC(pdev->devfn); + dbg("%s: ctrl bus=0x%x, device=%x, function=%x, irq=%x\n", __FUNCTION__, + ctrl->bus, ctrl->device, ctrl->function, pdev->irq); + + /* + * Save configuration headers for this and subordinate PCI buses + */ + + rc = get_ctlr_slot_config(ctrl); + if (rc) { + err(msg_initialization_err, rc); + goto err_out_free_ctrl_bus; + } + first_device_num = ctrl->slot_device_offset; + num_ctlr_slots = ctrl->num_slots; + + /* Store PCI Config Space for all devices on this bus */ + dbg("%s: Before calling pciehp_save_config, ctrl->bus %x,ctrl->slot_bus %x\n", + __FUNCTION__,ctrl->bus, ctrl->slot_bus); + rc = pciehp_save_config(ctrl, ctrl->slot_bus, num_ctlr_slots, first_device_num); + if (rc) { + err("%s: unable to save PCI configuration data, error %d\n", __FUNCTION__, rc); + goto err_out_free_ctrl_bus; + } + + /* Get IO, memory, and IRQ resources for new devices */ + rc = pciehprm_find_available_resources(ctrl); + ctrl->add_support = !rc; + + if (rc) { + dbg("pciehprm_find_available_resources = %#x\n", rc); + err("unable to locate PCI configuration resources for hot plug add.\n"); + goto err_out_free_ctrl_bus; + } + + /* Setup the slot information structures */ + rc = init_slots(ctrl); + if (rc) { + err(msg_initialization_err, 6); + goto err_out_free_ctrl_slot; + } + + t_slot = pciehp_find_slot(ctrl, first_device_num); + dbg("%s: t_slot %p\n", __FUNCTION__, t_slot); + + /* Finish setting up the hot plug ctrl device */ + ctrl->next_event = 0; + + if (!pciehp_ctrl_list) { + pciehp_ctrl_list = ctrl; + ctrl->next = NULL; + } else { + ctrl->next = pciehp_ctrl_list; + pciehp_ctrl_list = ctrl; + } + + /* Wait for exclusive access to hardware */ + down(&ctrl->crit_sect); + + t_slot->hpc_ops->get_adapter_status(t_slot, &value); /* Check if slot is occupied */ + dbg("%s: adpater value %x\n", __FUNCTION__, value); + + if ((POWER_CTRL(ctrl->ctrlcap)) && !value) { + rc = t_slot->hpc_ops->power_off_slot(t_slot); /* Power off slot if not occupied*/ + if (rc) { + /* Done with exclusive hardware access */ + up(&ctrl->crit_sect); + goto err_out_free_ctrl_slot; + } else + /* Wait for the command to complete */ + wait_for_ctrl_irq (ctrl); + } + + /* Done with exclusive hardware access */ + up(&ctrl->crit_sect); + + return 0; + +err_out_free_ctrl_slot: + cleanup_slots(ctrl); +err_out_free_ctrl_bus: + kfree(ctrl->pci_bus); +err_out_unmap_mmio_region: + ctrl->hpc_ops->release_ctlr(ctrl); +err_out_free_ctrl: + kfree(ctrl); +err_out_none: + return -ENODEV; +} + + +static int pcie_start_thread(void) +{ + int loop; + int retval = 0; + + dbg("Initialize + Start the notification/polling mechanism \n"); + + retval = pciehp_event_start_thread(); + if (retval) { + dbg("pciehp_event_start_thread() failed\n"); + return retval; + } + + dbg("Initialize slot lists\n"); + /* One slot list for each bus in the system */ + for (loop = 0; loop < 256; loop++) { + pciehp_slot_list[loop] = NULL; + } + + return retval; +} + +static inline void __exit +free_pciehp_res(struct pci_resource *res) +{ + struct pci_resource *tres; + + while (res) { + tres = res; + res = res->next; + kfree(tres); + } +} + +static void __exit unload_pciehpd(void) +{ + struct pci_func *next; + struct pci_func *TempSlot; + int loop; + struct controller *ctrl; + struct controller *tctrl; + + ctrl = pciehp_ctrl_list; + + while (ctrl) { + cleanup_slots(ctrl); + + free_pciehp_res(ctrl->io_head); + free_pciehp_res(ctrl->mem_head); + free_pciehp_res(ctrl->p_mem_head); + free_pciehp_res(ctrl->bus_head); + + kfree (ctrl->pci_bus); + + ctrl->hpc_ops->release_ctlr(ctrl); + + tctrl = ctrl; + ctrl = ctrl->next; + + kfree(tctrl); + } + + for (loop = 0; loop < 256; loop++) { + next = pciehp_slot_list[loop]; + while (next != NULL) { + free_pciehp_res(next->io_head); + free_pciehp_res(next->mem_head); + free_pciehp_res(next->p_mem_head); + free_pciehp_res(next->bus_head); + + TempSlot = next; + next = next->next; + kfree(TempSlot); + } + } + + /* Stop the notification mechanism */ + pciehp_event_stop_thread(); + +} + +int hpdriver_context = 0; + +static void pciehp_remove (struct pcie_device *device) +{ + printk("%s ENTRY\n", __FUNCTION__); + printk("%s -> Call free_irq for irq = %d\n", + __FUNCTION__, device->irq); + free_irq(device->irq, &hpdriver_context); +} + +#ifdef CONFIG_PM +static int pciehp_suspend (struct pcie_device *dev, u32 state) +{ + printk("%s ENTRY\n", __FUNCTION__); + return 0; +} + +static int pciehp_resume (struct pcie_device *dev) +{ + printk("%s ENTRY\n", __FUNCTION__); + return 0; +} +#endif + +static struct pcie_port_service_id port_pci_ids[] = { { + .vendor = PCI_ANY_ID, + .device = PCI_ANY_ID, + .port_type = PCIE_RC_PORT, + .service_type = PCIE_PORT_SERVICE_HP, + .driver_data = 0, + }, { /* end: all zeroes */ } +}; +static const char device_name[] = "hpdriver"; + +static struct pcie_port_service_driver hpdriver_portdrv = { + .name = (char *)device_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; + +#ifdef CONFIG_HOTPLUG_PCI_PCIE_POLL_EVENT_MODE + pciehp_poll_mode = 1; +#endif + + retval = pcie_start_thread(); + if (retval) + goto error_hpc_init; + + retval = pciehprm_init(PCI); + if (!retval) { + 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("%s: Failure to register service\n", __FUNCTION__); + } + +error_hpc_init: + if (retval) { + pciehprm_cleanup(); + pciehp_event_stop_thread(); + } else + pciehprm_print_pirt(); + + return retval; +} + +static void __exit pcied_cleanup(void) +{ + dbg("unload_pciehpd()\n"); + unload_pciehpd(); + + pciehprm_cleanup(); + + dbg("pcie_port_service_unregister\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 000000000000..0dbcf04aa35e --- /dev/null +++ b/drivers/pci/hotplug/pciehp_ctrl.c @@ -0,0 +1,2706 @@ +/* + * 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>, <dely.l.sy@intel.com> + * + */ + +#include <linux/config.h> +#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 "../pci.h" +#include "pciehp.h" +#include "pciehprm.h" + +static u32 configure_new_device(struct controller *ctrl, struct pci_func *func, + u8 behind_bridge, struct resource_lists *resources, u8 bridge_bus, u8 bridge_dev); +static int configure_new_function( struct controller *ctrl, struct pci_func *func, + u8 behind_bridge, struct resource_lists *resources, u8 bridge_bus, u8 bridge_dev); +static void interrupt_event_handler(struct controller *ctrl); + +static struct semaphore event_semaphore; /* mutex for process loop (up if something to process) */ +static struct semaphore event_exit; /* guard ensure thread has exited before calling it quits */ +static int event_finished; +static unsigned long pushbutton_pending; /* = 0 */ +static unsigned long surprise_rm_pending; /* = 0 */ + +u8 pciehp_handle_attention_button(u8 hp_slot, void *inst_id) +{ + struct controller *ctrl = (struct controller *) inst_id; + struct slot *p_slot; + u8 rc = 0; + u8 getstatus; + struct pci_func *func; + struct event_info *taskInfo; + + /* Attention Button Change */ + dbg("pciehp: Attention button interrupt received.\n"); + + func = pciehp_slot_find(ctrl->slot_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]); + p_slot = pciehp_find_slot(ctrl, hp_slot + ctrl->slot_device_offset); + + p_slot->hpc_ops->get_adapter_status(p_slot, &(func->presence_save)); + p_slot->hpc_ops->get_latch_status(p_slot, &getstatus); + + ctrl->next_event = (ctrl->next_event + 1) % 10; + taskInfo->hp_slot = hp_slot; + + rc++; + + /* + * Button pressed - See if need to TAKE ACTION!!! + */ + info("Button pressed on Slot(%d)\n", ctrl->first_slot + hp_slot); + taskInfo->event_type = INT_BUTTON_PRESS; + + if ((p_slot->state == BLINKINGON_STATE) + || (p_slot->state == BLINKINGOFF_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 + */ + taskInfo->event_type = INT_BUTTON_CANCEL; + info("Button cancel on Slot(%d)\n", ctrl->first_slot + hp_slot); + } else if ((p_slot->state == POWERON_STATE) + || (p_slot->state == POWEROFF_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 + */ + taskInfo->event_type = INT_BUTTON_IGNORE; + info("Button ignore on Slot(%d)\n", ctrl->first_slot + hp_slot); + } + + if (rc) + up(&event_semaphore); /* signal event thread that new event is posted */ + + return 0; + +} + +u8 pciehp_handle_switch_change(u8 hp_slot, void *inst_id) +{ + struct controller *ctrl = (struct controller *) inst_id; + struct slot *p_slot; + u8 rc = 0; + u8 getstatus; + struct pci_func *func; + struct event_info *taskInfo; + + /* Switch Change */ + dbg("pciehp: Switch interrupt received.\n"); + + func = pciehp_slot_find(ctrl->slot_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++; + p_slot = pciehp_find_slot(ctrl, hp_slot + ctrl->slot_device_offset); + p_slot->hpc_ops->get_adapter_status(p_slot, &(func->presence_save)); + p_slot->hpc_ops->get_latch_status(p_slot, &getstatus); + + if (getstatus) { + /* + * Switch opened + */ + info("Latch open on Slot(%d)\n", ctrl->first_slot + hp_slot); + func->switch_save = 0; + taskInfo->event_type = INT_SWITCH_OPEN; + } else { + /* + * Switch closed + */ + info("Latch close on Slot(%d)\n", ctrl->first_slot + hp_slot); + func->switch_save = 0x10; + taskInfo->event_type = INT_SWITCH_CLOSE; + } + + if (rc) + up(&event_semaphore); /* signal event thread that new event is posted */ + + return rc; +} + +u8 pciehp_handle_presence_change(u8 hp_slot, void *inst_id) +{ + struct controller *ctrl = (struct controller *) inst_id; + struct slot *p_slot; + u8 rc = 0; + struct pci_func *func; + struct event_info *taskInfo; + + /* Presence Change */ + dbg("pciehp: Presence/Notify input change.\n"); + + func = pciehp_slot_find(ctrl->slot_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++; + p_slot = pciehp_find_slot(ctrl, hp_slot + ctrl->slot_device_offset); + + /* Switch is open, assume a presence change + * Save the presence state + */ + p_slot->hpc_ops->get_adapter_status(p_slot, &(func->presence_save)); + if (func->presence_save) { + /* + * Card Present + */ + info("Card present on Slot(%d)\n", ctrl->first_slot + hp_slot); + taskInfo->event_type = INT_PRESENCE_ON; + } else { + /* + * Not Present + */ + info("Card not present on Slot(%d)\n", ctrl->first_slot + hp_slot); + taskInfo->event_type = INT_PRESENCE_OFF; + } + + if (rc) + up(&event_semaphore); /* signal event thread that new event is posted */ + + return rc; +} + +u8 pciehp_handle_power_fault(u8 hp_slot, void *inst_id) +{ + struct controller *ctrl = (struct controller *) inst_id; + struct slot *p_slot; + u8 rc = 0; + struct pci_func *func; + struct event_info *taskInfo; + + /* power fault */ + dbg("pciehp: Power fault interrupt received.\n"); + + func = pciehp_slot_find(ctrl->slot_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++; + p_slot = pciehp_find_slot(ctrl, hp_slot + ctrl->slot_device_offset); + + if ( !(p_slot->hpc_ops->query_power_fault(p_slot))) { + /* + * power fault Cleared + */ + info("Power fault cleared on Slot(%d)\n", ctrl->first_slot + hp_slot); + func->status = 0x00; + taskInfo->event_type = INT_POWER_FAULT_CLEAR; + } else { + /* + * power fault + */ + info("Power fault on Slot(%d)\n", ctrl->first_slot + hp_slot); + taskInfo->event_type = INT_POWER_FAULT; + /* set power fault status for this board */ + func->status = 0xFF; + info("power fault bit %x set\n", hp_slot); + } + if (rc) + up(&event_semaphore); /* signal event thread that new event is posted */ + + return rc; +} + + +/** + * sort_by_size: sort nodes 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 + * + * Sorts nodes on the list by their length. + * Largest first. + * + */ +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: return one unused resource node + * @head: list to scan + * + */ +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 = pciehp_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(struct pci_resource), 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: return one unused resource node + * @head: list to scan + * + */ +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; + + if (!(*head)) + return NULL; + + rc = pciehp_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) { + kfree(node); + return NULL; + } + + 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) { + kfree(node); + return NULL; + } + + node->length -= (temp_dword - node->base); + node->base = temp_dword; + } + + if (node->length & (alignment - 1)) { + /* There's stuff in use after this node */ + kfree(node); + return NULL; + } + + return node; +} + + +/* + * get_io_resource + * + * this function sorts the resource list by size and then + * 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. + * + * size must be a power of two. + */ +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 = NULL; + u32 temp_dword; + + if (!(*head)) + return NULL; + + if ( pciehp_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(struct pci_resource), + 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(struct pci_resource), + 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 */ + if (*head == node) { + *head = node->next; + } else { + prevnode = *head; + while (prevnode->next != node) + prevnode = prevnode->next; + + prevnode->next = node->next; + } + node->next = NULL; + /* Stop looping */ + break; + } + + return node; +} + + +/* + * get_max_resource + * + * 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. + * J.I. modified to put max size limits of; 64M->32M->16M->8M->4M->1M + * This is needed to avoid allocating entire ACPI _CRS res to one child bridge/slot. + */ +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; + u32 max_size[] = { 0x4000000, 0x2000000, 0x1000000, 0x0800000, 0x0400000, 0x0200000, 0x0100000, 0x00 }; + int i; + + if (!(*head)) + return NULL; + + if (pciehp_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(struct pci_resource), + 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; + + /* Put it next in the list */ + 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(struct pci_resource), + 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; + + /* Put it in the list */ + 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; + + for ( i = 0; max_size[i] > size; i++) { + if (max->length > max_size[i]) { + split_node = kmalloc(sizeof(struct pci_resource), + GFP_KERNEL); + if (!split_node) + break; /* return NULL; */ + split_node->base = max->base + max_size[i]; + split_node->length = max->length - max_size[i]; + max->length = max_size[i]; + /* Put it next in the list */ + split_node->next = max->next; + max->next = split_node; + break; + } + } + + /* Now take it out of the list */ + temp = (struct pci_resource*) *head; + if (temp == max) { + *head = max->next; + } else { + while (temp && temp->next != max) { + temp = temp->next; + } + + temp->next = max->next; + } + + max->next = NULL; + return max; + } + + /* If we get here, we couldn't find one */ + return NULL; +} + + +/* + * get_resource + * + * 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 (!(*head)) + return NULL; + + if ( pciehp_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 =0x%x node=%p, base=0x%x, length=0x%x\n", + __FUNCTION__, size, node, node->base, node->length); + if (node->length < size) + continue; + + if (node->base & (size - 1)) { + dbg("%s: not aligned\n", __FUNCTION__); + /* 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(struct pci_resource), + 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) { + dbg("%s: too big\n", __FUNCTION__); + /* this one is longer than we need + so we'll make a new entry and split it up */ + split_node = kmalloc(sizeof(struct pci_resource), + 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", __FUNCTION__); + /* 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; + /* Stop looping */ + break; + } + return node; +} + + +/* + * pciehp_resource_sort_and_combine + * + * 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 pciehp_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", __FUNCTION__, 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; +} + + +/** + * pciehp_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 *pciehp_slot_create(u8 busnumber) +{ + struct pci_func *new_slot; + struct pci_func *next; + dbg("%s: busnumber %x\n", __FUNCTION__, busnumber); + new_slot = kmalloc(sizeof(struct pci_func), GFP_KERNEL); + + if (new_slot == NULL) + return new_slot; + + memset(new_slot, 0, sizeof(struct pci_func)); + + new_slot->next = NULL; + new_slot->configured = 1; + + if (pciehp_slot_list[busnumber] == NULL) { + pciehp_slot_list[busnumber] = new_slot; + } else { + next = pciehp_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 = pciehp_slot_list[old_slot->bus]; + + if (next == NULL) + return 1; + + if (next == old_slot) { + pciehp_slot_list[old_slot->bus] = old_slot->next; + pciehp_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; + pciehp_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; + + if (bridge == NULL) + return 1; + + secondaryBus = (bridge->config_space[0x06] >> 8) & 0xFF; + subordinateBus = (bridge->config_space[0x06] >> 16) & 0xFF; + + for (tempBus = secondaryBus; tempBus <= subordinateBus; tempBus++) { + next = pciehp_slot_list[tempBus]; + + while (!slot_remove(next)) { + next = pciehp_slot_list[tempBus]; + } + } + + next = pciehp_slot_list[bridge->bus]; + + if (next == NULL) { + return 1; + } + + if (next == bridge) { + pciehp_slot_list[bridge->bus] = bridge->next; + kfree(bridge); + return 0; + } + + while ((next->next != bridge) && (next->next != NULL)) { + next = next->next; + } + + if (next->next == bridge) { + next->next = bridge->next; + kfree(bridge); + return 0; + } else + return 2; +} + + +/** + * pciehp_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 *pciehp_slot_find(u8 bus, u8 device, u8 index) +{ + int found = -1; + struct pci_func *func; + + func = pciehp_slot_list[bus]; + dbg("%s: bus %x device %x index %x\n", + __FUNCTION__, bus, device, index); + if (func != NULL) { + dbg("%s: func-> bus %x device %x function %x pci_dev %p\n", + __FUNCTION__, func->bus, func->device, func->function, + func->pci_dev); + } else + dbg("%s: func == NULL\n", __FUNCTION__); + + if ((func == NULL) || ((func->device == device) && (index == 0))) + return func; + + if (func->device == device) + found++; + + while (func->next != NULL) { + func = func->next; + + dbg("%s: In while loop, func-> bus %x device %x function %x pci_dev %p\n", + __FUNCTION__, func->bus, func->device, func->function, + func->pci_dev); + if (func->device == device) + found++; + dbg("%s: while loop, found %d, index %d\n", __FUNCTION__, + found, index); + + if ((found == index) || (func->function == index)) { + dbg("%s: Found bus %x dev %x func %x\n", __FUNCTION__, + func->bus, func->device, func->function); + return func; + } + } + + return NULL; +} + +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; +} + + +/* The following routines constitute the bulk of the + hotplug controller logic + */ + +static void set_slot_off(struct controller *ctrl, struct slot * pslot) +{ + /* Wait for exclusive access to hardware */ + down(&ctrl->crit_sect); + + /* turn off slot, turn on Amber LED, turn off Green LED if supported*/ + if (POWER_CTRL(ctrl->ctrlcap)) { + if (pslot->hpc_ops->power_off_slot(pslot)) { + err("%s: Issue of Slot Power Off command failed\n", __FUNCTION__); + up(&ctrl->crit_sect); + return; + } + wait_for_ctrl_irq (ctrl); + } + + if (PWR_LED(ctrl->ctrlcap)) { + pslot->hpc_ops->green_led_off(pslot); + wait_for_ctrl_irq (ctrl); + } + + if (ATTN_LED(ctrl->ctrlcap)) { + if (pslot->hpc_ops->set_attention_status(pslot, 1)) { + err("%s: Issue of Set Attention Led command failed\n", __FUNCTION__); + up(&ctrl->crit_sect); + return; + } + wait_for_ctrl_irq (ctrl); + } + + /* Done with exclusive hardware access */ + up(&ctrl->crit_sect); +} + +/** + * board_added - Called after a board has been added to the system. + * + * Turns power on for the board + * Configures board + * + */ +static u32 board_added(struct pci_func * func, struct controller * ctrl) +{ + u8 hp_slot; + int index; + u32 temp_register = 0xFFFFFFFF; + u32 rc = 0; + struct pci_func *new_func = NULL; + struct slot *p_slot; + struct resource_lists res_lists; + + p_slot = pciehp_find_slot(ctrl, func->device); + hp_slot = func->device - ctrl->slot_device_offset; + + dbg("%s: func->device, slot_offset, hp_slot = %d, %d ,%d\n", __FUNCTION__, func->device, ctrl->slot_device_offset, hp_slot); + + /* Wait for exclusive access to hardware */ + down(&ctrl->crit_sect); + + if (POWER_CTRL(ctrl->ctrlcap)) { + /* Power on slot */ + rc = p_slot->hpc_ops->power_on_slot(p_slot); + if (rc) { + up(&ctrl->crit_sect); + return -1; + } + + /* Wait for the command to complete */ + wait_for_ctrl_irq (ctrl); + } + + if (PWR_LED(ctrl->ctrlcap)) { + p_slot->hpc_ops->green_led_blink(p_slot); + + /* Wait for the command to complete */ + wait_for_ctrl_irq (ctrl); + } + + /* Done with exclusive hardware access */ + up(&ctrl->crit_sect); + + /* Wait for ~1 second */ + dbg("%s: before long_delay\n", __FUNCTION__); + wait_for_ctrl_irq (ctrl); + dbg("%s: afterlong_delay\n", __FUNCTION__); + + /* Check link training status */ + rc = p_slot->hpc_ops->check_lnk_status(ctrl); + if (rc) { + err("%s: Failed to check link status\n", __FUNCTION__); + set_slot_off(ctrl, p_slot); + return rc; + } + + dbg("%s: func status = %x\n", __FUNCTION__, 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", __FUNCTION__, temp_register); + rc = POWER_FAILURE; + func->status = 0; + } else { + /* Get vendor/device ID u32 */ + rc = pci_bus_read_config_dword (ctrl->pci_dev->subordinate, PCI_DEVFN(func->device, func->function), + PCI_VENDOR_ID, &temp_register); + dbg("%s: pci_bus_read_config_dword returns %d\n", __FUNCTION__, rc); + dbg("%s: temp_register is %x\n", __FUNCTION__, temp_register); + + if (rc != 0) { + /* Something's wrong here */ + temp_register = 0xFFFFFFFF; + dbg("%s: temp register set to %x by error\n", __FUNCTION__, 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, 0, 0); + dbg("%s: back from configure_new_device\n", __FUNCTION__); + + 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; + + pciehp_resource_sort_and_combine(&(ctrl->mem_head)); + pciehp_resource_sort_and_combine(&(ctrl->p_mem_head)); + pciehp_resource_sort_and_combine(&(ctrl->io_head)); + pciehp_resource_sort_and_combine(&(ctrl->bus_head)); + + if (rc) { + set_slot_off(ctrl, p_slot); + return rc; + } + pciehp_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) + */ + index = 0; + do { + new_func = pciehp_slot_find(ctrl->slot_bus, func->device, index++); + if (new_func && !new_func->pci_dev) { + dbg("%s:call pci_hp_configure_dev, func %x\n", + __FUNCTION__, index); + pciehp_configure_device(ctrl, new_func); + } + } while (new_func); + + /* + * 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->ctrlcap)) { + /* Wait for exclusive access to hardware */ + down(&ctrl->crit_sect); + + p_slot->hpc_ops->green_led_on(p_slot); + + /* Wait for the command to complete */ + wait_for_ctrl_irq (ctrl); + + /* Done with exclusive hardware access */ + up(&ctrl->crit_sect); + } + } else { + set_slot_off(ctrl, p_slot); + return -1; + } + return 0; +} + + +/** + * remove_board - Turns off slot and LED's + * + */ +static u32 remove_board(struct pci_func *func, struct controller *ctrl) +{ + int index; + u8 skip = 0; + u8 device; + u8 hp_slot; + u32 rc; + struct resource_lists res_lists; + struct pci_func *temp_func; + struct slot *p_slot; + + if (func == NULL) + return 1; + + if (pciehp_unconfigure_device(func)) + return 1; + + device = func->device; + + hp_slot = func->device - ctrl->slot_device_offset; + p_slot = pciehp_find_slot(ctrl, hp_slot + ctrl->slot_device_offset); + + dbg("In %s, hp_slot = %d\n", __FUNCTION__, hp_slot); + + if ((ctrl->add_support) && + !(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 = func; + + while ((temp_func = pciehp_slot_find(temp_func->bus, temp_func->device, index++))) { + if (temp_func->bus_head || temp_func->mem_head + || temp_func->p_mem_head || temp_func->io_head) { + skip = 1; + break; + } + } + + if (!skip) + rc = pciehp_save_used_resources(ctrl, func, DISABLE_CARD); + } + /* Change status to shutdown */ + if (func->is_a_board) + func->status = 0x01; + func->configured = 0; + + /* Wait for exclusive access to hardware */ + down(&ctrl->crit_sect); + + if (POWER_CTRL(ctrl->ctrlcap)) { + /* power off slot */ + rc = p_slot->hpc_ops->power_off_slot(p_slot); + if (rc) { + err("%s: Issue of Slot Disable command failed\n", __FUNCTION__); + up(&ctrl->crit_sect); + return rc; + } + /* Wait for the command to complete */ + wait_for_ctrl_irq (ctrl); + } + + if (PWR_LED(ctrl->ctrlcap)) { + /* turn off Green LED */ + p_slot->hpc_ops->green_led_off(p_slot); + + /* Wait for the command to complete */ + wait_for_ctrl_irq (ctrl); + } + + /* Done with exclusive hardware access */ + up(&ctrl->crit_sect); + + if (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; + + dbg("Returning resources to ctlr lists for (B/D/F) = (%#x/%#x/%#x)\n", + func->bus, func->device, func->function); + + pciehp_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; + + pciehp_resource_sort_and_combine(&(ctrl->mem_head)); + pciehp_resource_sort_and_combine(&(ctrl->p_mem_head)); + pciehp_resource_sort_and_combine(&(ctrl->io_head)); + pciehp_resource_sort_and_combine(&(ctrl->bus_head)); + + if (is_bridge(func)) { + dbg("PCI Bridge Hot-Remove s:b:d:f(%02x:%02x:%02x:%02x)\n", + ctrl->seg, func->bus, func->device, func->function); + bridge_slot_remove(func); + } else { + dbg("PCI Function Hot-Remove s:b:d:f(%02x:%02x:%02x:%02x)\n", + ctrl->seg, func->bus, func->device, func->function); + slot_remove(func); + } + + func = pciehp_slot_find(ctrl->slot_bus, device, 0); + } + + /* Setup slot structure with entry for empty slot */ + func = pciehp_slot_create(ctrl->slot_bus); + + if (func == NULL) { + return 1; + } + + func->bus = ctrl->slot_bus; + func->device = device; + func->function = 0; + func->configured = 0; + func->switch_save = 0x10; + func->is_a_board = 0; + } + + return 0; +} + + +static void pushbutton_helper_thread(unsigned long data) +{ + pushbutton_pending = data; + + up(&event_semaphore); +} + +/** + * pciehp_pushbutton_thread + * + * Scheduled procedure to handle blocking stuff for the pushbuttons + * Handles all pending events and exits. + * + */ +static void pciehp_pushbutton_thread(unsigned long slot) +{ + struct slot *p_slot = (struct slot *) slot; + u8 getstatus; + + pushbutton_pending = 0; + + if (!p_slot) { + dbg("%s: Error! slot NULL\n", __FUNCTION__); + return; + } + + p_slot->hpc_ops->get_power_status(p_slot, &getstatus); + if (getstatus) { + p_slot->state = POWEROFF_STATE; + dbg("In power_down_board, b:d(%x:%x)\n", p_slot->bus, p_slot->device); + + pciehp_disable_slot(p_slot); + p_slot->state = STATIC_STATE; + } else { + p_slot->state = POWERON_STATE; + dbg("In add_board, b:d(%x:%x)\n", p_slot->bus, p_slot->device); + + if (pciehp_enable_slot(p_slot) && PWR_LED(p_slot->ctrl->ctrlcap)) { + /* Wait for exclusive access to hardware */ + down(&p_slot->ctrl->crit_sect); + + p_slot->hpc_ops->green_led_off(p_slot); + + /* Wait for the command to complete */ + wait_for_ctrl_irq (p_slot->ctrl); + + /* Done with exclusive hardware access */ + up(&p_slot->ctrl->crit_sect); + } + p_slot->state = STATIC_STATE; + } + + return; +} + +/** + * pciehp_surprise_rm_thread + * + * Scheduled procedure to handle blocking stuff for the surprise removal + * Handles all pending events and exits. + * + */ +static void pciehp_surprise_rm_thread(unsigned long slot) +{ + struct slot *p_slot = (struct slot *) slot; + u8 getstatus; + + surprise_rm_pending = 0; + + if (!p_slot) { + dbg("%s: Error! slot NULL\n", __FUNCTION__); + return; + } + + p_slot->hpc_ops->get_adapter_status(p_slot, &getstatus); + if (!getstatus) { + p_slot->state = POWEROFF_STATE; + dbg("In removing board, b:d(%x:%x)\n", p_slot->bus, p_slot->device); + + pciehp_disable_slot(p_slot); + p_slot->state = STATIC_STATE; + } else { + p_slot->state = POWERON_STATE; + dbg("In add_board, b:d(%x:%x)\n", p_slot->bus, p_slot->device); + + if (pciehp_enable_slot(p_slot) && PWR_LED(p_slot->ctrl->ctrlcap)) { + /* Wait for exclusive access to hardware */ + down(&p_slot->ctrl->crit_sect); + + p_slot->hpc_ops->green_led_off(p_slot); + + /* Wait for the command to complete */ + wait_for_ctrl_irq (p_slot->ctrl); + + /* Done with exclusive hardware access */ + up(&p_slot->ctrl->crit_sect); + } + p_slot->state = STATIC_STATE; + } + + return; +} + + + +/* this is the main worker thread */ +static int event_thread(void* data) +{ + struct controller *ctrl; + lock_kernel(); + daemonize("pciehpd_event"); + + unlock_kernel(); + + while (1) { + dbg("!!!!event_thread sleeping\n"); + down_interruptible (&event_semaphore); + dbg("event_thread woken finished = %d\n", event_finished); + if (event_finished || signal_pending(current)) + break; + /* Do stuff here */ + if (pushbutton_pending) + pciehp_pushbutton_thread(pushbutton_pending); + else if (surprise_rm_pending) + pciehp_surprise_rm_thread(surprise_rm_pending); + else + for (ctrl = pciehp_ctrl_list; ctrl; ctrl=ctrl->next) + interrupt_event_handler(ctrl); + } + dbg("event_thread signals exit\n"); + up(&event_exit); + return 0; +} + +int pciehp_event_start_thread(void) +{ + int pid; + + /* initialize our semaphores */ + init_MUTEX_LOCKED(&event_exit); + event_finished=0; + + init_MUTEX_LOCKED(&event_semaphore); + pid = kernel_thread(event_thread, NULL, 0); + + if (pid < 0) { + err ("Can't start up our event thread\n"); + return -1; + } + dbg("Our event thread pid = %d\n", pid); + return 0; +} + + +void pciehp_event_stop_thread(void) +{ + event_finished = 1; + dbg("event_thread finish command given\n"); + up(&event_semaphore); + dbg("wait for event_thread to exit\n"); + down(&event_exit); +} + + +static int update_slot_info(struct slot *slot) +{ + struct hotplug_slot_info *info; + /* char buffer[SLOT_NAME_SIZE]; */ + int result; + + info = kmalloc(sizeof(struct hotplug_slot_info), GFP_KERNEL); + if (!info) + return -ENOMEM; + + /* make_slot_name (&buffer[0], SLOT_NAME_SIZE, slot); */ + + 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(buffer, info); */ + 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; + u8 getstatus; + struct slot *p_slot; + + while (change) { + change = 0; + + for (loop = 0; loop < 10; loop++) { + if (ctrl->event_queue[loop].event_type != 0) { + hp_slot = ctrl->event_queue[loop].hp_slot; + + func = pciehp_slot_find(ctrl->slot_bus, (hp_slot + ctrl->slot_device_offset), 0); + + p_slot = pciehp_find_slot(ctrl, hp_slot + ctrl->slot_device_offset); + + dbg("hp_slot %d, func %p, p_slot %p\n", hp_slot, func, p_slot); + + if (ctrl->event_queue[loop].event_type == INT_BUTTON_CANCEL) { + dbg("button cancel\n"); + del_timer(&p_slot->task_event); + + switch (p_slot->state) { + case BLINKINGOFF_STATE: + /* Wait for exclusive access to hardware */ + down(&ctrl->crit_sect); + + if (PWR_LED(ctrl->ctrlcap)) { + p_slot->hpc_ops->green_led_on(p_slot); + /* Wait for the command to complete */ + wait_for_ctrl_irq (ctrl); + } + if (ATTN_LED(ctrl->ctrlcap)) { + p_slot->hpc_ops->set_attention_status(p_slot, 0); + + /* Wait for the command to complete */ + wait_for_ctrl_irq (ctrl); + } + /* Done with exclusive hardware access */ + up(&ctrl->crit_sect); + break; + case BLINKINGON_STATE: + /* Wait for exclusive access to hardware */ + down(&ctrl->crit_sect); + + if (PWR_LED(ctrl->ctrlcap)) { + p_slot->hpc_ops->green_led_off(p_slot); + /* Wait for the command to complete */ + wait_for_ctrl_irq (ctrl); + } + if (ATTN_LED(ctrl->ctrlcap)){ + p_slot->hpc_ops->set_attention_status(p_slot, 0); + /* Wait for the command to complete */ + wait_for_ctrl_irq (ctrl); + } + /* Done with exclusive hardware access */ + up(&ctrl->crit_sect); + + break; + default: + warn("Not a valid state\n"); + return; + } + info(msg_button_cancel, p_slot->number); + p_slot->state = STATIC_STATE; + } + /* ***********Button Pressed (No action on 1st press...) */ + else if (ctrl->event_queue[loop].event_type == INT_BUTTON_PRESS) { + + if (ATTN_BUTTN(ctrl->ctrlcap)) { + dbg("Button pressed\n"); + p_slot->hpc_ops->get_power_status(p_slot, &getstatus); + if (getstatus) { + /* slot is on */ + dbg("slot is on\n"); + p_slot->state = BLINKINGOFF_STATE; + info(msg_button_off, p_slot->number); + } else { + /* slot is off */ + dbg("slot is off\n"); + p_slot->state = BLINKINGON_STATE; + info(msg_button_on, p_slot->number); + } + + /* Wait for exclusive access to hardware */ + down(&ctrl->crit_sect); + + /* blink green LED and turn off amber */ + if (PWR_LED(ctrl->ctrlcap)) { + p_slot->hpc_ops->green_led_blink(p_slot); + /* Wait for the command to complete */ + wait_for_ctrl_irq (ctrl); + } + + if (ATTN_LED(ctrl->ctrlcap)) { + p_slot->hpc_ops->set_attention_status(p_slot, 0); + + /* Wait for the command to complete */ + wait_for_ctrl_irq (ctrl); + } + + /* Done with exclusive hardware access */ + up(&ctrl->crit_sect); + + init_timer(&p_slot->task_event); + p_slot->task_event.expires = jiffies + 5 * HZ; /* 5 second delay */ + p_slot->task_event.function = (void (*)(unsigned long)) pushbutton_helper_thread; + p_slot->task_event.data = (unsigned long) p_slot; + + dbg("add_timer p_slot = %p\n", (void *) p_slot); + add_timer(&p_slot->task_event); + } + } + /***********POWER FAULT********************/ + else if (ctrl->event_queue[loop].event_type == INT_POWER_FAULT) { + if (POWER_CTRL(ctrl->ctrlcap)) { + dbg("power fault\n"); + /* Wait for exclusive access to hardware */ + down(&ctrl->crit_sect); + + if (ATTN_LED(ctrl->ctrlcap)) { + p_slot->hpc_ops->set_attention_status(p_slot, 1); + wait_for_ctrl_irq (ctrl); + } + + if (PWR_LED(ctrl->ctrlcap)) { + p_slot->hpc_ops->green_led_off(p_slot); + wait_for_ctrl_irq (ctrl); + } + + /* Done with exclusive hardware access */ + up(&ctrl->crit_sect); + } + } + /***********SURPRISE REMOVAL********************/ + else if ((ctrl->event_queue[loop].event_type == INT_PRESENCE_ON) || + (ctrl->event_queue[loop].event_type == INT_PRESENCE_OFF)) { + if (HP_SUPR_RM(ctrl->ctrlcap)) { + dbg("Surprise Removal\n"); + if (p_slot) { + surprise_rm_pending = (unsigned long) p_slot; + up(&event_semaphore); + update_slot_info(p_slot); + } + } + } else { + /* refresh notification */ + if (p_slot) + update_slot_info(p_slot); + } + + ctrl->event_queue[loop].event_type = 0; + + change = 1; + } + } /* End of FOR loop */ + } +} + + +int pciehp_enable_slot(struct slot *p_slot) +{ + u8 getstatus = 0; + int rc; + struct pci_func *func; + + func = pciehp_slot_find(p_slot->bus, p_slot->device, 0); + if (!func) { + dbg("%s: Error! slot NULL\n", __FUNCTION__); + return 1; + } + + /* Check to see if (latch closed, card present, power off) */ + down(&p_slot->ctrl->crit_sect); + + rc = p_slot->hpc_ops->get_adapter_status(p_slot, &getstatus); + if (rc || !getstatus) { + info("%s: no adapter on slot(%x)\n", __FUNCTION__, p_slot->number); + up(&p_slot->ctrl->crit_sect); + return 1; + } + if (MRL_SENS(p_slot->ctrl->ctrlcap)) { + rc = p_slot->hpc_ops->get_latch_status(p_slot, &getstatus); + if (rc || getstatus) { + info("%s: latch open on slot(%x)\n", __FUNCTION__, p_slot->number); + up(&p_slot->ctrl->crit_sect); + return 1; + } + } + + if (POWER_CTRL(p_slot->ctrl->ctrlcap)) { + rc = p_slot->hpc_ops->get_power_status(p_slot, &getstatus); + if (rc || getstatus) { + info("%s: already enabled on slot(%x)\n", __FUNCTION__, p_slot->number); + up(&p_slot->ctrl->crit_sect); + return 1; + } + } + up(&p_slot->ctrl->crit_sect); + + slot_remove(func); + + func = pciehp_slot_create(p_slot->bus); + if (func == NULL) + return 1; + + func->bus = p_slot->bus; + func->device = p_slot->device; + func->function = 0; + func->configured = 0; + func->is_a_board = 1; + + /* We have to save the presence info for these slots */ + p_slot->hpc_ops->get_adapter_status(p_slot, &(func->presence_save)); + p_slot->hpc_ops->get_latch_status(p_slot, &getstatus); + func->switch_save = !getstatus? 0x10:0; + + rc = board_added(func, p_slot->ctrl); + if (rc) { + if (is_bridge(func)) + bridge_slot_remove(func); + else + slot_remove(func); + + /* Setup slot structure with entry for empty slot */ + func = pciehp_slot_create(p_slot->bus); + if (func == NULL) + return 1; /* Out of memory */ + + func->bus = p_slot->bus; + func->device = p_slot->device; + func->function = 0; + func->configured = 0; + func->is_a_board = 1; + + /* We have to save the presence info for these slots */ + p_slot->hpc_ops->get_adapter_status(p_slot, &(func->presence_save)); + p_slot->hpc_ops->get_latch_status(p_slot, &getstatus); + func->switch_save = !getstatus? 0x10:0; + } + + if (p_slot) + update_slot_info(p_slot); + + return rc; +} + + +int pciehp_disable_slot(struct slot *p_slot) +{ + u8 class_code, header_type, BCR; + u8 index = 0; + u8 getstatus = 0; + u32 rc = 0; + int ret = 0; + unsigned int devfn; + struct pci_bus *pci_bus = p_slot->ctrl->pci_dev->subordinate; + struct pci_func *func; + + if (!p_slot->ctrl) + return 1; + + /* Check to see if (latch closed, card present, power on) */ + down(&p_slot->ctrl->crit_sect); + + if (!HP_SUPR_RM(p_slot->ctrl->ctrlcap)) { + ret = p_slot->hpc_ops->get_adapter_status(p_slot, &getstatus); + if (ret || !getstatus) { + info("%s: no adapter on slot(%x)\n", __FUNCTION__, p_slot->number); + up(&p_slot->ctrl->crit_sect); + return 1; + } + } + + if (MRL_SENS(p_slot->ctrl->ctrlcap)) { + ret = p_slot->hpc_ops->get_latch_status(p_slot, &getstatus); + if (ret || getstatus) { + info("%s: latch open on slot(%x)\n", __FUNCTION__, p_slot->number); + up(&p_slot->ctrl->crit_sect); + return 1; + } + } + + if (POWER_CTRL(p_slot->ctrl->ctrlcap)) { + ret = p_slot->hpc_ops->get_power_status(p_slot, &getstatus); + if (ret || !getstatus) { + info("%s: already disabled slot(%x)\n", __FUNCTION__, p_slot->number); + up(&p_slot->ctrl->crit_sect); + return 1; + } + } + + up(&p_slot->ctrl->crit_sect); + + func = pciehp_slot_find(p_slot->bus, p_slot->device, index++); + + /* Make sure there are no video controllers here + * for all func of p_slot + */ + 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 = pciehp_slot_find(p_slot->bus, p_slot->device, index++); + } + + func = pciehp_slot_find(p_slot->bus, p_slot->device, 0); + if ((func != NULL) && !rc) { + rc = remove_board(func, p_slot->ctrl); + } else if (!rc) + rc = 1; + + if (p_slot) + update_slot_info(p_slot); + + return rc; +} + + +/** + * 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 bridge_bus, u8 bridge_dev) +{ + u8 temp_byte, function, max_functions, stop_it; + int rc; + u32 ID; + struct pci_func *new_slot; + struct pci_bus lpci_bus, *pci_bus; + int index; + + new_slot = func; + + dbg("%s\n", __FUNCTION__); + memcpy(&lpci_bus, ctrl->pci_dev->subordinate, sizeof(lpci_bus)); + pci_bus = &lpci_bus; + pci_bus->number = func->bus; + + /* Check for Multi-function device */ + rc = pci_bus_read_config_byte(pci_bus, PCI_DEVFN(func->device, func->function), 0x0E, &temp_byte); + if (rc) { + dbg("%s: rc = %d\n", __FUNCTION__, 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, bridge_bus, bridge_dev); + + if (rc) { + dbg("configure_new_function failed: %d\n", rc); + index = 0; + + while (new_slot) { + new_slot = pciehp_slot_find(new_slot->bus, + new_slot->device, index++); + + if (new_slot) + pciehp_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(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 = pciehp_slot_create(func->bus); + + if (new_slot == NULL) { + /* Out of memory */ + 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 %s\n", __FUNCTION__); + + return 0; +} + +/* + * Configuration logic that involves the hotplug data structures and + * their bookkeeping + */ + +/** + * configure_bridge: fill bridge's registers, either configure or disable it. + */ +static int +configure_bridge(struct pci_bus *pci_bus, unsigned int devfn, + struct pci_resource *mem_node, + struct pci_resource **hold_mem_node, + int base_addr, int limit_addr) +{ + u16 temp_word; + u32 rc; + + if (mem_node) { + memcpy(*hold_mem_node, mem_node, sizeof(struct pci_resource)); + mem_node->next = NULL; + + /* set Mem base and Limit registers */ + RES_CHECK(mem_node->base, 16); + temp_word = (u16)(mem_node->base >> 16); + rc = pci_bus_write_config_word(pci_bus, devfn, base_addr, temp_word); + + RES_CHECK(mem_node->base + mem_node->length - 1, 16); + temp_word = (u16)((mem_node->base + mem_node->length - 1) >> 16); + rc = pci_bus_write_config_word(pci_bus, devfn, limit_addr, temp_word); + } else { + temp_word = 0xFFFF; + rc = pci_bus_write_config_word(pci_bus, devfn, base_addr, temp_word); + + temp_word = 0x0000; + rc = pci_bus_write_config_word(pci_bus, devfn, limit_addr, temp_word); + + kfree(*hold_mem_node); + *hold_mem_node = NULL; + } + return rc; +} + +static int +configure_new_bridge(struct controller *ctrl, struct pci_func *func, + u8 behind_bridge, struct resource_lists *resources, + struct pci_bus *pci_bus) +{ + int cloop; + u8 temp_byte; + u8 device; + u16 temp_word; + u32 rc; + 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 resource_lists temp_resources; + + devfn = PCI_DEVFN(func->device, func->function); + + /* set Primary bus */ + dbg("set Primary bus = 0x%x\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 */ + bus_node = get_max_resource(&resources->bus_head, 1L); + + /* If we don't have any busses to allocate, we can't continue */ + if (!bus_node) { + err("Got NO bus resource to use\n"); + return -ENOMEM; + } + dbg("Got ranges of buses to use: base:len=0x%x:%x\n", bus_node->base, bus_node->length); + + /* set Secondary bus */ + temp_byte = (u8)bus_node->base; + dbg("set Secondary bus = 0x%x\n", temp_byte); + rc = pci_bus_write_config_byte(pci_bus, devfn, PCI_SECONDARY_BUS, temp_byte); + if (rc) + return rc; + + /* set subordinate bus */ + temp_byte = (u8)(bus_node->base + bus_node->length - 1); + dbg("set subordinate bus = 0x%x\n", temp_byte); + rc = pci_bus_write_config_byte (pci_bus, devfn, PCI_SUBORDINATE_BUS, temp_byte); + if (rc) + return rc; + + /* Set HP parameters (Cache Line Size, Latency Timer) */ + rc = pciehprm_set_hpp(ctrl, func, PCI_HEADER_TYPE_BRIDGE); + if (rc) + return rc; + + /* Setup the IO, memory, and prefetchable windows */ + + io_node = get_max_resource(&(resources->io_head), 0x1000L); + if (io_node) { + dbg("io_node(base, len, next) (%x, %x, %p)\n", io_node->base, + io_node->length, io_node->next); + } + + mem_node = get_max_resource(&(resources->mem_head), 0x100000L); + if (mem_node) { + dbg("mem_node(base, len, next) (%x, %x, %p)\n", mem_node->base, + mem_node->length, mem_node->next); + } + + if (resources->p_mem_head) + p_mem_node = get_max_resource(&(resources->p_mem_head), 0x100000L); + else { + /* + * In some platform implementation, MEM and PMEM are not + * distinguished, and hence ACPI _CRS has only MEM entries + * for both MEM and PMEM. + */ + dbg("using MEM for PMEM\n"); + p_mem_node = get_max_resource(&(resources->mem_head), 0x100000L); + } + if (p_mem_node) { + dbg("p_mem_node(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(struct pci_resource), GFP_KERNEL); + hold_IO_node = kmalloc(sizeof(struct pci_resource), GFP_KERNEL); + hold_mem_node = kmalloc(sizeof(struct pci_resource), GFP_KERNEL); + hold_p_mem_node = kmalloc(sizeof(struct pci_resource), 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 */ + RES_CHECK(io_node->base, 8); + temp_byte = (u8)(io_node->base >> 8); + rc = pci_bus_write_config_byte (pci_bus, devfn, PCI_IO_BASE, temp_byte); + + RES_CHECK(io_node->base + io_node->length - 1, 8); + temp_byte = (u8)((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. + */ + rc = configure_bridge(pci_bus, devfn, mem_node, &hold_mem_node, + PCI_MEMORY_BASE, PCI_MEMORY_LIMIT); + + /* If we have prefetchable memory resources copy them and + * fill in the bridge's memory range registers. Otherwise, + * fill in the range registers with values that disable them. + */ + rc = configure_bridge(pci_bus, devfn, p_mem_node, &hold_p_mem_node, + PCI_PREF_MEMORY_BASE, PCI_PREF_MEMORY_LIMIT); + + /* 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), PCI_VENDOR_ID, &ID); + pci_bus->number = func->bus; + + if (ID != 0xFFFFFFFF) { /* device Present */ + /* Setup slot structure. */ + new_slot = pciehp_slot_create(hold_bus_node->base); + + if (new_slot == NULL) { + /* Out of memory */ + 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, func->bus, + func->device); + dbg("configure_new_device rc=0x%x\n",rc); + } /* End of IF (device in slot?) */ + } /* End of FOR loop */ + + if (rc) { + pciehp_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); + } + + /* 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 = pciehp_set_irq(func->bus, func->device, + 0x0A + cloop, irqs.interrupt[cloop]); + if (rc) { + pciehp_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; + } + } + } /* 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 = (u8)(temp_resources.bus_head->base - 1); + + /* set subordinate bus */ + dbg("re-set subordinate bus = 0x%x\n", temp_byte); + 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 { + dbg("return bus res of b:d(0x%x:%x) base:len(0x%x:%x)\n", + func->bus, func->device, temp_resources.bus_head->base, temp_resources.bus_head->length); + 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; + + RES_CHECK(hold_IO_node->base, 8); + temp_byte = (u8)((hold_IO_node->base) >> 8); + rc = pci_bus_write_config_byte (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; + + RES_CHECK(io_node->base - 1, 8); + temp_byte = (u8)((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_byte = 0x00; + rc = pci_bus_write_config_byte(pci_bus, devfn, PCI_IO_LIMIT, temp_byte); + + 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, 0x100000L); + + /* Check if we were able to split something off */ + if (mem_node) { + hold_mem_node->base = mem_node->base + mem_node->length; + + RES_CHECK(hold_mem_node->base, 16); + temp_word = (u16)((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), 0x100000L); + + /* 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 */ + RES_CHECK(mem_node->base - 1, 16); + temp_word = (u16)((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, 0x100000L); + + /* 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; + + RES_CHECK(hold_p_mem_node->base, 16); + temp_word = (u16)((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), 0x100000L); + + /* 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; + + RES_CHECK(p_mem_node->base - 1, 16); + temp_word = (u16)((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 + */ + + pciehprm_enable_card(ctrl, func, PCI_HEADER_TYPE_BRIDGE); + + dbg("PCI Bridge Hot-Added s:b:d:f(%02x:%02x:%02x:%02x)\n", ctrl->seg, func->bus, func->device, func->function); + + return rc; +} + +/** + * 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, + u8 bridge_bus, u8 bridge_dev) +{ + int cloop; + u8 temp_byte; + u8 class_code; + u16 temp_word; + u32 rc; + u32 temp_register; + u32 base; + unsigned int devfn; + struct pci_resource *mem_node; + struct pci_resource *io_node; + struct pci_bus lpci_bus, *pci_bus; + + memcpy(&lpci_bus, ctrl->pci_dev->subordinate, sizeof(lpci_bus)); + pci_bus = &lpci_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; + dbg("%s: bus %x dev %x func %x temp_byte = %x\n", __FUNCTION__, + func->bus, func->device, func->function, temp_byte); + + if ((temp_byte & 0x7F) == PCI_HEADER_TYPE_BRIDGE) { /* PCI-PCI Bridge */ + rc = configure_new_bridge(ctrl, func, behind_bridge, resources, + pci_bus); + + if (rc) + return rc; + } else if ((temp_byte & 0x7F) == PCI_HEADER_TYPE_NORMAL) { + /* Standard device */ + u64 base64; + rc = pci_bus_read_config_byte(pci_bus, devfn, 0x0B, &class_code); + + if (class_code == PCI_BASE_CLASS_DISPLAY) + return DEVICE_TYPE_NOT_SUPPORTED; + + /* Figure out IO and memory needs */ + for (cloop = PCI_BASE_ADDRESS_0; cloop <= PCI_BASE_ADDRESS_5; cloop += 4) { + temp_register = 0xFFFFFFFF; + + 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("Bar[%x]=0x%x on bus:dev:func(0x%x:%x:%x)\n", cloop, temp_register, + func->bus, func->device, func->function); + + if (!temp_register) + continue; + + base64 = 0L; + if (temp_register & PCI_BASE_ADDRESS_SPACE_IO) { + /* Map IO */ + + /* set base = amount of IO space */ + base = temp_register & 0xFFFFFFFC; + base = ~base + 1; + + dbg("NEED IO length(0x%x)\n", base); + io_node = get_io_resource(&(resources->io_head),(ulong)base); + + /* allocate the resource to the board */ + if (io_node) { + dbg("Got IO base=0x%x(length=0x%x)\n", io_node->base, io_node->length); + base = (u32)io_node->base; + io_node->next = func->io_head; + func->io_head = io_node; + } else { + err("Got NO IO resource(length=0x%x)\n", base); + return -ENOMEM; + } + } else { /* map MEM */ + int prefetchable = 1; + struct pci_resource **res_node = &func->p_mem_head; + char *res_type_str = "PMEM"; + u32 temp_register2; + + if (!(temp_register & PCI_BASE_ADDRESS_MEM_PREFETCH)) { + prefetchable = 0; + res_node = &func->mem_head; + res_type_str++; + } + + base = temp_register & 0xFFFFFFF0; + base = ~base + 1; + + switch (temp_register & PCI_BASE_ADDRESS_MEM_TYPE_MASK) { + case PCI_BASE_ADDRESS_MEM_TYPE_32: + dbg("NEED 32 %s bar=0x%x(length=0x%x)\n", res_type_str, temp_register, base); + + if (prefetchable && resources->p_mem_head) + mem_node=get_resource(&(resources->p_mem_head), (ulong)base); + else { + if (prefetchable) + dbg("using MEM for PMEM\n"); + mem_node = get_resource(&(resources->mem_head), (ulong)base); + } + + /* allocate the resource to the board */ + if (mem_node) { + base = (u32)mem_node->base; + mem_node->next = *res_node; + *res_node = mem_node; + dbg("Got 32 %s base=0x%x(length=0x%x)\n", res_type_str, mem_node->base, + mem_node->length); + } else { + err("Got NO 32 %s resource(length=0x%x)\n", res_type_str, base); + return -ENOMEM; + } + break; + case PCI_BASE_ADDRESS_MEM_TYPE_64: + rc = pci_bus_read_config_dword(pci_bus, devfn, cloop+4, &temp_register2); + dbg("NEED 64 %s bar=0x%x:%x(length=0x%x)\n", res_type_str, temp_register2, + temp_register, base); + + if (prefetchable && resources->p_mem_head) + mem_node = get_resource(&(resources->p_mem_head), (ulong)base); + else { + if (prefetchable) + dbg("using MEM for PMEM\n"); + mem_node = get_resource(&(resources->mem_head), (ulong)base); + } + + /* allocate the resource to the board */ + if (mem_node) { + base64 = mem_node->base; + mem_node->next = *res_node; + *res_node = mem_node; + dbg("Got 64 %s base=0x%x:%x(length=%x)\n", res_type_str, (u32)(base64 >> 32), + (u32)base64, mem_node->length); + } else { + err("Got NO 64 %s resource(length=0x%x)\n", res_type_str, base); + return -ENOMEM; + } + break; + default: + dbg("reserved BAR type=0x%x\n", temp_register); + break; + } + + } + + if (base64) { + rc = pci_bus_write_config_dword(pci_bus, devfn, cloop, (u32)base64); + cloop += 4; + base64 >>= 32; + + if (base64) { + dbg("%s: high dword of base64(0x%x) set to 0\n", __FUNCTION__, (u32)base64); + base64 = 0x0L; + } + + rc = pci_bus_write_config_dword(pci_bus, devfn, cloop, (u32)base64); + } else { + rc = pci_bus_write_config_dword(pci_bus, devfn, cloop, base); + } + } /* End of base register loop */ + + /* disable ROM base Address */ + temp_word = 0x00L; + rc = pci_bus_write_config_word (pci_bus, devfn, PCI_ROM_ADDRESS, temp_word); + + /* Set HP parameters (Cache Line Size, Latency Timer) */ + rc = pciehprm_set_hpp(ctrl, func, PCI_HEADER_TYPE_NORMAL); + if (rc) + return rc; + + pciehprm_enable_card(ctrl, func, PCI_HEADER_TYPE_NORMAL); + + dbg("PCI function Hot-Added s:b:d:f(%02x:%02x:%02x:%02x)\n", ctrl->seg, func->bus, func->device, + func->function); + } /* End of Not-A-Bridge else */ + else { + /* It's some strange type of PCI adapter (Cardbus?) */ + return DEVICE_TYPE_NOT_SUPPORTED; + } + + func->configured = 1; + + return 0; +} diff --git a/drivers/pci/hotplug/pciehp_hpc.c b/drivers/pci/hotplug/pciehp_hpc.c new file mode 100644 index 000000000000..9e70c4681f77 --- /dev/null +++ b/drivers/pci/hotplug/pciehp_hpc.c @@ -0,0 +1,1501 @@ +/* + * 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>,<dely.l.sy@intel.com> + * + */ + +#include <linux/config.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/types.h> +#include <linux/slab.h> +#include <linux/vmalloc.h> +#include <linux/interrupt.h> +#include <linux/spinlock.h> +#include <linux/pci.h> +#include <asm/system.h> +#include "../pci.h" +#include "pciehp.h" + +#ifdef DEBUG +#define DBG_K_TRACE_ENTRY ((unsigned int)0x00000001) /* On function entry */ +#define DBG_K_TRACE_EXIT ((unsigned int)0x00000002) /* On function exit */ +#define DBG_K_INFO ((unsigned int)0x00000004) /* Info messages */ +#define DBG_K_ERROR ((unsigned int)0x00000008) /* Error messages */ +#define DBG_K_TRACE (DBG_K_TRACE_ENTRY|DBG_K_TRACE_EXIT) +#define DBG_K_STANDARD (DBG_K_INFO|DBG_K_ERROR|DBG_K_TRACE) +/* Redefine this flagword to set debug level */ +#define DEBUG_LEVEL DBG_K_STANDARD + +#define DEFINE_DBG_BUFFER char __dbg_str_buf[256]; + +#define DBG_PRINT( dbg_flags, args... ) \ + do { \ + if ( DEBUG_LEVEL & ( dbg_flags ) ) \ + { \ + int len; \ + len = sprintf( __dbg_str_buf, "%s:%d: %s: ", \ + __FILE__, __LINE__, __FUNCTION__ ); \ + sprintf( __dbg_str_buf + len, args ); \ + printk( KERN_NOTICE "%s\n", __dbg_str_buf ); \ + } \ + } while (0) + +#define DBG_ENTER_ROUTINE DBG_PRINT (DBG_K_TRACE_ENTRY, "%s", "[Entry]"); +#define DBG_LEAVE_ROUTINE DBG_PRINT (DBG_K_TRACE_EXIT, "%s", "[Exit]"); +#else +#define DEFINE_DBG_BUFFER +#define DBG_ENTER_ROUTINE +#define DBG_LEAVE_ROUTINE +#endif /* DEBUG */ + +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 int pcie_cap_base = 0; /* Base of the PCI Express capability item structure */ + +#define PCIE_CAP_ID ( pcie_cap_base + PCIECAPID ) +#define NXT_CAP_PTR ( pcie_cap_base + NXTCAPPTR ) +#define CAP_REG ( pcie_cap_base + CAPREG ) +#define DEV_CAP ( pcie_cap_base + DEVCAP ) +#define DEV_CTRL ( pcie_cap_base + DEVCTRL ) +#define DEV_STATUS ( pcie_cap_base + DEVSTATUS ) +#define LNK_CAP ( pcie_cap_base + LNKCAP ) +#define LNK_CTRL ( pcie_cap_base + LNKCTRL ) +#define LNK_STATUS ( pcie_cap_base + LNKSTATUS ) +#define SLOT_CAP ( pcie_cap_base + SLOTCAP ) +#define SLOT_CTRL ( pcie_cap_base + SLOTCTRL ) +#define SLOT_STATUS ( pcie_cap_base + SLOTSTATUS ) +#define ROOT_CTRL ( pcie_cap_base + ROOTCTRL ) +#define ROOT_STATUS ( pcie_cap_base + ROOTSTATUS ) + +#define hp_register_read_word(pdev, reg , value) \ + pci_read_config_word(pdev, reg, &value) + +#define hp_register_read_dword(pdev, reg , value) \ + pci_read_config_dword(pdev, reg, &value) + +#define hp_register_write_word(pdev, reg , value) \ + pci_write_config_word(pdev, reg, value) + +#define hp_register_dwrite_word(pdev, reg , value) \ + pci_write_config_dword(pdev, 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 + +/* 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 + +/* 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 + +/* 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 + +/* 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 + +struct php_ctlr_state_s { + struct php_ctlr_state_s *pnext; + struct pci_dev *pci_dev; + unsigned int irq; + unsigned long flags; /* spinlock's */ + u32 slot_device_offset; + u32 num_slots; + struct timer_list int_poll_timer; /* Added for poll event */ + php_intr_callback_t attention_button_callback; + php_intr_callback_t switch_change_callback; + php_intr_callback_t presence_change_callback; + php_intr_callback_t power_fault_callback; + void *callback_instance_id; + struct ctrl_reg *creg; /* Ptr to controller register space */ +}; + + +static spinlock_t hpc_event_lock; + +DEFINE_DBG_BUFFER /* Debug string buffer for entire HPC defined here */ +static struct php_ctlr_state_s *php_ctlr_list_head; /* HPC state linked list */ +static int ctlr_seq_num = 0; /* Controller sequence # */ +static spinlock_t list_lock; + +static irqreturn_t pcie_isr(int IRQ, void *dev_id, struct pt_regs *regs); + +static void start_int_poll_timer(struct php_ctlr_state_s *php_ctlr, int seconds); + +/* This is the interrupt polling timeout function. */ +static void int_poll_timeout(unsigned long lphp_ctlr) +{ + struct php_ctlr_state_s *php_ctlr = (struct php_ctlr_state_s *)lphp_ctlr; + + DBG_ENTER_ROUTINE + + if ( !php_ctlr ) { + err("%s: Invalid HPC controller handle!\n", __FUNCTION__); + return; + } + + /* Poll for interrupt events. regs == NULL => polling */ + pcie_isr( 0, (void *)php_ctlr, NULL ); + + init_timer(&php_ctlr->int_poll_timer); + + if (!pciehp_poll_time) + pciehp_poll_time = 2; /* reset timer to poll in 2 secs if user doesn't specify at module installation*/ + + start_int_poll_timer(php_ctlr, pciehp_poll_time); + + return; +} + +/* This function starts the interrupt polling timer. */ +static void start_int_poll_timer(struct php_ctlr_state_s *php_ctlr, int seconds) +{ + if (!php_ctlr) { + err("%s: Invalid HPC controller handle!\n", __FUNCTION__); + return; + } + + if ( ( seconds <= 0 ) || ( seconds > 60 ) ) + seconds = 2; /* Clamp to sane value */ + + php_ctlr->int_poll_timer.function = &int_poll_timeout; + php_ctlr->int_poll_timer.data = (unsigned long)php_ctlr; /* Instance data */ + php_ctlr->int_poll_timer.expires = jiffies + seconds * HZ; + add_timer(&php_ctlr->int_poll_timer); + + return; +} + +static int pcie_write_cmd(struct slot *slot, u16 cmd) +{ + struct php_ctlr_state_s *php_ctlr = slot->ctrl->hpc_ctlr_handle; + int retval = 0; + u16 slot_status; + + DBG_ENTER_ROUTINE + + dbg("%s : Enter\n", __FUNCTION__); + if (!php_ctlr) { + err("%s: Invalid HPC controller handle!\n", __FUNCTION__); + return -1; + } + + retval = hp_register_read_word(php_ctlr->pci_dev, SLOT_STATUS, slot_status); + if (retval) { + err("%s : hp_register_read_word SLOT_STATUS failed\n", __FUNCTION__); + return retval; + } + dbg("%s : hp_register_read_word SLOT_STATUS %x\n", __FUNCTION__, slot_status); + + if ((slot_status & CMD_COMPLETED) == CMD_COMPLETED ) { + /* 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 */ + dbg("%s : CMD_COMPLETED not clear after 1 sec.\n", __FUNCTION__); + } + + dbg("%s: Before hp_register_write_word SLOT_CTRL %x\n", __FUNCTION__, cmd); + retval = hp_register_write_word(php_ctlr->pci_dev, SLOT_CTRL, cmd | CMD_CMPL_INTR_ENABLE); + if (retval) { + err("%s : hp_register_write_word SLOT_CTRL failed\n", __FUNCTION__); + return retval; + } + dbg("%s : hp_register_write_word SLOT_CTRL %x\n", __FUNCTION__, cmd | CMD_CMPL_INTR_ENABLE); + dbg("%s : Exit\n", __FUNCTION__); + + DBG_LEAVE_ROUTINE + return retval; +} + +static int hpc_check_lnk_status(struct controller *ctrl) +{ + struct php_ctlr_state_s *php_ctlr = ctrl->hpc_ctlr_handle; + u16 lnk_status; + int retval = 0; + + DBG_ENTER_ROUTINE + + if (!php_ctlr) { + err("%s: Invalid HPC controller handle!\n", __FUNCTION__); + return -1; + } + + retval = hp_register_read_word(php_ctlr->pci_dev, LNK_STATUS, lnk_status); + + if (retval) { + err("%s : hp_register_read_word LNK_STATUS failed\n", __FUNCTION__); + return retval; + } + + dbg("%s: lnk_status = %x\n", __FUNCTION__, lnk_status); + if ( (lnk_status & LNK_TRN) || (lnk_status & LNK_TRN_ERR) || + !(lnk_status & NEG_LINK_WD)) { + err("%s : Link Training Error occurs \n", __FUNCTION__); + retval = -1; + return retval; + } + + DBG_LEAVE_ROUTINE + return retval; +} + + +static int hpc_get_attention_status(struct slot *slot, u8 *status) +{ + struct php_ctlr_state_s *php_ctlr = slot->ctrl->hpc_ctlr_handle; + u16 slot_ctrl; + u8 atten_led_state; + int retval = 0; + + DBG_ENTER_ROUTINE + + if (!php_ctlr) { + err("%s: Invalid HPC controller handle!\n", __FUNCTION__); + return -1; + } + + retval = hp_register_read_word(php_ctlr->pci_dev, SLOT_CTRL, slot_ctrl); + + if (retval) { + err("%s : hp_register_read_word SLOT_CTRL failed\n", __FUNCTION__); + return retval; + } + + dbg("%s: SLOT_CTRL %x, value read %x\n", __FUNCTION__,SLOT_CTRL, 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; + } + + DBG_LEAVE_ROUTINE + return 0; +} + +static int hpc_get_power_status(struct slot * slot, u8 *status) +{ + struct php_ctlr_state_s *php_ctlr = slot->ctrl->hpc_ctlr_handle; + u16 slot_ctrl; + u8 pwr_state; + int retval = 0; + + DBG_ENTER_ROUTINE + + if (!php_ctlr) { + err("%s: Invalid HPC controller handle!\n", __FUNCTION__); + return -1; + } + + retval = hp_register_read_word(php_ctlr->pci_dev, SLOT_CTRL, slot_ctrl); + + if (retval) { + err("%s : hp_register_read_word SLOT_CTRL failed\n", __FUNCTION__); + return retval; + } + dbg("%s: SLOT_CTRL %x value read %x\n", __FUNCTION__, SLOT_CTRL, 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; + } + + DBG_LEAVE_ROUTINE + return retval; +} + + +static int hpc_get_latch_status(struct slot *slot, u8 *status) +{ + struct php_ctlr_state_s *php_ctlr = slot->ctrl->hpc_ctlr_handle; + u16 slot_status; + int retval = 0; + + DBG_ENTER_ROUTINE + + if (!php_ctlr) { + err("%s: Invalid HPC controller handle!\n", __FUNCTION__); + return -1; + } + + retval = hp_register_read_word(php_ctlr->pci_dev, SLOT_STATUS, slot_status); + + if (retval) { + err("%s : hp_register_read_word SLOT_STATUS failed\n", __FUNCTION__); + return retval; + } + + *status = (((slot_status & MRL_STATE) >> 5) == 0) ? 0 : 1; + + DBG_LEAVE_ROUTINE + return 0; +} + +static int hpc_get_adapter_status(struct slot *slot, u8 *status) +{ + struct php_ctlr_state_s *php_ctlr = slot->ctrl->hpc_ctlr_handle; + u16 slot_status; + u8 card_state; + int retval = 0; + + DBG_ENTER_ROUTINE + + if (!php_ctlr) { + err("%s: Invalid HPC controller handle!\n", __FUNCTION__); + return -1; + } + + retval = hp_register_read_word(php_ctlr->pci_dev, SLOT_STATUS, slot_status); + + if (retval) { + err("%s : hp_register_read_word SLOT_STATUS failed\n", __FUNCTION__); + return retval; + } + card_state = (u8)((slot_status & PRSN_STATE) >> 6); + *status = (card_state == 1) ? 1 : 0; + + DBG_LEAVE_ROUTINE + return 0; +} + +static int hpc_query_power_fault(struct slot * slot) +{ + struct php_ctlr_state_s *php_ctlr = slot->ctrl->hpc_ctlr_handle; + u16 slot_status; + u8 pwr_fault; + int retval = 0; + u8 status; + + DBG_ENTER_ROUTINE + + if (!php_ctlr) { + err("%s: Invalid HPC controller handle!\n", __FUNCTION__); + return -1; + } + + retval = hp_register_read_word(php_ctlr->pci_dev, SLOT_STATUS, slot_status); + + if (retval) { + err("%s : hp_register_read_word SLOT_STATUS failed\n", __FUNCTION__); + return retval; + } + pwr_fault = (u8)((slot_status & PWR_FAULT_DETECTED) >> 1); + status = (pwr_fault != 1) ? 1 : 0; + + DBG_LEAVE_ROUTINE + /* Note: Logic 0 => fault */ + return status; +} + +static int hpc_set_attention_status(struct slot *slot, u8 value) +{ + struct php_ctlr_state_s *php_ctlr = slot->ctrl->hpc_ctlr_handle; + u16 slot_cmd = 0; + u16 slot_ctrl; + int rc = 0; + + dbg("%s: \n", __FUNCTION__); + if (!php_ctlr) { + err("%s: Invalid HPC controller handle!\n", __FUNCTION__); + return -1; + } + + if (slot->hp_slot >= php_ctlr->num_slots) { + err("%s: Invalid HPC slot number!\n", __FUNCTION__); + return -1; + } + rc = hp_register_read_word(php_ctlr->pci_dev, SLOT_CTRL, slot_ctrl); + + if (rc) { + err("%s : hp_register_read_word SLOT_CTRL failed\n", __FUNCTION__); + return rc; + } + dbg("%s : hp_register_read_word SLOT_CTRL %x\n", __FUNCTION__, slot_ctrl); + + switch (value) { + case 0 : /* turn off */ + slot_cmd = (slot_ctrl & ~ATTN_LED_CTRL) | 0x00C0; + break; + case 1: /* turn on */ + slot_cmd = (slot_ctrl & ~ATTN_LED_CTRL) | 0x0040; + break; + case 2: /* turn blink */ + slot_cmd = (slot_ctrl & ~ATTN_LED_CTRL) | 0x0080; + break; + default: + return -1; + } + if (!pciehp_poll_mode) + slot_cmd = slot_cmd | HP_INTR_ENABLE; + + pcie_write_cmd(slot, slot_cmd); + dbg("%s: SLOT_CTRL %x write cmd %x\n", __FUNCTION__, SLOT_CTRL, slot_cmd); + + return rc; +} + + +static void hpc_set_green_led_on(struct slot *slot) +{ + struct php_ctlr_state_s *php_ctlr = slot->ctrl->hpc_ctlr_handle; + u16 slot_cmd; + u16 slot_ctrl; + int rc = 0; + + dbg("%s: \n", __FUNCTION__); + if (!php_ctlr) { + err("%s: Invalid HPC controller handle!\n", __FUNCTION__); + return ; + } + + if (slot->hp_slot >= php_ctlr->num_slots) { + err("%s: Invalid HPC slot number!\n", __FUNCTION__); + return ; + } + + rc = hp_register_read_word(php_ctlr->pci_dev, SLOT_CTRL, slot_ctrl); + + if (rc) { + err("%s : hp_register_read_word SLOT_CTRL failed\n", __FUNCTION__); + return; + } + dbg("%s : hp_register_read_word SLOT_CTRL %x\n", __FUNCTION__, slot_ctrl); + slot_cmd = (slot_ctrl & ~PWR_LED_CTRL) | 0x0100; + if (!pciehp_poll_mode) + slot_cmd = slot_cmd | HP_INTR_ENABLE; + + pcie_write_cmd(slot, slot_cmd); + + dbg("%s: SLOT_CTRL %x write cmd %x\n",__FUNCTION__, SLOT_CTRL, slot_cmd); + return; +} + +static void hpc_set_green_led_off(struct slot *slot) +{ + struct php_ctlr_state_s *php_ctlr = slot->ctrl->hpc_ctlr_handle; + u16 slot_cmd; + u16 slot_ctrl; + int rc = 0; + + dbg("%s: \n", __FUNCTION__); + if (!php_ctlr) { + err("%s: Invalid HPC controller handle!\n", __FUNCTION__); + return ; + } + + if (slot->hp_slot >= php_ctlr->num_slots) { + err("%s: Invalid HPC slot number!\n", __FUNCTION__); + return ; + } + + rc = hp_register_read_word(php_ctlr->pci_dev, SLOT_CTRL, slot_ctrl); + + if (rc) { + err("%s : hp_register_read_word SLOT_CTRL failed\n", __FUNCTION__); + return; + } + dbg("%s : hp_register_read_word SLOT_CTRL %x\n", __FUNCTION__, slot_ctrl); + + slot_cmd = (slot_ctrl & ~PWR_LED_CTRL) | 0x0300; + + if (!pciehp_poll_mode) + slot_cmd = slot_cmd | HP_INTR_ENABLE; + pcie_write_cmd(slot, slot_cmd); + dbg("%s: SLOT_CTRL %x write cmd %x\n", __FUNCTION__, SLOT_CTRL, slot_cmd); + + return; +} + +static void hpc_set_green_led_blink(struct slot *slot) +{ + struct php_ctlr_state_s *php_ctlr = slot->ctrl->hpc_ctlr_handle; + u16 slot_cmd; + u16 slot_ctrl; + int rc = 0; + + dbg("%s: \n", __FUNCTION__); + if (!php_ctlr) { + err("%s: Invalid HPC controller handle!\n", __FUNCTION__); + return ; + } + + if (slot->hp_slot >= php_ctlr->num_slots) { + err("%s: Invalid HPC slot number!\n", __FUNCTION__); + return ; + } + + rc = hp_register_read_word(php_ctlr->pci_dev, SLOT_CTRL, slot_ctrl); + + if (rc) { + err("%s : hp_register_read_word SLOT_CTRL failed\n", __FUNCTION__); + return; + } + dbg("%s : hp_register_read_word SLOT_CTRL %x\n", __FUNCTION__, slot_ctrl); + + slot_cmd = (slot_ctrl & ~PWR_LED_CTRL) | 0x0200; + + if (!pciehp_poll_mode) + slot_cmd = slot_cmd | HP_INTR_ENABLE; + pcie_write_cmd(slot, slot_cmd); + + dbg("%s: SLOT_CTRL %x write cmd %x\n",__FUNCTION__, SLOT_CTRL, slot_cmd); + return; +} + +int pcie_get_ctlr_slot_config(struct controller *ctrl, + int *num_ctlr_slots, /* number of slots in this HPC; only 1 in PCIE */ + int *first_device_num, /* PCI dev num of the first slot in this PCIE */ + int *physical_slot_num, /* phy slot num of the first slot in this PCIE */ + u8 *ctrlcap) +{ + struct php_ctlr_state_s *php_ctlr = ctrl->hpc_ctlr_handle; + u32 slot_cap; + int rc = 0; + + DBG_ENTER_ROUTINE + + if (!php_ctlr) { + err("%s: Invalid HPC controller handle!\n", __FUNCTION__); + return -1; + } + + *first_device_num = 0; + *num_ctlr_slots = 1; + + rc = hp_register_read_dword(php_ctlr->pci_dev, SLOT_CAP, slot_cap); + + if (rc) { + err("%s : hp_register_read_dword SLOT_CAP failed\n", __FUNCTION__); + return -1; + } + + *physical_slot_num = slot_cap >> 19; + dbg("%s: PSN %d \n", __FUNCTION__, *physical_slot_num); + + *ctrlcap = slot_cap & 0x0000007f; + + DBG_LEAVE_ROUTINE + return 0; +} + +static void hpc_release_ctlr(struct controller *ctrl) +{ + struct php_ctlr_state_s *php_ctlr = ctrl->hpc_ctlr_handle; + struct php_ctlr_state_s *p, *p_prev; + + DBG_ENTER_ROUTINE + + if (!php_ctlr) { + err("%s: Invalid HPC controller handle!\n", __FUNCTION__); + return ; + } + + if (pciehp_poll_mode) { + del_timer(&php_ctlr->int_poll_timer); + } else { + if (php_ctlr->irq) { + free_irq(php_ctlr->irq, ctrl); + php_ctlr->irq = 0; + if (!pcie_mch_quirk) + pci_disable_msi(php_ctlr->pci_dev); + } + } + if (php_ctlr->pci_dev) + php_ctlr->pci_dev = NULL; + + spin_lock(&list_lock); + p = php_ctlr_list_head; + p_prev = NULL; + while (p) { + if (p == php_ctlr) { + if (p_prev) + p_prev->pnext = p->pnext; + else + php_ctlr_list_head = p->pnext; + break; + } else { + p_prev = p; + p = p->pnext; + } + } + spin_unlock(&list_lock); + + kfree(php_ctlr); + + DBG_LEAVE_ROUTINE + +} + +static int hpc_power_on_slot(struct slot * slot) +{ + struct php_ctlr_state_s *php_ctlr = slot->ctrl->hpc_ctlr_handle; + u16 slot_cmd; + u16 slot_ctrl; + + int retval = 0; + + DBG_ENTER_ROUTINE + dbg("%s: \n", __FUNCTION__); + + if (!php_ctlr) { + err("%s: Invalid HPC controller handle!\n", __FUNCTION__); + return -1; + } + + dbg("%s: slot->hp_slot %x\n", __FUNCTION__, slot->hp_slot); + if (slot->hp_slot >= php_ctlr->num_slots) { + err("%s: Invalid HPC slot number!\n", __FUNCTION__); + return -1; + } + + retval = hp_register_read_word(php_ctlr->pci_dev, SLOT_CTRL, slot_ctrl); + + if (retval) { + err("%s : hp_register_read_word SLOT_CTRL failed\n", __FUNCTION__); + return retval; + } + dbg("%s: SLOT_CTRL %x, value read %xn", __FUNCTION__, SLOT_CTRL, + slot_ctrl); + + slot_cmd = (slot_ctrl & ~PWR_CTRL) | POWER_ON; + + if (!pciehp_poll_mode) + slot_cmd = slot_cmd | HP_INTR_ENABLE; + + retval = pcie_write_cmd(slot, slot_cmd); + + if (retval) { + err("%s: Write %x command failed!\n", __FUNCTION__, slot_cmd); + return -1; + } + dbg("%s: SLOT_CTRL %x write cmd %x\n",__FUNCTION__, SLOT_CTRL, slot_cmd); + + DBG_LEAVE_ROUTINE + + return retval; +} + +static int hpc_power_off_slot(struct slot * slot) +{ + struct php_ctlr_state_s *php_ctlr = slot->ctrl->hpc_ctlr_handle; + u16 slot_cmd; + u16 slot_ctrl; + + int retval = 0; + + DBG_ENTER_ROUTINE + dbg("%s: \n", __FUNCTION__); + + if (!php_ctlr) { + err("%s: Invalid HPC controller handle!\n", __FUNCTION__); + return -1; + } + + dbg("%s: slot->hp_slot %x\n", __FUNCTION__, slot->hp_slot); + slot->hp_slot = 0; + if (slot->hp_slot >= php_ctlr->num_slots) { + err("%s: Invalid HPC slot number!\n", __FUNCTION__); + return -1; + } + retval = hp_register_read_word(php_ctlr->pci_dev, SLOT_CTRL, slot_ctrl); + + if (retval) { + err("%s : hp_register_read_word SLOT_CTRL failed\n", __FUNCTION__); + return retval; + } + dbg("%s: SLOT_CTRL %x, value read %x\n", __FUNCTION__, SLOT_CTRL, + slot_ctrl); + + slot_cmd = (slot_ctrl & ~PWR_CTRL) | POWER_OFF; + + if (!pciehp_poll_mode) + slot_cmd = slot_cmd | HP_INTR_ENABLE; + + retval = pcie_write_cmd(slot, slot_cmd); + + if (retval) { + err("%s: Write command failed!\n", __FUNCTION__); + return -1; + } + dbg("%s: SLOT_CTRL %x write cmd %x\n",__FUNCTION__, SLOT_CTRL, slot_cmd); + + DBG_LEAVE_ROUTINE + + return retval; +} + +static irqreturn_t pcie_isr(int IRQ, void *dev_id, struct pt_regs *regs) +{ + struct controller *ctrl = NULL; + struct php_ctlr_state_s *php_ctlr; + u8 schedule_flag = 0; + u16 slot_status, intr_detect, intr_loc; + u16 temp_word; + int hp_slot = 0; /* only 1 slot per PCI Express port */ + int rc = 0; + + if (!dev_id) + return IRQ_NONE; + + if (!pciehp_poll_mode) { + ctrl = dev_id; + php_ctlr = ctrl->hpc_ctlr_handle; + } else { + php_ctlr = dev_id; + ctrl = (struct controller *)php_ctlr->callback_instance_id; + } + + if (!ctrl) { + dbg("%s: dev_id %p ctlr == NULL\n", __FUNCTION__, (void*) dev_id); + return IRQ_NONE; + } + + if (!php_ctlr) { + dbg("%s: php_ctlr == NULL\n", __FUNCTION__); + return IRQ_NONE; + } + + rc = hp_register_read_word(php_ctlr->pci_dev, SLOT_STATUS, slot_status); + if (rc) { + err("%s : hp_register_read_word SLOT_STATUS failed\n", __FUNCTION__); + return IRQ_NONE; + } + + intr_detect = ( ATTN_BUTTN_PRESSED | PWR_FAULT_DETECTED | MRL_SENS_CHANGED | + PRSN_DETECT_CHANGED | CMD_COMPLETED ); + + intr_loc = slot_status & intr_detect; + + /* Check to see if it was our interrupt */ + if ( !intr_loc ) + return IRQ_NONE; + + dbg("%s: intr_loc %x\n", __FUNCTION__, intr_loc); + /* Mask Hot-plug Interrupt Enable */ + if (!pciehp_poll_mode) { + rc = hp_register_read_word(php_ctlr->pci_dev, SLOT_CTRL, temp_word); + if (rc) { + err("%s : hp_register_read_word SLOT_CTRL failed\n", __FUNCTION__); + return IRQ_NONE; + } + + dbg("%s: Set Mask Hot-plug Interrupt Enable\n", __FUNCTION__); + dbg("%s: hp_register_read_word SLOT_CTRL with value %x\n", __FUNCTION__, temp_word); + temp_word = (temp_word & ~HP_INTR_ENABLE & ~CMD_CMPL_INTR_ENABLE) | 0x00; + + rc = hp_register_write_word(php_ctlr->pci_dev, SLOT_CTRL, temp_word); + if (rc) { + err("%s : hp_register_write_word SLOT_CTRL failed\n", __FUNCTION__); + return IRQ_NONE; + } + dbg("%s: hp_register_write_word SLOT_CTRL with value %x\n", __FUNCTION__, temp_word); + + rc = hp_register_read_word(php_ctlr->pci_dev, SLOT_STATUS, slot_status); + if (rc) { + err("%s : hp_register_read_word SLOT_STATUS failed\n", __FUNCTION__); + return IRQ_NONE; + } + dbg("%s: hp_register_read_word SLOT_STATUS with value %x\n", __FUNCTION__, slot_status); + + /* Clear command complete interrupt caused by this write */ + temp_word = 0x1f; + rc = hp_register_write_word(php_ctlr->pci_dev, SLOT_STATUS, temp_word); + if (rc) { + err("%s : hp_register_write_word SLOT_STATUS failed\n", __FUNCTION__); + return IRQ_NONE; + } + dbg("%s: hp_register_write_word SLOT_STATUS with value %x\n", __FUNCTION__, temp_word); + } + + if (intr_loc & CMD_COMPLETED) { + /* + * Command Complete Interrupt Pending + */ + dbg("%s: In Command Complete Interrupt Pending\n", __FUNCTION__); + wake_up_interruptible(&ctrl->queue); + } + + if ((php_ctlr->switch_change_callback) && (intr_loc & MRL_SENS_CHANGED)) + schedule_flag += php_ctlr->switch_change_callback( + hp_slot, php_ctlr->callback_instance_id); + if ((php_ctlr->attention_button_callback) && (intr_loc & ATTN_BUTTN_PRESSED)) + schedule_flag += php_ctlr->attention_button_callback( + hp_slot, php_ctlr->callback_instance_id); + if ((php_ctlr->presence_change_callback) && (intr_loc & PRSN_DETECT_CHANGED)) + schedule_flag += php_ctlr->presence_change_callback( + hp_slot , php_ctlr->callback_instance_id); + if ((php_ctlr->power_fault_callback) && (intr_loc & PWR_FAULT_DETECTED)) + schedule_flag += php_ctlr->power_fault_callback( + hp_slot, php_ctlr->callback_instance_id); + + /* Clear all events after serving them */ + temp_word = 0x1F; + rc = hp_register_write_word(php_ctlr->pci_dev, SLOT_STATUS, temp_word); + if (rc) { + err("%s : hp_register_write_word SLOT_STATUS failed\n", __FUNCTION__); + return IRQ_NONE; + } + /* Unmask Hot-plug Interrupt Enable */ + if (!pciehp_poll_mode) { + rc = hp_register_read_word(php_ctlr->pci_dev, SLOT_CTRL, temp_word); + if (rc) { + err("%s : hp_register_read_word SLOT_CTRL failed\n", __FUNCTION__); + return IRQ_NONE; + } + + dbg("%s: Unmask Hot-plug Interrupt Enable\n", __FUNCTION__); + dbg("%s: hp_register_read_word SLOT_CTRL with value %x\n", __FUNCTION__, temp_word); + temp_word = (temp_word & ~HP_INTR_ENABLE) | HP_INTR_ENABLE; + + rc = hp_register_write_word(php_ctlr->pci_dev, SLOT_CTRL, temp_word); + if (rc) { + err("%s : hp_register_write_word SLOT_CTRL failed\n", __FUNCTION__); + return IRQ_NONE; + } + dbg("%s: hp_register_write_word SLOT_CTRL with value %x\n", __FUNCTION__, temp_word); + + rc = hp_register_read_word(php_ctlr->pci_dev, SLOT_STATUS, slot_status); + if (rc) { + err("%s : hp_register_read_word SLOT_STATUS failed\n", __FUNCTION__); + return IRQ_NONE; + } + dbg("%s: hp_register_read_word SLOT_STATUS with value %x\n", __FUNCTION__, slot_status); + + /* Clear command complete interrupt caused by this write */ + temp_word = 0x1F; + rc = hp_register_write_word(php_ctlr->pci_dev, SLOT_STATUS, temp_word); + if (rc) { + err("%s : hp_register_write_word SLOT_STATUS failed\n", __FUNCTION__); + return IRQ_NONE; + } + dbg("%s: hp_register_write_word SLOT_STATUS with value %x\n", __FUNCTION__, temp_word); + } + + return IRQ_HANDLED; +} + +static int hpc_get_max_lnk_speed (struct slot *slot, enum pci_bus_speed *value) +{ + struct php_ctlr_state_s *php_ctlr = slot->ctrl->hpc_ctlr_handle; + enum pcie_link_speed lnk_speed; + u32 lnk_cap; + int retval = 0; + + DBG_ENTER_ROUTINE + + if (!php_ctlr) { + err("%s: Invalid HPC controller handle!\n", __FUNCTION__); + return -1; + } + + if (slot->hp_slot >= php_ctlr->num_slots) { + err("%s: Invalid HPC slot number!\n", __FUNCTION__); + return -1; + } + + retval = hp_register_read_dword(php_ctlr->pci_dev, LNK_CAP, lnk_cap); + + if (retval) { + err("%s : hp_register_read_dword LNK_CAP failed\n", __FUNCTION__); + return retval; + } + + switch (lnk_cap & 0x000F) { + case 1: + lnk_speed = PCIE_2PT5GB; + break; + default: + lnk_speed = PCIE_LNK_SPEED_UNKNOWN; + break; + } + + *value = lnk_speed; + dbg("Max link speed = %d\n", lnk_speed); + DBG_LEAVE_ROUTINE + return retval; +} + +static int hpc_get_max_lnk_width (struct slot *slot, enum pcie_link_width *value) +{ + struct php_ctlr_state_s *php_ctlr = slot->ctrl->hpc_ctlr_handle; + enum pcie_link_width lnk_wdth; + u32 lnk_cap; + int retval = 0; + + DBG_ENTER_ROUTINE + + if (!php_ctlr) { + err("%s: Invalid HPC controller handle!\n", __FUNCTION__); + return -1; + } + + if (slot->hp_slot >= php_ctlr->num_slots) { + err("%s: Invalid HPC slot number!\n", __FUNCTION__); + return -1; + } + + retval = hp_register_read_dword(php_ctlr->pci_dev, LNK_CAP, lnk_cap); + + if (retval) { + err("%s : hp_register_read_dword LNK_CAP failed\n", __FUNCTION__); + 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; + dbg("Max link width = %d\n", lnk_wdth); + DBG_LEAVE_ROUTINE + return retval; +} + +static int hpc_get_cur_lnk_speed (struct slot *slot, enum pci_bus_speed *value) +{ + struct php_ctlr_state_s *php_ctlr = slot->ctrl->hpc_ctlr_handle; + enum pcie_link_speed lnk_speed = PCI_SPEED_UNKNOWN; + int retval = 0; + u16 lnk_status; + + DBG_ENTER_ROUTINE + + if (!php_ctlr) { + err("%s: Invalid HPC controller handle!\n", __FUNCTION__); + return -1; + } + + if (slot->hp_slot >= php_ctlr->num_slots) { + err("%s: Invalid HPC slot number!\n", __FUNCTION__); + return -1; + } + + retval = hp_register_read_word(php_ctlr->pci_dev, LNK_STATUS, lnk_status); + + if (retval) { + err("%s : hp_register_read_word LNK_STATUS failed\n", __FUNCTION__); + return retval; + } + + switch (lnk_status & 0x0F) { + case 1: + lnk_speed = PCIE_2PT5GB; + break; + default: + lnk_speed = PCIE_LNK_SPEED_UNKNOWN; + break; + } + + *value = lnk_speed; + dbg("Current link speed = %d\n", lnk_speed); + DBG_LEAVE_ROUTINE + return retval; +} + +static int hpc_get_cur_lnk_width (struct slot *slot, enum pcie_link_width *value) +{ + struct php_ctlr_state_s *php_ctlr = slot->ctrl->hpc_ctlr_handle; + enum pcie_link_width lnk_wdth = PCIE_LNK_WIDTH_UNKNOWN; + int retval = 0; + u16 lnk_status; + + DBG_ENTER_ROUTINE + + if (!php_ctlr) { + err("%s: Invalid HPC controller handle!\n", __FUNCTION__); + return -1; + } + + if (slot->hp_slot >= php_ctlr->num_slots) { + err("%s: Invalid HPC slot number!\n", __FUNCTION__); + return -1; + } + + retval = hp_register_read_word(php_ctlr->pci_dev, LNK_STATUS, lnk_status); + + if (retval) { + err("%s : hp_register_read_word LNK_STATUS failed\n", __FUNCTION__); + 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; + dbg("Current link width = %d\n", lnk_wdth); + DBG_LEAVE_ROUTINE + return retval; +} + +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_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 = hpc_release_ctlr, + .check_lnk_status = hpc_check_lnk_status, +}; + +int pcie_init(struct controller * ctrl, + struct pcie_device *dev, + php_intr_callback_t attention_button_callback, + php_intr_callback_t switch_change_callback, + php_intr_callback_t presence_change_callback, + php_intr_callback_t power_fault_callback) +{ + struct php_ctlr_state_s *php_ctlr, *p; + void *instance_id = ctrl; + int rc; + static int first = 1; + u16 temp_word; + u16 cap_reg; + u16 intr_enable = 0; + u32 slot_cap; + int cap_base, saved_cap_base; + u16 slot_status, slot_ctrl; + struct pci_dev *pdev; + + DBG_ENTER_ROUTINE + + spin_lock_init(&list_lock); + php_ctlr = (struct php_ctlr_state_s *) kmalloc(sizeof(struct php_ctlr_state_s), GFP_KERNEL); + + if (!php_ctlr) { /* allocate controller state data */ + err("%s: HPC controller memory allocation error!\n", __FUNCTION__); + goto abort; + } + + memset(php_ctlr, 0, sizeof(struct php_ctlr_state_s)); + + pdev = dev->port; + php_ctlr->pci_dev = pdev; /* save pci_dev in context */ + + dbg("%s: pdev->vendor %x pdev->device %x\n", __FUNCTION__, + pdev->vendor, pdev->device); + + saved_cap_base = pcie_cap_base; + + if ((cap_base = pci_find_capability(pdev, PCI_CAP_ID_EXP)) == 0) { + dbg("%s: Can't find PCI_CAP_ID_EXP (0x10)\n", __FUNCTION__); + goto abort_free_ctlr; + } + + pcie_cap_base = cap_base; + + dbg("%s: pcie_cap_base %x\n", __FUNCTION__, pcie_cap_base); + + rc = hp_register_read_word(pdev, CAP_REG, cap_reg); + if (rc) { + err("%s : hp_register_read_word CAP_REG failed\n", __FUNCTION__); + goto abort_free_ctlr; + } + dbg("%s: CAP_REG offset %x cap_reg %x\n", __FUNCTION__, CAP_REG, cap_reg); + + if (((cap_reg & SLOT_IMPL) == 0) || ((cap_reg & DEV_PORT_TYPE) != 0x0040)){ + dbg("%s : This is not a root port or the port is not connected to a slot\n", __FUNCTION__); + goto abort_free_ctlr; + } + + rc = hp_register_read_dword(php_ctlr->pci_dev, SLOT_CAP, slot_cap); + if (rc) { + err("%s : hp_register_read_word CAP_REG failed\n", __FUNCTION__); + goto abort_free_ctlr; + } + dbg("%s: SLOT_CAP offset %x slot_cap %x\n", __FUNCTION__, SLOT_CAP, slot_cap); + + if (!(slot_cap & HP_CAP)) { + dbg("%s : This slot is not hot-plug capable\n", __FUNCTION__); + goto abort_free_ctlr; + } + /* For debugging purpose */ + rc = hp_register_read_word(php_ctlr->pci_dev, SLOT_STATUS, slot_status); + if (rc) { + err("%s : hp_register_read_word SLOT_STATUS failed\n", __FUNCTION__); + goto abort_free_ctlr; + } + dbg("%s: SLOT_STATUS offset %x slot_status %x\n", __FUNCTION__, SLOT_STATUS, slot_status); + + rc = hp_register_read_word(php_ctlr->pci_dev, SLOT_CTRL, slot_ctrl); + if (rc) { + err("%s : hp_register_read_word SLOT_CTRL failed\n", __FUNCTION__); + goto abort_free_ctlr; + } + dbg("%s: SLOT_CTRL offset %x slot_ctrl %x\n", __FUNCTION__, SLOT_CTRL, slot_ctrl); + + if (first) { + spin_lock_init(&hpc_event_lock); + first = 0; + } + + dbg("pdev = %p: b:d:f:irq=0x%x:%x:%x:%x\n", pdev, pdev->bus->number, + PCI_SLOT(pdev->devfn), PCI_FUNC(pdev->devfn), dev->irq); + for ( rc = 0; rc < DEVICE_COUNT_RESOURCE; rc++) + if (pci_resource_len(pdev, rc) > 0) + dbg("pci resource[%d] start=0x%lx(len=0x%lx)\n", rc, + pci_resource_start(pdev, rc), pci_resource_len(pdev, rc)); + + info("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 (pci_enable_device(pdev)) + goto abort_free_ctlr; + + init_MUTEX(&ctrl->crit_sect); + /* setup wait queue */ + init_waitqueue_head(&ctrl->queue); + + /* find the IRQ */ + php_ctlr->irq = dev->irq; + dbg("HPC interrupt = %d\n", php_ctlr->irq); + + /* Save interrupt callback info */ + php_ctlr->attention_button_callback = attention_button_callback; + php_ctlr->switch_change_callback = switch_change_callback; + php_ctlr->presence_change_callback = presence_change_callback; + php_ctlr->power_fault_callback = power_fault_callback; + php_ctlr->callback_instance_id = instance_id; + + /* return PCI Controller Info */ + php_ctlr->slot_device_offset = 0; + php_ctlr->num_slots = 1; + + /* Mask Hot-plug Interrupt Enable */ + rc = hp_register_read_word(pdev, SLOT_CTRL, temp_word); + if (rc) { + err("%s : hp_register_read_word SLOT_CTRL failed\n", __FUNCTION__); + goto abort_free_ctlr; + } + + dbg("%s: SLOT_CTRL %x value read %x\n", __FUNCTION__, SLOT_CTRL, temp_word); + temp_word = (temp_word & ~HP_INTR_ENABLE & ~CMD_CMPL_INTR_ENABLE) | 0x00; + + rc = hp_register_write_word(pdev, SLOT_CTRL, temp_word); + if (rc) { + err("%s : hp_register_write_word SLOT_CTRL failed\n", __FUNCTION__); + goto abort_free_ctlr; + } + dbg("%s : Mask HPIE hp_register_write_word SLOT_CTRL %x\n", __FUNCTION__, temp_word); + + rc = hp_register_read_word(php_ctlr->pci_dev, SLOT_STATUS, slot_status); + if (rc) { + err("%s : hp_register_read_word SLOT_STATUS failed\n", __FUNCTION__); + goto abort_free_ctlr; + } + dbg("%s: Mask HPIE SLOT_STATUS offset %x reads slot_status %x\n", __FUNCTION__, SLOT_STATUS, slot_status); + + temp_word = 0x1F; /* Clear all events */ + rc = hp_register_write_word(php_ctlr->pci_dev, SLOT_STATUS, temp_word); + if (rc) { + err("%s : hp_register_write_word SLOT_STATUS failed\n", __FUNCTION__); + goto abort_free_ctlr; + } + dbg("%s: SLOT_STATUS offset %x writes slot_status %x\n", __FUNCTION__, SLOT_STATUS, temp_word); + + if (pciehp_poll_mode) {/* Install interrupt polling code */ + /* Install and start the interrupt polling timer */ + init_timer(&php_ctlr->int_poll_timer); + start_int_poll_timer( php_ctlr, 10 ); /* start with 10 second delay */ + } else { + /* Installs the interrupt handler */ + rc = request_irq(php_ctlr->irq, pcie_isr, SA_SHIRQ, MY_NAME, (void *) ctrl); + dbg("%s: request_irq %d for hpc%d (returns %d)\n", __FUNCTION__, php_ctlr->irq, ctlr_seq_num, rc); + if (rc) { + err("Can't get irq %d for the hotplug controller\n", php_ctlr->irq); + goto abort_free_ctlr; + } + } + + rc = hp_register_read_word(pdev, SLOT_CTRL, temp_word); + if (rc) { + err("%s : hp_register_read_word SLOT_CTRL failed\n", __FUNCTION__); + goto abort_free_ctlr; + } + dbg("%s: SLOT_CTRL %x value read %x\n", __FUNCTION__, SLOT_CTRL, temp_word); + dbg("%s: slot_cap %x\n", __FUNCTION__, slot_cap); + + intr_enable = intr_enable | PRSN_DETECT_ENABLE; + + if (ATTN_BUTTN(slot_cap)) + intr_enable = intr_enable | ATTN_BUTTN_ENABLE; + + if (POWER_CTRL(slot_cap)) + intr_enable = intr_enable | PWR_FAULT_DETECT_ENABLE; + + if (MRL_SENS(slot_cap)) + intr_enable = intr_enable | MRL_DETECT_ENABLE; + + temp_word = (temp_word & ~intr_enable) | intr_enable; + + if (pciehp_poll_mode) { + temp_word = (temp_word & ~HP_INTR_ENABLE) | 0x0; + } else { + temp_word = (temp_word & ~HP_INTR_ENABLE) | HP_INTR_ENABLE; + } + dbg("%s: temp_word %x\n", __FUNCTION__, temp_word); + + /* Unmask Hot-plug Interrupt Enable for the interrupt notification mechanism case */ + rc = hp_register_write_word(pdev, SLOT_CTRL, temp_word); + if (rc) { + err("%s : hp_register_write_word SLOT_CTRL failed\n", __FUNCTION__); + goto abort_free_ctlr; + } + dbg("%s : Unmask HPIE hp_register_write_word SLOT_CTRL with %x\n", __FUNCTION__, temp_word); + rc = hp_register_read_word(php_ctlr->pci_dev, SLOT_STATUS, slot_status); + if (rc) { + err("%s : hp_register_read_word SLOT_STATUS failed\n", __FUNCTION__); + goto abort_free_ctlr; + } + dbg("%s: Unmask HPIE SLOT_STATUS offset %x reads slot_status %x\n", __FUNCTION__, + SLOT_STATUS, slot_status); + + temp_word = 0x1F; /* Clear all events */ + rc = hp_register_write_word(php_ctlr->pci_dev, SLOT_STATUS, temp_word); + if (rc) { + err("%s : hp_register_write_word SLOT_STATUS failed\n", __FUNCTION__); + goto abort_free_ctlr; + } + dbg("%s: SLOT_STATUS offset %x writes slot_status %x\n", __FUNCTION__, SLOT_STATUS, temp_word); + + /* Add this HPC instance into the HPC list */ + spin_lock(&list_lock); + if (php_ctlr_list_head == 0) { + php_ctlr_list_head = php_ctlr; + p = php_ctlr_list_head; + p->pnext = NULL; + } else { + p = php_ctlr_list_head; + + while (p->pnext) + p = p->pnext; + + p->pnext = php_ctlr; + } + spin_unlock(&list_lock); + + ctlr_seq_num++; + ctrl->hpc_ctlr_handle = php_ctlr; + ctrl->hpc_ops = &pciehp_hpc_ops; + + DBG_LEAVE_ROUTINE + return 0; + + /* We end up here for the many possible ways to fail this API. */ +abort_free_ctlr: + pcie_cap_base = saved_cap_base; + kfree(php_ctlr); +abort: + DBG_LEAVE_ROUTINE + return -1; +} diff --git a/drivers/pci/hotplug/pciehp_pci.c b/drivers/pci/hotplug/pciehp_pci.c new file mode 100644 index 000000000000..723b12c0bb7c --- /dev/null +++ b/drivers/pci/hotplug/pciehp_pci.c @@ -0,0 +1,827 @@ +/* + * 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>, <dely.l.sy@intel.com> + * + */ + +#include <linux/config.h> +#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 "../pci.h" +#include "pciehp.h" +#ifndef CONFIG_IA64 +#include "../../../arch/i386/pci/pci.h" /* horrible hack showing how processor dependant we are... */ +#endif + + +int pciehp_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)); + + /* Still NULL ? Well then scan for it ! */ + if (func->pci_dev == NULL) { + dbg("%s: pci_dev still null. do pci_scan_slot\n", __FUNCTION__); + + num = pci_scan_slot(ctrl->pci_dev->subordinate, PCI_DEVFN(func->device, func->function)); + + if (num) + pci_bus_add_devices(ctrl->pci_dev->subordinate); + + 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 = pci_add_new_bus(func->pci_dev->bus, (func->pci_dev), bus); + pci_do_scan_bus(child); + + } + + return 0; +} + + +int pciehp_unconfigure_device(struct pci_func* func) +{ + int rc = 0; + int j; + struct pci_bus *pbus; + + dbg("%s: bus/dev/func = %x/%x/%x\n", __FUNCTION__, func->bus, + func->device, func->function); + pbus = func->pci_dev->bus; + + for (j=0; j<8 ; j++) { + struct pci_dev* temp = pci_find_slot(func->bus, + (func->device << 3) | j); + if (temp) { + pci_remove_bus_device(temp); + } + } + /* + * Some PCI Express root ports require fixup after hot-plug operation. + */ + if (pcie_mch_quirk) + pci_fixup_device(pci_fixup_final, pbus->self); + + return rc; +} + +/* + * pciehp_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 pciehp_set_irq (u8 bus_num, u8 dev_num, u8 int_pin, u8 irq_num) +{ +#if defined(CONFIG_X86) && !defined(CONFIG_X86_IO_APIC) && !defined(CONFIG_X86_64) + int rc; + u16 temp_word; + struct pci_dev fakedev; + struct pci_bus fakebus; + + fakedev.devfn = dev_num << 3; + fakedev.bus = &fakebus; + fakebus.number = bus_num; + dbg("%s: dev %d, bus %d, pin %d, num %d\n", + __FUNCTION__, dev_num, bus_num, int_pin, irq_num); + rc = pcibios_set_irq_routing(&fakedev, int_pin - 0x0a, irq_num); + dbg("%s: rc %d\n", __FUNCTION__, 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); +#endif + return 0; +} + +/* More PCI configuration routines; this time centered around hotplug controller */ + + +/* + * pciehp_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 pciehp_save_config(struct controller *ctrl, int busnumber, int num_ctlr_slots, int first_device_num) +{ + int rc; + u8 class_code; + u8 header_type; + u32 ID; + u8 secondary_bus; + struct pci_func *new_slot; + int sub_bus; + int max_functions; + int function; + u8 DevError; + int device = 0; + int cloop = 0; + int stop_it; + int index; + int is_hot_plug = num_ctlr_slots || first_device_num; + struct pci_bus lpci_bus, *pci_bus; + int FirstSupported, LastSupported; + + dbg("%s: Enter\n", __FUNCTION__); + + memcpy(&lpci_bus, ctrl->pci_dev->subordinate, sizeof(lpci_bus)); + pci_bus = &lpci_bus; + + dbg("%s: num_ctlr_slots = %d, first_device_num = %d\n", __FUNCTION__, + num_ctlr_slots, first_device_num); + + /* Decide which slots are supported */ + if (is_hot_plug) { + /********************************* + * is_hot_plug is the slot mask + *********************************/ + FirstSupported = first_device_num; + LastSupported = FirstSupported + num_ctlr_slots - 1; + } else { + FirstSupported = 0; + LastSupported = 0x1F; + } + + dbg("FirstSupported = %d, LastSupported = %d\n", FirstSupported, + LastSupported); + + /* Save PCI configuration space for all devices in supported slots */ + dbg("%s: pci_bus->number = %x\n", __FUNCTION__, pci_bus->number); + pci_bus->number = busnumber; + dbg("%s: bus = %x, dev = %x\n", __FUNCTION__, busnumber, device); + for (device = FirstSupported; device <= LastSupported; device++) { + ID = 0xFFFFFFFF; + rc = pci_bus_read_config_dword(pci_bus, PCI_DEVFN(device, 0), + PCI_VENDOR_ID, &ID); + + if (ID != 0xFFFFFFFF) { /* device in slot */ + dbg("%s: ID = %x\n", __FUNCTION__, ID); + rc = pci_bus_read_config_byte(pci_bus, PCI_DEVFN(device, 0), + 0x0B, &class_code); + if (rc) + return rc; + + rc = pci_bus_read_config_byte(pci_bus, PCI_DEVFN(device, 0), + PCI_HEADER_TYPE, &header_type); + if (rc) + return rc; + + dbg("class_code = %x, header_type = %x\n", class_code, header_type); + + /* 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; + dbg("%s: In do loop\n", __FUNCTION__); + + 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(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 = pciehp_save_config(ctrl, sub_bus, 0, 0); + if (rc) + return rc; + } + } + + index = 0; + new_slot = pciehp_slot_find(busnumber, device, index++); + + dbg("%s: new_slot = %p bus %x dev %x fun %x\n", + __FUNCTION__, new_slot, busnumber, device, index-1); + + while (new_slot && (new_slot->function != (u8) function)) { + new_slot = pciehp_slot_find(busnumber, device, index++); + dbg("%s: while loop, new_slot = %p bus %x dev %x fun %x\n", + __FUNCTION__, new_slot, busnumber, device, index-1); + } + if (!new_slot) { + /* Setup slot structure. */ + new_slot = pciehp_slot_create(busnumber); + dbg("%s: if, new_slot = %p bus %x dev %x fun %x\n", + __FUNCTION__, new_slot, busnumber, device, function); + + 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); + dbg("new_slot->pci_dev = %p\n", new_slot->pci_dev); + + for (cloop = 0; cloop < 0x20; cloop++) { + rc = pci_bus_read_config_dword(pci_bus, + PCI_DEVFN(device, function), + cloop << 2, + (u32 *) &(new_slot->config_space [cloop])); + /* dbg("new_slot->config_space[%x] = %x\n", + cloop, 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)) { + dbg("%s: In while loop \n", __FUNCTION__); + rc = pci_bus_read_config_dword(pci_bus, + PCI_DEVFN(device, function), + PCI_VENDOR_ID, &ID); + + if (ID == 0xFFFFFFFF) { /* nothing there. */ + function++; + dbg("Nothing there\n"); + } else { /* Something there */ + rc = pci_bus_read_config_byte(pci_bus, + PCI_DEVFN(device, function), + 0x0B, &class_code); + if (rc) + return rc; + + rc = pci_bus_read_config_byte(pci_bus, + PCI_DEVFN(device, function), + PCI_HEADER_TYPE, &header_type); + if (rc) + return rc; + + dbg("class_code = %x, header_type = %x\n", class_code, header_type); + 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 = pciehp_slot_create(busnumber); + + if (new_slot == NULL) { + return(1); + } + dbg("new_slot = %p, bus = %x, dev = %x, fun = %x\n", new_slot, + new_slot->bus, new_slot->device, new_slot->function); + + 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 */ + + dbg("%s: Exit\n", __FUNCTION__); + return(0); +} + + +/* + * pciehp_save_slot_config + * + * Saves configuration info for all PCI devices in a given slot + * including subordinate busses. + * + * returns 0 if success + */ +int pciehp_save_slot_config(struct controller *ctrl, struct pci_func * new_slot) +{ + int 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; + struct pci_bus lpci_bus, *pci_bus; + memcpy(&lpci_bus, ctrl->pci_dev->subordinate, sizeof(lpci_bus)); + pci_bus = &lpci_bus; + pci_bus->number = new_slot->bus; + + ID = 0xFFFFFFFF; + + pci_bus_read_config_dword(pci_bus, PCI_DEVFN(new_slot->device, 0), + PCI_VENDOR_ID, &ID); + + if (ID != 0xFFFFFFFF) { /* device in slot */ + pci_bus_read_config_byte(pci_bus, PCI_DEVFN(new_slot->device, 0), + 0x0B, &class_code); + + pci_bus_read_config_byte(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(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 = pciehp_save_config(ctrl, sub_bus, 0, 0); + + if (rc) + return rc; + + } /* End of IF */ + + new_slot->status = 0; + + for (cloop = 0; cloop < 0x20; cloop++) { + pci_bus_read_config_dword(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(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(pci_bus, + PCI_DEVFN(new_slot->device, function), + 0x0B, &class_code); + + pci_bus_read_config_byte(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; +} + + +/* + * pciehp_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 + * if disable == 1(DISABLE_CARD), + * it loops for all functions of the slot and disables them. + * else, it just get resources of the function and return. + */ +int pciehp_save_used_resources(struct controller *ctrl, struct pci_func *func, int disable) +{ + u8 cloop; + u8 header_type; + u8 secondary_bus; + u8 temp_byte; + u16 command; + u16 save_command; + u16 w_base, w_length; + u32 temp_register; + u32 save_base; + u32 base, length; + u64 base64 = 0; + int index = 0; + unsigned int devfn; + struct pci_resource *mem_node = NULL; + struct pci_resource *p_mem_node = NULL; + struct pci_resource *t_mem_node; + struct pci_resource *io_node; + struct pci_resource *bus_node; + struct pci_bus lpci_bus, *pci_bus; + memcpy(&lpci_bus, ctrl->pci_dev->subordinate, sizeof(lpci_bus)); + pci_bus = &lpci_bus; + + if (disable) + func = pciehp_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); + + if (disable) { + /* 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 */ + dbg("Save_used_res of PCI bridge b:d=0x%x:%x, sc=0x%x\n", + func->bus, func->device, save_command); + if (disable) { + /* 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(struct pci_resource), + GFP_KERNEL); + if (!bus_node) + return -ENOMEM; + + bus_node->base = (ulong)secondary_bus; + bus_node->length = (ulong)(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, &temp_byte); + base = temp_byte; + pci_bus_read_config_byte(pci_bus, devfn, PCI_IO_LIMIT, &temp_byte); + length = temp_byte; + + if ((base <= length) && (!disable || (save_command & PCI_COMMAND_IO))) { + io_node = kmalloc(sizeof(struct pci_resource), + GFP_KERNEL); + if (!io_node) + return -ENOMEM; + + io_node->base = (ulong)(base & PCI_IO_RANGE_MASK) << 8; + io_node->length = (ulong)(length - 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) && (!disable || (save_command & PCI_COMMAND_MEMORY))) { + mem_node = kmalloc(sizeof(struct pci_resource), + GFP_KERNEL); + if (!mem_node) + return -ENOMEM; + + mem_node->base = (ulong)w_base << 16; + mem_node->length = (ulong)(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) && (!disable || (save_command & PCI_COMMAND_MEMORY))) { + p_mem_node = kmalloc(sizeof(struct pci_resource), + GFP_KERNEL); + if (!p_mem_node) + return -ENOMEM; + + p_mem_node->base = (ulong)w_base << 16; + p_mem_node->length = (ulong)(w_length - w_base + 0x10) << 16; + + p_mem_node->next = func->p_mem_head; + func->p_mem_head = p_mem_node; + } + } else if ((header_type & 0x7F) == PCI_HEADER_TYPE_NORMAL) { + dbg("Save_used_res of PCI adapter b:d=0x%x:%x, sc=0x%x\n", + func->bus, func->device, save_command); + + /* Figure out IO and memory base lengths */ + for (cloop = PCI_BASE_ADDRESS_0; cloop <= PCI_BASE_ADDRESS_5; 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, &temp_register); + + if (!disable) + pci_bus_write_config_dword(pci_bus, devfn, cloop, save_base); + + if (!temp_register) + continue; + + base = temp_register; + + if ((base & PCI_BASE_ADDRESS_SPACE_IO) && + (!disable || (save_command & PCI_COMMAND_IO))) { + /* IO base */ + /* set temp_register = amount of IO space requested */ + base = base & 0xFFFFFFFCL; + base = (~base) + 1; + + io_node = kmalloc(sizeof (struct pci_resource), + GFP_KERNEL); + if (!io_node) + return -ENOMEM; + + io_node->base = (ulong)save_base & PCI_BASE_ADDRESS_IO_MASK; + io_node->length = (ulong)base; + dbg("sur adapter: IO bar=0x%x(length=0x%x)\n", + io_node->base, io_node->length); + + io_node->next = func->io_head; + func->io_head = io_node; + } else { /* map Memory */ + int prefetchable = 1; + /* struct pci_resources **res_node; */ + char *res_type_str = "PMEM"; + u32 temp_register2; + + t_mem_node = kmalloc(sizeof (struct pci_resource), + GFP_KERNEL); + if (!t_mem_node) + return -ENOMEM; + + if (!(base & PCI_BASE_ADDRESS_MEM_PREFETCH) && + (!disable || (save_command & PCI_COMMAND_MEMORY))) { + prefetchable = 0; + mem_node = t_mem_node; + res_type_str++; + } else + p_mem_node = t_mem_node; + + base = base & 0xFFFFFFF0L; + base = (~base) + 1; + + switch (temp_register & PCI_BASE_ADDRESS_MEM_TYPE_MASK) { + case PCI_BASE_ADDRESS_MEM_TYPE_32: + if (prefetchable) { + p_mem_node->base = (ulong)save_base & PCI_BASE_ADDRESS_MEM_MASK; + p_mem_node->length = (ulong)base; + dbg("sur adapter: 32 %s bar=0x%x(length=0x%x)\n", + res_type_str, + p_mem_node->base, + p_mem_node->length); + + p_mem_node->next = func->p_mem_head; + func->p_mem_head = p_mem_node; + } else { + mem_node->base = (ulong)save_base & PCI_BASE_ADDRESS_MEM_MASK; + mem_node->length = (ulong)base; + dbg("sur adapter: 32 %s bar=0x%x(length=0x%x)\n", + res_type_str, + mem_node->base, + mem_node->length); + + mem_node->next = func->mem_head; + func->mem_head = mem_node; + } + break; + case PCI_BASE_ADDRESS_MEM_TYPE_64: + pci_bus_read_config_dword(pci_bus, devfn, cloop+4, &temp_register2); + base64 = temp_register2; + base64 = (base64 << 32) | save_base; + + if (temp_register2) { + dbg("sur adapter: 64 %s high dword of base64(0x%x:%x) masked to 0\n", + res_type_str, temp_register2, (u32)base64); + base64 &= 0x00000000FFFFFFFFL; + } + + if (prefetchable) { + p_mem_node->base = base64 & PCI_BASE_ADDRESS_MEM_MASK; + p_mem_node->length = base; + dbg("sur adapter: 64 %s base=0x%x(len=0x%x)\n", + res_type_str, + p_mem_node->base, + p_mem_node->length); + + p_mem_node->next = func->p_mem_head; + func->p_mem_head = p_mem_node; + } else { + mem_node->base = base64 & PCI_BASE_ADDRESS_MEM_MASK; + mem_node->length = base; + dbg("sur adapter: 64 %s base=0x%x(len=0x%x)\n", + res_type_str, + mem_node->base, + mem_node->length); + + mem_node->next = func->mem_head; + func->mem_head = mem_node; + } + cloop += 4; + break; + default: + dbg("asur: reserved BAR type=0x%x\n", + temp_register); + break; + } + } + } /* End of base register loop */ + } else { /* Some other unknown header type */ + dbg("Save_used_res of PCI unknown type b:d=0x%x:%x. skip.\n", + func->bus, func->device); + } + + /* find the next device in this slot */ + if (!disable) + break; + func = pciehp_slot_find(func->bus, func->device, index++); + } + + return 0; +} + + +/** + * kfree_resource_list: release memory of all list members + * @res: resource list to free + */ +static inline void +return_resource_list(struct pci_resource **func, struct pci_resource **res) +{ + struct pci_resource *node; + struct pci_resource *t_node; + + node = *func; + *func = NULL; + while (node) { + t_node = node->next; + return_resource(res, node); + node = t_node; + } +} + +/* + * pciehp_return_board_resources + * + * this routine returns all resources allocated to a board to + * the available pool. + * + * returns 0 if success + */ +int pciehp_return_board_resources(struct pci_func * func, + struct resource_lists * resources) +{ + int rc; + + dbg("%s\n", __FUNCTION__); + + if (!func) + return 1; + + return_resource_list(&(func->io_head),&(resources->io_head)); + return_resource_list(&(func->mem_head),&(resources->mem_head)); + return_resource_list(&(func->p_mem_head),&(resources->p_mem_head)); + return_resource_list(&(func->bus_head),&(resources->bus_head)); + + rc = pciehp_resource_sort_and_combine(&(resources->mem_head)); + rc |= pciehp_resource_sort_and_combine(&(resources->p_mem_head)); + rc |= pciehp_resource_sort_and_combine(&(resources->io_head)); + rc |= pciehp_resource_sort_and_combine(&(resources->bus_head)); + + return rc; +} + +/** + * kfree_resource_list: release memory of all list members + * @res: resource list to free + */ +static inline void +kfree_resource_list(struct pci_resource **r) +{ + struct pci_resource *res, *tres; + + res = *r; + *r = NULL; + + while (res) { + tres = res; + res = res->next; + kfree(tres); + } +} + +/** + * pciehp_destroy_resource_list: put node back in the resource list + * @resources: list to put nodes back + */ +void pciehp_destroy_resource_list(struct resource_lists * resources) +{ + kfree_resource_list(&(resources->io_head)); + kfree_resource_list(&(resources->mem_head)); + kfree_resource_list(&(resources->p_mem_head)); + kfree_resource_list(&(resources->bus_head)); +} + +/** + * pciehp_destroy_board_resources: put node back in the resource list + * @resources: list to put nodes back + */ +void pciehp_destroy_board_resources(struct pci_func * func) +{ + kfree_resource_list(&(func->io_head)); + kfree_resource_list(&(func->mem_head)); + kfree_resource_list(&(func->p_mem_head)); + kfree_resource_list(&(func->bus_head)); +} diff --git a/drivers/pci/hotplug/pciehprm.h b/drivers/pci/hotplug/pciehprm.h new file mode 100644 index 000000000000..966775ffb0ff --- /dev/null +++ b/drivers/pci/hotplug/pciehprm.h @@ -0,0 +1,52 @@ +/* + * PCIEHPRM : PCIEHP Resource Manager for ACPI/non-ACPI platform + * + * Copyright (C) 1995,2001 Compaq Computer Corporation + * Copyright (C) 2001,2003 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>, <dely.l.sy@intel.com> + * + */ + +#ifndef _PCIEHPRM_H_ +#define _PCIEHPRM_H_ + +#ifdef CONFIG_HOTPLUG_PCI_PCIE_PHPRM_NONACPI +#include "pciehprm_nonacpi.h" +#endif + +int pciehprm_init(enum php_ctlr_type ct); +void pciehprm_cleanup(void); +int pciehprm_print_pirt(void); +int pciehprm_find_available_resources(struct controller *ctrl); +int pciehprm_set_hpp(struct controller *ctrl, struct pci_func *func, u8 card_type); +void pciehprm_enable_card(struct controller *ctrl, struct pci_func *func, u8 card_type); + +#ifdef DEBUG +#define RES_CHECK(this, bits) \ + { if (((this) & (bits - 1))) \ + printk("%s:%d ERR: potential res loss!\n", __FUNCTION__, __LINE__); } +#else +#define RES_CHECK(this, bits) +#endif + +#endif /* _PCIEHPRM_H_ */ diff --git a/drivers/pci/hotplug/pciehprm_acpi.c b/drivers/pci/hotplug/pciehprm_acpi.c new file mode 100644 index 000000000000..57f4e6d1b27c --- /dev/null +++ b/drivers/pci/hotplug/pciehprm_acpi.c @@ -0,0 +1,1737 @@ +/* + * PCIEHPRM ACPI: PHP Resource Manager for ACPI platform + * + * 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 <dely.l.sy@intel.com> + * + */ + +#include <linux/config.h> +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/types.h> +#include <linux/pci.h> +#include <linux/init.h> +#include <linux/acpi.h> +#include <linux/efi.h> +#include <linux/pci-acpi.h> +#include <asm/uaccess.h> +#include <asm/system.h> +#ifdef CONFIG_IA64 +#include <asm/iosapic.h> +#endif +#include <acpi/acpi.h> +#include <acpi/acpi_bus.h> +#include <acpi/actypes.h> +#include "pciehp.h" +#include "pciehprm.h" + +#define PCI_MAX_BUS 0x100 +#define ACPI_STA_DEVICE_PRESENT 0x01 + +#define METHOD_NAME__SUN "_SUN" +#define METHOD_NAME__HPP "_HPP" +#define METHOD_NAME_OSHP "OSHP" + +/* Status code for running acpi method to gain native control */ +#define NC_NOT_RUN 0 +#define OSC_NOT_EXIST 1 +#define OSC_RUN_FAILED 2 +#define OSHP_NOT_EXIST 3 +#define OSHP_RUN_FAILED 4 +#define NC_RUN_SUCCESS 5 + +#define PHP_RES_BUS 0xA0 +#define PHP_RES_IO 0xA1 +#define PHP_RES_MEM 0xA2 +#define PHP_RES_PMEM 0xA3 + +#define BRIDGE_TYPE_P2P 0x00 +#define BRIDGE_TYPE_HOST 0x01 + +/* this should go to drivers/acpi/include/ */ +struct acpi__hpp { + u8 cache_line_size; + u8 latency_timer; + u8 enable_serr; + u8 enable_perr; +}; + +struct acpi_php_slot { + struct acpi_php_slot *next; + struct acpi_bridge *bridge; + acpi_handle handle; + int seg; + int bus; + int dev; + int fun; + u32 sun; + struct pci_resource *mem_head; + struct pci_resource *p_mem_head; + struct pci_resource *io_head; + struct pci_resource *bus_head; + void *slot_ops; /* _STA, _EJx, etc */ + struct slot *slot; +}; /* per func */ + +struct acpi_bridge { + struct acpi_bridge *parent; + struct acpi_bridge *next; + struct acpi_bridge *child; + acpi_handle handle; + int seg; + int pbus; /* pdev->bus->number */ + int pdevice; /* PCI_SLOT(pdev->devfn) */ + int pfunction; /* PCI_DEVFN(pdev->devfn) */ + int bus; /* pdev->subordinate->number */ + struct acpi__hpp *_hpp; + struct acpi_php_slot *slots; + struct pci_resource *tmem_head; /* total from crs */ + struct pci_resource *tp_mem_head; /* total from crs */ + struct pci_resource *tio_head; /* total from crs */ + struct pci_resource *tbus_head; /* total from crs */ + struct pci_resource *mem_head; /* available */ + struct pci_resource *p_mem_head; /* available */ + struct pci_resource *io_head; /* available */ + struct pci_resource *bus_head; /* available */ + int scanned; + int type; +}; + +static struct acpi_bridge *acpi_bridges_head; + +static u8 * acpi_path_name( acpi_handle handle) +{ + acpi_status status; + static u8 path_name[ACPI_PATHNAME_MAX]; + struct acpi_buffer ret_buf = { ACPI_PATHNAME_MAX, path_name }; + + memset(path_name, 0, sizeof (path_name)); + status = acpi_get_name(handle, ACPI_FULL_PATHNAME, &ret_buf); + + if (ACPI_FAILURE(status)) + return NULL; + else + return path_name; +} + +static void acpi_get__hpp ( struct acpi_bridge *ab); +static int acpi_run_oshp ( struct acpi_bridge *ab); +static int osc_run_status = NC_NOT_RUN; +static int oshp_run_status = NC_NOT_RUN; + +static int acpi_add_slot_to_php_slots( + struct acpi_bridge *ab, + int bus_num, + acpi_handle handle, + u32 adr, + u32 sun + ) +{ + struct acpi_php_slot *aps; + static long samesun = -1; + + aps = (struct acpi_php_slot *) kmalloc (sizeof(struct acpi_php_slot), GFP_KERNEL); + if (!aps) { + err ("acpi_pciehprm: alloc for aps fail\n"); + return -1; + } + memset(aps, 0, sizeof(struct acpi_php_slot)); + + aps->handle = handle; + aps->bus = bus_num; + aps->dev = (adr >> 16) & 0xffff; + aps->fun = adr & 0xffff; + aps->sun = sun; + + aps->next = ab->slots; /* cling to the bridge */ + aps->bridge = ab; + ab->slots = aps; + + ab->scanned += 1; + if (!ab->_hpp) + acpi_get__hpp(ab); + + if (osc_run_status == OSC_NOT_EXIST) + oshp_run_status = acpi_run_oshp(ab); + + if (sun != samesun) { + info("acpi_pciehprm: Slot sun(%x) at s:b:d:f=0x%02x:%02x:%02x:%02x\n", + aps->sun, ab->seg, aps->bus, aps->dev, aps->fun); + samesun = sun; + } + return 0; +} + +static void acpi_get__hpp ( struct acpi_bridge *ab) +{ + acpi_status status; + u8 nui[4]; + struct acpi_buffer ret_buf = { 0, NULL}; + union acpi_object *ext_obj, *package; + u8 *path_name = acpi_path_name(ab->handle); + int i, len = 0; + + /* get _hpp */ + status = acpi_evaluate_object(ab->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) { + err ("acpi_pciehprm:%s alloc for _HPP fail\n", path_name); + return; + } + status = acpi_evaluate_object(ab->handle, METHOD_NAME__HPP, NULL, &ret_buf); + if (ACPI_SUCCESS(status)) + break; + default: + if (ACPI_FAILURE(status)) { + err("acpi_pciehprm:%s _HPP fail=0x%x\n", path_name, status); + return; + } + } + + ext_obj = (union acpi_object *) ret_buf.pointer; + if (ext_obj->type != ACPI_TYPE_PACKAGE) { + err ("acpi_pciehprm:%s _HPP obj not a package\n", path_name); + 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: + err ("acpi_pciehprm:%s _HPP obj type incorrect\n", path_name); + goto free_and_return; + } + } + + ab->_hpp = kmalloc (sizeof (struct acpi__hpp), GFP_KERNEL); + if (!ab->_hpp) { + err ("acpi_pciehprm:%s alloc for _HPP failed\n", path_name); + goto free_and_return; + } + memset(ab->_hpp, 0, sizeof(struct acpi__hpp)); + + ab->_hpp->cache_line_size = nui[0]; + ab->_hpp->latency_timer = nui[1]; + ab->_hpp->enable_serr = nui[2]; + ab->_hpp->enable_perr = nui[3]; + + dbg(" _HPP: cache_line_size=0x%x\n", ab->_hpp->cache_line_size); + dbg(" _HPP: latency timer =0x%x\n", ab->_hpp->latency_timer); + dbg(" _HPP: enable SERR =0x%x\n", ab->_hpp->enable_serr); + dbg(" _HPP: enable PERR =0x%x\n", ab->_hpp->enable_perr); + +free_and_return: + kfree(ret_buf.pointer); +} + +static int acpi_run_oshp ( struct acpi_bridge *ab) +{ + acpi_status status; + u8 *path_name = acpi_path_name(ab->handle); + + /* run OSHP */ + status = acpi_evaluate_object(ab->handle, METHOD_NAME_OSHP, NULL, NULL); + if (ACPI_FAILURE(status)) { + err("acpi_pciehprm:%s OSHP fails=0x%x\n", path_name, status); + oshp_run_status = (status == AE_NOT_FOUND) ? OSHP_NOT_EXIST : OSHP_RUN_FAILED; + } else { + oshp_run_status = NC_RUN_SUCCESS; + dbg("acpi_pciehprm:%s OSHP passes =0x%x\n", path_name, status); + dbg("acpi_pciehprm:%s oshp_run_status =0x%x\n", path_name, oshp_run_status); + } + return oshp_run_status; +} + +static acpi_status acpi_evaluate_crs( + acpi_handle handle, + struct acpi_resource **retbuf + ) +{ + acpi_status status; + struct acpi_buffer crsbuf; + u8 *path_name = acpi_path_name(handle); + + crsbuf.length = 0; + crsbuf.pointer = NULL; + + status = acpi_get_current_resources (handle, &crsbuf); + + switch (status) { + case AE_BUFFER_OVERFLOW: + break; /* found */ + case AE_NOT_FOUND: + dbg("acpi_pciehprm:%s _CRS not found\n", path_name); + return status; + default: + err ("acpi_pciehprm:%s _CRS fail=0x%x\n", path_name, status); + return status; + } + + crsbuf.pointer = kmalloc (crsbuf.length, GFP_KERNEL); + if (!crsbuf.pointer) { + err ("acpi_pciehprm: alloc %ld bytes for %s _CRS fail\n", (ulong)crsbuf.length, path_name); + return AE_NO_MEMORY; + } + + status = acpi_get_current_resources (handle, &crsbuf); + if (ACPI_FAILURE(status)) { + err("acpi_pciehprm: %s _CRS fail=0x%x.\n", path_name, status); + kfree(crsbuf.pointer); + return status; + } + + *retbuf = crsbuf.pointer; + + return status; +} + +static void free_pci_resource ( struct pci_resource *aprh) +{ + struct pci_resource *res, *next; + + for (res = aprh; res; res = next) { + next = res->next; + kfree(res); + } +} + +static void print_pci_resource ( struct pci_resource *aprh) +{ + struct pci_resource *res; + + for (res = aprh; res; res = res->next) + dbg(" base= 0x%x length= 0x%x\n", res->base, res->length); +} + +static void print_slot_resources( struct acpi_php_slot *aps) +{ + if (aps->bus_head) { + dbg(" BUS Resources:\n"); + print_pci_resource (aps->bus_head); + } + + if (aps->io_head) { + dbg(" IO Resources:\n"); + print_pci_resource (aps->io_head); + } + + if (aps->mem_head) { + dbg(" MEM Resources:\n"); + print_pci_resource (aps->mem_head); + } + + if (aps->p_mem_head) { + dbg(" PMEM Resources:\n"); + print_pci_resource (aps->p_mem_head); + } +} + +static void print_pci_resources( struct acpi_bridge *ab) +{ + if (ab->tbus_head) { + dbg(" Total BUS Resources:\n"); + print_pci_resource (ab->tbus_head); + } + if (ab->bus_head) { + dbg(" BUS Resources:\n"); + print_pci_resource (ab->bus_head); + } + + if (ab->tio_head) { + dbg(" Total IO Resources:\n"); + print_pci_resource (ab->tio_head); + } + if (ab->io_head) { + dbg(" IO Resources:\n"); + print_pci_resource (ab->io_head); + } + + if (ab->tmem_head) { + dbg(" Total MEM Resources:\n"); + print_pci_resource (ab->tmem_head); + } + if (ab->mem_head) { + dbg(" MEM Resources:\n"); + print_pci_resource (ab->mem_head); + } + + if (ab->tp_mem_head) { + dbg(" Total PMEM Resources:\n"); + print_pci_resource (ab->tp_mem_head); + } + if (ab->p_mem_head) { + dbg(" PMEM Resources:\n"); + print_pci_resource (ab->p_mem_head); + } + if (ab->_hpp) { + dbg(" _HPP: cache_line_size=0x%x\n", ab->_hpp->cache_line_size); + dbg(" _HPP: latency timer =0x%x\n", ab->_hpp->latency_timer); + dbg(" _HPP: enable SERR =0x%x\n", ab->_hpp->enable_serr); + dbg(" _HPP: enable PERR =0x%x\n", ab->_hpp->enable_perr); + } +} + +static int pciehprm_delete_resource( + struct pci_resource **aprh, + ulong base, + ulong size) +{ + struct pci_resource *res; + struct pci_resource *prevnode; + struct pci_resource *split_node; + ulong tbase; + + pciehp_resource_sort_and_combine(aprh); + + for (res = *aprh; res; res = res->next) { + if (res->base > base) + continue; + + if ((res->base + res->length) < (base + size)) + continue; + + if (res->base < base) { + tbase = base; + + if ((res->length - (tbase - res->base)) < size) + continue; + + split_node = (struct pci_resource *) kmalloc(sizeof(struct pci_resource), GFP_KERNEL); + if (!split_node) + return -ENOMEM; + + split_node->base = res->base; + split_node->length = tbase - res->base; + res->base = tbase; + res->length -= split_node->length; + + split_node->next = res->next; + res->next = split_node; + } + + if (res->length >= size) { + split_node = (struct pci_resource*) kmalloc(sizeof(struct pci_resource), GFP_KERNEL); + if (!split_node) + return -ENOMEM; + + split_node->base = res->base + size; + split_node->length = res->length - size; + res->length = size; + + split_node->next = res->next; + res->next = split_node; + } + + if (*aprh == res) { + *aprh = res->next; + } else { + prevnode = *aprh; + while (prevnode->next != res) + prevnode = prevnode->next; + + prevnode->next = res->next; + } + res->next = NULL; + kfree(res); + break; + } + + return 0; +} + +static int pciehprm_delete_resources( + struct pci_resource **aprh, + struct pci_resource *this + ) +{ + struct pci_resource *res; + + for (res = this; res; res = res->next) + pciehprm_delete_resource(aprh, res->base, res->length); + + return 0; +} + +static int pciehprm_add_resource( + struct pci_resource **aprh, + ulong base, + ulong size) +{ + struct pci_resource *res; + + for (res = *aprh; res; res = res->next) { + if ((res->base + res->length) == base) { + res->length += size; + size = 0L; + break; + } + if (res->next == *aprh) + break; + } + + if (size) { + res = kmalloc(sizeof(struct pci_resource), GFP_KERNEL); + if (!res) { + err ("acpi_pciehprm: alloc for res fail\n"); + return -ENOMEM; + } + memset(res, 0, sizeof (struct pci_resource)); + + res->base = base; + res->length = size; + res->next = *aprh; + *aprh = res; + } + + return 0; +} + +static int pciehprm_add_resources( + struct pci_resource **aprh, + struct pci_resource *this + ) +{ + struct pci_resource *res; + int rc = 0; + + for (res = this; res && !rc; res = res->next) + rc = pciehprm_add_resource(aprh, res->base, res->length); + + return rc; +} + +static void acpi_parse_io ( + struct acpi_bridge *ab, + union acpi_resource_data *data + ) +{ + struct acpi_resource_io *dataio; + dataio = (struct acpi_resource_io *) data; + + dbg("Io Resource\n"); + dbg(" %d bit decode\n", ACPI_DECODE_16 == dataio->io_decode ? 16:10); + dbg(" Range minimum base: %08X\n", dataio->min_base_address); + dbg(" Range maximum base: %08X\n", dataio->max_base_address); + dbg(" Alignment: %08X\n", dataio->alignment); + dbg(" Range Length: %08X\n", dataio->range_length); +} + +static void acpi_parse_fixed_io ( + struct acpi_bridge *ab, + union acpi_resource_data *data + ) +{ + struct acpi_resource_fixed_io *datafio; + datafio = (struct acpi_resource_fixed_io *) data; + + dbg("Fixed Io Resource\n"); + dbg(" Range base address: %08X", datafio->base_address); + dbg(" Range length: %08X", datafio->range_length); +} + +static void acpi_parse_address16_32 ( + struct acpi_bridge *ab, + union acpi_resource_data *data, + acpi_resource_type id + ) +{ + /* + * acpi_resource_address16 == acpi_resource_address32 + * acpi_resource_address16 *data16 = (acpi_resource_address16 *) data; + */ + struct acpi_resource_address32 *data32 = (struct acpi_resource_address32 *) data; + struct pci_resource **aprh, **tprh; + + if (id == ACPI_RSTYPE_ADDRESS16) + dbg("acpi_pciehprm:16-Bit Address Space Resource\n"); + else + dbg("acpi_pciehprm:32-Bit Address Space Resource\n"); + + switch (data32->resource_type) { + case ACPI_MEMORY_RANGE: + dbg(" Resource Type: Memory Range\n"); + aprh = &ab->mem_head; + tprh = &ab->tmem_head; + + switch (data32->attribute.memory.cache_attribute) { + case ACPI_NON_CACHEABLE_MEMORY: + dbg(" Type Specific: Noncacheable memory\n"); + break; + case ACPI_CACHABLE_MEMORY: + dbg(" Type Specific: Cacheable memory\n"); + break; + case ACPI_WRITE_COMBINING_MEMORY: + dbg(" Type Specific: Write-combining memory\n"); + break; + case ACPI_PREFETCHABLE_MEMORY: + aprh = &ab->p_mem_head; + dbg(" Type Specific: Prefetchable memory\n"); + break; + default: + dbg(" Type Specific: Invalid cache attribute\n"); + break; + } + + dbg(" Type Specific: Read%s\n", ACPI_READ_WRITE_MEMORY == data32->attribute.memory.read_write_attribute ? "/Write":" Only"); + break; + + case ACPI_IO_RANGE: + dbg(" Resource Type: I/O Range\n"); + aprh = &ab->io_head; + tprh = &ab->tio_head; + + switch (data32->attribute.io.range_attribute) { + case ACPI_NON_ISA_ONLY_RANGES: + dbg(" Type Specific: Non-ISA Io Addresses\n"); + break; + case ACPI_ISA_ONLY_RANGES: + dbg(" Type Specific: ISA Io Addresses\n"); + break; + case ACPI_ENTIRE_RANGE: + dbg(" Type Specific: ISA and non-ISA Io Addresses\n"); + break; + default: + dbg(" Type Specific: Invalid range attribute\n"); + break; + } + break; + + case ACPI_BUS_NUMBER_RANGE: + dbg(" Resource Type: Bus Number Range(fixed)\n"); + /* fixup to be compatible with the rest of php driver */ + data32->min_address_range++; + data32->address_length--; + aprh = &ab->bus_head; + tprh = &ab->tbus_head; + break; + default: + dbg(" Resource Type: Invalid resource type. Exiting.\n"); + return; + } + + dbg(" Resource %s\n", ACPI_CONSUMER == data32->producer_consumer ? "Consumer":"Producer"); + dbg(" %s decode\n", ACPI_SUB_DECODE == data32->decode ? "Subtractive":"Positive"); + dbg(" Min address is %s fixed\n", ACPI_ADDRESS_FIXED == data32->min_address_fixed ? "":"not"); + dbg(" Max address is %s fixed\n", ACPI_ADDRESS_FIXED == data32->max_address_fixed ? "":"not"); + dbg(" Granularity: %08X\n", data32->granularity); + dbg(" Address range min: %08X\n", data32->min_address_range); + dbg(" Address range max: %08X\n", data32->max_address_range); + dbg(" Address translation offset: %08X\n", data32->address_translation_offset); + dbg(" Address Length: %08X\n", data32->address_length); + + if (0xFF != data32->resource_source.index) { + dbg(" Resource Source Index: %X\n", data32->resource_source.index); + /* dbg(" Resource Source: %s\n", data32->resource_source.string_ptr); */ + } + + pciehprm_add_resource(aprh, data32->min_address_range, data32->address_length); +} + +static acpi_status acpi_parse_crs( + struct acpi_bridge *ab, + struct acpi_resource *crsbuf + ) +{ + acpi_status status = AE_OK; + struct acpi_resource *resource = crsbuf; + u8 count = 0; + u8 done = 0; + + while (!done) { + dbg("acpi_pciehprm: PCI bus 0x%x Resource structure %x.\n", ab->bus, count++); + switch (resource->id) { + case ACPI_RSTYPE_IRQ: + dbg("Irq -------- Resource\n"); + break; + case ACPI_RSTYPE_DMA: + dbg("DMA -------- Resource\n"); + break; + case ACPI_RSTYPE_START_DPF: + dbg("Start DPF -------- Resource\n"); + break; + case ACPI_RSTYPE_END_DPF: + dbg("End DPF -------- Resource\n"); + break; + case ACPI_RSTYPE_IO: + acpi_parse_io (ab, &resource->data); + break; + case ACPI_RSTYPE_FIXED_IO: + acpi_parse_fixed_io (ab, &resource->data); + break; + case ACPI_RSTYPE_VENDOR: + dbg("Vendor -------- Resource\n"); + break; + case ACPI_RSTYPE_END_TAG: + dbg("End_tag -------- Resource\n"); + done = 1; + break; + case ACPI_RSTYPE_MEM24: + dbg("Mem24 -------- Resource\n"); + break; + case ACPI_RSTYPE_MEM32: + dbg("Mem32 -------- Resource\n"); + break; + case ACPI_RSTYPE_FIXED_MEM32: + dbg("Fixed Mem32 -------- Resource\n"); + break; + case ACPI_RSTYPE_ADDRESS16: + acpi_parse_address16_32(ab, &resource->data, ACPI_RSTYPE_ADDRESS16); + break; + case ACPI_RSTYPE_ADDRESS32: + acpi_parse_address16_32(ab, &resource->data, ACPI_RSTYPE_ADDRESS32); + break; + case ACPI_RSTYPE_ADDRESS64: + info("Address64 -------- Resource unparsed\n"); + break; + case ACPI_RSTYPE_EXT_IRQ: + dbg("Ext Irq -------- Resource\n"); + break; + default: + dbg("Invalid -------- resource type 0x%x\n", resource->id); + break; + } + + resource = (struct acpi_resource *) ((char *)resource + resource->length); + } + + return status; +} + +static acpi_status acpi_get_crs( struct acpi_bridge *ab) +{ + acpi_status status; + struct acpi_resource *crsbuf; + + status = acpi_evaluate_crs(ab->handle, &crsbuf); + if (ACPI_SUCCESS(status)) { + status = acpi_parse_crs(ab, crsbuf); + kfree(crsbuf); + + pciehp_resource_sort_and_combine(&ab->bus_head); + pciehp_resource_sort_and_combine(&ab->io_head); + pciehp_resource_sort_and_combine(&ab->mem_head); + pciehp_resource_sort_and_combine(&ab->p_mem_head); + + pciehprm_add_resources (&ab->tbus_head, ab->bus_head); + pciehprm_add_resources (&ab->tio_head, ab->io_head); + pciehprm_add_resources (&ab->tmem_head, ab->mem_head); + pciehprm_add_resources (&ab->tp_mem_head, ab->p_mem_head); + } + + return status; +} + +/* find acpi_bridge downword from ab. */ +static struct acpi_bridge * +find_acpi_bridge_by_bus( + struct acpi_bridge *ab, + int seg, + int bus /* pdev->subordinate->number */ + ) +{ + struct acpi_bridge *lab = NULL; + + if (!ab) + return NULL; + + if ((ab->bus == bus) && (ab->seg == seg)) + return ab; + + if (ab->child) + lab = find_acpi_bridge_by_bus(ab->child, seg, bus); + + if (!lab) + if (ab->next) + lab = find_acpi_bridge_by_bus(ab->next, seg, bus); + + return lab; +} + +/* + * Build a device tree of ACPI PCI Bridges + */ +static void pciehprm_acpi_register_a_bridge ( + struct acpi_bridge **head, + struct acpi_bridge *pab, /* parent bridge to which child bridge is added */ + struct acpi_bridge *cab /* child bridge to add */ + ) +{ + struct acpi_bridge *lpab; + struct acpi_bridge *lcab; + + lpab = find_acpi_bridge_by_bus(*head, pab->seg, pab->bus); + if (!lpab) { + if (!(pab->type & BRIDGE_TYPE_HOST)) + warn("PCI parent bridge s:b(%x:%x) not in list.\n", pab->seg, pab->bus); + pab->next = *head; + *head = pab; + lpab = pab; + } + + if ((cab->type & BRIDGE_TYPE_HOST) && (pab == cab)) + return; + + lcab = find_acpi_bridge_by_bus(*head, cab->seg, cab->bus); + if (lcab) { + if ((pab->bus != lcab->parent->bus) || (lcab->bus != cab->bus)) + err("PCI child bridge s:b(%x:%x) in list with diff parent.\n", cab->seg, cab->bus); + return; + } else + lcab = cab; + + lcab->parent = lpab; + lcab->next = lpab->child; + lpab->child = lcab; +} + +static acpi_status pciehprm_acpi_build_php_slots_callback( + acpi_handle handle, + u32 Level, + void *context, + void **retval + ) +{ + ulong bus_num; + ulong seg_num; + ulong sun, adr; + ulong padr = 0; + acpi_handle phandle = NULL; + struct acpi_bridge *pab = (struct acpi_bridge *)context; + struct acpi_bridge *lab; + acpi_status status; + u8 *path_name = acpi_path_name(handle); + + /* get _SUN */ + status = acpi_evaluate_integer(handle, METHOD_NAME__SUN, NULL, &sun); + switch(status) { + case AE_NOT_FOUND: + return AE_OK; + default: + if (ACPI_FAILURE(status)) { + err("acpi_pciehprm:%s _SUN fail=0x%x\n", path_name, status); + return status; + } + } + + /* get _ADR. _ADR must exist if _SUN exists */ + status = acpi_evaluate_integer(handle, METHOD_NAME__ADR, NULL, &adr); + if (ACPI_FAILURE(status)) { + err("acpi_pciehprm:%s _ADR fail=0x%x\n", path_name, status); + return status; + } + + dbg("acpi_pciehprm:%s sun=0x%08x adr=0x%08x\n", path_name, (u32)sun, (u32)adr); + + status = acpi_get_parent(handle, &phandle); + if (ACPI_FAILURE(status)) { + err("acpi_pciehprm:%s get_parent fail=0x%x\n", path_name, status); + return (status); + } + + bus_num = pab->bus; + seg_num = pab->seg; + + if (pab->bus == bus_num) { + lab = pab; + } else { + dbg("WARN: pab is not parent\n"); + lab = find_acpi_bridge_by_bus(pab, seg_num, bus_num); + if (!lab) { + dbg("acpi_pciehprm: alloc new P2P bridge(%x) for sun(%08x)\n", (u32)bus_num, (u32)sun); + lab = (struct acpi_bridge *)kmalloc(sizeof(struct acpi_bridge), GFP_KERNEL); + if (!lab) { + err("acpi_pciehprm: alloc for ab fail\n"); + return AE_NO_MEMORY; + } + memset(lab, 0, sizeof(struct acpi_bridge)); + + lab->handle = phandle; + lab->pbus = pab->bus; + lab->pdevice = (int)(padr >> 16) & 0xffff; + lab->pfunction = (int)(padr & 0xffff); + lab->bus = (int)bus_num; + lab->scanned = 0; + lab->type = BRIDGE_TYPE_P2P; + + pciehprm_acpi_register_a_bridge (&acpi_bridges_head, pab, lab); + } else + dbg("acpi_pciehprm: found P2P bridge(%x) for sun(%08x)\n", (u32)bus_num, (u32)sun); + } + + acpi_add_slot_to_php_slots(lab, (int)bus_num, handle, (u32)adr, (u32)sun); + + return (status); +} + +static int pciehprm_acpi_build_php_slots( + struct acpi_bridge *ab, + u32 depth + ) +{ + acpi_status status; + u8 *path_name = acpi_path_name(ab->handle); + + /* Walk down this pci bridge to get _SUNs if any behind P2P */ + status = acpi_walk_namespace ( ACPI_TYPE_DEVICE, + ab->handle, + depth, + pciehprm_acpi_build_php_slots_callback, + ab, + NULL ); + if (ACPI_FAILURE(status)) { + dbg("acpi_pciehprm:%s walk for _SUN on pci bridge seg:bus(%x:%x) fail=0x%x\n", path_name, ab->seg, ab->bus, status); + return -1; + } + + return 0; +} + +static void build_a_bridge( + struct acpi_bridge *pab, + struct acpi_bridge *ab + ) +{ + u8 *path_name = acpi_path_name(ab->handle); + + pciehprm_acpi_register_a_bridge (&acpi_bridges_head, pab, ab); + + switch (ab->type) { + case BRIDGE_TYPE_HOST: + dbg("acpi_pciehprm: Registered PCI HOST Bridge(%02x) on s:b:d:f(%02x:%02x:%02x:%02x) [%s]\n", + ab->bus, ab->seg, ab->pbus, ab->pdevice, ab->pfunction, path_name); + break; + case BRIDGE_TYPE_P2P: + dbg("acpi_pciehprm: Registered PCI P2P Bridge(%02x-%02x) on s:b:d:f(%02x:%02x:%02x:%02x) [%s]\n", + ab->pbus, ab->bus, ab->seg, ab->pbus, ab->pdevice, ab->pfunction, path_name); + break; + }; + + /* build any immediate PHP slots under this pci bridge */ + pciehprm_acpi_build_php_slots(ab, 1); +} + +static struct acpi_bridge * add_p2p_bridge( + acpi_handle handle, + struct acpi_bridge *pab, /* parent */ + ulong adr + ) +{ + struct acpi_bridge *ab; + struct pci_dev *pdev; + ulong devnum, funcnum; + u8 *path_name = acpi_path_name(handle); + + ab = (struct acpi_bridge *) kmalloc (sizeof(struct acpi_bridge), GFP_KERNEL); + if (!ab) { + err("acpi_pciehprm: alloc for ab fail\n"); + return NULL; + } + memset(ab, 0, sizeof(struct acpi_bridge)); + + devnum = (adr >> 16) & 0xffff; + funcnum = adr & 0xffff; + + pdev = pci_find_slot(pab->bus, PCI_DEVFN(devnum, funcnum)); + if (!pdev || !pdev->subordinate) { + err("acpi_pciehprm:%s is not a P2P Bridge\n", path_name); + kfree(ab); + return NULL; + } + + ab->handle = handle; + ab->seg = pab->seg; + ab->pbus = pab->bus; /* or pdev->bus->number */ + ab->pdevice = devnum; /* or PCI_SLOT(pdev->devfn) */ + ab->pfunction = funcnum; /* or PCI_FUNC(pdev->devfn) */ + ab->bus = pdev->subordinate->number; + ab->scanned = 0; + ab->type = BRIDGE_TYPE_P2P; + + dbg("acpi_pciehprm: P2P(%x-%x) on pci=b:d:f(%x:%x:%x) acpi=b:d:f(%x:%x:%x) [%s]\n", + pab->bus, ab->bus, pdev->bus->number, PCI_SLOT(pdev->devfn), PCI_FUNC(pdev->devfn), + pab->bus, (u32)devnum, (u32)funcnum, path_name); + + build_a_bridge(pab, ab); + + return ab; +} + +static acpi_status scan_p2p_bridge( + acpi_handle handle, + u32 Level, + void *context, + void **retval + ) +{ + struct acpi_bridge *pab = (struct acpi_bridge *)context; + struct acpi_bridge *ab; + acpi_status status; + ulong adr = 0; + u8 *path_name = acpi_path_name(handle); + ulong devnum, funcnum; + struct pci_dev *pdev; + + /* get device, function */ + status = acpi_evaluate_integer(handle, METHOD_NAME__ADR, NULL, &adr); + if (ACPI_FAILURE(status)) { + if (status != AE_NOT_FOUND) + err("acpi_pciehprm:%s _ADR fail=0x%x\n", path_name, status); + return AE_OK; + } + + devnum = (adr >> 16) & 0xffff; + funcnum = adr & 0xffff; + + pdev = pci_find_slot(pab->bus, PCI_DEVFN(devnum, funcnum)); + if (!pdev) + return AE_OK; + if (!pdev->subordinate) + return AE_OK; + + ab = add_p2p_bridge(handle, pab, adr); + if (ab) { + status = acpi_walk_namespace ( ACPI_TYPE_DEVICE, + handle, + (u32)1, + scan_p2p_bridge, + ab, + NULL); + if (ACPI_FAILURE(status)) + dbg("acpi_pciehprm:%s find_p2p fail=0x%x\n", path_name, status); + } + + return AE_OK; +} + +static struct acpi_bridge * add_host_bridge( + acpi_handle handle, + ulong segnum, + ulong busnum + ) +{ + ulong adr = 0; + acpi_status status; + struct acpi_bridge *ab; + u8 *path_name = acpi_path_name(handle); + + /* get device, function: host br adr is always 0000 though. */ + status = acpi_evaluate_integer(handle, METHOD_NAME__ADR, NULL, &adr); + if (ACPI_FAILURE(status)) { + err("acpi_pciehprm:%s _ADR fail=0x%x\n", path_name, status); + return NULL; + } + dbg("acpi_pciehprm: ROOT PCI seg(0x%x)bus(0x%x)dev(0x%x)func(0x%x) [%s]\n", (u32)segnum, + (u32)busnum, (u32)(adr >> 16) & 0xffff, (u32)adr & 0xffff, path_name); + + ab = (struct acpi_bridge *) kmalloc (sizeof(struct acpi_bridge), GFP_KERNEL); + if (!ab) { + err("acpi_pciehprm: alloc for ab fail\n"); + return NULL; + } + memset(ab, 0, sizeof(struct acpi_bridge)); + + ab->handle = handle; + ab->seg = (int)segnum; + ab->bus = ab->pbus = (int)busnum; + ab->pdevice = (int)(adr >> 16) & 0xffff; + ab->pfunction = (int)(adr & 0xffff); + ab->scanned = 0; + ab->type = BRIDGE_TYPE_HOST; + + /* get root pci bridge's current resources */ + status = acpi_get_crs(ab); + if (ACPI_FAILURE(status)) { + err("acpi_pciehprm:%s evaluate _CRS fail=0x%x\n", path_name, status); + kfree(ab); + return NULL; + } + + status = pci_osc_control_set (OSC_PCI_EXPRESS_NATIVE_HP_CONTROL); + if (ACPI_FAILURE(status)) { + err("%s: status %x\n", __FUNCTION__, status); + osc_run_status = (status == AE_NOT_FOUND) ? OSC_NOT_EXIST : OSC_RUN_FAILED; + } else { + osc_run_status = NC_RUN_SUCCESS; + } + dbg("%s: osc_run_status %x\n", __FUNCTION__, osc_run_status); + + build_a_bridge(ab, ab); + + return ab; +} + +static acpi_status acpi_scan_from_root_pci_callback ( + acpi_handle handle, + u32 Level, + void *context, + void **retval + ) +{ + ulong segnum = 0; + ulong busnum = 0; + acpi_status status; + struct acpi_bridge *ab; + u8 *path_name = acpi_path_name(handle); + + /* get bus number of this pci root bridge */ + status = acpi_evaluate_integer(handle, METHOD_NAME__SEG, NULL, &segnum); + if (ACPI_FAILURE(status)) { + if (status != AE_NOT_FOUND) { + err("acpi_pciehprm:%s evaluate _SEG fail=0x%x\n", path_name, status); + return status; + } + segnum = 0; + } + + /* get bus number of this pci root bridge */ + status = acpi_evaluate_integer(handle, METHOD_NAME__BBN, NULL, &busnum); + if (ACPI_FAILURE(status)) { + err("acpi_pciehprm:%s evaluate _BBN fail=0x%x\n", path_name, status); + return (status); + } + + ab = add_host_bridge(handle, segnum, busnum); + if (ab) { + status = acpi_walk_namespace ( ACPI_TYPE_DEVICE, + handle, + 1, + scan_p2p_bridge, + ab, + NULL); + if (ACPI_FAILURE(status)) + dbg("acpi_pciehprm:%s find_p2p fail=0x%x\n", path_name, status); + } + + return AE_OK; +} + +static int pciehprm_acpi_scan_pci (void) +{ + acpi_status status; + + /* + * TBD: traverse LDM device tree with the help of + * unified ACPI augmented for php device population. + */ + status = acpi_get_devices ( PCI_ROOT_HID_STRING, + acpi_scan_from_root_pci_callback, + NULL, + NULL ); + if (ACPI_FAILURE(status)) { + err("acpi_pciehprm:get_device PCI ROOT HID fail=0x%x\n", status); + return -1; + } + + return 0; +} + +int pciehprm_init(enum php_ctlr_type ctlr_type) +{ + int rc; + + if (ctlr_type != PCI) + return -ENODEV; + + dbg("pciehprm ACPI init <enter>\n"); + acpi_bridges_head = NULL; + + /* construct PCI bus:device tree of acpi_handles */ + rc = pciehprm_acpi_scan_pci(); + if (rc) + return rc; + + if ((oshp_run_status != NC_RUN_SUCCESS) && (osc_run_status != NC_RUN_SUCCESS)) { + err("Fails to gain control of native hot-plug\n"); + rc = -ENODEV; + } + + dbg("pciehprm ACPI init %s\n", (rc)?"fail":"success"); + return rc; +} + +static void free_a_slot(struct acpi_php_slot *aps) +{ + dbg(" free a php func of slot(0x%02x) on PCI b:d:f=0x%02x:%02x:%02x\n", aps->sun, aps->bus, aps->dev, aps->fun); + + free_pci_resource (aps->io_head); + free_pci_resource (aps->bus_head); + free_pci_resource (aps->mem_head); + free_pci_resource (aps->p_mem_head); + + kfree(aps); +} + +static void free_a_bridge( struct acpi_bridge *ab) +{ + struct acpi_php_slot *aps, *next; + + switch (ab->type) { + case BRIDGE_TYPE_HOST: + dbg("Free ACPI PCI HOST Bridge(%x) [%s] on s:b:d:f(%x:%x:%x:%x)\n", + ab->bus, acpi_path_name(ab->handle), ab->seg, ab->pbus, ab->pdevice, ab->pfunction); + break; + case BRIDGE_TYPE_P2P: + dbg("Free ACPI PCI P2P Bridge(%x-%x) [%s] on s:b:d:f(%x:%x:%x:%x)\n", + ab->pbus, ab->bus, acpi_path_name(ab->handle), ab->seg, ab->pbus, ab->pdevice, ab->pfunction); + break; + }; + + /* free slots first */ + for (aps = ab->slots; aps; aps = next) { + next = aps->next; + free_a_slot(aps); + } + + free_pci_resource (ab->io_head); + free_pci_resource (ab->tio_head); + free_pci_resource (ab->bus_head); + free_pci_resource (ab->tbus_head); + free_pci_resource (ab->mem_head); + free_pci_resource (ab->tmem_head); + free_pci_resource (ab->p_mem_head); + free_pci_resource (ab->tp_mem_head); + + kfree(ab); +} + +static void pciehprm_free_bridges ( struct acpi_bridge *ab) +{ + if (!ab) + return; + + if (ab->child) + pciehprm_free_bridges (ab->child); + + if (ab->next) + pciehprm_free_bridges (ab->next); + + free_a_bridge(ab); +} + +void pciehprm_cleanup(void) +{ + pciehprm_free_bridges (acpi_bridges_head); +} + +static int get_number_of_slots ( + struct acpi_bridge *ab, + int selfonly + ) +{ + struct acpi_php_slot *aps; + int prev_slot = -1; + int slot_num = 0; + + for ( aps = ab->slots; aps; aps = aps->next) + if (aps->dev != prev_slot) { + prev_slot = aps->dev; + slot_num++; + } + + if (ab->child) + slot_num += get_number_of_slots (ab->child, 0); + + if (selfonly) + return slot_num; + + if (ab->next) + slot_num += get_number_of_slots (ab->next, 0); + + return slot_num; +} + +static int print_acpi_resources (struct acpi_bridge *ab) +{ + struct acpi_php_slot *aps; + int i; + + switch (ab->type) { + case BRIDGE_TYPE_HOST: + dbg("PCI HOST Bridge (%x) [%s]\n", ab->bus, acpi_path_name(ab->handle)); + break; + case BRIDGE_TYPE_P2P: + dbg("PCI P2P Bridge (%x-%x) [%s]\n", ab->pbus, ab->bus, acpi_path_name(ab->handle)); + break; + }; + + print_pci_resources (ab); + + for ( i = -1, aps = ab->slots; aps; aps = aps->next) { + if (aps->dev == i) + continue; + dbg(" Slot sun(%x) s:b:d:f(%02x:%02x:%02x:%02x)\n", aps->sun, aps->seg, aps->bus, aps->dev, aps->fun); + print_slot_resources(aps); + i = aps->dev; + } + + if (ab->child) + print_acpi_resources (ab->child); + + if (ab->next) + print_acpi_resources (ab->next); + + return 0; +} + +int pciehprm_print_pirt(void) +{ + dbg("PCIEHPRM ACPI Slots\n"); + if (acpi_bridges_head) + print_acpi_resources (acpi_bridges_head); + + return 0; +} + +static struct acpi_php_slot * get_acpi_slot ( + struct acpi_bridge *ab, + u32 sun + ) +{ + struct acpi_php_slot *aps = NULL; + + for ( aps = ab->slots; aps; aps = aps->next) + if (aps->sun == sun) + return aps; + + if (!aps && ab->child) { + aps = (struct acpi_php_slot *)get_acpi_slot (ab->child, sun); + if (aps) + return aps; + } + + if (!aps && ab->next) { + aps = (struct acpi_php_slot *)get_acpi_slot (ab->next, sun); + if (aps) + return aps; + } + + return aps; + +} + +#if 0 +void * pciehprm_get_slot(struct slot *slot) +{ + struct acpi_bridge *ab = acpi_bridges_head; + struct acpi_php_slot *aps = get_acpi_slot (ab, slot->number); + + aps->slot = slot; + + dbg("Got acpi slot sun(%x): s:b:d:f(%x:%x:%x:%x)\n", aps->sun, aps->seg, aps->bus, aps->dev, aps->fun); + + return (void *)aps; +} +#endif + +static void pciehprm_dump_func_res( struct pci_func *fun) +{ + struct pci_func *func = fun; + + if (func->bus_head) { + dbg(": BUS Resources:\n"); + print_pci_resource (func->bus_head); + } + if (func->io_head) { + dbg(": IO Resources:\n"); + print_pci_resource (func->io_head); + } + if (func->mem_head) { + dbg(": MEM Resources:\n"); + print_pci_resource (func->mem_head); + } + if (func->p_mem_head) { + dbg(": PMEM Resources:\n"); + print_pci_resource (func->p_mem_head); + } +} + +static void pciehprm_dump_ctrl_res( struct controller *ctlr) +{ + struct controller *ctrl = ctlr; + + if (ctrl->bus_head) { + dbg(": BUS Resources:\n"); + print_pci_resource (ctrl->bus_head); + } + if (ctrl->io_head) { + dbg(": IO Resources:\n"); + print_pci_resource (ctrl->io_head); + } + if (ctrl->mem_head) { + dbg(": MEM Resources:\n"); + print_pci_resource (ctrl->mem_head); + } + if (ctrl->p_mem_head) { + dbg(": PMEM Resources:\n"); + print_pci_resource (ctrl->p_mem_head); + } +} + +static int pciehprm_get_used_resources ( + struct controller *ctrl, + struct pci_func *func + ) +{ + return pciehp_save_used_resources (ctrl, func, !DISABLE_CARD); +} + +static int configure_existing_function( + struct controller *ctrl, + struct pci_func *func + ) +{ + int rc; + + /* see how much resources the func has used. */ + rc = pciehprm_get_used_resources (ctrl, func); + + if (!rc) { + /* subtract the resources used by the func from ctrl resources */ + rc = pciehprm_delete_resources (&ctrl->bus_head, func->bus_head); + rc |= pciehprm_delete_resources (&ctrl->io_head, func->io_head); + rc |= pciehprm_delete_resources (&ctrl->mem_head, func->mem_head); + rc |= pciehprm_delete_resources (&ctrl->p_mem_head, func->p_mem_head); + if (rc) + warn("aCEF: cannot del used resources\n"); + } else + err("aCEF: cannot get used resources\n"); + + return rc; +} + +static int bind_pci_resources_to_slots ( struct controller *ctrl) +{ + struct pci_func *func, new_func; + int busn = ctrl->slot_bus; + int devn, funn; + u32 vid; + + for (devn = 0; devn < 32; devn++) { + for (funn = 0; funn < 8; funn++) { + /* + if (devn == ctrl->device && funn == ctrl->function) + continue; + */ + /* find out if this entry is for an occupied slot */ + vid = 0xFFFFFFFF; + pci_bus_read_config_dword(ctrl->pci_dev->subordinate, PCI_DEVFN(devn, funn), PCI_VENDOR_ID, &vid); + + if (vid != 0xFFFFFFFF) { + dbg("%s: vid = %x\n", __FUNCTION__, vid); + func = pciehp_slot_find(busn, devn, funn); + if (!func) { + memset(&new_func, 0, sizeof(struct pci_func)); + new_func.bus = busn; + new_func.device = devn; + new_func.function = funn; + new_func.is_a_board = 1; + configure_existing_function(ctrl, &new_func); + pciehprm_dump_func_res(&new_func); + } else { + configure_existing_function(ctrl, func); + pciehprm_dump_func_res(func); + } + dbg("aCCF:existing PCI 0x%x Func ResourceDump\n", ctrl->bus); + } + } + } + + return 0; +} + +static int bind_pci_resources( + struct controller *ctrl, + struct acpi_bridge *ab + ) +{ + int status = 0; + + if (ab->bus_head) { + dbg("bapr: BUS Resources add on PCI 0x%x\n", ab->bus); + status = pciehprm_add_resources (&ctrl->bus_head, ab->bus_head); + if (pciehprm_delete_resources (&ab->bus_head, ctrl->bus_head)) + warn("bapr: cannot sub BUS Resource on PCI 0x%x\n", ab->bus); + if (status) { + err("bapr: BUS Resource add on PCI 0x%x: fail=0x%x\n", ab->bus, status); + return status; + } + } else + info("bapr: No BUS Resource on PCI 0x%x.\n", ab->bus); + + if (ab->io_head) { + dbg("bapr: IO Resources add on PCI 0x%x\n", ab->bus); + status = pciehprm_add_resources (&ctrl->io_head, ab->io_head); + if (pciehprm_delete_resources (&ab->io_head, ctrl->io_head)) + warn("bapr: cannot sub IO Resource on PCI 0x%x\n", ab->bus); + if (status) { + err("bapr: IO Resource add on PCI 0x%x: fail=0x%x\n", ab->bus, status); + return status; + } + } else + info("bapr: No IO Resource on PCI 0x%x.\n", ab->bus); + + if (ab->mem_head) { + dbg("bapr: MEM Resources add on PCI 0x%x\n", ab->bus); + status = pciehprm_add_resources (&ctrl->mem_head, ab->mem_head); + if (pciehprm_delete_resources (&ab->mem_head, ctrl->mem_head)) + warn("bapr: cannot sub MEM Resource on PCI 0x%x\n", ab->bus); + if (status) { + err("bapr: MEM Resource add on PCI 0x%x: fail=0x%x\n", ab->bus, status); + return status; + } + } else + info("bapr: No MEM Resource on PCI 0x%x.\n", ab->bus); + + if (ab->p_mem_head) { + dbg("bapr: PMEM Resources add on PCI 0x%x\n", ab->bus); + status = pciehprm_add_resources (&ctrl->p_mem_head, ab->p_mem_head); + if (pciehprm_delete_resources (&ab->p_mem_head, ctrl->p_mem_head)) + warn("bapr: cannot sub PMEM Resource on PCI 0x%x\n", ab->bus); + if (status) { + err("bapr: PMEM Resource add on PCI 0x%x: fail=0x%x\n", ab->bus, status); + return status; + } + } else + info("bapr: No PMEM Resource on PCI 0x%x.\n", ab->bus); + + return status; +} + +static int no_pci_resources( struct acpi_bridge *ab) +{ + return !(ab->p_mem_head || ab->mem_head || ab->io_head || ab->bus_head); +} + +static int find_pci_bridge_resources ( + struct controller *ctrl, + struct acpi_bridge *ab + ) +{ + int rc = 0; + struct pci_func func; + + memset(&func, 0, sizeof(struct pci_func)); + + func.bus = ab->pbus; + func.device = ab->pdevice; + func.function = ab->pfunction; + func.is_a_board = 1; + + /* Get used resources for this PCI bridge */ + rc = pciehp_save_used_resources (ctrl, &func, !DISABLE_CARD); + + ab->io_head = func.io_head; + ab->mem_head = func.mem_head; + ab->p_mem_head = func.p_mem_head; + ab->bus_head = func.bus_head; + if (ab->bus_head) + pciehprm_delete_resource(&ab->bus_head, ctrl->pci_dev->subordinate->number, 1); + + return rc; +} + +static int get_pci_resources_from_bridge( + struct controller *ctrl, + struct acpi_bridge *ab + ) +{ + int rc = 0; + + dbg("grfb: Get Resources for PCI 0x%x from actual PCI bridge 0x%x.\n", ctrl->bus, ab->bus); + + rc = find_pci_bridge_resources (ctrl, ab); + + pciehp_resource_sort_and_combine(&ab->bus_head); + pciehp_resource_sort_and_combine(&ab->io_head); + pciehp_resource_sort_and_combine(&ab->mem_head); + pciehp_resource_sort_and_combine(&ab->p_mem_head); + + pciehprm_add_resources (&ab->tbus_head, ab->bus_head); + pciehprm_add_resources (&ab->tio_head, ab->io_head); + pciehprm_add_resources (&ab->tmem_head, ab->mem_head); + pciehprm_add_resources (&ab->tp_mem_head, ab->p_mem_head); + + return rc; +} + +static int get_pci_resources( + struct controller *ctrl, + struct acpi_bridge *ab + ) +{ + int rc = 0; + + if (no_pci_resources(ab)) { + dbg("spbr:PCI 0x%x has no resources. Get parent resources.\n", ab->bus); + rc = get_pci_resources_from_bridge(ctrl, ab); + } + + return rc; +} + +/* + * Get resources for this ctrl. + * 1. get total resources from ACPI _CRS or bridge (this ctrl) + * 2. find used resources of existing adapters + * 3. subtract used resources from total resources + */ +int pciehprm_find_available_resources( struct controller *ctrl) +{ + int rc = 0; + struct acpi_bridge *ab; + + ab = find_acpi_bridge_by_bus(acpi_bridges_head, ctrl->seg, ctrl->pci_dev->subordinate->number); + if (!ab) { + err("pfar:cannot locate acpi bridge of PCI 0x%x.\n", ctrl->pci_dev->subordinate->number); + return -1; + } + if (no_pci_resources(ab)) { + rc = get_pci_resources(ctrl, ab); + if (rc) { + err("pfar:cannot get pci resources of PCI 0x%x.\n", ctrl->pci_dev->subordinate->number); + return -1; + } + } + + rc = bind_pci_resources(ctrl, ab); + dbg("pfar:pre-Bind PCI 0x%x Ctrl Resource Dump\n", ctrl->pci_dev->subordinate->number); + pciehprm_dump_ctrl_res(ctrl); + + bind_pci_resources_to_slots (ctrl); + + dbg("pfar:post-Bind PCI 0x%x Ctrl Resource Dump\n", ctrl->pci_dev->subordinate->number); + pciehprm_dump_ctrl_res(ctrl); + + return rc; +} + +int pciehprm_set_hpp( + struct controller *ctrl, + struct pci_func *func, + u8 card_type + ) +{ + struct acpi_bridge *ab; + struct pci_bus lpci_bus, *pci_bus; + int rc = 0; + unsigned int devfn; + u8 cls= 0x08; /* default cache line size */ + u8 lt = 0x40; /* default latency timer */ + u8 ep = 0; + u8 es = 0; + + memcpy(&lpci_bus, ctrl->pci_bus, sizeof(lpci_bus)); + pci_bus = &lpci_bus; + pci_bus->number = func->bus; + devfn = PCI_DEVFN(func->device, func->function); + + ab = find_acpi_bridge_by_bus(acpi_bridges_head, ctrl->seg, ctrl->bus); + + if (ab) { + if (ab->_hpp) { + lt = (u8)ab->_hpp->latency_timer; + cls = (u8)ab->_hpp->cache_line_size; + ep = (u8)ab->_hpp->enable_perr; + es = (u8)ab->_hpp->enable_serr; + } else + dbg("_hpp: no _hpp for B/D/F=%#x/%#x/%#x. use default value\n", func->bus, func->device, func->function); + } else + dbg("_hpp: no acpi bridge for B/D/F = %#x/%#x/%#x. use default value\n", func->bus, func->device, func->function); + + + if (card_type == PCI_HEADER_TYPE_BRIDGE) { + /* set subordinate Latency Timer */ + rc |= pci_bus_write_config_byte(pci_bus, devfn, PCI_SEC_LATENCY_TIMER, lt); + } + + /* set base Latency Timer */ + rc |= pci_bus_write_config_byte(pci_bus, devfn, PCI_LATENCY_TIMER, lt); + dbg(" set latency timer =0x%02x: %x\n", lt, rc); + + rc |= pci_bus_write_config_byte(pci_bus, devfn, PCI_CACHE_LINE_SIZE, cls); + dbg(" set cache_line_size=0x%02x: %x\n", cls, rc); + + return rc; +} + +void pciehprm_enable_card( + struct controller *ctrl, + struct pci_func *func, + u8 card_type) +{ + u16 command, cmd, bcommand, bcmd; + struct pci_bus lpci_bus, *pci_bus; + struct acpi_bridge *ab; + unsigned int devfn; + int rc; + + memcpy(&lpci_bus, ctrl->pci_bus, sizeof(lpci_bus)); + pci_bus = &lpci_bus; + pci_bus->number = func->bus; + devfn = PCI_DEVFN(func->device, func->function); + + rc = pci_bus_read_config_word(pci_bus, devfn, PCI_COMMAND, &command); + + if (card_type == PCI_HEADER_TYPE_BRIDGE) { + rc = pci_bus_read_config_word(pci_bus, devfn, PCI_BRIDGE_CONTROL, &bcommand); + } + + cmd = command = command | PCI_COMMAND_MASTER | PCI_COMMAND_INVALIDATE + | PCI_COMMAND_IO | PCI_COMMAND_MEMORY; + bcmd = bcommand = bcommand | PCI_BRIDGE_CTL_NO_ISA; + + ab = find_acpi_bridge_by_bus(acpi_bridges_head, ctrl->seg, ctrl->bus); + if (ab) { + if (ab->_hpp) { + if (ab->_hpp->enable_perr) { + command |= PCI_COMMAND_PARITY; + bcommand |= PCI_BRIDGE_CTL_PARITY; + } else { + command &= ~PCI_COMMAND_PARITY; + bcommand &= ~PCI_BRIDGE_CTL_PARITY; + } + if (ab->_hpp->enable_serr) { + command |= PCI_COMMAND_SERR; + bcommand |= PCI_BRIDGE_CTL_SERR; + } else { + command &= ~PCI_COMMAND_SERR; + bcommand &= ~PCI_BRIDGE_CTL_SERR; + } + } else + dbg("no _hpp for B/D/F = %#x/%#x/%#x.\n", func->bus, func->device, func->function); + } else + dbg("no acpi bridge for B/D/F = %#x/%#x/%#x.\n", func->bus, func->device, func->function); + + if (command != cmd) { + rc = pci_bus_write_config_word(pci_bus, devfn, PCI_COMMAND, command); + } + if ((card_type == PCI_HEADER_TYPE_BRIDGE) && (bcommand != bcmd)) { + rc = pci_bus_write_config_word(pci_bus, devfn, PCI_BRIDGE_CONTROL, bcommand); + } +} diff --git a/drivers/pci/hotplug/pciehprm_nonacpi.c b/drivers/pci/hotplug/pciehprm_nonacpi.c new file mode 100644 index 000000000000..79a0aa6238ef --- /dev/null +++ b/drivers/pci/hotplug/pciehprm_nonacpi.c @@ -0,0 +1,501 @@ +/* + * PCIEHPRM NONACPI: PHP Resource Manager for Non-ACPI/Legacy platform + * + * 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>, <dely.l.sy@intel.com> + * + */ + +#include <linux/config.h> +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/types.h> +#include <linux/pci.h> +#include <linux/init.h> +#include <asm/uaccess.h> +#ifdef CONFIG_IA64 +#include <asm/iosapic.h> +#endif +#include "pciehp.h" +#include "pciehprm.h" +#include "pciehprm_nonacpi.h" + + +void pciehprm_cleanup(void) +{ + return; +} + +int pciehprm_print_pirt(void) +{ + return 0; +} + +int pciehprm_get_physical_slot_number(struct controller *ctrl, u32 *sun, u8 busnum, u8 devnum) +{ + + *sun = (u8) (ctrl->first_slot); + return 0; +} + + +static void print_pci_resource ( struct pci_resource *aprh) +{ + struct pci_resource *res; + + for (res = aprh; res; res = res->next) + dbg(" base= 0x%x length= 0x%x\n", res->base, res->length); +} + + +static void phprm_dump_func_res( struct pci_func *fun) +{ + struct pci_func *func = fun; + + if (func->bus_head) { + dbg(": BUS Resources:\n"); + print_pci_resource (func->bus_head); + } + if (func->io_head) { + dbg(": IO Resources:\n"); + print_pci_resource (func->io_head); + } + if (func->mem_head) { + dbg(": MEM Resources:\n"); + print_pci_resource (func->mem_head); + } + if (func->p_mem_head) { + dbg(": PMEM Resources:\n"); + print_pci_resource (func->p_mem_head); + } +} + +static int phprm_get_used_resources ( + struct controller *ctrl, + struct pci_func *func + ) +{ + return pciehp_save_used_resources (ctrl, func, !DISABLE_CARD); +} + +static int phprm_delete_resource( + struct pci_resource **aprh, + ulong base, + ulong size) +{ + struct pci_resource *res; + struct pci_resource *prevnode; + struct pci_resource *split_node; + ulong tbase; + + pciehp_resource_sort_and_combine(aprh); + + for (res = *aprh; res; res = res->next) { + if (res->base > base) + continue; + + if ((res->base + res->length) < (base + size)) + continue; + + if (res->base < base) { + tbase = base; + + if ((res->length - (tbase - res->base)) < size) + continue; + + split_node = (struct pci_resource *) kmalloc(sizeof(struct pci_resource), GFP_KERNEL); + if (!split_node) + return -ENOMEM; + + split_node->base = res->base; + split_node->length = tbase - res->base; + res->base = tbase; + res->length -= split_node->length; + + split_node->next = res->next; + res->next = split_node; + } + + if (res->length >= size) { + split_node = (struct pci_resource*) kmalloc(sizeof(struct pci_resource), GFP_KERNEL); + if (!split_node) + return -ENOMEM; + + split_node->base = res->base + size; + split_node->length = res->length - size; + res->length = size; + + split_node->next = res->next; + res->next = split_node; + } + + if (*aprh == res) { + *aprh = res->next; + } else { + prevnode = *aprh; + while (prevnode->next != res) + prevnode = prevnode->next; + + prevnode->next = res->next; + } + res->next = NULL; + kfree(res); + break; + } + + return 0; +} + + +static int phprm_delete_resources( + struct pci_resource **aprh, + struct pci_resource *this + ) +{ + struct pci_resource *res; + + for (res = this; res; res = res->next) + phprm_delete_resource(aprh, res->base, res->length); + + return 0; +} + + +static int configure_existing_function( + struct controller *ctrl, + struct pci_func *func + ) +{ + int rc; + + /* see how much resources the func has used. */ + rc = phprm_get_used_resources (ctrl, func); + + if (!rc) { + /* subtract the resources used by the func from ctrl resources */ + rc = phprm_delete_resources (&ctrl->bus_head, func->bus_head); + rc |= phprm_delete_resources (&ctrl->io_head, func->io_head); + rc |= phprm_delete_resources (&ctrl->mem_head, func->mem_head); + rc |= phprm_delete_resources (&ctrl->p_mem_head, func->p_mem_head); + if (rc) + warn("aCEF: cannot del used resources\n"); + } else + err("aCEF: cannot get used resources\n"); + + return rc; +} + +static int pciehprm_delete_resource( + struct pci_resource **aprh, + ulong base, + ulong size) +{ + struct pci_resource *res; + struct pci_resource *prevnode; + struct pci_resource *split_node; + ulong tbase; + + pciehp_resource_sort_and_combine(aprh); + + for (res = *aprh; res; res = res->next) { + if (res->base > base) + continue; + + if ((res->base + res->length) < (base + size)) + continue; + + if (res->base < base) { + tbase = base; + + if ((res->length - (tbase - res->base)) < size) + continue; + + split_node = (struct pci_resource *) kmalloc(sizeof(struct pci_resource), GFP_KERNEL); + if (!split_node) + return -ENOMEM; + + split_node->base = res->base; + split_node->length = tbase - res->base; + res->base = tbase; + res->length -= split_node->length; + + split_node->next = res->next; + res->next = split_node; + } + + if (res->length >= size) { + split_node = (struct pci_resource*) kmalloc(sizeof(struct pci_resource), GFP_KERNEL); + if (!split_node) + return -ENOMEM; + + split_node->base = res->base + size; + split_node->length = res->length - size; + res->length = size; + + split_node->next = res->next; + res->next = split_node; + } + + if (*aprh == res) { + *aprh = res->next; + } else { + prevnode = *aprh; + while (prevnode->next != res) + prevnode = prevnode->next; + + prevnode->next = res->next; + } + res->next = NULL; + kfree(res); + break; + } + + return 0; +} + +static int bind_pci_resources_to_slots ( struct controller *ctrl) +{ + struct pci_func *func, new_func; + int busn = ctrl->slot_bus; + int devn, funn; + u32 vid; + + for (devn = 0; devn < 32; devn++) { + for (funn = 0; funn < 8; funn++) { + /* + if (devn == ctrl->device && funn == ctrl->function) + continue; + */ + /* find out if this entry is for an occupied slot */ + vid = 0xFFFFFFFF; + + pci_bus_read_config_dword(ctrl->pci_dev->subordinate, PCI_DEVFN(devn, funn), PCI_VENDOR_ID, &vid); + + if (vid != 0xFFFFFFFF) { + dbg("%s: vid = %x bus %x dev %x fun %x\n", __FUNCTION__, + vid, busn, devn, funn); + func = pciehp_slot_find(busn, devn, funn); + dbg("%s: func = %p\n", __FUNCTION__,func); + if (!func) { + memset(&new_func, 0, sizeof(struct pci_func)); + new_func.bus = busn; + new_func.device = devn; + new_func.function = funn; + new_func.is_a_board = 1; + configure_existing_function(ctrl, &new_func); + phprm_dump_func_res(&new_func); + } else { + configure_existing_function(ctrl, func); + phprm_dump_func_res(func); + } + dbg("aCCF:existing PCI 0x%x Func ResourceDump\n", ctrl->bus); + } + } + } + + return 0; +} + +static void phprm_dump_ctrl_res( struct controller *ctlr) +{ + struct controller *ctrl = ctlr; + + if (ctrl->bus_head) { + dbg(": BUS Resources:\n"); + print_pci_resource (ctrl->bus_head); + } + if (ctrl->io_head) { + dbg(": IO Resources:\n"); + print_pci_resource (ctrl->io_head); + } + if (ctrl->mem_head) { + dbg(": MEM Resources:\n"); + print_pci_resource (ctrl->mem_head); + } + if (ctrl->p_mem_head) { + dbg(": PMEM Resources:\n"); + print_pci_resource (ctrl->p_mem_head); + } +} + +/* + * phprm_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 pciehprm_find_available_resources(struct controller *ctrl) +{ + struct pci_func func; + u32 rc; + + memset(&func, 0, sizeof(struct pci_func)); + + func.bus = ctrl->bus; + func.device = ctrl->device; + func.function = ctrl->function; + func.is_a_board = 1; + + /* Get resources for this PCI bridge */ + rc = pciehp_save_used_resources (ctrl, &func, !DISABLE_CARD); + dbg("%s: pciehp_save_used_resources rc = %d\n", __FUNCTION__, rc); + + if (func.mem_head) + func.mem_head->next = ctrl->mem_head; + ctrl->mem_head = func.mem_head; + + if (func.p_mem_head) + func.p_mem_head->next = ctrl->p_mem_head; + ctrl->p_mem_head = func.p_mem_head; + + if (func.io_head) + func.io_head->next = ctrl->io_head; + ctrl->io_head = func.io_head; + + if(func.bus_head) + func.bus_head->next = ctrl->bus_head; + ctrl->bus_head = func.bus_head; + + if (ctrl->bus_head) + pciehprm_delete_resource(&ctrl->bus_head, ctrl->pci_dev->subordinate->number, 1); + + dbg("%s:pre-Bind PCI 0x%x Ctrl Resource Dump\n", __FUNCTION__, ctrl->bus); + phprm_dump_ctrl_res(ctrl); + + dbg("%s: before bind_pci_resources_to slots\n", __FUNCTION__); + + bind_pci_resources_to_slots (ctrl); + + dbg("%s:post-Bind PCI 0x%x Ctrl Resource Dump\n", __FUNCTION__, ctrl->bus); + phprm_dump_ctrl_res(ctrl); + + return (rc); +} + +int pciehprm_set_hpp( + struct controller *ctrl, + struct pci_func *func, + u8 card_type) +{ + u32 rc; + u8 temp_byte; + struct pci_bus lpci_bus, *pci_bus; + unsigned int devfn; + memcpy(&lpci_bus, ctrl->pci_bus, sizeof(lpci_bus)); + pci_bus = &lpci_bus; + pci_bus->number = func->bus; + devfn = PCI_DEVFN(func->device, func->function); + + temp_byte = 0x40; /* hard coded value for LT */ + if (card_type == PCI_HEADER_TYPE_BRIDGE) { + /* set subordinate Latency Timer */ + rc = pci_bus_write_config_byte(pci_bus, devfn, PCI_SEC_LATENCY_TIMER, temp_byte); + + if (rc) { + dbg("%s: set secondary LT error. b:d:f(%02x:%02x:%02x)\n", __FUNCTION__, + func->bus, func->device, func->function); + return rc; + } + } + + /* set base Latency Timer */ + rc = pci_bus_write_config_byte(pci_bus, devfn, PCI_LATENCY_TIMER, temp_byte); + + if (rc) { + dbg("%s: set LT error. b:d:f(%02x:%02x:%02x)\n", __FUNCTION__, func->bus, func->device, func->function); + return rc; + } + + /* set Cache Line size */ + temp_byte = 0x08; /* hard coded value for CLS */ + + rc = pci_bus_write_config_byte(pci_bus, devfn, PCI_CACHE_LINE_SIZE, temp_byte); + + if (rc) { + dbg("%s: set CLS error. b:d:f(%02x:%02x:%02x)\n", __FUNCTION__, func->bus, func->device, func->function); + } + + /* set enable_perr */ + /* set enable_serr */ + + return rc; +} + +void pciehprm_enable_card( + struct controller *ctrl, + struct pci_func *func, + u8 card_type) +{ + u16 command, bcommand; + struct pci_bus lpci_bus, *pci_bus; + unsigned int devfn; + int rc; + + memcpy(&lpci_bus, ctrl->pci_bus, sizeof(lpci_bus)); + pci_bus = &lpci_bus; + pci_bus->number = func->bus; + devfn = PCI_DEVFN(func->device, func->function); + + rc = pci_bus_read_config_word(pci_bus, devfn, PCI_COMMAND, &command); + + command |= PCI_COMMAND_PARITY | PCI_COMMAND_SERR + | PCI_COMMAND_MASTER | PCI_COMMAND_INVALIDATE + | PCI_COMMAND_IO | PCI_COMMAND_MEMORY; + + rc = pci_bus_write_config_word(pci_bus, devfn, PCI_COMMAND, command); + + if (card_type == PCI_HEADER_TYPE_BRIDGE) { + + rc = pci_bus_read_config_word(pci_bus, devfn, PCI_BRIDGE_CONTROL, &bcommand); + + bcommand |= 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, bcommand); + } +} + +static int legacy_pciehprm_init_pci(void) +{ + return 0; +} + +int pciehprm_init(enum php_ctlr_type ctrl_type) +{ + int retval; + + switch (ctrl_type) { + case PCI: + retval = legacy_pciehprm_init_pci(); + break; + default: + retval = -ENODEV; + break; + } + + return retval; +} diff --git a/drivers/pci/hotplug/pciehprm_nonacpi.h b/drivers/pci/hotplug/pciehprm_nonacpi.h new file mode 100644 index 000000000000..87c90e85ede9 --- /dev/null +++ b/drivers/pci/hotplug/pciehprm_nonacpi.h @@ -0,0 +1,56 @@ +/* + * PCIEHPRM NONACPI: PHP Resource Manager for Non-ACPI/Legacy platform + * + * 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>, <dely.l.sy@intel.com> + * + */ + +#ifndef _PCIEHPRM_NONACPI_H_ +#define _PCIEHPRM_NONACPI_H_ + +struct irq_info { + u8 bus, devfn; /* bus, device and function */ + struct { + u8 link; /* IRQ line ID, chipset dependent, 0=not routed */ + u16 bitmap; /* Available IRQs */ + } __attribute__ ((packed)) irq[4]; + u8 slot; /* slot number, 0=onboard */ + u8 rfu; +} __attribute__ ((packed)); + +struct irq_routing_table { + u32 signature; /* PIRQ_SIGNATURE should be here */ + u16 version; /* PIRQ_VERSION */ + u16 size; /* Table size in bytes */ + u8 rtr_bus, rtr_devfn; /* Where the interrupt router lies */ + u16 exclusive_irqs; /* IRQs devoted exclusively to PCI usage */ + u16 rtr_vendor, rtr_device; /* Vendor and device ID of interrupt router */ + u32 miniport_data; /* Crap */ + u8 rfu[11]; + u8 checksum; /* Modulo 256 checksum must give zero */ + struct irq_info slots[0]; +} __attribute__ ((packed)); + +#endif /* _PCIEHPRM_NONACPI_H_ */ diff --git a/drivers/pci/hotplug/pcihp_skeleton.c b/drivers/pci/hotplug/pcihp_skeleton.c new file mode 100644 index 000000000000..6605d6bda529 --- /dev/null +++ b/drivers/pci/hotplug/pcihp_skeleton.c @@ -0,0 +1,375 @@ +/* + * PCI Hot Plug Controller Skeleton Driver - 0.2 + * + * 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 be show how to interface + * with the pci hotplug core easily. + * + * Send feedback to <greg@kroah.com> + * + */ + +#include <linux/config.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/pci.h> +#include <linux/init.h> +#include "pci_hotplug.h" + +struct slot { + u8 number; + struct hotplug_slot *hotplug_slot; + struct list_head slot_list; +}; + +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", __FUNCTION__, 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", __FUNCTION__, 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", __FUNCTION__, 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", __FUNCTION__, 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", __FUNCTION__, 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", __FUNCTION__, 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", __FUNCTION__, 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", __FUNCTION__, 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", __FUNCTION__, hotplug_slot->name); + kfree(slot->hotplug_slot->info); + kfree(slot->hotplug_slot->name); + kfree(slot->hotplug_slot); + kfree(slot); +} + +#define SLOT_NAME_SIZE 10 +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; + char *name; + 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 = kmalloc(sizeof(struct slot), GFP_KERNEL); + if (!slot) + goto error; + memset(slot, 0, sizeof(struct slot)); + + hotplug_slot = kmalloc(sizeof(struct hotplug_slot), + GFP_KERNEL); + if (!hotplug_slot) + goto error_slot; + memset(hotplug_slot, 0, sizeof (struct hotplug_slot)); + slot->hotplug_slot = hotplug_slot; + + info = kmalloc(sizeof(struct hotplug_slot_info), GFP_KERNEL); + if (!info) + goto error_hpslot; + memset(info, 0, sizeof (struct hotplug_slot_info)); + hotplug_slot->info = info; + + name = kmalloc(SLOT_NAME_SIZE, GFP_KERNEL); + if (!name) + goto error_info; + hotplug_slot->name = name; + + slot->number = i; + + hotplug_slot->private = slot; + hotplug_slot->release = &release_slot; + make_slot_name(slot); + hotplug_slot->ops = &skel_hotplug_slot_ops; + + /* + * Initilize the slot info structure with some known + * good values. + */ + info->power_status = get_power_status(slot); + info->attention_status = get_attention_status(slot); + info->latch_status = get_latch_status(slot); + info->adapter_status = get_adapter_status(slot); + + 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_name; + } + + /* add slot to our internal list */ + list_add(&slot->slot_list, &slot_list); + } + + return 0; +error_name: + kfree(name); +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 000000000000..4a0a59b82eae --- /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 000000000000..86b384e42717 --- /dev/null +++ b/drivers/pci/hotplug/rpadlpar_core.c @@ -0,0 +1,503 @@ +/* + * 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 <asm/pci-bridge.h> +#include <asm/semaphore.h> +#include <asm/rtas.h> +#include "../pci.h" +#include "rpaphp.h" +#include "rpadlpar.h" + +static DECLARE_MUTEX(rpadlpar_sem); + +#define NODE_TYPE_VIO 1 +#define NODE_TYPE_SLOT 2 +#define NODE_TYPE_PHB 3 + +static struct device_node *find_php_slot_vio_node(char *drc_name) +{ + struct device_node *child; + struct device_node *parent = of_find_node_by_name(NULL, "vdevice"); + char *loc_code; + + if (!parent) + return NULL; + + for (child = of_get_next_child(parent, NULL); + child; child = of_get_next_child(parent, child)) { + loc_code = get_property(child, "ibm,loc-code", NULL); + if (loc_code && !strncmp(loc_code, drc_name, strlen(drc_name))) + return child; + } + + return NULL; +} + +/* 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_type(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_newly_added_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_php_slot_vio_node(drc_name); + if (dn) { + *node_type = NODE_TYPE_VIO; + return dn; + } + + return NULL; +} + +static struct slot *find_slot(char *drc_name) +{ + 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 (strcmp(slot->location, drc_name) == 0) + return slot; + } + + return NULL; +} + +static void rpadlpar_claim_one_bus(struct pci_bus *b) +{ + struct list_head *ld; + struct pci_bus *child_bus; + + for (ld = b->devices.next; ld != &b->devices; ld = ld->next) { + struct pci_dev *dev = pci_dev_b(ld); + int i; + + for (i = 0; i < PCI_NUM_RESOURCES; i++) { + struct resource *r = &dev->resource[i]; + + if (r->parent || !r->start || !r->flags) + continue; + rpaphp_claim_resource(dev, i); + } + } + + list_for_each_entry(child_bus, &b->children, node) + rpadlpar_claim_one_bus(child_bus); +} + +static int pci_add_secondary_bus(struct device_node *dn, + struct pci_dev *bridge_dev) +{ + struct pci_controller *hose = dn->phb; + struct pci_bus *child; + u8 sec_busno; + + /* Get busno of downstream bus */ + pci_read_config_byte(bridge_dev, PCI_SECONDARY_BUS, &sec_busno); + + /* Allocate and add to children of bridge_dev->bus */ + child = pci_add_new_bus(bridge_dev->bus, bridge_dev, sec_busno); + if (!child) { + printk(KERN_ERR "%s: could not add secondary bus\n", __FUNCTION__); + return -ENOMEM; + } + + sprintf(child->name, "PCI Bus #%02x", child->number); + + /* Fixup subordinate bridge bases and resources */ + pcibios_fixup_bus(child); + + /* Claim new bus resources */ + rpadlpar_claim_one_bus(bridge_dev->bus); + + if (hose->last_busno < child->number) + hose->last_busno = child->number; + + dn->bussubno = child->number; + + /* ioremap() for child bus, which may or may not succeed */ + remap_bus_range(child); + + return 0; +} + +static struct pci_dev *dlpar_pci_add_bus(struct device_node *dn) +{ + struct pci_controller *hose = dn->phb; + struct pci_dev *dev = NULL; + + /* Scan phb bus for EADS device, adding new one to bus->devices */ + if (!pci_scan_single_device(hose->bus, dn->devfn)) { + printk(KERN_ERR "%s: found no device on bus\n", __FUNCTION__); + return NULL; + } + + /* Add new devices to global lists. Register in proc, sysfs. */ + pci_bus_add_devices(hose->bus); + + /* Confirm new bridge dev was created */ + dev = rpaphp_find_pci_dev(dn); + if (!dev) { + printk(KERN_ERR "%s: failed to add pci device\n", __FUNCTION__); + return NULL; + } + + if (dev->hdr_type != PCI_HEADER_TYPE_BRIDGE) { + printk(KERN_ERR "%s: unexpected header type %d\n", + __FUNCTION__, dev->hdr_type); + return NULL; + } + + if (pci_add_secondary_bus(dn, dev)) + return NULL; + + return dev; +} + +static int dlpar_pci_remove_bus(struct pci_dev *bridge_dev) +{ + struct pci_bus *secondary_bus; + + if (!bridge_dev) { + printk(KERN_ERR "%s: unexpected null device\n", + __FUNCTION__); + return -EINVAL; + } + + secondary_bus = bridge_dev->subordinate; + + if (unmap_bus_range(secondary_bus)) { + printk(KERN_ERR "%s: failed to unmap bus range\n", + __FUNCTION__); + return -ERANGE; + } + + pci_remove_bus_device(bridge_dev); + return 0; +} + +static inline int dlpar_add_pci_slot(char *drc_name, struct device_node *dn) +{ + struct pci_dev *dev; + + /* Add pci bus */ + dev = dlpar_pci_add_bus(dn); + if (!dev) { + printk(KERN_ERR "%s: unable to add bus %s\n", __FUNCTION__, + 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(struct slot *slot) +{ + struct pci_controller *phb; + struct device_node *dn; + int rc = 0; + + dn = slot->dn; + if (!dn) { + printk(KERN_ERR "%s: unexpected NULL slot device node\n", + __FUNCTION__); + return -EIO; + } + + phb = dn->phb; + if (!phb) { + printk(KERN_ERR "%s: unexpected NULL phb pointer\n", + __FUNCTION__); + return -EIO; + } + + if (rpaphp_remove_slot(slot)) { + printk(KERN_ERR "%s: unable to remove hotplug slot %s\n", + __FUNCTION__, slot->location); + return -EIO; + } + + rc = dlpar_remove_root_bus(phb); + if (rc < 0) + return rc; + + return 0; +} + +static int dlpar_add_phb(struct device_node *dn) +{ + struct pci_controller *phb; + + phb = init_phb_dynamic(dn); + if (!phb) + return -EINVAL; + + 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 = 0; + + if (down_interruptible(&rpadlpar_sem)) + return -ERESTARTSYS; + + /* Check for existing hotplug slot */ + if (find_slot(drc_name)) { + rc = -EINVAL; + goto exit; + } + + dn = find_newly_added_node(drc_name, &node_type); + if (!dn) { + rc = -ENODEV; + goto exit; + } + + switch (node_type) { + case NODE_TYPE_VIO: + /* Just add hotplug slot */ + break; + case NODE_TYPE_SLOT: + rc = dlpar_add_pci_slot(drc_name, dn); + break; + case NODE_TYPE_PHB: + rc = dlpar_add_phb(dn); + break; + default: + printk("%s: unexpected node type\n", __FUNCTION__); + return -EIO; + } + + if (!rc && rpaphp_add_slot(dn)) { + printk(KERN_ERR "%s: unable to add hotplug slot %s\n", + __FUNCTION__, drc_name); + rc = -EIO; + } +exit: + up(&rpadlpar_sem); + return rc; +} + +/** + * dlpar_remove_vio_slot - DLPAR remove a virtual 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 + * -EIO Internal Error + */ +int dlpar_remove_vio_slot(struct slot *slot, char *drc_name) +{ + /* Remove hotplug slot */ + + if (rpaphp_remove_slot(slot)) { + printk(KERN_ERR "%s: unable to remove hotplug slot %s\n", + __FUNCTION__, drc_name); + return -EIO; + } + return 0; +} + +/** + * dlpar_remove_slot - DLPAR remove a PCI I/O Slot + * @drc_name: drc-name of newly added slot + * + * 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(struct slot *slot, char *drc_name) +{ + struct pci_dev *bridge_dev; + + bridge_dev = slot->bridge; + if (!bridge_dev) { + printk(KERN_ERR "%s: unexpected null bridge device\n", + __FUNCTION__); + return -EIO; + } + + /* Remove hotplug slot */ + if (rpaphp_remove_slot(slot)) { + printk(KERN_ERR "%s: unable to remove hotplug slot %s\n", + __FUNCTION__, drc_name); + return -EIO; + } + + /* Remove pci bus */ + + if (dlpar_pci_remove_bus(bridge_dev)) { + printk(KERN_ERR "%s: unable to remove pci bus %s\n", + __FUNCTION__, drc_name); + return -EIO; + } + 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 slot *slot; + int rc = 0; + + if (down_interruptible(&rpadlpar_sem)) + return -ERESTARTSYS; + + if (!find_php_slot_vio_node(drc_name) && + !find_php_slot_pci_node(drc_name, "SLOT") && + !find_php_slot_pci_node(drc_name, "PHB")) { + rc = -ENODEV; + goto exit; + } + + slot = find_slot(drc_name); + if (!slot) { + rc = -EINVAL; + goto exit; + } + + if (slot->type == PHB) { + rc = dlpar_remove_phb(slot); + } else { + switch (slot->dev_type) { + case PCI_DEV: + rc = dlpar_remove_pci_slot(slot, drc_name); + break; + + case VIO_DEV: + rc = dlpar_remove_vio_slot(slot, drc_name); + break; + } + } +exit: + up(&rpadlpar_sem); + 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", + __FUNCTION__); + 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 000000000000..3285b822478d --- /dev/null +++ b/drivers/pci/hotplug/rpadlpar_sysfs.c @@ -0,0 +1,151 @@ +/* + * 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 "pci_hotplug.h" +#include "rpadlpar.h" + +#define DLPAR_KOBJ_NAME "control" +#define ADD_SLOT_ATTR_NAME "add_slot" +#define REMOVE_SLOT_ATTR_NAME "remove_slot" + +#define MAX_DRC_NAME_LEN 64 + +/* Store return code of dlpar operation in attribute struct */ +struct dlpar_io_attr { + int rc; + struct attribute attr; + ssize_t (*store)(struct dlpar_io_attr *dlpar_attr, const char *buf, + size_t nbytes); +}; + +/* Common show callback for all attrs, display the return code + * of the dlpar op */ +static ssize_t +dlpar_attr_show(struct kobject * kobj, struct attribute * attr, char * buf) +{ + struct dlpar_io_attr *dlpar_attr = container_of(attr, + struct dlpar_io_attr, attr); + return sprintf(buf, "%d\n", dlpar_attr->rc); +} + +static ssize_t +dlpar_attr_store(struct kobject * kobj, struct attribute * attr, + const char *buf, size_t nbytes) +{ + struct dlpar_io_attr *dlpar_attr = container_of(attr, + struct dlpar_io_attr, attr); + return dlpar_attr->store ? + dlpar_attr->store(dlpar_attr, buf, nbytes) : 0; +} + +static struct sysfs_ops dlpar_attr_sysfs_ops = { + .show = dlpar_attr_show, + .store = dlpar_attr_store, +}; + +static ssize_t add_slot_store(struct dlpar_io_attr *dlpar_attr, + const char *buf, size_t nbytes) +{ + char drc_name[MAX_DRC_NAME_LEN]; + 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'; + + dlpar_attr->rc = dlpar_add_slot(drc_name); + + return nbytes; +} + +static ssize_t remove_slot_store(struct dlpar_io_attr *dlpar_attr, + const char *buf, size_t nbytes) +{ + char drc_name[MAX_DRC_NAME_LEN]; + 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'; + + dlpar_attr->rc = dlpar_remove_slot(drc_name); + + return nbytes; +} + +static struct dlpar_io_attr add_slot_attr = { + .rc = 0, + .attr = { .name = ADD_SLOT_ATTR_NAME, .mode = 0644, }, + .store = add_slot_store, +}; + +static struct dlpar_io_attr remove_slot_attr = { + .rc = 0, + .attr = { .name = REMOVE_SLOT_ATTR_NAME, .mode = 0644}, + .store = remove_slot_store, +}; + +static struct attribute *default_attrs[] = { + &add_slot_attr.attr, + &remove_slot_attr.attr, + NULL, +}; + +static void dlpar_io_release(struct kobject *kobj) +{ + /* noop */ + return; +} + +struct kobj_type ktype_dlpar_io = { + .release = dlpar_io_release, + .sysfs_ops = &dlpar_attr_sysfs_ops, + .default_attrs = default_attrs, +}; + +struct kset dlpar_io_kset = { + .subsys = &pci_hotplug_slots_subsys, + .kobj = {.name = DLPAR_KOBJ_NAME, .ktype=&ktype_dlpar_io,}, + .ktype = &ktype_dlpar_io, +}; + +int dlpar_sysfs_init(void) +{ + if (kset_register(&dlpar_io_kset)) { + printk(KERN_ERR "rpadlpar_io: cannot register kset for %s\n", + dlpar_io_kset.kobj.name); + return -EINVAL; + } + + return 0; +} + +void dlpar_sysfs_exit(void) +{ + kset_unregister(&dlpar_io_kset); +} diff --git a/drivers/pci/hotplug/rpaphp.h b/drivers/pci/hotplug/rpaphp.h new file mode 100644 index 000000000000..81746e6e0e0f --- /dev/null +++ b/drivers/pci/hotplug/rpaphp.h @@ -0,0 +1,138 @@ +/* + * 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 "pci_hotplug.h" + +#define PHB 2 +#define HOTPLUG 1 +#define EMBEDDED 0 + +#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 debug; +#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 warn(format, arg...) printk(KERN_WARNING "%s: " format, MY_NAME , ## arg) + +/* slot types */ +#define VIO_DEV 1 +#define PCI_DEV 2 + +/* slot states */ + +#define NOT_VALID 3 +#define NOT_CONFIGURED 2 +#define CONFIGURED 1 +#define EMPTY 0 + +struct rpaphp_pci_func { + struct pci_dev *pci_dev; + struct list_head sibling; +}; + +/* + * 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; + char *location; + u8 removable; + u8 dev_type; /* VIO or PCI */ + struct device_node *dn; /* slot's device_node in OFDT */ + /* dn has phb info */ + struct pci_dev *bridge; /* slot's pci_dev in pci_devices */ + union { + struct list_head *pci_devs; /* pci_devs in PCI slot */ + struct vio_dev *vio_dev; /* vio_dev in VIO slot */ + } dev; + struct hotplug_slot *hotplug_slot; +}; + +extern struct hotplug_slot_ops rpaphp_hotplug_slot_ops; +extern struct list_head rpaphp_slot_head; +extern int num_slots; + +/* function prototypes */ + +/* rpaphp_pci.c */ +extern struct pci_dev *rpaphp_find_pci_dev(struct device_node *dn); +extern int rpaphp_claim_resource(struct pci_dev *dev, int resource); +extern int rpaphp_enable_pci_slot(struct slot *slot); +extern int register_pci_slot(struct slot *slot); +extern int rpaphp_unconfig_pci_adapter(struct slot *slot); +extern int rpaphp_get_pci_adapter_status(struct slot *slot, int is_init, u8 * value); +extern struct hotplug_slot *rpaphp_find_hotplug_slot(struct pci_dev *dev); + +/* rpaphp_core.c */ +extern int rpaphp_add_slot(struct device_node *dn); +extern int rpaphp_remove_slot(struct slot *slot); +extern int rpaphp_get_drc_props(struct device_node *dn, int *drc_index, + char **drc_name, char **drc_type, int *drc_power_domain); + +/* rpaphp_vio.c */ +extern int rpaphp_get_vio_adapter_status(struct slot *slot, int is_init, u8 * value); +extern int rpaphp_unconfig_vio_adapter(struct slot *slot); +extern int register_vio_slot(struct device_node *dn); +extern int rpaphp_enable_vio_slot(struct slot *slot); + +/* 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 register_slot(struct slot *slot); +extern int deregister_slot(struct slot *slot); +extern int rpaphp_get_power_status(struct slot *slot, u8 * value); +extern int rpaphp_set_attention_status(struct slot *slot, u8 status); + +#endif /* _PPC64PHP_H */ diff --git a/drivers/pci/hotplug/rpaphp_core.c b/drivers/pci/hotplug/rpaphp_core.c new file mode 100644 index 000000000000..29117a3a3287 --- /dev/null +++ b/drivers/pci/hotplug/rpaphp_core.c @@ -0,0 +1,536 @@ +/* + * 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/config.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/pci.h> +#include <linux/slab.h> +#include <linux/smp.h> +#include <linux/smp_lock.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" +#include "pci_hotplug.h" + +int debug; +static struct semaphore rpaphp_sem; +LIST_HEAD(rpaphp_slot_head); +int num_slots; + +#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(debug, bool, 0644); + +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_max_bus_speed(struct hotplug_slot *hotplug_slot, enum pci_bus_speed *value); + +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, +}; + +static int rpaphp_get_attention_status(struct slot *slot) +{ + return slot->hotplug_slot->info->attention_status; +} + +/** + * set_attention_status - set attention LED + * 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 retval = 0; + struct slot *slot = (struct slot *)hotplug_slot->private; + + down(&rpaphp_sem); + switch (value) { + case 0: + retval = rpaphp_set_attention_status(slot, LED_OFF); + hotplug_slot->info->attention_status = 0; + break; + case 1: + default: + retval = rpaphp_set_attention_status(slot, LED_ON); + hotplug_slot->info->attention_status = 1; + break; + case 2: + retval = rpaphp_set_attention_status(slot, LED_ID); + hotplug_slot->info->attention_status = 2; + break; + } + up(&rpaphp_sem); + return retval; +} + +/** + * 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; + struct slot *slot = (struct slot *)hotplug_slot->private; + + down(&rpaphp_sem); + retval = rpaphp_get_power_status(slot, value); + up(&rpaphp_sem); + return retval; +} + +/** + * get_attention_status - get attention LED status + * + * + */ +static int get_attention_status(struct hotplug_slot *hotplug_slot, u8 * value) +{ + int retval = 0; + struct slot *slot = (struct slot *)hotplug_slot->private; + + down(&rpaphp_sem); + *value = rpaphp_get_attention_status(slot); + up(&rpaphp_sem); + return retval; +} + +static int get_adapter_status(struct hotplug_slot *hotplug_slot, u8 * value) +{ + struct slot *slot = (struct slot *)hotplug_slot->private; + int retval = 0; + + down(&rpaphp_sem); + /* have to go through this */ + switch (slot->dev_type) { + case PCI_DEV: + retval = rpaphp_get_pci_adapter_status(slot, 0, value); + break; + case VIO_DEV: + retval = rpaphp_get_vio_adapter_status(slot, 0, value); + break; + default: + retval = -EINVAL; + } + up(&rpaphp_sem); + return retval; +} + +static int get_max_bus_speed(struct hotplug_slot *hotplug_slot, enum pci_bus_speed *value) +{ + struct slot *slot = (struct slot *)hotplug_slot->private; + + down(&rpaphp_sem); + 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; + + } + up(&rpaphp_sem); + return 0; +} + +int rpaphp_remove_slot(struct slot *slot) +{ + return deregister_slot(slot); +} + +static int get_children_props(struct device_node *dn, int **drc_indexes, + int **drc_names, int **drc_types, int **drc_power_domains) +{ + int *indexes, *names; + int *types, *domains; + + indexes = (int *) get_property(dn, "ibm,drc-indexes", NULL); + names = (int *) get_property(dn, "ibm,drc-names", NULL); + types = (int *) get_property(dn, "ibm,drc-types", NULL); + domains = (int *) 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) +{ + int *indexes, *names; + int *types, *domains; + unsigned int *my_index; + char *name_tmp, *type_tmp; + int i, rc; + + my_index = (int *) 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; +} + +static int is_php_dn(struct device_node *dn, int **indexes, int **names, + int **types, int **power_domains) +{ + int *drc_types; + int rc; + + rc = get_children_props(dn, indexes, names, &drc_types, power_domains); + if (rc >= 0) { + if (is_php_type((char *) &drc_types[1])) { + *types = drc_types; + return 1; + } + } + + return 0; +} + +static int is_dr_dn(struct device_node *dn, int **indexes, int **names, + int **types, int **power_domains, int **my_drc_index) +{ + int rc; + + *my_drc_index = (int *) get_property(dn, "ibm,my-drc-index", NULL); + if(!*my_drc_index) + return (0); + + if (!dn->parent) + return (0); + + rc = get_children_props(dn->parent, indexes, names, types, + power_domains); + return (rc >= 0); +} + +static inline int is_vdevice_root(struct device_node *dn) +{ + return !strcmp(dn->name, "vdevice"); +} + +int is_dlpar_type(const char *type_str) +{ + /* Only register DLPAR-capable nodes of drc-type PHB or SLOT */ + return (!strcmp(type_str, "PHB") || !strcmp(type_str, "SLOT")); +} + +/**************************************************************** + * rpaphp not only registers PCI hotplug slots(HOTPLUG), + * but also logical DR slots(EMBEDDED). + * HOTPLUG slot: An adapter can be physically added/removed. + * EMBEDDED slot: An adapter can be logically removed/added + * from/to a partition with the slot. + ***************************************************************/ +int rpaphp_add_slot(struct device_node *dn) +{ + struct slot *slot; + int retval = 0; + int i, *my_drc_index, slot_type; + int *indexes, *names, *types, *power_domains; + char *name, *type; + + dbg("Entry %s: dn->full_name=%s\n", __FUNCTION__, dn->full_name); + + if (dn->parent && is_vdevice_root(dn->parent)) { + /* register a VIO device */ + retval = register_vio_slot(dn); + goto exit; + } + + /* register PCI devices */ + if (dn->name != 0 && strcmp(dn->name, "pci") == 0) { + if (is_php_dn(dn, &indexes, &names, &types, &power_domains)) + slot_type = HOTPLUG; + else if (is_dr_dn(dn, &indexes, &names, &types, &power_domains, &my_drc_index)) + slot_type = EMBEDDED; + else goto exit; + + name = (char *) &names[1]; + type = (char *) &types[1]; + for (i = 0; i < indexes[0]; i++, + name += (strlen(name) + 1), type += (strlen(type) + 1)) { + + if (slot_type == HOTPLUG || + (slot_type == EMBEDDED && + indexes[i + 1] == my_drc_index[0] && + is_dlpar_type(type))) { + if (!(slot = alloc_slot_struct(dn, indexes[i + 1], name, + power_domains[i + 1]))) { + retval = -ENOMEM; + goto exit; + } + if (!strcmp(type, "PHB")) + slot->type = PHB; + else if (slot_type == EMBEDDED) + slot->type = EMBEDDED; + else + 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 = register_pci_slot(slot); + if (slot_type == EMBEDDED) + goto exit; + } + } + } +exit: + dbg("%s - Exit: num_slots=%d rc[%d]\n", + __FUNCTION__, num_slots, retval); + return retval; +} + +/* + * init_slots - initialize 'struct slot' structures for each slot + * + */ +static void init_slots(void) +{ + struct device_node *dn; + + for (dn = find_all_nodes(); dn; dn = dn->next) + rpaphp_add_slot(dn); +} + +static int __init init_rpa(void) +{ + + init_MUTEX(&rpaphp_sem); + + /* initialize internal data structure etc. */ + init_slots(); + if (!num_slots) + return -ENODEV; + + return 0; +} + +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) +{ + info(DRIVER_DESC " version: " DRIVER_VERSION "\n"); + + /* read all the PRA info from the system */ + return init_rpa(); +} + +static void __exit rpaphp_exit(void) +{ + cleanup_slots(); +} + +static int enable_slot(struct hotplug_slot *hotplug_slot) +{ + int retval = 0; + struct slot *slot = (struct slot *)hotplug_slot->private; + + if (slot->state == CONFIGURED) { + dbg("%s: %s is already enabled\n", __FUNCTION__, slot->name); + goto exit; + } + + dbg("ENABLING SLOT %s\n", slot->name); + down(&rpaphp_sem); + switch (slot->dev_type) { + case PCI_DEV: + retval = rpaphp_enable_pci_slot(slot); + break; + case VIO_DEV: + retval = rpaphp_enable_vio_slot(slot); + break; + default: + retval = -EINVAL; + } + up(&rpaphp_sem); +exit: + dbg("%s - Exit: rc[%d]\n", __FUNCTION__, retval); + return retval; +} + +static int disable_slot(struct hotplug_slot *hotplug_slot) +{ + int retval = -EINVAL; + struct slot *slot = (struct slot *)hotplug_slot->private; + + dbg("%s - Entry: slot[%s]\n", __FUNCTION__, slot->name); + + if (slot->state == NOT_CONFIGURED) { + dbg("%s: %s is already disabled\n", __FUNCTION__, slot->name); + goto exit; + } + + dbg("DISABLING SLOT %s\n", slot->name); + down(&rpaphp_sem); + switch (slot->dev_type) { + case PCI_DEV: + retval = rpaphp_unconfig_pci_adapter(slot); + break; + case VIO_DEV: + retval = rpaphp_unconfig_vio_adapter(slot); + break; + default: + retval = -ENODEV; + } + up(&rpaphp_sem); +exit: + dbg("%s - Exit: rc[%d]\n", __FUNCTION__, retval); + return retval; +} + +module_init(rpaphp_init); +module_exit(rpaphp_exit); + +EXPORT_SYMBOL_GPL(rpaphp_add_slot); +EXPORT_SYMBOL_GPL(rpaphp_remove_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 000000000000..d8305a935aab --- /dev/null +++ b/drivers/pci/hotplug/rpaphp_pci.c @@ -0,0 +1,538 @@ +/* + * 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 <asm/pci-bridge.h> +#include <asm/rtas.h> +#include <asm/machdep.h> +#include "../pci.h" /* for pci_add_new_bus */ + +#include "rpaphp.h" + +struct pci_dev *rpaphp_find_pci_dev(struct device_node *dn) +{ + struct pci_dev *dev = NULL; + char bus_id[BUS_ID_SIZE]; + + sprintf(bus_id, "%04x:%02x:%02x.%d", dn->phb->global_number, + dn->busno, PCI_SLOT(dn->devfn), PCI_FUNC(dn->devfn)); + for_each_pci_dev(dev) { + if (!strcmp(pci_name(dev), bus_id)) { + break; + } + } + return dev; +} + +EXPORT_SYMBOL_GPL(rpaphp_find_pci_dev); + +int rpaphp_claim_resource(struct pci_dev *dev, int resource) +{ + struct resource *res = &dev->resource[resource]; + struct resource *root = pci_find_parent_resource(dev, res); + char *dtype = resource < PCI_BRIDGE_RESOURCES ? "device" : "bridge"; + int err = -EINVAL; + + if (root != NULL) { + err = request_resource(root, res); + } + + if (err) { + err("PCI: %s region %d of %s %s [%lx:%lx]\n", + root ? "Address space collision on" : + "No parent found for", + resource, dtype, pci_name(dev), res->start, res->end); + } + return err; +} + +EXPORT_SYMBOL_GPL(rpaphp_claim_resource); + +static struct pci_dev *rpaphp_find_bridge_pdev(struct slot *slot) +{ + return rpaphp_find_pci_dev(slot->dn); +} + +static 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", + __FUNCTION__); + + /* 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", + __FUNCTION__, slot->name, rc); + } else { + rc = rtas_get_sensor(DR_ENTITY_SENSE, + slot->index, state); + } + } else if (rc == -ENODEV) + info("%s: slot is unusable\n", __FUNCTION__); + else + err("%s failed to get sensor state\n", __FUNCTION__); + } + return rc; +} + +/** + * get_pci_adapter_status - get the status of a slot + * + * 0-- slot is empty + * 1-- adapter is configured + * 2-- adapter is not configured + * 3-- not valid + */ +int rpaphp_get_pci_adapter_status(struct slot *slot, int is_init, u8 * value) +{ + int state, rc; + struct device_node *child_dn; + struct pci_dev *child_dev = NULL; + + *value = NOT_VALID; + rc = rpaphp_get_sensor_state(slot, &state); + if (rc) + goto exit; + + if ((state == EMPTY) || (slot->type == PHB)) { + dbg("slot is empty\n"); + *value = EMPTY; + } + else if (state == PRESENT) { + if (!is_init) { + /* at run-time slot->state can be changed by */ + /* config/unconfig adapter */ + *value = slot->state; + } else { + child_dn = slot->dn->child; + if (child_dn) + child_dev = rpaphp_find_pci_dev(child_dn); + + if (child_dev) + *value = CONFIGURED; + else if (!child_dn) + dbg("%s: %s is not valid OFDT node\n", + __FUNCTION__, slot->dn->full_name); + else { + err("%s: can't find pdev of adapter in slot[%s]\n", + __FUNCTION__, slot->dn->full_name); + *value = NOT_CONFIGURED; + } + } + } +exit: + return rc; +} + +/* Must be called before pci_bus_add_devices */ +static void +rpaphp_fixup_new_pci_devices(struct pci_bus *bus, int fix_bus) +{ + struct pci_dev *dev; + + list_for_each_entry(dev, &bus->devices, bus_list) { + /* + * Skip already-present devices (which are on the + * global device list.) + */ + if (list_empty(&dev->global_list)) { + int i; + + /* Need to setup IOMMU tables */ + ppc_md.iommu_dev_setup(dev); + + if(fix_bus) + pcibios_fixup_device_resources(dev, bus); + pci_read_irq_line(dev); + for (i = 0; i < PCI_NUM_RESOURCES; i++) { + struct resource *r = &dev->resource[i]; + + if (r->parent || !r->start || !r->flags) + continue; + rpaphp_claim_resource(dev, i); + } + } + } +} + +static int rpaphp_pci_config_bridge(struct pci_dev *dev); + +/***************************************************************************** + rpaphp_pci_config_slot() will configure all devices under the + given slot->dn and return the the first pci_dev. + *****************************************************************************/ +static struct pci_dev * +rpaphp_pci_config_slot(struct device_node *dn, struct pci_bus *bus) +{ + struct device_node *eads_first_child = dn->child; + struct pci_dev *dev = NULL; + int num; + + dbg("Enter %s: dn=%s bus=%s\n", __FUNCTION__, dn->full_name, bus->name); + + if (eads_first_child) { + /* pci_scan_slot should find all children of EADs */ + num = pci_scan_slot(bus, PCI_DEVFN(PCI_SLOT(eads_first_child->devfn), 0)); + if (num) { + rpaphp_fixup_new_pci_devices(bus, 1); + pci_bus_add_devices(bus); + } + dev = rpaphp_find_pci_dev(eads_first_child); + if (!dev) { + err("No new device found\n"); + return NULL; + } + if (dev->hdr_type == PCI_HEADER_TYPE_BRIDGE) + rpaphp_pci_config_bridge(dev); + } + return dev; +} + +static int rpaphp_pci_config_bridge(struct pci_dev *dev) +{ + u8 sec_busno; + struct pci_bus *child_bus; + struct pci_dev *child_dev; + + dbg("Enter %s: BRIDGE dev=%s\n", __FUNCTION__, pci_name(dev)); + + /* get busno of downstream bus */ + pci_read_config_byte(dev, PCI_SECONDARY_BUS, &sec_busno); + + /* add to children of PCI bridge dev->bus */ + child_bus = pci_add_new_bus(dev->bus, dev, sec_busno); + if (!child_bus) { + err("%s: could not add second bus\n", __FUNCTION__); + return -EIO; + } + sprintf(child_bus->name, "PCI Bus #%02x", child_bus->number); + /* do pci_scan_child_bus */ + pci_scan_child_bus(child_bus); + + list_for_each_entry(child_dev, &child_bus->devices, bus_list) { + eeh_add_device_late(child_dev); + } + + /* fixup new pci devices without touching bus struct */ + rpaphp_fixup_new_pci_devices(child_bus, 0); + + /* Make the discovered devices available */ + pci_bus_add_devices(child_bus); + return 0; +} + +static void enable_eeh(struct device_node *dn) +{ + struct device_node *sib; + + for (sib = dn->child; sib; sib = sib->sibling) + enable_eeh(sib); + eeh_add_device_early(dn); + return; + +} + +static void print_slot_pci_funcs(struct slot *slot) +{ + struct pci_dev *dev; + + if (slot->dev_type == PCI_DEV) { + dbg("%s: pci_devs of slot[%s]\n", __FUNCTION__, slot->name); + list_for_each_entry (dev, slot->dev.pci_devs, bus_list) + dbg("\t%s\n", pci_name(dev)); + } + return; +} + +static int rpaphp_config_pci_adapter(struct slot *slot) +{ + struct pci_bus *pci_bus; + struct pci_dev *dev; + int rc = -ENODEV; + + dbg("Entry %s: slot[%s]\n", __FUNCTION__, slot->name); + + if (slot->bridge) { + + pci_bus = slot->bridge->subordinate; + if (!pci_bus) { + err("%s: can't find bus structure\n", __FUNCTION__); + goto exit; + } + enable_eeh(slot->dn); + dev = rpaphp_pci_config_slot(slot->dn, pci_bus); + if (!dev) { + err("%s: can't find any devices.\n", __FUNCTION__); + goto exit; + } + print_slot_pci_funcs(slot); + rc = 0; + } else { + /* slot is not enabled */ + err("slot doesn't have pci_dev structure\n"); + } +exit: + dbg("Exit %s: rc=%d\n", __FUNCTION__, rc); + return rc; +} + +static void rpaphp_eeh_remove_bus_device(struct pci_dev *dev) +{ + eeh_remove_device(dev); + if (dev->hdr_type == PCI_HEADER_TYPE_BRIDGE) { + struct pci_bus *bus = dev->subordinate; + struct list_head *ln; + if (!bus) + return; + for (ln = bus->devices.next; ln != &bus->devices; ln = ln->next) { + struct pci_dev *pdev = pci_dev_b(ln); + if (pdev) + rpaphp_eeh_remove_bus_device(pdev); + } + + } + return; +} + +int rpaphp_unconfig_pci_adapter(struct slot *slot) +{ + struct pci_dev *dev; + int retval = 0; + + list_for_each_entry(dev, slot->dev.pci_devs, bus_list) + rpaphp_eeh_remove_bus_device(dev); + + pci_remove_behind_bridge(slot->bridge); + slot->state = NOT_CONFIGURED; + info("%s: devices in slot[%s] unconfigured.\n", __FUNCTION__, + slot->name); + return retval; +} + +static int setup_pci_hotplug_slot_info(struct slot *slot) +{ + dbg("%s Initilize the PCI slot's hotplug->info structure ...\n", + __FUNCTION__); + rpaphp_get_power_status(slot, &slot->hotplug_slot->info->power_status); + rpaphp_get_pci_adapter_status(slot, 1, + &slot->hotplug_slot->info-> + adapter_status); + if (slot->hotplug_slot->info->adapter_status == NOT_VALID) { + err("%s: NOT_VALID: skip dn->full_name=%s\n", + __FUNCTION__, slot->dn->full_name); + return -EINVAL; + } + return 0; +} + +static int set_phb_slot_name(struct slot *slot) +{ + struct device_node *dn; + struct pci_controller *phb; + struct pci_bus *bus; + + dn = slot->dn; + if (!dn) { + return -EINVAL; + } + phb = dn->phb; + if (!phb) { + return -EINVAL; + } + bus = phb->bus; + if (!bus) { + return -EINVAL; + } + + sprintf(slot->name, "%04x:%02x:%02x.%x", pci_domain_nr(bus), + bus->number, 0, 0); + return 0; +} + +static int setup_pci_slot(struct slot *slot) +{ + struct pci_bus *bus; + int rc; + + if (slot->type == PHB) { + rc = set_phb_slot_name(slot); + if (rc < 0) { + err("%s: failed to set phb slot name\n", __FUNCTION__); + goto exit_rc; + } + } else { + slot->bridge = rpaphp_find_bridge_pdev(slot); + if (!slot->bridge) { + /* slot being added doesn't have pci_dev yet */ + err("%s: no pci_dev for bridge dn %s\n", + __FUNCTION__, slot->name); + goto exit_rc; + } + + bus = slot->bridge->subordinate; + if (!bus) + goto exit_rc; + slot->dev.pci_devs = &bus->devices; + + dbg("%s set slot->name to %s\n", __FUNCTION__, + pci_name(slot->bridge)); + strcpy(slot->name, pci_name(slot->bridge)); + } + + /* find slot's pci_dev if it's not empty */ + if (slot->hotplug_slot->info->adapter_status == EMPTY) { + slot->state = EMPTY; /* slot is empty */ + } else { + /* slot is occupied */ + if (!(slot->dn->child)) { + /* non-empty slot has to have child */ + err("%s: slot[%s]'s device_node doesn't have child for adapter\n", + __FUNCTION__, slot->name); + goto exit_rc; + } + + if (slot->hotplug_slot->info->adapter_status == NOT_CONFIGURED) { + dbg("%s CONFIGURING pci adapter in slot[%s]\n", + __FUNCTION__, slot->name); + if (rpaphp_config_pci_adapter(slot)) { + err("%s: CONFIG pci adapter failed\n", __FUNCTION__); + goto exit_rc; + } + + } else if (slot->hotplug_slot->info->adapter_status != CONFIGURED) { + err("%s: slot[%s]'s adapter_status is NOT_VALID.\n", + __FUNCTION__, slot->name); + goto exit_rc; + } + print_slot_pci_funcs(slot); + if (!list_empty(slot->dev.pci_devs)) { + slot->state = CONFIGURED; + } else { + /* DLPAR add as opposed to + * boot time */ + slot->state = NOT_CONFIGURED; + } + } + return 0; +exit_rc: + dealloc_slot_struct(slot); + return -EINVAL; +} + +int register_pci_slot(struct slot *slot) +{ + int rc = -EINVAL; + + slot->dev_type = PCI_DEV; + if ((slot->type == EMBEDDED) || (slot->type == PHB)) + slot->removable = 0; + else + slot->removable = 1; + if (setup_pci_hotplug_slot_info(slot)) + goto exit_rc; + if (setup_pci_slot(slot)) + goto exit_rc; + rc = register_slot(slot); +exit_rc: + return rc; +} + +int rpaphp_enable_pci_slot(struct slot *slot) +{ + int retval = 0, state; + + retval = rpaphp_get_sensor_state(slot, &state); + if (retval) + goto exit; + dbg("%s: sensor state[%d]\n", __FUNCTION__, state); + /* if slot is not empty, enable the adapter */ + if (state == PRESENT) { + dbg("%s : slot[%s] is occupied.\n", __FUNCTION__, slot->name); + retval = rpaphp_config_pci_adapter(slot); + if (!retval) { + slot->state = CONFIGURED; + dbg("%s: PCI devices in slot[%s] has been configured\n", + __FUNCTION__, slot->name); + } else { + slot->state = NOT_CONFIGURED; + dbg("%s: no pci_dev struct for adapter in slot[%s]\n", + __FUNCTION__, slot->name); + } + } else if (state == EMPTY) { + dbg("%s : slot[%s] is empty\n", __FUNCTION__, slot->name); + slot->state = EMPTY; + } else { + err("%s: slot[%s] is in invalid state\n", __FUNCTION__, + slot->name); + slot->state = NOT_VALID; + retval = -EINVAL; + } +exit: + dbg("%s - Exit: rc[%d]\n", __FUNCTION__, retval); + return retval; +} + +struct hotplug_slot *rpaphp_find_hotplug_slot(struct pci_dev *dev) +{ + struct list_head *tmp, *n; + struct slot *slot; + + list_for_each_safe(tmp, n, &rpaphp_slot_head) { + struct pci_bus *bus; + struct list_head *ln; + + slot = list_entry(tmp, struct slot, rpaphp_slot_list); + if (slot->bridge == NULL) { + if (slot->dev_type == PCI_DEV) { + printk(KERN_WARNING "PCI slot missing bridge %s %s \n", + slot->name, slot->location); + } + continue; + } + + bus = slot->bridge->subordinate; + if (!bus) { + continue; /* should never happen? */ + } + for (ln = bus->devices.next; ln != &bus->devices; ln = ln->next) { + struct pci_dev *pdev = pci_dev_b(ln); + if (pdev == dev) + return slot->hotplug_slot; + } + } + + return NULL; +} + +EXPORT_SYMBOL_GPL(rpaphp_find_hotplug_slot); diff --git a/drivers/pci/hotplug/rpaphp_slot.c b/drivers/pci/hotplug/rpaphp_slot.c new file mode 100644 index 000000000000..ff2cbf0652d8 --- /dev/null +++ b/drivers/pci/hotplug/rpaphp_slot.c @@ -0,0 +1,267 @@ +/* + * 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 <asm/rtas.h> +#include "rpaphp.h" + +static ssize_t removable_read_file (struct hotplug_slot *php_slot, char *buf) +{ + u8 value; + int retval = -ENOENT; + struct slot *slot = (struct slot *)php_slot->private; + + if (!slot) + return retval; + + value = slot->removable; + retval = sprintf (buf, "%d\n", value); + return retval; +} + +static struct hotplug_slot_attribute hotplug_slot_attr_removable = { + .attr = {.name = "phy_removable", .mode = S_IFREG | S_IRUGO}, + .show = removable_read_file, +}; + +static void rpaphp_sysfs_add_attr_removable (struct hotplug_slot *slot) +{ + sysfs_create_file(&slot->kobj, &hotplug_slot_attr_removable.attr); +} + +static void rpaphp_sysfs_remove_attr_removable (struct hotplug_slot *slot) +{ + sysfs_remove_file(&slot->kobj, &hotplug_slot_attr_removable.attr); +} + +static ssize_t location_read_file (struct hotplug_slot *php_slot, char *buf) +{ + char *value; + int retval = -ENOENT; + struct slot *slot = (struct slot *)php_slot->private; + + if (!slot) + return retval; + + value = slot->location; + retval = sprintf (buf, "%s\n", value); + return retval; +} + +static struct hotplug_slot_attribute hotplug_slot_attr_location = { + .attr = {.name = "phy_location", .mode = S_IFREG | S_IRUGO}, + .show = location_read_file, +}; + +static void rpaphp_sysfs_add_attr_location (struct hotplug_slot *slot) +{ + sysfs_create_file(&slot->kobj, &hotplug_slot_attr_location.attr); +} + +static void rpaphp_sysfs_remove_attr_location (struct hotplug_slot *slot) +{ + sysfs_remove_file(&slot->kobj, &hotplug_slot_attr_location.attr); +} + +/* 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->hotplug_slot->name); + kfree(slot->hotplug_slot); + kfree(slot); + return; +} + +struct slot *alloc_slot_struct(struct device_node *dn, int drc_index, char *drc_name, + int power_domain) +{ + struct slot *slot; + + slot = kmalloc(sizeof (struct slot), GFP_KERNEL); + if (!slot) + goto error_nomem; + memset(slot, 0, sizeof (struct slot)); + slot->hotplug_slot = kmalloc(sizeof (struct hotplug_slot), GFP_KERNEL); + if (!slot->hotplug_slot) + goto error_slot; + memset(slot->hotplug_slot, 0, sizeof (struct hotplug_slot)); + slot->hotplug_slot->info = kmalloc(sizeof (struct hotplug_slot_info), + GFP_KERNEL); + if (!slot->hotplug_slot->info) + goto error_hpslot; + memset(slot->hotplug_slot->info, 0, sizeof (struct hotplug_slot_info)); + slot->hotplug_slot->name = kmalloc(BUS_ID_SIZE + 1, GFP_KERNEL); + if (!slot->hotplug_slot->name) + goto error_info; + slot->location = kmalloc(strlen(drc_name) + 1, GFP_KERNEL); + if (!slot->location) + goto error_name; + slot->name = slot->hotplug_slot->name; + slot->dn = dn; + slot->index = drc_index; + strcpy(slot->location, drc_name); + 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_name: + kfree(slot->hotplug_slot->name); +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 deregister_slot(struct slot *slot) +{ + int retval = 0; + struct hotplug_slot *php_slot = slot->hotplug_slot; + + dbg("%s - Entry: deregistering slot=%s\n", + __FUNCTION__, slot->name); + + list_del(&slot->rpaphp_slot_list); + + /* remove "phy_location" file */ + rpaphp_sysfs_remove_attr_location(php_slot); + + /* remove "phy_removable" file */ + rpaphp_sysfs_remove_attr_removable(php_slot); + + retval = pci_hp_deregister(php_slot); + if (retval) + err("Problem unregistering a slot %s\n", slot->name); + else + num_slots--; + + dbg("%s - Exit: rc[%d]\n", __FUNCTION__, retval); + return retval; +} + +int register_slot(struct slot *slot) +{ + int retval; + + dbg("%s registering slot:path[%s] index[%x], name[%s] pdomain[%x] type[%d]\n", + __FUNCTION__, 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)) { /* should't be here */ + err("register_slot: slot[%s] is already registered\n", slot->name); + rpaphp_release_slot(slot->hotplug_slot); + return -EAGAIN; + } + retval = pci_hp_register(slot->hotplug_slot); + if (retval) { + err("pci_hp_register failed with error %d\n", retval); + rpaphp_release_slot(slot->hotplug_slot); + return retval; + } + + /* create "phy_locatoin" file */ + rpaphp_sysfs_add_attr_location(slot->hotplug_slot); + + /* create "phy_removable" file */ + rpaphp_sysfs_add_attr_removable(slot->hotplug_slot); + + /* add slot to our internal list */ + dbg("%s adding slot[%s] to rpaphp_slot_list\n", + __FUNCTION__, slot->name); + + list_add(&slot->rpaphp_slot_list, &rpaphp_slot_head); + + if (slot->dev_type == VIO_DEV) + info("Slot [%s](VIO location=%s) registered\n", + slot->name, slot->location); + else + info("Slot [%s](PCI location=%s) registered\n", + slot->name, slot->location); + num_slots++; + return 0; +} + +int rpaphp_get_power_status(struct slot *slot, u8 * value) +{ + int rc = 0, level; + + if (slot->type == HOTPLUG) { + rc = rtas_get_power_level(slot->power_domain, &level); + if (!rc) { + dbg("%s the power level of slot %s(pwd-domain:0x%x) is %d\n", + __FUNCTION__, slot->name, slot->power_domain, level); + *value = level; + } else + err("failed to get power-level for slot(%s), rc=0x%x\n", + slot->location, rc); + } else { + dbg("%s report POWER_ON for EMBEDDED or PHB slot %s\n", + __FUNCTION__, slot->location); + *value = (u8) POWER_ON; + } + + return rc; +} + +int rpaphp_set_attention_status(struct slot *slot, u8 status) +{ + int rc; + + /* status: LED_OFF or LED_ON */ + rc = rtas_set_indicator(DR_INDICATOR, slot->index, status); + if (rc < 0) + err("slot(name=%s location=%s index=0x%x) set attention-status(%d) failed! rc=0x%x\n", + slot->name, slot->location, slot->index, status, rc); + + return rc; +} diff --git a/drivers/pci/hotplug/rpaphp_vio.c b/drivers/pci/hotplug/rpaphp_vio.c new file mode 100644 index 000000000000..74df6a305e64 --- /dev/null +++ b/drivers/pci/hotplug/rpaphp_vio.c @@ -0,0 +1,129 @@ +/* + * RPA Hot Plug 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 <asm/vio.h> +#include "rpaphp.h" + +/* + * get_vio_adapter_status - get the status of a slot + * + * status: + * + * 1-- adapter is configured + * 2-- adapter is not configured + * 3-- not valid + */ +inline int rpaphp_get_vio_adapter_status(struct slot *slot, int is_init, u8 *value) +{ + *value = slot->state; + return 0; +} + +int rpaphp_unconfig_vio_adapter(struct slot *slot) +{ + int retval = 0; + + dbg("Entry %s: slot[%s]\n", __FUNCTION__, slot->name); + if (!slot->dev.vio_dev) { + info("%s: no VIOA in slot[%s]\n", __FUNCTION__, slot->name); + retval = -EINVAL; + goto exit; + } + /* remove the device from the vio core */ + vio_unregister_device(slot->dev.vio_dev); + slot->state = NOT_CONFIGURED; + info("%s: adapter in slot[%s] unconfigured.\n", __FUNCTION__, slot->name); +exit: + dbg("Exit %s, rc=0x%x\n", __FUNCTION__, retval); + return retval; +} + +static int setup_vio_hotplug_slot_info(struct slot *slot) +{ + slot->hotplug_slot->info->power_status = 1; + rpaphp_get_vio_adapter_status(slot, 1, + &slot->hotplug_slot->info->adapter_status); + return 0; +} + +int register_vio_slot(struct device_node *dn) +{ + u32 *index; + char *name; + int rc = -EINVAL; + struct slot *slot = NULL; + + rc = rpaphp_get_drc_props(dn, NULL, &name, NULL, NULL); + if (rc < 0) + goto exit_rc; + index = (u32 *) get_property(dn, "ibm,my-drc-index", NULL); + if (!index) + goto exit_rc; + if (!(slot = alloc_slot_struct(dn, *index, name, 0))) { + rc = -ENOMEM; + goto exit_rc; + } + slot->dev_type = VIO_DEV; + slot->dev.vio_dev = vio_find_node(dn); + if (slot->dev.vio_dev) { + /* + * rpaphp is the only owner of vio devices and + * does not need extra reference taken by + * vio_find_node + */ + put_device(&slot->dev.vio_dev->dev); + } else + slot->dev.vio_dev = vio_register_device_node(dn); + if (slot->dev.vio_dev) + slot->state = CONFIGURED; + else + slot->state = NOT_CONFIGURED; + if (setup_vio_hotplug_slot_info(slot)) + goto exit_rc; + strcpy(slot->name, slot->dev.vio_dev->dev.bus_id); + info("%s: registered VIO device[name=%s vio_dev=%p]\n", + __FUNCTION__, slot->name, slot->dev.vio_dev); + rc = register_slot(slot); +exit_rc: + if (rc && slot) + dealloc_slot_struct(slot); + return (rc); +} + +int rpaphp_enable_vio_slot(struct slot *slot) +{ + int retval = 0; + + if ((slot->dev.vio_dev = vio_register_device_node(slot->dn))) { + info("%s: VIO adapter %s in slot[%s] has been configured\n", + __FUNCTION__, slot->dn->name, slot->name); + slot->state = CONFIGURED; + } else { + info("%s: no vio_dev struct for adapter in slot[%s]\n", + __FUNCTION__, slot->name); + slot->state = NOT_CONFIGURED; + } + + return retval; +} diff --git a/drivers/pci/hotplug/shpchp.h b/drivers/pci/hotplug/shpchp.h new file mode 100644 index 000000000000..67b6a3370ceb --- /dev/null +++ b/drivers/pci/hotplug/shpchp.h @@ -0,0 +1,463 @@ +/* + * 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>,<dely.l.sy@intel.com> + * + */ +#ifndef _SHPCHP_H +#define _SHPCHP_H + +#include <linux/types.h> +#include <linux/pci.h> +#include <linux/delay.h> +#include <asm/semaphore.h> +#include <asm/io.h> +#include "pci_hotplug.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; + +/*#define dbg(format, arg...) do { if (shpchp_debug) printk(KERN_DEBUG "%s: " format, MY_NAME , ## arg); } while (0)*/ +#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) + +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; + u8 pwr_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 pci_dev* pci_dev; +}; + +#define SLOT_MAGIC 0x67267321 +struct slot { + u32 magic; + struct slot *next; + u8 bus; + u8 device; + u32 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; + struct hpc_ops *hpc_ops; + struct hotplug_slot *hotplug_slot; + struct list_head slot_list; +}; + +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; + struct semaphore crit_sect; /* critical section semaphore */ + void * hpc_ctlr_handle; /* HPC controller handle */ + int num_slots; /* Number of slots on ctlr */ + int slot_num_inc; /* 1 or -1 */ + 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; + struct hpc_ops *hpc_ops; + wait_queue_head_t queue; /* sleep & wake process */ + u8 next_event; + u8 seg; + u8 bus; + u8 device; + u8 function; + u8 rev; + u8 slot_device_offset; + u8 add_support; + enum pci_bus_speed speed; + u32 first_slot; /* First physical slot number */ + u8 slot_bus; /* Bus where the slots handled by this controller sit */ + u8 push_flag; + u16 ctlrcap; + u16 vendor_id; +}; + +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 AMD SHPC ID */ +#define PCI_DEVICE_ID_AMD_GOLAM_7450 0x7450 + +#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 PCI_TO_PCI_BRIDGE_CLASS 0x00060400 + +/* 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 + +#define REMOVE_NOT_SUPPORTED 0x00000003 + +#define DISABLE_CARD 1 + +/* + * 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_shpc "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 shpcphd mdoule. Upgrade to a newer version of shpchpd\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" + +/* sysfs functions for the hotplug controller info */ +extern void shpchp_create_ctrl_files (struct controller *ctrl); + +/* controller functions */ +extern int shpchprm_find_available_resources(struct controller *ctrl); +extern int shpchp_event_start_thread(void); +extern void shpchp_event_stop_thread(void); +extern struct pci_func *shpchp_slot_create(unsigned char busnumber); +extern struct pci_func *shpchp_slot_find(unsigned char bus, unsigned char device, unsigned char index); +extern int shpchp_enable_slot(struct slot *slot); +extern int shpchp_disable_slot(struct slot *slot); + +extern u8 shpchp_handle_attention_button(u8 hp_slot, void *inst_id); +extern u8 shpchp_handle_switch_change(u8 hp_slot, void *inst_id); +extern u8 shpchp_handle_presence_change(u8 hp_slot, void *inst_id); +extern u8 shpchp_handle_power_fault(u8 hp_slot, void *inst_id); + +/* resource functions */ +extern int shpchp_resource_sort_and_combine(struct pci_resource **head); + +/* pci functions */ +extern int shpchp_set_irq(u8 bus_num, u8 dev_num, u8 int_pin, u8 irq_num); +/*extern int shpchp_get_bus_dev(struct controller *ctrl, u8 *bus_num, u8 *dev_num, struct slot *slot);*/ +extern int shpchp_save_config(struct controller *ctrl, int busnumber, int num_ctlr_slots, int first_device_num); +extern int shpchp_save_used_resources(struct controller *ctrl, struct pci_func * func, int flag); +extern int shpchp_save_slot_config(struct controller *ctrl, struct pci_func * new_slot); +extern void shpchp_destroy_board_resources(struct pci_func * func); +extern int shpchp_return_board_resources(struct pci_func * func, struct resource_lists * resources); +extern void shpchp_destroy_resource_list(struct resource_lists * resources); +extern int shpchp_configure_device(struct controller* ctrl, struct pci_func* func); +extern int shpchp_unconfigure_device(struct pci_func* func); + + +/* Global variables */ +extern struct controller *shpchp_ctrl_list; +extern struct pci_func *shpchp_slot_list[256]; + +/* These are added to support AMD shpc */ +extern u8 shpchp_nic_irq; +extern u8 shpchp_disk_irq; + +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; + volatile u32 slot2; + volatile u32 slot3; + volatile u32 slot4; + volatile u32 slot5; + volatile u32 slot6; + volatile u32 slot7; + volatile u32 slot8; + volatile u32 slot9; + volatile u32 slot10; + volatile u32 slot11; + volatile u32 slot12; +} __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), + SLOT2 = offsetof(struct ctrl_reg, slot2), + SLOT3 = offsetof(struct ctrl_reg, slot3), + SLOT4 = offsetof(struct ctrl_reg, slot4), + SLOT5 = offsetof(struct ctrl_reg, slot5), + SLOT6 = offsetof(struct ctrl_reg, slot6), + SLOT7 = offsetof(struct ctrl_reg, slot7), + SLOT8 = offsetof(struct ctrl_reg, slot8), + SLOT9 = offsetof(struct ctrl_reg, slot9), + SLOT10 = offsetof(struct ctrl_reg, slot10), + SLOT11 = offsetof(struct ctrl_reg, slot11), + SLOT12 = offsetof(struct ctrl_reg, slot12), +}; +typedef u8(*php_intr_callback_t) (unsigned int change_id, void *instance_id); +struct php_ctlr_state_s { + struct php_ctlr_state_s *pnext; + struct pci_dev *pci_dev; + unsigned int irq; + unsigned long flags; /* spinlock's */ + u32 slot_device_offset; + u32 num_slots; + struct timer_list int_poll_timer; /* Added for poll event */ + php_intr_callback_t attention_button_callback; + php_intr_callback_t switch_change_callback; + php_intr_callback_t presence_change_callback; + php_intr_callback_t power_fault_callback; + void *callback_instance_id; + void __iomem *creg; /* Ptr to controller register space */ +}; +/* Inline functions */ + + +/* Inline functions to check the sanity of a pointer that is passed to us */ +static inline int slot_paranoia_check (struct slot *slot, const char *function) +{ + if (!slot) { + dbg("%s - slot == NULL", function); + return -1; + } + if (slot->magic != SLOT_MAGIC) { + dbg("%s - bad magic number for slot", function); + return -1; + } + if (!slot->hotplug_slot) { + dbg("%s - slot->hotplug_slot == NULL!", function); + return -1; + } + return 0; +} + +static inline struct slot *get_slot (struct hotplug_slot *hotplug_slot, const char *function) +{ + struct slot *slot; + + if (!hotplug_slot) { + dbg("%s - hotplug_slot == NULL\n", function); + return NULL; + } + + slot = (struct slot *)hotplug_slot->private; + if (slot_paranoia_check (slot, function)) + return NULL; + return slot; +} + +static inline struct slot *shpchp_find_slot (struct controller *ctrl, u8 device) +{ + struct slot *p_slot, *tmp_slot = NULL; + + if (!ctrl) + return NULL; + + p_slot = ctrl->slot; + + dbg("p_slot = %p\n", p_slot); + + while (p_slot && (p_slot->device != device)) { + tmp_slot = p_slot; + p_slot = p_slot->next; + dbg("In while loop, p_slot = %p\n", p_slot); + } + if (p_slot == NULL) { + err("ERROR: shpchp_find_slot device=0x%x\n", device); + p_slot = tmp_slot; + } + + return (p_slot); +} + +static inline int wait_for_ctrl_irq (struct controller *ctrl) +{ + DECLARE_WAITQUEUE(wait, current); + int retval = 0; + + dbg("%s : start\n",__FUNCTION__); + + add_wait_queue(&ctrl->queue, &wait); + + if (!shpchp_poll_mode) { + /* Sleep for up to 1 second */ + msleep_interruptible(1000); + } else { + /* Sleep for up to 2 seconds */ + msleep_interruptible(2000); + } + remove_wait_queue(&ctrl->queue, &wait); + if (signal_pending(current)) + retval = -EINTR; + + dbg("%s : end\n", __FUNCTION__); + return retval; +} + +/* 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; +} + +#define SLOT_NAME_SIZE 10 + +static inline void make_slot_name(char *buffer, int buffer_size, struct slot *slot) +{ + snprintf(buffer, buffer_size, "%d", slot->number); +} + +enum php_ctlr_type { + PCI, + ISA, + ACPI +}; + +int shpc_init( struct controller *ctrl, struct pci_dev *pdev, + php_intr_callback_t attention_button_callback, + php_intr_callback_t switch_change_callback, + php_intr_callback_t presence_change_callback, + php_intr_callback_t power_fault_callback); + +int shpc_get_ctlr_slot_config( struct controller *ctrl, + int *num_ctlr_slots, + int *first_device_num, + int *physical_slot_num, + int *updown, + int *flags); + +struct hpc_ops { + int (*power_on_slot ) (struct slot *slot); + int (*slot_enable ) (struct slot *slot); + int (*slot_disable ) (struct slot *slot); + int (*enable_all_slots) (struct slot *slot); + int (*pwr_on_all_slots) (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 000000000000..f0c53f850aed --- /dev/null +++ b/drivers/pci/hotplug/shpchp_core.c @@ -0,0 +1,630 @@ +/* + * 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>, <dely.l.sy@intel.com> + * + */ + +#include <linux/config.h> +#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/init.h> +#include <asm/uaccess.h> +#include "shpchp.h" +#include "shpchprm.h" + +/* Global variables */ +int shpchp_debug; +int shpchp_poll_mode; +int shpchp_poll_time; +struct controller *shpchp_ctrl_list; /* = NULL */ +struct pci_func *shpchp_slot_list[256]; + +#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 shpc_start_thread (void); +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 = (struct slot *)hotplug_slot->private; + + dbg("%s - physical_slot = %s\n", __FUNCTION__, hotplug_slot->name); + + kfree(slot->hotplug_slot->info); + kfree(slot->hotplug_slot->name); + kfree(slot->hotplug_slot); + kfree(slot); +} + +static int init_slots(struct controller *ctrl) +{ + struct slot *new_slot; + u8 number_of_slots; + u8 slot_device; + u32 slot_number, sun; + int result = -ENOMEM; + + dbg("%s\n",__FUNCTION__); + + number_of_slots = ctrl->num_slots; + slot_device = ctrl->slot_device_offset; + slot_number = ctrl->first_slot; + + while (number_of_slots) { + new_slot = (struct slot *) kmalloc(sizeof(struct slot), GFP_KERNEL); + if (!new_slot) + goto error; + + memset(new_slot, 0, sizeof(struct slot)); + new_slot->hotplug_slot = kmalloc (sizeof (struct hotplug_slot), GFP_KERNEL); + if (!new_slot->hotplug_slot) + goto error_slot; + memset(new_slot->hotplug_slot, 0, sizeof (struct hotplug_slot)); + + new_slot->hotplug_slot->info = kmalloc (sizeof (struct hotplug_slot_info), GFP_KERNEL); + if (!new_slot->hotplug_slot->info) + goto error_hpslot; + memset(new_slot->hotplug_slot->info, 0, sizeof (struct hotplug_slot_info)); + new_slot->hotplug_slot->name = kmalloc (SLOT_NAME_SIZE, GFP_KERNEL); + if (!new_slot->hotplug_slot->name) + goto error_info; + + new_slot->magic = SLOT_MAGIC; + new_slot->ctrl = ctrl; + new_slot->bus = ctrl->slot_bus; + new_slot->device = slot_device; + new_slot->hpc_ops = ctrl->hpc_ops; + + if (shpchprm_get_physical_slot_number(ctrl, &sun, + new_slot->bus, new_slot->device)) + goto error_name; + + new_slot->number = sun; + new_slot->hp_slot = slot_device - ctrl->slot_device_offset; + + /* register this slot with the hotplug pci core */ + new_slot->hotplug_slot->private = new_slot; + new_slot->hotplug_slot->release = &release_slot; + make_slot_name(new_slot->hotplug_slot->name, SLOT_NAME_SIZE, new_slot); + new_slot->hotplug_slot->ops = &shpchp_hotplug_slot_ops; + + new_slot->hpc_ops->get_power_status(new_slot, &(new_slot->hotplug_slot->info->power_status)); + new_slot->hpc_ops->get_attention_status(new_slot, &(new_slot->hotplug_slot->info->attention_status)); + new_slot->hpc_ops->get_latch_status(new_slot, &(new_slot->hotplug_slot->info->latch_status)); + new_slot->hpc_ops->get_adapter_status(new_slot, &(new_slot->hotplug_slot->info->adapter_status)); + + dbg("Registering bus=%x dev=%x hp_slot=%x sun=%x slot_device_offset=%x\n", new_slot->bus, + new_slot->device, new_slot->hp_slot, new_slot->number, ctrl->slot_device_offset); + result = pci_hp_register (new_slot->hotplug_slot); + if (result) { + err ("pci_hp_register failed with error %d\n", result); + goto error_name; + } + + new_slot->next = ctrl->slot; + ctrl->slot = new_slot; + + number_of_slots--; + slot_device++; + slot_number += ctrl->slot_num_inc; + } + + return 0; + +error_name: + kfree(new_slot->hotplug_slot->name); +error_info: + kfree(new_slot->hotplug_slot->info); +error_hpslot: + kfree(new_slot->hotplug_slot); +error_slot: + kfree(new_slot); +error: + return result; +} + +static void cleanup_slots(struct controller *ctrl) +{ + struct slot *old_slot, *next_slot; + + old_slot = ctrl->slot; + ctrl->slot = NULL; + + while (old_slot) { + next_slot = old_slot->next; + pci_hp_deregister(old_slot->hotplug_slot); + old_slot = next_slot; + } +} + +static int get_ctlr_slot_config(struct controller *ctrl) +{ + int num_ctlr_slots; + int first_device_num; + int physical_slot_num; + int updown; + int rc; + int flags; + + rc = shpc_get_ctlr_slot_config(ctrl, &num_ctlr_slots, &first_device_num, &physical_slot_num, &updown, &flags); + if (rc) { + err("%s: get_ctlr_slot_config fail for b:d (%x:%x)\n", __FUNCTION__, ctrl->bus, ctrl->device); + return -1; + } + + ctrl->num_slots = num_ctlr_slots; + ctrl->slot_device_offset = first_device_num; + ctrl->first_slot = physical_slot_num; + ctrl->slot_num_inc = updown; /* either -1 or 1 */ + + dbg("%s: num_slot(0x%x) 1st_dev(0x%x) psn(0x%x) updown(%d) for b:d (%x:%x)\n", + __FUNCTION__, num_ctlr_slots, first_device_num, physical_slot_num, updown, ctrl->bus, ctrl->device); + + return 0; +} + + +/* + * 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, __FUNCTION__); + + dbg("%s - physical_slot = %s\n", __FUNCTION__, hotplug_slot->name); + + 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, __FUNCTION__); + + dbg("%s - physical_slot = %s\n", __FUNCTION__, hotplug_slot->name); + + return shpchp_enable_slot(slot); +} + + +static int disable_slot (struct hotplug_slot *hotplug_slot) +{ + struct slot *slot = get_slot (hotplug_slot, __FUNCTION__); + + dbg("%s - physical_slot = %s\n", __FUNCTION__, hotplug_slot->name); + + return shpchp_disable_slot(slot); +} + +static int get_power_status (struct hotplug_slot *hotplug_slot, u8 *value) +{ + struct slot *slot = get_slot (hotplug_slot, __FUNCTION__); + int retval; + + dbg("%s - physical_slot = %s\n", __FUNCTION__, hotplug_slot->name); + + 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, __FUNCTION__); + int retval; + + dbg("%s - physical_slot = %s\n", __FUNCTION__, hotplug_slot->name); + + 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, __FUNCTION__); + int retval; + + dbg("%s - physical_slot = %s\n", __FUNCTION__, hotplug_slot->name); + + 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, __FUNCTION__); + int retval; + + dbg("%s - physical_slot = %s\n", __FUNCTION__, hotplug_slot->name); + + 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, __FUNCTION__); + int retval; + + dbg("%s - physical_slot = %s\n", __FUNCTION__, hotplug_slot->name); + + 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, __FUNCTION__); + int retval; + + dbg("%s - physical_slot = %s\n", __FUNCTION__, hotplug_slot->name); + + retval = slot->hpc_ops->get_cur_bus_speed(slot, value); + if (retval < 0) + *value = PCI_SPEED_UNKNOWN; + + return 0; +} + +static int shpc_probe(struct pci_dev *pdev, const struct pci_device_id *ent) +{ + int rc; + struct controller *ctrl; + struct slot *t_slot; + int first_device_num; /* first PCI device number supported by this SHPC */ + int num_ctlr_slots; /* number of slots supported by this SHPC */ + + ctrl = (struct controller *) kmalloc(sizeof(struct controller), GFP_KERNEL); + if (!ctrl) { + err("%s : out of memory\n", __FUNCTION__); + goto err_out_none; + } + memset(ctrl, 0, sizeof(struct controller)); + + dbg("DRV_thread pid = %d\n", current->pid); + + rc = shpc_init(ctrl, pdev, + (php_intr_callback_t) shpchp_handle_attention_button, + (php_intr_callback_t) shpchp_handle_switch_change, + (php_intr_callback_t) shpchp_handle_presence_change, + (php_intr_callback_t) shpchp_handle_power_fault); + if (rc) { + dbg("%s: controller initialization failed\n", SHPC_MODULE_NAME); + goto err_out_free_ctrl; + } + + dbg("%s: controller initialization success\n", __FUNCTION__); + ctrl->pci_dev = pdev; /* pci_dev of the P2P bridge */ + + pci_set_drvdata(pdev, ctrl); + + ctrl->pci_bus = kmalloc (sizeof (*ctrl->pci_bus), GFP_KERNEL); + if (!ctrl->pci_bus) { + err("out of memory\n"); + rc = -ENOMEM; + goto err_out_unmap_mmio_region; + } + + memcpy (ctrl->pci_bus, pdev->bus, sizeof (*ctrl->pci_bus)); + ctrl->bus = pdev->bus->number; + ctrl->slot_bus = pdev->subordinate->number; + + ctrl->device = PCI_SLOT(pdev->devfn); + ctrl->function = PCI_FUNC(pdev->devfn); + dbg("ctrl bus=0x%x, device=%x, function=%x, irq=%x\n", ctrl->bus, ctrl->device, ctrl->function, pdev->irq); + + /* + * Save configuration headers for this and subordinate PCI buses + */ + + rc = get_ctlr_slot_config(ctrl); + if (rc) { + err(msg_initialization_err, rc); + goto err_out_free_ctrl_bus; + } + first_device_num = ctrl->slot_device_offset; + num_ctlr_slots = ctrl->num_slots; + + /* Store PCI Config Space for all devices on this bus */ + rc = shpchp_save_config(ctrl, ctrl->slot_bus, num_ctlr_slots, first_device_num); + if (rc) { + err("%s: unable to save PCI configuration data, error %d\n", __FUNCTION__, rc); + goto err_out_free_ctrl_bus; + } + + /* Get IO, memory, and IRQ resources for new devices */ + rc = shpchprm_find_available_resources(ctrl); + ctrl->add_support = !rc; + + if (rc) { + dbg("shpchprm_find_available_resources = %#x\n", rc); + err("unable to locate PCI configuration resources for hot plug add.\n"); + goto err_out_free_ctrl_bus; + } + + /* Setup the slot information structures */ + rc = init_slots(ctrl); + if (rc) { + err(msg_initialization_err, 6); + goto err_out_free_ctrl_slot; + } + + /* Now hpc_functions (slot->hpc_ops->functions) are ready */ + t_slot = shpchp_find_slot(ctrl, first_device_num); + + /* Check for operation bus speed */ + rc = t_slot->hpc_ops->get_cur_bus_speed(t_slot, &ctrl->speed); + dbg("%s: t_slot->hp_slot %x\n", __FUNCTION__,t_slot->hp_slot); + + if (rc || ctrl->speed == PCI_SPEED_UNKNOWN) { + err(SHPC_MODULE_NAME ": Can't get current bus speed. Set to 33MHz PCI.\n"); + ctrl->speed = PCI_SPEED_33MHz; + } + + /* Finish setting up the hot plug ctrl device */ + ctrl->next_event = 0; + + if (!shpchp_ctrl_list) { + shpchp_ctrl_list = ctrl; + ctrl->next = NULL; + } else { + ctrl->next = shpchp_ctrl_list; + shpchp_ctrl_list = ctrl; + } + + shpchp_create_ctrl_files(ctrl); + + return 0; + +err_out_free_ctrl_slot: + cleanup_slots(ctrl); +err_out_free_ctrl_bus: + kfree(ctrl->pci_bus); +err_out_unmap_mmio_region: + ctrl->hpc_ops->release_ctlr(ctrl); +err_out_free_ctrl: + kfree(ctrl); +err_out_none: + return -ENODEV; +} + + +static int shpc_start_thread(void) +{ + int loop; + int retval = 0; + + dbg("Initialize + Start the notification/polling mechanism \n"); + + retval = shpchp_event_start_thread(); + if (retval) { + dbg("shpchp_event_start_thread() failed\n"); + return retval; + } + + dbg("Initialize slot lists\n"); + /* One slot list for each bus in the system */ + for (loop = 0; loop < 256; loop++) { + shpchp_slot_list[loop] = NULL; + } + + return retval; +} + +static inline void __exit +free_shpchp_res(struct pci_resource *res) +{ + struct pci_resource *tres; + + while (res) { + tres = res; + res = res->next; + kfree(tres); + } +} + +static void __exit unload_shpchpd(void) +{ + struct pci_func *next; + struct pci_func *TempSlot; + int loop; + struct controller *ctrl; + struct controller *tctrl; + + ctrl = shpchp_ctrl_list; + + while (ctrl) { + cleanup_slots(ctrl); + + free_shpchp_res(ctrl->io_head); + free_shpchp_res(ctrl->mem_head); + free_shpchp_res(ctrl->p_mem_head); + free_shpchp_res(ctrl->bus_head); + + kfree (ctrl->pci_bus); + + dbg("%s: calling release_ctlr\n", __FUNCTION__); + ctrl->hpc_ops->release_ctlr(ctrl); + + tctrl = ctrl; + ctrl = ctrl->next; + + kfree(tctrl); + } + + for (loop = 0; loop < 256; loop++) { + next = shpchp_slot_list[loop]; + while (next != NULL) { + free_shpchp_res(next->io_head); + free_shpchp_res(next->mem_head); + free_shpchp_res(next->p_mem_head); + free_shpchp_res(next->bus_head); + + TempSlot = next; + next = next->next; + kfree(TempSlot); + } + } + + /* Stop the notification mechanism */ + shpchp_event_stop_thread(); + +} + + +static struct pci_device_id shpcd_pci_tbl[] = { + { + .class = ((PCI_CLASS_BRIDGE_PCI << 8) | 0x00), + .class_mask = ~0, + .vendor = PCI_ANY_ID, + .device = PCI_ANY_ID, + .subvendor = PCI_ANY_ID, + .subdevice = PCI_ANY_ID, + }, + + { /* 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_one, */ +}; + + + +static int __init shpcd_init(void) +{ + int retval = 0; + +#ifdef CONFIG_HOTPLUG_PCI_SHPC_POLL_EVENT_MODE + shpchp_poll_mode = 1; +#endif + + retval = shpc_start_thread(); + if (retval) + goto error_hpc_init; + + retval = shpchprm_init(PCI); + if (!retval) { + retval = pci_register_driver(&shpc_driver); + dbg("%s: pci_register_driver = %d\n", __FUNCTION__, retval); + info(DRIVER_DESC " version: " DRIVER_VERSION "\n"); + } + +error_hpc_init: + if (retval) { + shpchprm_cleanup(); + shpchp_event_stop_thread(); + } else + shpchprm_print_pirt(); + + return retval; +} + +static void __exit shpcd_cleanup(void) +{ + dbg("unload_shpchpd()\n"); + unload_shpchpd(); + + shpchprm_cleanup(); + + dbg("pci_unregister_driver\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 000000000000..9f90eb8e6ecd --- /dev/null +++ b/drivers/pci/hotplug/shpchp_ctrl.c @@ -0,0 +1,2848 @@ +/* + * 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>, <dely.l.sy@intel.com> + * + */ + +#include <linux/config.h> +#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 "shpchp.h" +#include "shpchprm.h" + +static u32 configure_new_device(struct controller *ctrl, struct pci_func *func, + u8 behind_bridge, struct resource_lists *resources, u8 bridge_bus, u8 bridge_dev); +static int configure_new_function( struct controller *ctrl, struct pci_func *func, + u8 behind_bridge, struct resource_lists *resources, u8 bridge_bus, u8 bridge_dev); +static void interrupt_event_handler(struct controller *ctrl); + +static struct semaphore event_semaphore; /* mutex for process loop (up if something to process) */ +static struct semaphore event_exit; /* guard ensure thread has exited before calling it quits */ +static int event_finished; +static unsigned long pushbutton_pending; /* = 0 */ + +u8 shpchp_disk_irq; +u8 shpchp_nic_irq; + +u8 shpchp_handle_attention_button(u8 hp_slot, void *inst_id) +{ + struct controller *ctrl = (struct controller *) inst_id; + struct slot *p_slot; + u8 rc = 0; + u8 getstatus; + struct pci_func *func; + struct event_info *taskInfo; + + /* Attention Button Change */ + dbg("shpchp: Attention button interrupt received.\n"); + + func = shpchp_slot_find(ctrl->slot_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]); + p_slot = shpchp_find_slot(ctrl, hp_slot + ctrl->slot_device_offset); + + p_slot->hpc_ops->get_adapter_status(p_slot, &(func->presence_save)); + p_slot->hpc_ops->get_latch_status(p_slot, &getstatus); + + ctrl->next_event = (ctrl->next_event + 1) % 10; + taskInfo->hp_slot = hp_slot; + + rc++; + + /* + * Button pressed - See if need to TAKE ACTION!!! + */ + info("Button pressed on Slot(%d)\n", ctrl->first_slot + hp_slot); + taskInfo->event_type = INT_BUTTON_PRESS; + + if ((p_slot->state == BLINKINGON_STATE) + || (p_slot->state == BLINKINGOFF_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 + */ + taskInfo->event_type = INT_BUTTON_CANCEL; + info("Button cancel on Slot(%d)\n", ctrl->first_slot + hp_slot); + } else if ((p_slot->state == POWERON_STATE) + || (p_slot->state == POWEROFF_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 + */ + taskInfo->event_type = INT_BUTTON_IGNORE; + info("Button ignore on Slot(%d)\n", ctrl->first_slot + hp_slot); + } + + if (rc) + up(&event_semaphore); /* signal event thread that new event is posted */ + + return 0; + +} + +u8 shpchp_handle_switch_change(u8 hp_slot, void *inst_id) +{ + struct controller *ctrl = (struct controller *) inst_id; + struct slot *p_slot; + u8 rc = 0; + u8 getstatus; + struct pci_func *func; + struct event_info *taskInfo; + + /* Switch Change */ + dbg("shpchp: Switch interrupt received.\n"); + + func = shpchp_slot_find(ctrl->slot_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++; + p_slot = shpchp_find_slot(ctrl, hp_slot + ctrl->slot_device_offset); + p_slot->hpc_ops->get_adapter_status(p_slot, &(func->presence_save)); + p_slot->hpc_ops->get_latch_status(p_slot, &getstatus); + dbg("%s: Card present %x Power status %x\n", __FUNCTION__, + func->presence_save, func->pwr_save); + + if (getstatus) { + /* + * Switch opened + */ + info("Latch open on Slot(%d)\n", ctrl->first_slot + hp_slot); + func->switch_save = 0; + taskInfo->event_type = INT_SWITCH_OPEN; + if (func->pwr_save && func->presence_save) { + taskInfo->event_type = INT_POWER_FAULT; + err("Surprise Removal of card\n"); + } + } else { + /* + * Switch closed + */ + info("Latch close on Slot(%d)\n", ctrl->first_slot + hp_slot); + func->switch_save = 0x10; + taskInfo->event_type = INT_SWITCH_CLOSE; + } + + if (rc) + up(&event_semaphore); /* signal event thread that new event is posted */ + + return rc; +} + +u8 shpchp_handle_presence_change(u8 hp_slot, void *inst_id) +{ + struct controller *ctrl = (struct controller *) inst_id; + struct slot *p_slot; + u8 rc = 0; + /*u8 temp_byte;*/ + struct pci_func *func; + struct event_info *taskInfo; + + /* Presence Change */ + dbg("shpchp: Presence/Notify input change.\n"); + + func = shpchp_slot_find(ctrl->slot_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++; + 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, &(func->presence_save)); + if (func->presence_save) { + /* + * Card Present + */ + info("Card present on Slot(%d)\n", ctrl->first_slot + hp_slot); + taskInfo->event_type = INT_PRESENCE_ON; + } else { + /* + * Not Present + */ + info("Card not present on Slot(%d)\n", ctrl->first_slot + hp_slot); + taskInfo->event_type = INT_PRESENCE_OFF; + } + + if (rc) + up(&event_semaphore); /* signal event thread that new event is posted */ + + return rc; +} + +u8 shpchp_handle_power_fault(u8 hp_slot, void *inst_id) +{ + struct controller *ctrl = (struct controller *) inst_id; + struct slot *p_slot; + u8 rc = 0; + struct pci_func *func; + struct event_info *taskInfo; + + /* Power fault */ + dbg("shpchp: Power fault interrupt received.\n"); + + func = shpchp_slot_find(ctrl->slot_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++; + 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 + */ + info("Power fault cleared on Slot(%d)\n", ctrl->first_slot + hp_slot); + func->status = 0x00; + taskInfo->event_type = INT_POWER_FAULT_CLEAR; + } else { + /* + * Power fault + */ + info("Power fault on Slot(%d)\n", ctrl->first_slot + hp_slot); + taskInfo->event_type = INT_POWER_FAULT; + /* set power fault status for this board */ + func->status = 0xFF; + info("power fault bit %x set\n", hp_slot); + } + if (rc) + up(&event_semaphore); /* signal event thread that new event is posted */ + + return rc; +} + + +/* + * sort_by_size + * + * Sorts nodes on the list by their length. + * Smallest first. + * + */ +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 + * + * Sorts nodes on the list by their length. + * Largest first. + * + */ +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 + * + * Returns zero or one node of resources that aren't in use + * + */ +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 = shpchp_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; + node->next = NULL; + } else { + prevnode = *head; + while (prevnode->next != node) + prevnode = prevnode->next; + + prevnode->next = node->next; + node->next = NULL; + } + + return(node); +} + + +/* + * do_bridge_resource_split + * + * Returns zero or one node of resources that aren't in use + * + */ +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; + + if (!(*head)) + return(NULL); + + rc = shpchp_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) { + kfree(node); + return(NULL); + } + + 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) { + kfree(node); + return(NULL); + } + + node->length -= (temp_dword - node->base); + node->base = temp_dword; + } + + if (node->length & (alignment - 1)) { + /* There's stuff in use after this node */ + kfree(node); + return(NULL); + } + + return(node); +} + + +/* + * get_io_resource + * + * this function sorts the resource list by size and then + * 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. + * + * size must be a power of two. + */ +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 = NULL; + u32 temp_dword; + + if (!(*head)) + return(NULL); + + if ( shpchp_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 */ + if (*head == node) { + *head = node->next; + } else { + prevnode = *head; + while (prevnode->next != node) + prevnode = prevnode->next; + + prevnode->next = node->next; + } + node->next = NULL; + /* Stop looping */ + break; + } + + return(node); +} + + +/* + * get_max_resource + * + * 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. + * J.I. modified to put max size limits of; 64M->32M->16M->8M->4M->1M + * This is needed to avoid allocating entire ACPI _CRS res to one child bridge/slot. + */ +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; + u32 max_size[] = { 0x4000000, 0x2000000, 0x1000000, 0x0800000, 0x0400000, 0x0200000, 0x0100000, 0x00 }; + int i; + + if (!(*head)) + return(NULL); + + if (shpchp_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; + + /* Put it next in the list */ + 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; + + /* Put it in the list */ + 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; + + for ( i = 0; max_size[i] > size; i++) { + if (max->length > max_size[i]) { + split_node = kmalloc(sizeof(*split_node), + GFP_KERNEL); + if (!split_node) + break; /* return (NULL); */ + split_node->base = max->base + max_size[i]; + split_node->length = max->length - max_size[i]; + max->length = max_size[i]; + /* Put it next in the list */ + split_node->next = max->next; + max->next = split_node; + break; + } + } + + /* Now take it out of the list */ + temp = (struct pci_resource*) *head; + if (temp == max) { + *head = max->next; + } else { + while (temp && temp->next != max) { + temp = temp->next; + } + + temp->next = max->next; + } + + max->next = NULL; + return(max); + } + + /* If we get here, we couldn't find one */ + return(NULL); +} + + +/* + * get_resource + * + * 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 (!(*head)) + return(NULL); + + if ( shpchp_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 =0x%x node=%p, base=0x%x, length=0x%x\n", + __FUNCTION__, size, node, node->base, node->length); + if (node->length < size) + continue; + + if (node->base & (size - 1)) { + dbg("%s: not aligned\n", __FUNCTION__); + /* 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) { + dbg("%s: too big\n", __FUNCTION__); + /* 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", __FUNCTION__); + /* 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; + /* Stop looping */ + break; + } + return(node); +} + + +/* + * shpchp_resource_sort_and_combine + * + * 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 shpchp_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", __FUNCTION__, 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); +} + + +/** + * shpchp_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 *shpchp_slot_create(u8 busnumber) +{ + struct pci_func *new_slot; + struct pci_func *next; + + new_slot = kmalloc(sizeof(*new_slot), GFP_KERNEL); + + if (new_slot == NULL) { + return(new_slot); + } + + memset(new_slot, 0, sizeof(struct pci_func)); + + new_slot->next = NULL; + new_slot->configured = 1; + + if (shpchp_slot_list[busnumber] == NULL) { + shpchp_slot_list[busnumber] = new_slot; + } else { + next = shpchp_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 = shpchp_slot_list[old_slot->bus]; + + if (next == NULL) { + return(1); + } + + if (next == old_slot) { + shpchp_slot_list[old_slot->bus] = old_slot->next; + shpchp_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; + shpchp_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; + + if (bridge == NULL) + return(1); + + secondaryBus = (bridge->config_space[0x06] >> 8) & 0xFF; + subordinateBus = (bridge->config_space[0x06] >> 16) & 0xFF; + + for (tempBus = secondaryBus; tempBus <= subordinateBus; tempBus++) { + next = shpchp_slot_list[tempBus]; + + while (!slot_remove(next)) { + next = shpchp_slot_list[tempBus]; + } + } + + next = shpchp_slot_list[bridge->bus]; + + if (next == NULL) { + return(1); + } + + if (next == bridge) { + shpchp_slot_list[bridge->bus] = bridge->next; + kfree(bridge); + return(0); + } + + while ((next->next != bridge) && (next->next != NULL)) { + next = next->next; + } + + if (next->next == bridge) { + next->next = bridge->next; + kfree(bridge); + return(0); + } else + return(2); +} + + +/** + * shpchp_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 *shpchp_slot_find(u8 bus, u8 device, u8 index) +{ + int found = -1; + struct pci_func *func; + + func = shpchp_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); +} + +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; +} + + +/* The following routines constitute the bulk of the + hotplug controller logic + */ +static u32 change_bus_speed(struct controller *ctrl, struct slot *p_slot, enum pci_bus_speed speed) +{ + u32 rc = 0; + + dbg("%s: change to speed %d\n", __FUNCTION__, speed); + down(&ctrl->crit_sect); + if ((rc = p_slot->hpc_ops->set_bus_speed_mode(p_slot, speed))) { + err("%s: Issue of set bus speed mode command failed\n", __FUNCTION__); + up(&ctrl->crit_sect); + return WRONG_BUS_FREQUENCY; + } + wait_for_ctrl_irq (ctrl); + + if ((rc = p_slot->hpc_ops->check_cmd_status(ctrl))) { + err("%s: Can't set bus speed/mode in the case of adapter & bus mismatch\n", + __FUNCTION__); + err("%s: Error code (%d)\n", __FUNCTION__, rc); + up(&ctrl->crit_sect); + return WRONG_BUS_FREQUENCY; + } + up(&ctrl->crit_sect); + return rc; +} + +static u32 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) +{ + u32 rc = 0; + + if (flag != 0) { /* Other slots on the same bus are occupied */ + if ( asp < bsp ) { + err("%s: speed of bus %x and adapter %x mismatch\n", __FUNCTION__, bsp, asp); + return WRONG_BUS_FREQUENCY; + } + } else { + /* Other slots on the same bus are empty */ + if (msp == bsp) { + /* if adapter_speed >= bus_speed, do nothing */ + if (asp < bsp) { + /* + * Try to lower bus speed to accommodate the adapter if other slots + * on the same controller are empty + */ + if ((rc = change_bus_speed(ctrl, pslot, asp))) + return rc; + } + } else { + if (asp < msp) { + if ((rc = change_bus_speed(ctrl, pslot, asp))) + return rc; + } else { + if ((rc = change_bus_speed(ctrl, pslot, msp))) + return rc; + } + } + } + return rc; +} + +/** + * board_added - Called after a board has been added to the system. + * + * Turns power on for the board + * Configures board + * + */ +static u32 board_added(struct pci_func * func, struct controller * ctrl) +{ + u8 hp_slot; + u8 slots_not_empty = 0; + int index; + u32 temp_register = 0xFFFFFFFF; + u32 retval, rc = 0; + struct pci_func *new_func = NULL; + struct slot *p_slot; + struct resource_lists res_lists; + enum pci_bus_speed adapter_speed, bus_speed, max_bus_speed; + u8 pi, mode; + + p_slot = shpchp_find_slot(ctrl, func->device); + hp_slot = func->device - ctrl->slot_device_offset; + + dbg("%s: func->device, slot_offset, hp_slot = %d, %d ,%d\n", __FUNCTION__, func->device, ctrl->slot_device_offset, hp_slot); + + /* Wait for exclusive access to hardware */ + down(&ctrl->crit_sect); + + /* Power on slot without connecting to bus */ + rc = p_slot->hpc_ops->power_on_slot(p_slot); + if (rc) { + err("%s: Failed to power on slot\n", __FUNCTION__); + /* Done with exclusive hardware access */ + up(&ctrl->crit_sect); + return -1; + } + + /* Wait for the command to complete */ + wait_for_ctrl_irq (ctrl); + + rc = p_slot->hpc_ops->check_cmd_status(ctrl); + if (rc) { + err("%s: Failed to power on slot, error code(%d)\n", __FUNCTION__, rc); + /* Done with exclusive hardware access */ + up(&ctrl->crit_sect); + 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))) { + err("%s: Issue of set bus speed mode command failed\n", __FUNCTION__); + up(&ctrl->crit_sect); + return WRONG_BUS_FREQUENCY; + } + wait_for_ctrl_irq (ctrl); + + if ((rc = p_slot->hpc_ops->check_cmd_status(ctrl))) { + err("%s: Can't set bus speed/mode in the case of adapter & bus mismatch\n", + __FUNCTION__); + err("%s: Error code (%d)\n", __FUNCTION__, rc); + up(&ctrl->crit_sect); + return WRONG_BUS_FREQUENCY; + } + /* turn on board, blink green LED, turn off Amber LED */ + if ((rc = p_slot->hpc_ops->slot_enable(p_slot))) { + err("%s: Issue of Slot Enable command failed\n", __FUNCTION__); + up(&ctrl->crit_sect); + return rc; + } + wait_for_ctrl_irq (ctrl); + + if ((rc = p_slot->hpc_ops->check_cmd_status(ctrl))) { + err("%s: Failed to enable slot, error code(%d)\n", __FUNCTION__, rc); + up(&ctrl->crit_sect); + return rc; + } + } + + rc = p_slot->hpc_ops->get_adapter_speed(p_slot, &adapter_speed); + /* 0 = PCI 33Mhz, 1 = PCI 66 Mhz, 2 = PCI-X 66 PA, 4 = PCI-X 66 ECC, */ + /* 5 = PCI-X 133 PA, 7 = PCI-X 133 ECC, 0xa = PCI-X 133 Mhz 266, */ + /* 0xd = PCI-X 133 Mhz 533 */ + /* This encoding is different from the one used in cur_bus_speed & */ + /* max_bus_speed */ + + if (rc || adapter_speed == PCI_SPEED_UNKNOWN) { + err("%s: Can't get adapter speed or bus mode mismatch\n", __FUNCTION__); + /* Done with exclusive hardware access */ + up(&ctrl->crit_sect); + return WRONG_BUS_FREQUENCY; + } + + rc = p_slot->hpc_ops->get_cur_bus_speed(p_slot, &bus_speed); + if (rc || bus_speed == PCI_SPEED_UNKNOWN) { + err("%s: Can't get bus operation speed\n", __FUNCTION__); + /* Done with exclusive hardware access */ + up(&ctrl->crit_sect); + return WRONG_BUS_FREQUENCY; + } + + rc = p_slot->hpc_ops->get_max_bus_speed(p_slot, &max_bus_speed); + if (rc || max_bus_speed == PCI_SPEED_UNKNOWN) { + err("%s: Can't get max bus operation speed\n", __FUNCTION__); + max_bus_speed = bus_speed; + } + + /* Done with exclusive hardware access */ + up(&ctrl->crit_sect); + + if ((rc = p_slot->hpc_ops->get_prog_int(p_slot, &pi))) { + err("%s: Can't get controller programming interface, set it to 1\n", __FUNCTION__); + pi = 1; + } + + /* Check if there are other slots or devices on the same bus */ + if (!list_empty(&ctrl->pci_dev->subordinate->devices)) + slots_not_empty = 1; + + dbg("%s: slots_not_empty %d, pi %d\n", __FUNCTION__, + slots_not_empty, pi); + dbg("adapter_speed %d, bus_speed %d, max_bus_speed %d\n", + adapter_speed, bus_speed, max_bus_speed); + + if (pi == 2) { + dbg("%s: In PI = %d\n", __FUNCTION__, pi); + if ((rc = p_slot->hpc_ops->get_mode1_ECC_cap(p_slot, &mode))) { + err("%s: Can't get Mode1_ECC, set mode to 0\n", __FUNCTION__); + mode = 0; + } + + switch (adapter_speed) { + case PCI_SPEED_133MHz_PCIX_533: + case PCI_SPEED_133MHz_PCIX_266: + if ((bus_speed != adapter_speed) && + ((rc = fix_bus_speed(ctrl, p_slot, slots_not_empty, adapter_speed, bus_speed, max_bus_speed)))) + return rc; + break; + case PCI_SPEED_133MHz_PCIX_ECC: + case PCI_SPEED_133MHz_PCIX: + if (mode) { /* Bus - Mode 1 ECC */ + if ((bus_speed != 0x7) && + ((rc = fix_bus_speed(ctrl, p_slot, slots_not_empty, adapter_speed, bus_speed, max_bus_speed)))) + return rc; + } else { + if ((bus_speed != 0x4) && + ((rc = fix_bus_speed(ctrl, p_slot, slots_not_empty, adapter_speed, bus_speed, max_bus_speed)))) + return rc; + } + break; + case PCI_SPEED_66MHz_PCIX_ECC: + case PCI_SPEED_66MHz_PCIX: + if (mode) { /* Bus - Mode 1 ECC */ + if ((bus_speed != 0x5) && + ((rc = fix_bus_speed(ctrl, p_slot, slots_not_empty, adapter_speed, bus_speed, max_bus_speed)))) + return rc; + } else { + if ((bus_speed != 0x2) && + ((rc = fix_bus_speed(ctrl, p_slot, slots_not_empty, adapter_speed, bus_speed, max_bus_speed)))) + return rc; + } + break; + case PCI_SPEED_66MHz: + if ((bus_speed != 0x1) && + ((rc = fix_bus_speed(ctrl, p_slot, slots_not_empty, adapter_speed, bus_speed, max_bus_speed)))) + return rc; + break; + case PCI_SPEED_33MHz: + if (bus_speed > 0x0) { + if (slots_not_empty == 0) { + if ((rc = change_bus_speed(ctrl, p_slot, adapter_speed))) + return rc; + } else { + err("%s: speed of bus %x and adapter %x mismatch\n", __FUNCTION__, bus_speed, adapter_speed); + return WRONG_BUS_FREQUENCY; + } + } + break; + default: + err("%s: speed of bus %x and adapter %x mismatch\n", __FUNCTION__, bus_speed, adapter_speed); + return WRONG_BUS_FREQUENCY; + } + } else { + /* If adpater_speed == bus_speed, nothing to do here */ + dbg("%s: In PI = %d\n", __FUNCTION__, pi); + if ((adapter_speed != bus_speed) && + ((rc = fix_bus_speed(ctrl, p_slot, slots_not_empty, adapter_speed, bus_speed, max_bus_speed)))) + return rc; + } + + down(&ctrl->crit_sect); + /* turn on board, blink green LED, turn off Amber LED */ + if ((rc = p_slot->hpc_ops->slot_enable(p_slot))) { + err("%s: Issue of Slot Enable command failed\n", __FUNCTION__); + up(&ctrl->crit_sect); + return rc; + } + wait_for_ctrl_irq (ctrl); + + if ((rc = p_slot->hpc_ops->check_cmd_status(ctrl))) { + err("%s: Failed to enable slot, error code(%d)\n", __FUNCTION__, rc); + up(&ctrl->crit_sect); + return rc; + } + + up(&ctrl->crit_sect); + + /* Wait for ~1 second */ + dbg("%s: before long_delay\n", __FUNCTION__); + wait_for_ctrl_irq (ctrl); + dbg("%s: after long_delay\n", __FUNCTION__); + + dbg("%s: func status = %x\n", __FUNCTION__, 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", __FUNCTION__, temp_register); + rc = POWER_FAILURE; + func->status = 0; + } else { + /* Get vendor/device ID u32 */ + rc = pci_bus_read_config_dword (ctrl->pci_dev->subordinate, PCI_DEVFN(func->device, func->function), + PCI_VENDOR_ID, &temp_register); + dbg("%s: pci_bus_read_config_dword returns %d\n", __FUNCTION__, rc); + dbg("%s: temp_register is %x\n", __FUNCTION__, temp_register); + + if (rc != 0) { + /* Something's wrong here */ + temp_register = 0xFFFFFFFF; + dbg("%s: temp register set to %x by error\n", __FUNCTION__, 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, 0, 0); + dbg("%s: back from configure_new_device\n", __FUNCTION__); + + 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; + + shpchp_resource_sort_and_combine(&(ctrl->mem_head)); + shpchp_resource_sort_and_combine(&(ctrl->p_mem_head)); + shpchp_resource_sort_and_combine(&(ctrl->io_head)); + shpchp_resource_sort_and_combine(&(ctrl->bus_head)); + + if (rc) { + /* Wait for exclusive access to hardware */ + down(&ctrl->crit_sect); + + /* turn off slot, turn on Amber LED, turn off Green LED */ + retval = p_slot->hpc_ops->slot_disable(p_slot); + if (retval) { + err("%s: Issue of Slot Enable command failed\n", __FUNCTION__); + /* Done with exclusive hardware access */ + up(&ctrl->crit_sect); + return retval; + } + /* Wait for the command to complete */ + wait_for_ctrl_irq (ctrl); + + retval = p_slot->hpc_ops->check_cmd_status(ctrl); + if (retval) { + err("%s: Failed to disable slot, error code(%d)\n", __FUNCTION__, retval); + /* Done with exclusive hardware access */ + up(&ctrl->crit_sect); + return retval; + } + + /* Done with exclusive hardware access */ + up(&ctrl->crit_sect); + + return(rc); + } + shpchp_save_slot_config(ctrl, func); + + func->status = 0; + func->switch_save = 0x10; + func->is_a_board = 0x01; + func->pwr_save = 1; + + /* Next, we will instantiate the linux pci_dev structures + * (with appropriate driver notification, if already present) + */ + index = 0; + do { + new_func = shpchp_slot_find(ctrl->slot_bus, func->device, index++); + if (new_func && !new_func->pci_dev) { + dbg("%s:call pci_hp_configure_dev\n", __FUNCTION__); + shpchp_configure_device(ctrl, new_func); + } + } while (new_func); + + /* Wait for exclusive access to hardware */ + down(&ctrl->crit_sect); + + p_slot->hpc_ops->green_led_on(p_slot); + + /* Wait for the command to complete */ + wait_for_ctrl_irq (ctrl); + + + /* Done with exclusive hardware access */ + up(&ctrl->crit_sect); + + } else { + /* Wait for exclusive access to hardware */ + down(&ctrl->crit_sect); + + /* turn off slot, turn on Amber LED, turn off Green LED */ + rc = p_slot->hpc_ops->slot_disable(p_slot); + if (rc) { + err("%s: Issue of Slot Disable command failed\n", __FUNCTION__); + /* Done with exclusive hardware access */ + up(&ctrl->crit_sect); + return rc; + } + /* Wait for the command to complete */ + wait_for_ctrl_irq (ctrl); + + rc = p_slot->hpc_ops->check_cmd_status(ctrl); + if (rc) { + err("%s: Failed to disable slot, error code(%d)\n", __FUNCTION__, rc); + /* Done with exclusive hardware access */ + up(&ctrl->crit_sect); + return rc; + } + + /* Done with exclusive hardware access */ + up(&ctrl->crit_sect); + + return(rc); + } + return 0; +} + + +/** + * remove_board - Turns off slot and LED's + * + */ +static u32 remove_board(struct pci_func *func, struct controller *ctrl) +{ + int index; + u8 skip = 0; + u8 device; + u8 hp_slot; + u32 rc; + struct resource_lists res_lists; + struct pci_func *temp_func; + struct slot *p_slot; + + if (func == NULL) + return(1); + + if (shpchp_unconfigure_device(func)) + return(1); + + device = func->device; + + hp_slot = func->device - ctrl->slot_device_offset; + p_slot = shpchp_find_slot(ctrl, hp_slot + ctrl->slot_device_offset); + + dbg("In %s, hp_slot = %d\n", __FUNCTION__, hp_slot); + + if ((ctrl->add_support) && + !(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 = func; + + while ((temp_func = shpchp_slot_find(temp_func->bus, temp_func->device, index++))) { + if (temp_func->bus_head || temp_func->mem_head + || temp_func->p_mem_head || temp_func->io_head) { + skip = 1; + break; + } + } + + if (!skip) + rc = shpchp_save_used_resources(ctrl, func, DISABLE_CARD); + } + /* Change status to shutdown */ + if (func->is_a_board) + func->status = 0x01; + func->configured = 0; + + /* Wait for exclusive access to hardware */ + down(&ctrl->crit_sect); + + /* turn off slot, turn on Amber LED, turn off Green LED */ + rc = p_slot->hpc_ops->slot_disable(p_slot); + if (rc) { + err("%s: Issue of Slot Disable command failed\n", __FUNCTION__); + /* Done with exclusive hardware access */ + up(&ctrl->crit_sect); + return rc; + } + /* Wait for the command to complete */ + wait_for_ctrl_irq (ctrl); + + rc = p_slot->hpc_ops->check_cmd_status(ctrl); + if (rc) { + err("%s: Failed to disable slot, error code(%d)\n", __FUNCTION__, rc); + /* Done with exclusive hardware access */ + up(&ctrl->crit_sect); + return rc; + } + + rc = p_slot->hpc_ops->set_attention_status(p_slot, 0); + if (rc) { + err("%s: Issue of Set Attention command failed\n", __FUNCTION__); + /* Done with exclusive hardware access */ + up(&ctrl->crit_sect); + return rc; + } + /* Wait for the command to complete */ + wait_for_ctrl_irq (ctrl); + + /* Done with exclusive hardware access */ + up(&ctrl->crit_sect); + + if (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; + + dbg("Returning resources to ctlr lists for (B/D/F) = (%#x/%#x/%#x)\n", func->bus, + func->device, func->function); + + shpchp_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; + + shpchp_resource_sort_and_combine(&(ctrl->mem_head)); + shpchp_resource_sort_and_combine(&(ctrl->p_mem_head)); + shpchp_resource_sort_and_combine(&(ctrl->io_head)); + shpchp_resource_sort_and_combine(&(ctrl->bus_head)); + + if (is_bridge(func)) { + dbg("PCI Bridge Hot-Remove s:b:d:f(%02x:%02x:%02x:%02x)\n", ctrl->seg, func->bus, + func->device, func->function); + bridge_slot_remove(func); + } else + dbg("PCI Function Hot-Remove s:b:d:f(%02x:%02x:%02x:%02x)\n", ctrl->seg, func->bus, + func->device, func->function); + slot_remove(func); + + func = shpchp_slot_find(ctrl->slot_bus, device, 0); + } + + /* Setup slot structure with entry for empty slot */ + func = shpchp_slot_create(ctrl->slot_bus); + + if (func == NULL) { + return(1); + } + + func->bus = ctrl->slot_bus; + func->device = device; + func->function = 0; + func->configured = 0; + func->switch_save = 0x10; + func->pwr_save = 0; + func->is_a_board = 0; + } + + return 0; +} + + +static void pushbutton_helper_thread (unsigned long data) +{ + pushbutton_pending = data; + + up(&event_semaphore); +} + + +/** + * shpchp_pushbutton_thread + * + * Scheduled procedure to handle blocking stuff for the pushbuttons + * Handles all pending events and exits. + * + */ +static void shpchp_pushbutton_thread (unsigned long slot) +{ + struct slot *p_slot = (struct slot *) slot; + u8 getstatus; + + pushbutton_pending = 0; + + if (!p_slot) { + dbg("%s: Error! slot NULL\n", __FUNCTION__); + return; + } + + p_slot->hpc_ops->get_power_status(p_slot, &getstatus); + if (getstatus) { + p_slot->state = POWEROFF_STATE; + dbg("In power_down_board, b:d(%x:%x)\n", p_slot->bus, p_slot->device); + + shpchp_disable_slot(p_slot); + p_slot->state = STATIC_STATE; + } else { + p_slot->state = POWERON_STATE; + dbg("In add_board, b:d(%x:%x)\n", p_slot->bus, p_slot->device); + + if (shpchp_enable_slot(p_slot)) { + /* Wait for exclusive access to hardware */ + down(&p_slot->ctrl->crit_sect); + + p_slot->hpc_ops->green_led_off(p_slot); + + /* Wait for the command to complete */ + wait_for_ctrl_irq (p_slot->ctrl); + + /* Done with exclusive hardware access */ + up(&p_slot->ctrl->crit_sect); + } + p_slot->state = STATIC_STATE; + } + + return; +} + + +/* this is the main worker thread */ +static int event_thread(void* data) +{ + struct controller *ctrl; + lock_kernel(); + daemonize("shpchpd_event"); + unlock_kernel(); + + while (1) { + dbg("!!!!event_thread sleeping\n"); + down_interruptible (&event_semaphore); + dbg("event_thread woken finished = %d\n", event_finished); + if (event_finished || signal_pending(current)) + break; + /* Do stuff here */ + if (pushbutton_pending) + shpchp_pushbutton_thread(pushbutton_pending); + else + for (ctrl = shpchp_ctrl_list; ctrl; ctrl=ctrl->next) + interrupt_event_handler(ctrl); + } + dbg("event_thread signals exit\n"); + up(&event_exit); + return 0; +} + +int shpchp_event_start_thread (void) +{ + int pid; + + /* initialize our semaphores */ + init_MUTEX_LOCKED(&event_exit); + event_finished=0; + + init_MUTEX_LOCKED(&event_semaphore); + pid = kernel_thread(event_thread, NULL, 0); + + if (pid < 0) { + err ("Can't start up our event thread\n"); + return -1; + } + dbg("Our event thread pid = %d\n", pid); + return 0; +} + + +void shpchp_event_stop_thread (void) +{ + event_finished = 1; + dbg("event_thread finish command given\n"); + up(&event_semaphore); + dbg("wait for event_thread to exit\n"); + down(&event_exit); +} + + +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; +} + +static void interrupt_event_handler(struct controller *ctrl) +{ + int loop = 0; + int change = 1; + struct pci_func *func; + u8 hp_slot; + u8 getstatus; + struct slot *p_slot; + + dbg("%s:\n", __FUNCTION__); + while (change) { + change = 0; + + for (loop = 0; loop < 10; loop++) { + if (ctrl->event_queue[loop].event_type != 0) { + dbg("%s:loop %x event_type %x\n", __FUNCTION__, loop, + ctrl->event_queue[loop].event_type); + hp_slot = ctrl->event_queue[loop].hp_slot; + + func = shpchp_slot_find(ctrl->slot_bus, (hp_slot + ctrl->slot_device_offset), 0); + + p_slot = shpchp_find_slot(ctrl, hp_slot + ctrl->slot_device_offset); + + dbg("%s: hp_slot %d, func %p, p_slot %p\n", __FUNCTION__, hp_slot, func, p_slot); + + if (ctrl->event_queue[loop].event_type == INT_BUTTON_CANCEL) { + dbg("%s: button cancel\n", __FUNCTION__); + del_timer(&p_slot->task_event); + + switch (p_slot->state) { + case BLINKINGOFF_STATE: + /* Wait for exclusive access to hardware */ + down(&ctrl->crit_sect); + + p_slot->hpc_ops->green_led_on(p_slot); + /* Wait for the command to complete */ + wait_for_ctrl_irq (ctrl); + + p_slot->hpc_ops->set_attention_status(p_slot, 0); + + /* Wait for the command to complete */ + wait_for_ctrl_irq (ctrl); + + /* Done with exclusive hardware access */ + up(&ctrl->crit_sect); + break; + case BLINKINGON_STATE: + /* Wait for exclusive access to hardware */ + down(&ctrl->crit_sect); + + p_slot->hpc_ops->green_led_off(p_slot); + /* Wait for the command to complete */ + wait_for_ctrl_irq (ctrl); + + p_slot->hpc_ops->set_attention_status(p_slot, 0); + /* Wait for the command to complete */ + wait_for_ctrl_irq (ctrl); + + /* Done with exclusive hardware access */ + up(&ctrl->crit_sect); + + break; + default: + warn("Not a valid state\n"); + return; + } + info(msg_button_cancel, p_slot->number); + p_slot->state = STATIC_STATE; + } else if (ctrl->event_queue[loop].event_type == INT_BUTTON_PRESS) { + /* Button Pressed (No action on 1st press...) */ + dbg("%s: Button pressed\n", __FUNCTION__); + + p_slot->hpc_ops->get_power_status(p_slot, &getstatus); + if (getstatus) { + /* slot is on */ + dbg("%s: slot is on\n", __FUNCTION__); + p_slot->state = BLINKINGOFF_STATE; + info(msg_button_off, p_slot->number); + } else { + /* slot is off */ + dbg("%s: slot is off\n", __FUNCTION__); + p_slot->state = BLINKINGON_STATE; + info(msg_button_on, p_slot->number); + } + + /* Wait for exclusive access to hardware */ + down(&ctrl->crit_sect); + + /* blink green LED and turn off amber */ + p_slot->hpc_ops->green_led_blink(p_slot); + /* Wait for the command to complete */ + wait_for_ctrl_irq (ctrl); + + p_slot->hpc_ops->set_attention_status(p_slot, 0); + + /* Wait for the command to complete */ + wait_for_ctrl_irq (ctrl); + + /* Done with exclusive hardware access */ + up(&ctrl->crit_sect); + + init_timer(&p_slot->task_event); + p_slot->task_event.expires = jiffies + 5 * HZ; /* 5 second delay */ + p_slot->task_event.function = (void (*)(unsigned long)) pushbutton_helper_thread; + p_slot->task_event.data = (unsigned long) p_slot; + + dbg("%s: add_timer p_slot = %p\n", __FUNCTION__,(void *) p_slot); + add_timer(&p_slot->task_event); + } else if (ctrl->event_queue[loop].event_type == INT_POWER_FAULT) { + /***********POWER FAULT********************/ + dbg("%s: power fault\n", __FUNCTION__); + /* Wait for exclusive access to hardware */ + down(&ctrl->crit_sect); + + p_slot->hpc_ops->set_attention_status(p_slot, 1); + /* Wait for the command to complete */ + wait_for_ctrl_irq (ctrl); + + p_slot->hpc_ops->green_led_off(p_slot); + /* Wait for the command to complete */ + wait_for_ctrl_irq (ctrl); + + /* Done with exclusive hardware access */ + up(&ctrl->crit_sect); + } else { + /* refresh notification */ + if (p_slot) + update_slot_info(p_slot); + } + + ctrl->event_queue[loop].event_type = 0; + + change = 1; + } + } /* End of FOR loop */ + } + + return; +} + + +int shpchp_enable_slot (struct slot *p_slot) +{ + u8 getstatus = 0; + int rc; + struct pci_func *func; + + func = shpchp_slot_find(p_slot->bus, p_slot->device, 0); + if (!func) { + dbg("%s: Error! slot NULL\n", __FUNCTION__); + return 1; + } + + /* Check to see if (latch closed, card present, power off) */ + down(&p_slot->ctrl->crit_sect); + rc = p_slot->hpc_ops->get_adapter_status(p_slot, &getstatus); + if (rc || !getstatus) { + info("%s: no adapter on slot(%x)\n", __FUNCTION__, p_slot->number); + up(&p_slot->ctrl->crit_sect); + return 1; + } + rc = p_slot->hpc_ops->get_latch_status(p_slot, &getstatus); + if (rc || getstatus) { + info("%s: latch open on slot(%x)\n", __FUNCTION__, p_slot->number); + up(&p_slot->ctrl->crit_sect); + return 1; + } + rc = p_slot->hpc_ops->get_power_status(p_slot, &getstatus); + if (rc || getstatus) { + info("%s: already enabled on slot(%x)\n", __FUNCTION__, p_slot->number); + up(&p_slot->ctrl->crit_sect); + return 1; + } + up(&p_slot->ctrl->crit_sect); + + slot_remove(func); + + func = shpchp_slot_create(p_slot->bus); + if (func == NULL) + return 1; + + func->bus = p_slot->bus; + func->device = p_slot->device; + func->function = 0; + func->configured = 0; + func->is_a_board = 1; + + /* We have to save the presence info for these slots */ + p_slot->hpc_ops->get_adapter_status(p_slot, &(func->presence_save)); + p_slot->hpc_ops->get_power_status(p_slot, &(func->pwr_save)); + dbg("%s: func->pwr_save %x\n", __FUNCTION__, func->pwr_save); + p_slot->hpc_ops->get_latch_status(p_slot, &getstatus); + func->switch_save = !getstatus? 0x10:0; + + rc = board_added(func, p_slot->ctrl); + if (rc) { + if (is_bridge(func)) + bridge_slot_remove(func); + else + slot_remove(func); + + /* Setup slot structure with entry for empty slot */ + func = shpchp_slot_create(p_slot->bus); + if (func == NULL) + return (1); /* Out of memory */ + + func->bus = p_slot->bus; + func->device = p_slot->device; + func->function = 0; + func->configured = 0; + func->is_a_board = 1; + + /* We have to save the presence info for these slots */ + p_slot->hpc_ops->get_adapter_status(p_slot, &(func->presence_save)); + p_slot->hpc_ops->get_latch_status(p_slot, &getstatus); + func->switch_save = !getstatus? 0x10:0; + } + + if (p_slot) + update_slot_info(p_slot); + + return rc; +} + + +int shpchp_disable_slot (struct slot *p_slot) +{ + u8 class_code, header_type, BCR; + u8 index = 0; + u8 getstatus = 0; + u32 rc = 0; + int ret = 0; + unsigned int devfn; + struct pci_bus *pci_bus; + struct pci_func *func; + + if (!p_slot->ctrl) + return 1; + + pci_bus = p_slot->ctrl->pci_dev->subordinate; + + /* Check to see if (latch closed, card present, power on) */ + down(&p_slot->ctrl->crit_sect); + + ret = p_slot->hpc_ops->get_adapter_status(p_slot, &getstatus); + if (ret || !getstatus) { + info("%s: no adapter on slot(%x)\n", __FUNCTION__, p_slot->number); + up(&p_slot->ctrl->crit_sect); + return 1; + } + ret = p_slot->hpc_ops->get_latch_status(p_slot, &getstatus); + if (ret || getstatus) { + info("%s: latch open on slot(%x)\n", __FUNCTION__, p_slot->number); + up(&p_slot->ctrl->crit_sect); + return 1; + } + ret = p_slot->hpc_ops->get_power_status(p_slot, &getstatus); + if (ret || !getstatus) { + info("%s: already disabled slot(%x)\n", __FUNCTION__, p_slot->number); + up(&p_slot->ctrl->crit_sect); + return 1; + } + up(&p_slot->ctrl->crit_sect); + + func = shpchp_slot_find(p_slot->bus, p_slot->device, index++); + + /* Make sure there are no video controllers here + * for all func of p_slot + */ + 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 = shpchp_slot_find(p_slot->bus, p_slot->device, index++); + } + + func = shpchp_slot_find(p_slot->bus, p_slot->device, 0); + if ((func != NULL) && !rc) { + rc = remove_board(func, p_slot->ctrl); + } else if (!rc) + rc = 1; + + if (p_slot) + update_slot_info(p_slot); + + return(rc); +} + + +/** + * 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 bridge_bus, u8 bridge_dev) +{ + u8 temp_byte, function, max_functions, stop_it; + int rc; + u32 ID; + struct pci_func *new_slot; + struct pci_bus lpci_bus, *pci_bus; + int index; + + new_slot = func; + + dbg("%s\n", __FUNCTION__); + memcpy(&lpci_bus, ctrl->pci_dev->subordinate, sizeof(lpci_bus)); + pci_bus = &lpci_bus; + pci_bus->number = func->bus; + + /* Check for Multi-function device */ + rc = pci_bus_read_config_byte(pci_bus, PCI_DEVFN(func->device, func->function), 0x0E, &temp_byte); + if (rc) { + dbg("%s: rc = %d\n", __FUNCTION__, 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, bridge_bus, bridge_dev); + + if (rc) { + dbg("configure_new_function failed %d\n",rc); + index = 0; + + while (new_slot) { + new_slot = shpchp_slot_find(new_slot->bus, new_slot->device, index++); + + if (new_slot) + shpchp_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(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 = shpchp_slot_create(func->bus); + + if (new_slot == NULL) { + /* Out of memory */ + 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, u8 bridge_bus, u8 bridge_dev) +{ + int cloop; + u8 temp_byte; + u8 device; + u8 class_code; + u16 temp_word; + 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 lpci_bus, *pci_bus; + struct resource_lists temp_resources; +#if defined(CONFIG_X86_64) + u8 IRQ=0; +#endif + + memcpy(&lpci_bus, ctrl->pci_dev->subordinate, sizeof(lpci_bus)); + pci_bus = &lpci_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 = 0x%x\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 */ + bus_node = get_max_resource(&resources->bus_head, 1L); + + /* If we don't have any busses to allocate, we can't continue */ + if (!bus_node) { + err("Got NO bus resource to use\n"); + return -ENOMEM; + } + dbg("Got ranges of buses to use: base:len=0x%x:%x\n", bus_node->base, bus_node->length); + + /* set Secondary bus */ + temp_byte = (u8)bus_node->base; + dbg("set Secondary bus = 0x%x\n", temp_byte); + rc = pci_bus_write_config_byte(pci_bus, devfn, PCI_SECONDARY_BUS, temp_byte); + if (rc) + return rc; + + /* set subordinate bus */ + temp_byte = (u8)(bus_node->base + bus_node->length - 1); + dbg("set subordinate bus = 0x%x\n", temp_byte); + rc = pci_bus_write_config_byte (pci_bus, devfn, PCI_SUBORDINATE_BUS, temp_byte); + if (rc) + return rc; + + /* Set HP parameters (Cache Line Size, Latency Timer) */ + rc = shpchprm_set_hpp(ctrl, func, PCI_HEADER_TYPE_BRIDGE); + if (rc) + return rc; + + /* Setup the IO, memory, and prefetchable windows */ + + io_node = get_max_resource(&(resources->io_head), 0x1000L); + if (io_node) { + dbg("io_node(base, len, next) (%x, %x, %p)\n", io_node->base, io_node->length, io_node->next); + } + + mem_node = get_max_resource(&(resources->mem_head), 0x100000L); + if (mem_node) { + dbg("mem_node(base, len, next) (%x, %x, %p)\n", mem_node->base, mem_node->length, mem_node->next); + } + + if (resources->p_mem_head) + p_mem_node = get_max_resource(&(resources->p_mem_head), 0x100000L); + else { + /* + * In some platform implementation, MEM and PMEM are not + * distinguished, and hence ACPI _CRS has only MEM entries + * for both MEM and PMEM. + */ + dbg("using MEM for PMEM\n"); + p_mem_node = get_max_resource(&(resources->mem_head), 0x100000L); + } + if (p_mem_node) { + dbg("p_mem_node(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 */ + RES_CHECK(io_node->base, 8); + temp_byte = (u8)(io_node->base >> 8); + rc = pci_bus_write_config_byte(pci_bus, devfn, PCI_IO_BASE, temp_byte); + + RES_CHECK(io_node->base + io_node->length - 1, 8); + temp_byte = (u8)((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 */ + RES_CHECK(mem_node->base, 16); + temp_word = (u32)(mem_node->base >> 16); + rc = pci_bus_write_config_word(pci_bus, devfn, PCI_MEMORY_BASE, temp_word); + + RES_CHECK(mem_node->base + mem_node->length - 1, 16); + temp_word = (u32)((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; + } + + /* If we have prefetchable 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 (p_mem_node) { + memcpy(hold_p_mem_node, p_mem_node, sizeof(struct pci_resource)); + p_mem_node->next = NULL; + + /* set Pre Mem base and Limit registers */ + RES_CHECK(p_mem_node->base, 16); + temp_word = (u32)(p_mem_node->base >> 16); + rc = pci_bus_write_config_word(pci_bus, devfn, PCI_PREF_MEMORY_BASE, temp_word); + + RES_CHECK(p_mem_node->base + p_mem_node->length - 1, 16); + temp_word = (u32)((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); + } else { + temp_word = 0xFFFF; + rc = pci_bus_write_config_word(pci_bus, devfn, PCI_PREF_MEMORY_BASE, temp_word); + + temp_word = 0x0000; + rc = pci_bus_write_config_word(pci_bus, devfn, PCI_PREF_MEMORY_LIMIT, temp_word); + + kfree(hold_p_mem_node); + hold_p_mem_node = NULL; + } + + /* 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), + PCI_VENDOR_ID, &ID); + pci_bus->number = func->bus; + + if (ID != 0xFFFFFFFF) { /* device Present */ + /* Setup slot structure. */ + new_slot = shpchp_slot_create(hold_bus_node->base); + + if (new_slot == NULL) { + /* Out of memory */ + 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, func->bus, func->device); + dbg("configure_new_device rc=0x%x\n",rc); + } /* End of IF (device in slot?) */ + } /* End of FOR loop */ + + if (rc) { + shpchp_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); + } + + /* 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 = shpchp_set_irq(func->bus, func->device, + 0x0A + cloop, irqs.interrupt[cloop]); + if (rc) { + shpchp_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; + } + } + } /* 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 = (u8)(temp_resources.bus_head->base - 1); + + /* set subordinate bus */ + dbg("re-set subordinate bus = 0x%x\n", temp_byte); + 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 { + dbg("return bus res of b:d(0x%x:%x) base:len(0x%x:%x)\n", + func->bus, func->device, temp_resources.bus_head->base, temp_resources.bus_head->length); + 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; + + RES_CHECK(hold_IO_node->base, 8); + temp_byte = (u8)((hold_IO_node->base) >> 8); + rc = pci_bus_write_config_byte (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; + + RES_CHECK(io_node->base - 1, 8); + temp_byte = (u8)((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_byte = 0x00; + rc = pci_bus_write_config_byte(pci_bus, devfn, PCI_IO_LIMIT, temp_byte); + + 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, 0x100000L); + + /* Check if we were able to split something off */ + if (mem_node) { + hold_mem_node->base = mem_node->base + mem_node->length; + + RES_CHECK(hold_mem_node->base, 16); + temp_word = (u32)((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), 0x100000L); + + /* 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 */ + RES_CHECK(mem_node->base - 1, 16); + temp_word = (u32)((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, 0x100000L); + + /* 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; + + RES_CHECK(hold_p_mem_node->base, 16); + temp_word = (u32)((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), 0x100000L); + + /* 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; + + RES_CHECK(p_mem_node->base - 1, 16); + temp_word = (u32)((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 + */ + + shpchprm_enable_card(ctrl, func, PCI_HEADER_TYPE_BRIDGE); + + dbg("PCI Bridge Hot-Added s:b:d:f(%02x:%02x:%02x:%02x)\n", ctrl->seg, func->bus, func->device, func->function); + } else if ((temp_byte & 0x7F) == PCI_HEADER_TYPE_NORMAL) { + /* Standard device */ + u64 base64; + rc = pci_bus_read_config_byte (pci_bus, devfn, 0x0B, &class_code); + + if (class_code == PCI_BASE_CLASS_DISPLAY) + return (DEVICE_TYPE_NOT_SUPPORTED); + + /* Figure out IO and memory needs */ + for (cloop = PCI_BASE_ADDRESS_0; cloop <= PCI_BASE_ADDRESS_5; cloop += 4) { + temp_register = 0xFFFFFFFF; + + 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("Bar[%x]=0x%x on bus:dev:func(0x%x:%x:%x)\n", cloop, temp_register, func->bus, func->device, + func->function); + + if (!temp_register) + continue; + + base64 = 0L; + if (temp_register & PCI_BASE_ADDRESS_SPACE_IO) { + /* Map IO */ + + /* set base = amount of IO space */ + base = temp_register & 0xFFFFFFFC; + base = ~base + 1; + + dbg("NEED IO length(0x%x)\n", base); + io_node = get_io_resource(&(resources->io_head),(ulong)base); + + /* allocate the resource to the board */ + if (io_node) { + dbg("Got IO base=0x%x(length=0x%x)\n", io_node->base, io_node->length); + base = (u32)io_node->base; + io_node->next = func->io_head; + func->io_head = io_node; + } else { + err("Got NO IO resource(length=0x%x)\n", base); + return -ENOMEM; + } + } else { /* map MEM */ + int prefetchable = 1; + struct pci_resource **res_node = &func->p_mem_head; + char *res_type_str = "PMEM"; + u32 temp_register2; + + if (!(temp_register & PCI_BASE_ADDRESS_MEM_PREFETCH)) { + prefetchable = 0; + res_node = &func->mem_head; + res_type_str++; + } + + base = temp_register & 0xFFFFFFF0; + base = ~base + 1; + + switch (temp_register & PCI_BASE_ADDRESS_MEM_TYPE_MASK) { + case PCI_BASE_ADDRESS_MEM_TYPE_32: + dbg("NEED 32 %s bar=0x%x(length=0x%x)\n", res_type_str, temp_register, base); + + if (prefetchable && resources->p_mem_head) + mem_node=get_resource(&(resources->p_mem_head), (ulong)base); + else { + if (prefetchable) + dbg("using MEM for PMEM\n"); + mem_node=get_resource(&(resources->mem_head), (ulong)base); + } + + /* allocate the resource to the board */ + if (mem_node) { + base = (u32)mem_node->base; + mem_node->next = *res_node; + *res_node = mem_node; + dbg("Got 32 %s base=0x%x(length=0x%x)\n", res_type_str, mem_node->base, + mem_node->length); + } else { + err("Got NO 32 %s resource(length=0x%x)\n", res_type_str, base); + return -ENOMEM; + } + break; + case PCI_BASE_ADDRESS_MEM_TYPE_64: + rc = pci_bus_read_config_dword(pci_bus, devfn, cloop+4, &temp_register2); + dbg("NEED 64 %s bar=0x%x:%x(length=0x%x)\n", res_type_str, temp_register2, + temp_register, base); + + if (prefetchable && resources->p_mem_head) + mem_node = get_resource(&(resources->p_mem_head), (ulong)base); + else { + if (prefetchable) + dbg("using MEM for PMEM\n"); + mem_node = get_resource(&(resources->mem_head), (ulong)base); + } + + /* allocate the resource to the board */ + if (mem_node) { + base64 = mem_node->base; + mem_node->next = *res_node; + *res_node = mem_node; + dbg("Got 64 %s base=0x%x:%x(length=%x)\n", res_type_str, (u32)(base64 >> 32), + (u32)base64, mem_node->length); + } else { + err("Got NO 64 %s resource(length=0x%x)\n", res_type_str, base); + return -ENOMEM; + } + break; + default: + dbg("reserved BAR type=0x%x\n", temp_register); + break; + } + + } + + if (base64) { + rc = pci_bus_write_config_dword(pci_bus, devfn, cloop, (u32)base64); + cloop += 4; + base64 >>= 32; + + if (base64) { + dbg("%s: high dword of base64(0x%x) set to 0\n", __FUNCTION__, (u32)base64); + base64 = 0x0L; + } + + rc = pci_bus_write_config_dword(pci_bus, devfn, cloop, (u32)base64); + } else { + rc = pci_bus_write_config_dword(pci_bus, devfn, cloop, base); + } + } /* End of base register loop */ + +#if defined(CONFIG_X86_64) + /* 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 = shpchp_disk_irq; + } else { + IRQ = shpchp_nic_irq; + } + } + + /* IRQ Line */ + rc = pci_bus_write_config_byte (pci_bus, devfn, PCI_INTERRUPT_LINE, IRQ); + + if (!behind_bridge) { + rc = shpchp_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; + } +#endif + /* Disable ROM base Address */ + temp_word = 0x00L; + rc = pci_bus_write_config_word (pci_bus, devfn, PCI_ROM_ADDRESS, temp_word); + + /* Set HP parameters (Cache Line Size, Latency Timer) */ + rc = shpchprm_set_hpp(ctrl, func, PCI_HEADER_TYPE_NORMAL); + if (rc) + return rc; + + shpchprm_enable_card(ctrl, func, PCI_HEADER_TYPE_NORMAL); + + dbg("PCI function Hot-Added s:b:d:f(%02x:%02x:%02x:%02x)\n", ctrl->seg, func->bus, func->device, func->function); + } /* End of Not-A-Bridge else */ + else { + /* It's some strange type of PCI adapter (Cardbus?) */ + return(DEVICE_TYPE_NOT_SUPPORTED); + } + + func->configured = 1; + + return 0; +} + diff --git a/drivers/pci/hotplug/shpchp_hpc.c b/drivers/pci/hotplug/shpchp_hpc.c new file mode 100644 index 000000000000..38c5d9066697 --- /dev/null +++ b/drivers/pci/hotplug/shpchp_hpc.c @@ -0,0 +1,1620 @@ +/* + * 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>,<dely.l.sy@intel.com> + * + */ + +#include <linux/config.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/types.h> +#include <linux/slab.h> +#include <linux/vmalloc.h> +#include <linux/interrupt.h> +#include <linux/spinlock.h> +#include <linux/delay.h> +#include <linux/pci.h> +#include <asm/system.h> +#include "shpchp.h" + +#ifdef DEBUG +#define DBG_K_TRACE_ENTRY ((unsigned int)0x00000001) /* On function entry */ +#define DBG_K_TRACE_EXIT ((unsigned int)0x00000002) /* On function exit */ +#define DBG_K_INFO ((unsigned int)0x00000004) /* Info messages */ +#define DBG_K_ERROR ((unsigned int)0x00000008) /* Error messages */ +#define DBG_K_TRACE (DBG_K_TRACE_ENTRY|DBG_K_TRACE_EXIT) +#define DBG_K_STANDARD (DBG_K_INFO|DBG_K_ERROR|DBG_K_TRACE) +/* Redefine this flagword to set debug level */ +#define DEBUG_LEVEL DBG_K_STANDARD + +#define DEFINE_DBG_BUFFER char __dbg_str_buf[256]; + +#define DBG_PRINT( dbg_flags, args... ) \ + do { \ + if ( DEBUG_LEVEL & ( dbg_flags ) ) \ + { \ + int len; \ + len = sprintf( __dbg_str_buf, "%s:%d: %s: ", \ + __FILE__, __LINE__, __FUNCTION__ ); \ + sprintf( __dbg_str_buf + len, args ); \ + printk( KERN_NOTICE "%s\n", __dbg_str_buf ); \ + } \ + } while (0) + +#define DBG_ENTER_ROUTINE DBG_PRINT (DBG_K_TRACE_ENTRY, "%s", "[Entry]"); +#define DBG_LEAVE_ROUTINE DBG_PRINT (DBG_K_TRACE_EXIT, "%s", "[Exit]"); +#else +#define DEFINE_DBG_BUFFER +#define DBG_ENTER_ROUTINE +#define DBG_LEAVE_ROUTINE +#endif /* DEBUG */ + +/* 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 + + +/* Secondary Bus Configuration Register */ +/* For PI = 1, Bits 0 to 2 have been encoded as follows to show current bus speed/mode */ +#define PCI_33MHZ 0x0 +#define PCI_66MHZ 0x1 +#define PCIX_66MHZ 0x2 +#define PCIX_100MHZ 0x3 +#define PCIX_133MHZ 0x4 + +/* For PI = 2, Bits 0 to 3 have been encoded as follows to show current bus speed/mode */ +#define PCI_33MHZ 0x0 +#define PCI_66MHZ 0x1 +#define PCIX_66MHZ 0x2 +#define PCIX_100MHZ 0x3 +#define PCIX_133MHZ 0x4 +#define PCIX_66MHZ_ECC 0x5 +#define PCIX_100MHZ_ECC 0x6 +#define PCIX_133MHZ_ECC 0x7 +#define PCIX_66MHZ_266 0x9 +#define PCIX_100MHZ_266 0xa +#define PCIX_133MHZ_266 0xb +#define PCIX_66MHZ_533 0x11 +#define PCIX_100MHZ_533 0x12 +#define PCIX_133MHZ_533 0x13 + +/* Slot Configuration */ +#define SLOT_NUM 0x0000001F +#define FIRST_DEV_NUM 0x00001F00 +#define PSN 0x07FF0000 +#define UPDOWN 0x20000000 +#define MRLSENSOR 0x40000000 +#define ATTN_BUTTON 0x80000000 + +/* Slot Status Field Definitions */ +/* Slot State */ +#define PWR_ONLY 0x0001 +#define ENABLED 0x0002 +#define DISABLED 0x0003 + +/* Power Indicator State */ +#define PWR_LED_ON 0x0004 +#define PWR_LED_BLINK 0x0008 +#define PWR_LED_OFF 0x000c + +/* Attention Indicator State */ +#define ATTEN_LED_ON 0x0010 +#define ATTEN_LED_BLINK 0x0020 +#define ATTEN_LED_OFF 0x0030 + +/* Power Fault */ +#define pwr_fault 0x0040 + +/* Attention Button */ +#define ATTEN_BUTTON 0x0080 + +/* MRL Sensor */ +#define MRL_SENSOR 0x0100 + +/* 66 MHz Capable */ +#define IS_66MHZ_CAP 0x0200 + +/* PRSNT1#/PRSNT2# */ +#define SLOT_EMP 0x0c00 + +/* PCI-X Capability */ +#define NON_PCIX 0x0000 +#define PCIX_66 0x1000 +#define PCIX_133 0x3000 +#define PCIX_266 0x4000 /* For PI = 2 only */ +#define PCIX_533 0x5000 /* For PI = 2 only */ + +/* SHPC 'write' operations/commands */ + +/* Slot operation - 0x00h to 0x3Fh */ + +#define NO_CHANGE 0x00 + +/* Slot state - Bits 0 & 1 of controller command register */ +#define SET_SLOT_PWR 0x01 +#define SET_SLOT_ENABLE 0x02 +#define SET_SLOT_DISABLE 0x03 + +/* Power indicator state - Bits 2 & 3 of controller command register*/ +#define SET_PWR_ON 0x04 +#define SET_PWR_BLINK 0x08 +#define SET_PWR_OFF 0x0C + +/* Attention indicator state - Bits 4 & 5 of controller command register*/ +#define SET_ATTN_ON 0x010 +#define SET_ATTN_BLINK 0x020 +#define SET_ATTN_OFF 0x030 + +/* Set bus speed/mode A - 0x40h to 0x47h */ +#define SETA_PCI_33MHZ 0x40 +#define SETA_PCI_66MHZ 0x41 +#define SETA_PCIX_66MHZ 0x42 +#define SETA_PCIX_100MHZ 0x43 +#define SETA_PCIX_133MHZ 0x44 +#define RESERV_1 0x45 +#define RESERV_2 0x46 +#define RESERV_3 0x47 + +/* Set bus speed/mode B - 0x50h to 0x5fh */ +#define SETB_PCI_33MHZ 0x50 +#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 + + +/* Power-on all slots - 0x48h */ +#define SET_PWR_ON_ALL 0x48 + +/* Enable all slots - 0x49h */ +#define SET_ENABLE_ALL 0x49 + +/* SHPC controller command error code */ +#define SWITCH_OPEN 0x1 +#define INVALID_CMD 0x2 +#define INVALID_SPEED_MODE 0x4 + +/* For accessing SHPC Working Register Set */ +#define DWORD_SELECT 0x2 +#define DWORD_DATA 0x4 +#define BASE_OFFSET 0x0 + +/* Field Offset in Logical Slot Register - byte boundary */ +#define SLOT_EVENT_LATCH 0x2 +#define SLOT_SERR_INT_MASK 0x3 + +static spinlock_t hpc_event_lock; + +DEFINE_DBG_BUFFER /* Debug string buffer for entire HPC defined here */ +static struct php_ctlr_state_s *php_ctlr_list_head; /* HPC state linked list */ +static int ctlr_seq_num = 0; /* Controller sequenc # */ +static spinlock_t list_lock; + +static irqreturn_t shpc_isr(int IRQ, void *dev_id, struct pt_regs *regs); + +static void start_int_poll_timer(struct php_ctlr_state_s *php_ctlr, int seconds); + +/* This is the interrupt polling timeout function. */ +static void int_poll_timeout(unsigned long lphp_ctlr) +{ + struct php_ctlr_state_s *php_ctlr = (struct php_ctlr_state_s *)lphp_ctlr; + + DBG_ENTER_ROUTINE + + if ( !php_ctlr ) { + err("%s: Invalid HPC controller handle!\n", __FUNCTION__); + return; + } + + /* Poll for interrupt events. regs == NULL => polling */ + shpc_isr( 0, (void *)php_ctlr, NULL ); + + init_timer(&php_ctlr->int_poll_timer); + if (!shpchp_poll_time) + shpchp_poll_time = 2; /* reset timer to poll in 2 secs if user doesn't specify at module installation*/ + + start_int_poll_timer(php_ctlr, shpchp_poll_time); + + return; +} + +/* This function starts the interrupt polling timer. */ +static void start_int_poll_timer(struct php_ctlr_state_s *php_ctlr, int seconds) +{ + if (!php_ctlr) { + err("%s: Invalid HPC controller handle!\n", __FUNCTION__); + return; + } + + if ( ( seconds <= 0 ) || ( seconds > 60 ) ) + seconds = 2; /* Clamp to sane value */ + + php_ctlr->int_poll_timer.function = &int_poll_timeout; + php_ctlr->int_poll_timer.data = (unsigned long)php_ctlr; /* Instance data */ + php_ctlr->int_poll_timer.expires = jiffies + seconds * HZ; + add_timer(&php_ctlr->int_poll_timer); + + return; +} + +static int shpc_write_cmd(struct slot *slot, u8 t_slot, u8 cmd) +{ + struct php_ctlr_state_s *php_ctlr = (struct php_ctlr_state_s *) slot->ctrl->hpc_ctlr_handle; + u16 cmd_status; + int retval = 0; + u16 temp_word; + int i; + + DBG_ENTER_ROUTINE + + if (!php_ctlr) { + err("%s: Invalid HPC controller handle!\n", __FUNCTION__); + return -1; + } + + for (i = 0; i < 10; i++) { + cmd_status = readw(php_ctlr->creg + CMD_STATUS); + + if (!(cmd_status & 0x1)) + break; + /* Check every 0.1 sec for a total of 1 sec*/ + msleep(100); + } + + cmd_status = readw(php_ctlr->creg + CMD_STATUS); + + if (cmd_status & 0x1) { + /* After 1 sec and and the controller is still busy */ + err("%s : Controller is still busy after 1 sec.\n", __FUNCTION__); + return -1; + } + + ++t_slot; + temp_word = (t_slot << 8) | (cmd & 0xFF); + dbg("%s: t_slot %x cmd %x\n", __FUNCTION__, t_slot, cmd); + + /* To make sure the Controller Busy bit is 0 before we send out the + * command. + */ + writew(temp_word, php_ctlr->creg + CMD); + dbg("%s: temp_word written %x\n", __FUNCTION__, temp_word); + + DBG_LEAVE_ROUTINE + return retval; +} + +static int hpc_check_cmd_status(struct controller *ctrl) +{ + struct php_ctlr_state_s *php_ctlr = (struct php_ctlr_state_s *) ctrl->hpc_ctlr_handle; + u16 cmd_status; + int retval = 0; + + DBG_ENTER_ROUTINE + + if (!ctrl->hpc_ctlr_handle) { + err("%s: Invalid HPC controller handle!\n", __FUNCTION__); + return -1; + } + + cmd_status = readw(php_ctlr->creg + CMD_STATUS) & 0x000F; + + switch (cmd_status >> 1) { + case 0: + retval = 0; + break; + case 1: + retval = SWITCH_OPEN; + err("%s: Switch opened!\n", __FUNCTION__); + break; + case 2: + retval = INVALID_CMD; + err("%s: Invalid HPC command!\n", __FUNCTION__); + break; + case 4: + retval = INVALID_SPEED_MODE; + err("%s: Invalid bus speed/mode!\n", __FUNCTION__); + break; + default: + retval = cmd_status; + } + + DBG_LEAVE_ROUTINE + return retval; +} + + +static int hpc_get_attention_status(struct slot *slot, u8 *status) +{ + struct php_ctlr_state_s *php_ctlr = (struct php_ctlr_state_s *) slot->ctrl->hpc_ctlr_handle; + u32 slot_reg; + u16 slot_status; + u8 atten_led_state; + + DBG_ENTER_ROUTINE + + if (!slot->ctrl->hpc_ctlr_handle) { + err("%s: Invalid HPC controller handle!\n", __FUNCTION__); + return -1; + } + + slot_reg = readl(php_ctlr->creg + SLOT1 + 4*(slot->hp_slot)); + slot_status = (u16) slot_reg; + atten_led_state = (slot_status & 0x0030) >> 4; + + 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; + } + + DBG_LEAVE_ROUTINE + return 0; +} + +static int hpc_get_power_status(struct slot * slot, u8 *status) +{ + struct php_ctlr_state_s *php_ctlr = (struct php_ctlr_state_s *) slot->ctrl->hpc_ctlr_handle; + u32 slot_reg; + u16 slot_status; + u8 slot_state; + int retval = 0; + + DBG_ENTER_ROUTINE + + if (!slot->ctrl->hpc_ctlr_handle) { + err("%s: Invalid HPC controller handle!\n", __FUNCTION__); + return -1; + } + + slot_reg = readl(php_ctlr->creg + SLOT1 + 4*(slot->hp_slot)); + slot_status = (u16) slot_reg; + slot_state = (slot_status & 0x0003); + + switch (slot_state) { + case 0: + *status = 0xFF; + break; + case 1: + *status = 2; /* Powered only */ + break; + case 2: + *status = 1; /* Enabled */ + break; + case 3: + *status = 0; /* Disabled */ + break; + default: + *status = 0xFF; + break; + } + + DBG_LEAVE_ROUTINE + return retval; +} + + +static int hpc_get_latch_status(struct slot *slot, u8 *status) +{ + struct php_ctlr_state_s *php_ctlr = (struct php_ctlr_state_s *) slot->ctrl->hpc_ctlr_handle; + u32 slot_reg; + u16 slot_status; + + DBG_ENTER_ROUTINE + + if (!slot->ctrl->hpc_ctlr_handle) { + err("%s: Invalid HPC controller handle!\n", __FUNCTION__); + return -1; + } + + slot_reg = readl(php_ctlr->creg + SLOT1 + 4*(slot->hp_slot)); + slot_status = (u16)slot_reg; + + *status = ((slot_status & 0x0100) == 0) ? 0 : 1; /* 0 -> close; 1 -> open */ + + + DBG_LEAVE_ROUTINE + return 0; +} + +static int hpc_get_adapter_status(struct slot *slot, u8 *status) +{ + struct php_ctlr_state_s *php_ctlr = (struct php_ctlr_state_s *) slot->ctrl->hpc_ctlr_handle; + u32 slot_reg; + u16 slot_status; + u8 card_state; + + DBG_ENTER_ROUTINE + + if (!slot->ctrl->hpc_ctlr_handle) { + err("%s: Invalid HPC controller handle!\n", __FUNCTION__); + return -1; + } + + slot_reg = readl(php_ctlr->creg + SLOT1 + 4*(slot->hp_slot)); + slot_status = (u16)slot_reg; + card_state = (u8)((slot_status & 0x0C00) >> 10); + *status = (card_state != 0x3) ? 1 : 0; + + DBG_LEAVE_ROUTINE + return 0; +} + +static int hpc_get_prog_int(struct slot *slot, u8 *prog_int) +{ + struct php_ctlr_state_s *php_ctlr = (struct php_ctlr_state_s *) slot->ctrl->hpc_ctlr_handle; + + DBG_ENTER_ROUTINE + + if (!slot->ctrl->hpc_ctlr_handle) { + err("%s: Invalid HPC controller handle!\n", __FUNCTION__); + return -1; + } + + *prog_int = readb(php_ctlr->creg + PROG_INTERFACE); + + DBG_LEAVE_ROUTINE + return 0; +} + +static int hpc_get_adapter_speed(struct slot *slot, enum pci_bus_speed *value) +{ + struct php_ctlr_state_s *php_ctlr = (struct php_ctlr_state_s *) slot->ctrl->hpc_ctlr_handle; + u32 slot_reg; + u16 slot_status, sec_bus_status; + u8 m66_cap, pcix_cap, pi; + int retval = 0; + + DBG_ENTER_ROUTINE + + if (!slot->ctrl->hpc_ctlr_handle) { + err("%s: Invalid HPC controller handle!\n", __FUNCTION__); + return -1; + } + + if (slot->hp_slot >= php_ctlr->num_slots) { + err("%s: Invalid HPC slot number!\n", __FUNCTION__); + return -1; + } + + pi = readb(php_ctlr->creg + PROG_INTERFACE); + slot_reg = readl(php_ctlr->creg + SLOT1 + 4*(slot->hp_slot)); + dbg("%s: pi = %d, slot_reg = %x\n", __FUNCTION__, pi, slot_reg); + slot_status = (u16) slot_reg; + dbg("%s: slot_status = %x\n", __FUNCTION__, slot_status); + sec_bus_status = readw(php_ctlr->creg + SEC_BUS_CONFIG); + + pcix_cap = (u8) ((slot_status & 0x3000) >> 12); + dbg("%s: pcix_cap = %x\n", __FUNCTION__, pcix_cap); + m66_cap = (u8) ((slot_status & 0x0200) >> 9); + dbg("%s: m66_cap = %x\n", __FUNCTION__, m66_cap); + + + if (pi == 2) { + switch (pcix_cap) { + case 0: + *value = m66_cap ? PCI_SPEED_66MHz : PCI_SPEED_33MHz; + break; + case 1: + *value = PCI_SPEED_66MHz_PCIX; + break; + case 3: + *value = PCI_SPEED_133MHz_PCIX; + break; + case 4: + *value = PCI_SPEED_133MHz_PCIX_266; + break; + case 5: + *value = PCI_SPEED_133MHz_PCIX_533; + break; + case 2: /* Reserved */ + default: + *value = PCI_SPEED_UNKNOWN; + retval = -ENODEV; + break; + } + } else { + switch (pcix_cap) { + case 0: + *value = m66_cap ? PCI_SPEED_66MHz : PCI_SPEED_33MHz; + break; + case 1: + *value = PCI_SPEED_66MHz_PCIX; + break; + case 3: + *value = PCI_SPEED_133MHz_PCIX; + break; + case 2: /* Reserved */ + default: + *value = PCI_SPEED_UNKNOWN; + retval = -ENODEV; + break; + } + } + + dbg("Adapter speed = %d\n", *value); + + DBG_LEAVE_ROUTINE + return retval; +} + +static int hpc_get_mode1_ECC_cap(struct slot *slot, u8 *mode) +{ + struct php_ctlr_state_s *php_ctlr = (struct php_ctlr_state_s *) slot->ctrl->hpc_ctlr_handle; + u16 sec_bus_status; + u8 pi; + int retval = 0; + + DBG_ENTER_ROUTINE + + if (!slot->ctrl->hpc_ctlr_handle) { + err("%s: Invalid HPC controller handle!\n", __FUNCTION__); + return -1; + } + + pi = readb(php_ctlr->creg + PROG_INTERFACE); + sec_bus_status = readw(php_ctlr->creg + SEC_BUS_CONFIG); + + if (pi == 2) { + *mode = (sec_bus_status & 0x0100) >> 7; + } else { + retval = -1; + } + + dbg("Mode 1 ECC cap = %d\n", *mode); + + DBG_LEAVE_ROUTINE + return retval; +} + +static int hpc_query_power_fault(struct slot * slot) +{ + struct php_ctlr_state_s *php_ctlr = (struct php_ctlr_state_s *) slot->ctrl->hpc_ctlr_handle; + u32 slot_reg; + u16 slot_status; + u8 pwr_fault_state, status; + + DBG_ENTER_ROUTINE + + if (!slot->ctrl->hpc_ctlr_handle) { + err("%s: Invalid HPC controller handle!\n", __FUNCTION__); + return -1; + } + + slot_reg = readl(php_ctlr->creg + SLOT1 + 4*(slot->hp_slot)); + slot_status = (u16) slot_reg; + pwr_fault_state = (slot_status & 0x0040) >> 7; + status = (pwr_fault_state == 1) ? 0 : 1; + + DBG_LEAVE_ROUTINE + /* Note: Logic 0 => fault */ + return status; +} + +static int hpc_set_attention_status(struct slot *slot, u8 value) +{ + struct php_ctlr_state_s *php_ctlr =(struct php_ctlr_state_s *) slot->ctrl->hpc_ctlr_handle; + u8 slot_cmd = 0; + int rc = 0; + + if (!slot->ctrl->hpc_ctlr_handle) { + err("%s: Invalid HPC controller handle!\n", __FUNCTION__); + return -1; + } + + if (slot->hp_slot >= php_ctlr->num_slots) { + err("%s: Invalid HPC slot number!\n", __FUNCTION__); + return -1; + } + + switch (value) { + case 0 : + slot_cmd = 0x30; /* OFF */ + break; + case 1: + slot_cmd = 0x10; /* ON */ + break; + case 2: + slot_cmd = 0x20; /* BLINK */ + break; + default: + return -1; + } + + shpc_write_cmd(slot, slot->hp_slot, slot_cmd); + + return rc; +} + + +static void hpc_set_green_led_on(struct slot *slot) +{ + struct php_ctlr_state_s *php_ctlr =(struct php_ctlr_state_s *) slot->ctrl->hpc_ctlr_handle; + u8 slot_cmd; + + if (!slot->ctrl->hpc_ctlr_handle) { + err("%s: Invalid HPC controller handle!\n", __FUNCTION__); + return ; + } + + if (slot->hp_slot >= php_ctlr->num_slots) { + err("%s: Invalid HPC slot number!\n", __FUNCTION__); + return ; + } + + slot_cmd = 0x04; + + shpc_write_cmd(slot, slot->hp_slot, slot_cmd); + + return; +} + +static void hpc_set_green_led_off(struct slot *slot) +{ + struct php_ctlr_state_s *php_ctlr =(struct php_ctlr_state_s *) slot->ctrl->hpc_ctlr_handle; + u8 slot_cmd; + + if (!slot->ctrl->hpc_ctlr_handle) { + err("%s: Invalid HPC controller handle!\n", __FUNCTION__); + return ; + } + + if (slot->hp_slot >= php_ctlr->num_slots) { + err("%s: Invalid HPC slot number!\n", __FUNCTION__); + return ; + } + + slot_cmd = 0x0C; + + shpc_write_cmd(slot, slot->hp_slot, slot_cmd); + + return; +} + +static void hpc_set_green_led_blink(struct slot *slot) +{ + struct php_ctlr_state_s *php_ctlr =(struct php_ctlr_state_s *) slot->ctrl->hpc_ctlr_handle; + u8 slot_cmd; + + if (!slot->ctrl->hpc_ctlr_handle) { + err("%s: Invalid HPC controller handle!\n", __FUNCTION__); + return ; + } + + if (slot->hp_slot >= php_ctlr->num_slots) { + err("%s: Invalid HPC slot number!\n", __FUNCTION__); + return ; + } + + slot_cmd = 0x08; + + shpc_write_cmd(slot, slot->hp_slot, slot_cmd); + + return; +} + +int shpc_get_ctlr_slot_config(struct controller *ctrl, + int *num_ctlr_slots, /* number of slots in this HPC */ + int *first_device_num, /* PCI dev num of the first slot in this SHPC */ + int *physical_slot_num, /* phy slot num of the first slot in this SHPC */ + int *updown, /* physical_slot_num increament: 1 or -1 */ + int *flags) +{ + struct php_ctlr_state_s *php_ctlr = (struct php_ctlr_state_s *) ctrl->hpc_ctlr_handle; + + DBG_ENTER_ROUTINE + + if (!ctrl->hpc_ctlr_handle) { + err("%s: Invalid HPC controller handle!\n", __FUNCTION__); + return -1; + } + + *first_device_num = php_ctlr->slot_device_offset; /* Obtained in shpc_init() */ + *num_ctlr_slots = php_ctlr->num_slots; /* Obtained in shpc_init() */ + + *physical_slot_num = (readl(php_ctlr->creg + SLOT_CONFIG) & PSN) >> 16; + dbg("%s: physical_slot_num = %x\n", __FUNCTION__, *physical_slot_num); + *updown = ((readl(php_ctlr->creg + SLOT_CONFIG) & UPDOWN ) >> 29) ? 1 : -1; + + DBG_LEAVE_ROUTINE + return 0; +} + +static void hpc_release_ctlr(struct controller *ctrl) +{ + struct php_ctlr_state_s *php_ctlr = (struct php_ctlr_state_s *) ctrl->hpc_ctlr_handle; + struct php_ctlr_state_s *p, *p_prev; + + DBG_ENTER_ROUTINE + + if (!ctrl->hpc_ctlr_handle) { + err("%s: Invalid HPC controller handle!\n", __FUNCTION__); + return ; + } + + if (shpchp_poll_mode) { + del_timer(&php_ctlr->int_poll_timer); + } else { + if (php_ctlr->irq) { + free_irq(php_ctlr->irq, ctrl); + php_ctlr->irq = 0; + pci_disable_msi(php_ctlr->pci_dev); + } + } + if (php_ctlr->pci_dev) { + dbg("%s: before calling iounmap & release_mem_region\n", __FUNCTION__); + iounmap(php_ctlr->creg); + release_mem_region(pci_resource_start(php_ctlr->pci_dev, 0), pci_resource_len(php_ctlr->pci_dev, 0)); + dbg("%s: before calling iounmap & release_mem_region\n", __FUNCTION__); + php_ctlr->pci_dev = NULL; + } + + spin_lock(&list_lock); + p = php_ctlr_list_head; + p_prev = NULL; + while (p) { + if (p == php_ctlr) { + if (p_prev) + p_prev->pnext = p->pnext; + else + php_ctlr_list_head = p->pnext; + break; + } else { + p_prev = p; + p = p->pnext; + } + } + spin_unlock(&list_lock); + + kfree(php_ctlr); + +DBG_LEAVE_ROUTINE + +} + +static int hpc_power_on_slot(struct slot * slot) +{ + struct php_ctlr_state_s *php_ctlr = (struct php_ctlr_state_s *) slot->ctrl->hpc_ctlr_handle; + u8 slot_cmd; + int retval = 0; + + DBG_ENTER_ROUTINE + + if (!slot->ctrl->hpc_ctlr_handle) { + err("%s: Invalid HPC controller handle!\n", __FUNCTION__); + return -1; + } + + if (slot->hp_slot >= php_ctlr->num_slots) { + err("%s: Invalid HPC slot number!\n", __FUNCTION__); + return -1; + } + slot_cmd = 0x01; + + retval = shpc_write_cmd(slot, slot->hp_slot, slot_cmd); + + if (retval) { + err("%s: Write command failed!\n", __FUNCTION__); + return -1; + } + + DBG_LEAVE_ROUTINE + + return retval; +} + +static int hpc_slot_enable(struct slot * slot) +{ + struct php_ctlr_state_s *php_ctlr = (struct php_ctlr_state_s *) slot->ctrl->hpc_ctlr_handle; + u8 slot_cmd; + int retval = 0; + + DBG_ENTER_ROUTINE + + if (!slot->ctrl->hpc_ctlr_handle) { + err("%s: Invalid HPC controller handle!\n", __FUNCTION__); + return -1; + } + + if (slot->hp_slot >= php_ctlr->num_slots) { + err("%s: Invalid HPC slot number!\n", __FUNCTION__); + return -1; + } + /* 3A => Slot - Enable, Power Indicator - Blink, Attention Indicator - Off */ + slot_cmd = 0x3A; + + retval = shpc_write_cmd(slot, slot->hp_slot, slot_cmd); + + if (retval) { + err("%s: Write command failed!\n", __FUNCTION__); + return -1; + } + + DBG_LEAVE_ROUTINE + return retval; +} + +static int hpc_slot_disable(struct slot * slot) +{ + struct php_ctlr_state_s *php_ctlr = (struct php_ctlr_state_s *) slot->ctrl->hpc_ctlr_handle; + u8 slot_cmd; + int retval = 0; + + DBG_ENTER_ROUTINE + + if (!slot->ctrl->hpc_ctlr_handle) { + err("%s: Invalid HPC controller handle!\n", __FUNCTION__); + return -1; + } + + if (slot->hp_slot >= php_ctlr->num_slots) { + err("%s: Invalid HPC slot number!\n", __FUNCTION__); + return -1; + } + + /* 1F => Slot - Disable, Power Indicator - Off, Attention Indicator - On */ + slot_cmd = 0x1F; + + retval = shpc_write_cmd(slot, slot->hp_slot, slot_cmd); + + if (retval) { + err("%s: Write command failed!\n", __FUNCTION__); + return -1; + } + + DBG_LEAVE_ROUTINE + return retval; +} + +static int hpc_enable_all_slots( struct slot *slot ) +{ + int retval = 0; + + DBG_ENTER_ROUTINE + + if (!slot->ctrl->hpc_ctlr_handle) { + err("%s: Invalid HPC controller handle!\n", __FUNCTION__); + return -1; + } + + retval = shpc_write_cmd(slot, 0, SET_ENABLE_ALL); + if (retval) { + err("%s: Write command failed!\n", __FUNCTION__); + return -1; + } + + DBG_LEAVE_ROUTINE + + return retval; +} + +static int hpc_pwr_on_all_slots(struct slot *slot) +{ + int retval = 0; + + DBG_ENTER_ROUTINE + + retval = shpc_write_cmd(slot, 0, SET_PWR_ON_ALL); + + if (retval) { + err("%s: Write command failed!\n", __FUNCTION__); + return -1; + } + + DBG_LEAVE_ROUTINE + return retval; +} + +static int hpc_set_bus_speed_mode(struct slot * slot, enum pci_bus_speed value) +{ + u8 slot_cmd; + u8 pi; + int retval = 0; + struct php_ctlr_state_s *php_ctlr = (struct php_ctlr_state_s *) slot->ctrl->hpc_ctlr_handle; + + DBG_ENTER_ROUTINE + + if (!slot->ctrl->hpc_ctlr_handle) { + err("%s: Invalid HPC controller handle!\n", __FUNCTION__); + return -1; + } + + pi = readb(php_ctlr->creg + PROG_INTERFACE); + + if (pi == 1) { + switch (value) { + case 0: + slot_cmd = SETA_PCI_33MHZ; + break; + case 1: + slot_cmd = SETA_PCI_66MHZ; + break; + case 2: + slot_cmd = SETA_PCIX_66MHZ; + break; + case 3: + slot_cmd = SETA_PCIX_100MHZ; + break; + case 4: + slot_cmd = SETA_PCIX_133MHZ; + break; + default: + slot_cmd = PCI_SPEED_UNKNOWN; + retval = -ENODEV; + return retval; + } + } else { + switch (value) { + case 0: + slot_cmd = SETB_PCI_33MHZ; + break; + case 1: + slot_cmd = SETB_PCI_66MHZ; + break; + case 2: + slot_cmd = SETB_PCIX_66MHZ_PM; + break; + case 3: + slot_cmd = SETB_PCIX_100MHZ_PM; + break; + case 4: + slot_cmd = SETB_PCIX_133MHZ_PM; + break; + case 5: + slot_cmd = SETB_PCIX_66MHZ_EM; + break; + case 6: + slot_cmd = SETB_PCIX_100MHZ_EM; + break; + case 7: + slot_cmd = SETB_PCIX_133MHZ_EM; + break; + case 8: + slot_cmd = SETB_PCIX_66MHZ_266; + break; + case 0x9: + slot_cmd = SETB_PCIX_100MHZ_266; + break; + case 0xa: + slot_cmd = SETB_PCIX_133MHZ_266; + break; + case 0xb: + slot_cmd = SETB_PCIX_66MHZ_533; + break; + case 0xc: + slot_cmd = SETB_PCIX_100MHZ_533; + break; + case 0xd: + slot_cmd = SETB_PCIX_133MHZ_533; + break; + default: + slot_cmd = PCI_SPEED_UNKNOWN; + retval = -ENODEV; + return retval; + } + + } + retval = shpc_write_cmd(slot, 0, slot_cmd); + if (retval) { + err("%s: Write command failed!\n", __FUNCTION__); + return -1; + } + + DBG_LEAVE_ROUTINE + return retval; +} + +static irqreturn_t shpc_isr(int IRQ, void *dev_id, struct pt_regs *regs) +{ + struct controller *ctrl = NULL; + struct php_ctlr_state_s *php_ctlr; + u8 schedule_flag = 0; + u8 temp_byte; + u32 temp_dword, intr_loc, intr_loc2; + int hp_slot; + + if (!dev_id) + return IRQ_NONE; + + if (!shpchp_poll_mode) { + ctrl = (struct controller *)dev_id; + php_ctlr = ctrl->hpc_ctlr_handle; + } else { + php_ctlr = (struct php_ctlr_state_s *) dev_id; + ctrl = (struct controller *)php_ctlr->callback_instance_id; + } + + if (!ctrl) + return IRQ_NONE; + + if (!php_ctlr || !php_ctlr->creg) + return IRQ_NONE; + + /* Check to see if it was our interrupt */ + intr_loc = readl(php_ctlr->creg + INTR_LOC); + + if (!intr_loc) + return IRQ_NONE; + dbg("%s: shpc_isr proceeds\n", __FUNCTION__); + dbg("%s: intr_loc = %x\n",__FUNCTION__, intr_loc); + + if(!shpchp_poll_mode) { + /* Mask Global Interrupt Mask - see implementation note on p. 139 */ + /* of SHPC spec rev 1.0*/ + temp_dword = readl(php_ctlr->creg + SERR_INTR_ENABLE); + dbg("%s: Before masking global interrupt, temp_dword = %x\n", + __FUNCTION__, temp_dword); + temp_dword |= 0x00000001; + dbg("%s: After masking global interrupt, temp_dword = %x\n", + __FUNCTION__, temp_dword); + writel(temp_dword, php_ctlr->creg + SERR_INTR_ENABLE); + + intr_loc2 = readl(php_ctlr->creg + INTR_LOC); + dbg("%s: intr_loc2 = %x\n",__FUNCTION__, intr_loc2); + } + + if (intr_loc & 0x0001) { + /* + * Command Complete Interrupt Pending + * RO only - clear by writing 0 to the Command Completion + * Detect bit in Controller SERR-INT register + */ + temp_dword = readl(php_ctlr->creg + SERR_INTR_ENABLE); + dbg("%s: Before clearing CCIP, temp_dword = %x\n", + __FUNCTION__, temp_dword); + temp_dword &= 0xfffeffff; + dbg("%s: After clearing CCIP, temp_dword = %x\n", + __FUNCTION__, temp_dword); + writel(temp_dword, php_ctlr->creg + SERR_INTR_ENABLE); + wake_up_interruptible(&ctrl->queue); + } + + if ((intr_loc = (intr_loc >> 1)) == 0) { + /* Unmask Global Interrupt Mask */ + temp_dword = readl(php_ctlr->creg + SERR_INTR_ENABLE); + dbg("%s: 1-Before unmasking global interrupt, temp_dword = %x\n", + __FUNCTION__, temp_dword); + temp_dword &= 0xfffffffe; + dbg("%s: 1-After unmasking global interrupt, temp_dword = %x\n", + __FUNCTION__, temp_dword); + writel(temp_dword, php_ctlr->creg + SERR_INTR_ENABLE); + + return IRQ_NONE; + } + + for (hp_slot = 0; hp_slot < ctrl->num_slots; hp_slot++) { + /* To find out which slot has interrupt pending */ + if ((intr_loc >> hp_slot) & 0x01) { + temp_dword = readl(php_ctlr->creg + SLOT1 + (4*hp_slot)); + dbg("%s: Slot %x with intr, temp_dword = %x\n", + __FUNCTION__, hp_slot, temp_dword); + temp_byte = (temp_dword >> 16) & 0xFF; + dbg("%s: Slot with intr, temp_byte = %x\n", + __FUNCTION__, temp_byte); + if ((php_ctlr->switch_change_callback) && (temp_byte & 0x08)) + schedule_flag += php_ctlr->switch_change_callback( + hp_slot, php_ctlr->callback_instance_id); + if ((php_ctlr->attention_button_callback) && (temp_byte & 0x04)) + schedule_flag += php_ctlr->attention_button_callback( + hp_slot, php_ctlr->callback_instance_id); + if ((php_ctlr->presence_change_callback) && (temp_byte & 0x01)) + schedule_flag += php_ctlr->presence_change_callback( + hp_slot , php_ctlr->callback_instance_id); + if ((php_ctlr->power_fault_callback) && (temp_byte & 0x12)) + schedule_flag += php_ctlr->power_fault_callback( + hp_slot, php_ctlr->callback_instance_id); + + /* Clear all slot events */ + temp_dword = 0xe01f3fff; + dbg("%s: Clearing slot events, temp_dword = %x\n", + __FUNCTION__, temp_dword); + writel(temp_dword, php_ctlr->creg + SLOT1 + (4*hp_slot)); + + intr_loc2 = readl(php_ctlr->creg + INTR_LOC); + dbg("%s: intr_loc2 = %x\n",__FUNCTION__, intr_loc2); + } + } + if (!shpchp_poll_mode) { + /* Unmask Global Interrupt Mask */ + temp_dword = readl(php_ctlr->creg + SERR_INTR_ENABLE); + dbg("%s: 2-Before unmasking global interrupt, temp_dword = %x\n", + __FUNCTION__, temp_dword); + temp_dword &= 0xfffffffe; + dbg("%s: 2-After unmasking global interrupt, temp_dword = %x\n", + __FUNCTION__, temp_dword); + writel(temp_dword, php_ctlr->creg + SERR_INTR_ENABLE); + } + + return IRQ_HANDLED; +} + +static int hpc_get_max_bus_speed (struct slot *slot, enum pci_bus_speed *value) +{ + struct php_ctlr_state_s *php_ctlr = (struct php_ctlr_state_s *) slot->ctrl->hpc_ctlr_handle; + enum pci_bus_speed bus_speed = PCI_SPEED_UNKNOWN; + int retval = 0; + u8 pi; + u32 slot_avail1, slot_avail2; + int slot_num; + + DBG_ENTER_ROUTINE + + if (!slot->ctrl->hpc_ctlr_handle) { + err("%s: Invalid HPC controller handle!\n", __FUNCTION__); + return -1; + } + + if (slot->hp_slot >= php_ctlr->num_slots) { + err("%s: Invalid HPC slot number!\n", __FUNCTION__); + return -1; + } + + pi = readb(php_ctlr->creg + PROG_INTERFACE); + slot_avail1 = readl(php_ctlr->creg + SLOT_AVAIL1); + slot_avail2 = readl(php_ctlr->creg + SLOT_AVAIL2); + + if (pi == 2) { + if ((slot_num = ((slot_avail2 & SLOT_133MHZ_PCIX_533) >> 27) ) != 0 ) + bus_speed = PCIX_133MHZ_533; + else if ((slot_num = ((slot_avail2 & SLOT_100MHZ_PCIX_533) >> 23) ) != 0 ) + bus_speed = PCIX_100MHZ_533; + else if ((slot_num = ((slot_avail2 & SLOT_66MHZ_PCIX_533) >> 19) ) != 0 ) + bus_speed = PCIX_66MHZ_533; + else if ((slot_num = ((slot_avail2 & SLOT_133MHZ_PCIX_266) >> 15) ) != 0 ) + bus_speed = PCIX_133MHZ_266; + else if ((slot_num = ((slot_avail2 & SLOT_100MHZ_PCIX_266) >> 11) ) != 0 ) + bus_speed = PCIX_100MHZ_266; + else if ((slot_num = ((slot_avail2 & SLOT_66MHZ_PCIX_266) >> 7) ) != 0 ) + bus_speed = PCIX_66MHZ_266; + else if ((slot_num = ((slot_avail1 & SLOT_133MHZ_PCIX) >> 23) ) != 0 ) + bus_speed = PCIX_133MHZ; + else if ((slot_num = ((slot_avail1 & SLOT_100MHZ_PCIX) >> 15) ) != 0 ) + bus_speed = PCIX_100MHZ; + else if ((slot_num = ((slot_avail1 & SLOT_66MHZ_PCIX) >> 7) ) != 0 ) + bus_speed = PCIX_66MHZ; + else if ((slot_num = (slot_avail2 & SLOT_66MHZ)) != 0 ) + bus_speed = PCI_66MHZ; + else if ((slot_num = (slot_avail1 & SLOT_33MHZ)) != 0 ) + bus_speed = PCI_33MHZ; + else bus_speed = PCI_SPEED_UNKNOWN; + } else { + if ((slot_num = ((slot_avail1 & SLOT_133MHZ_PCIX) >> 23) ) != 0 ) + bus_speed = PCIX_133MHZ; + else if ((slot_num = ((slot_avail1 & SLOT_100MHZ_PCIX) >> 15) ) != 0 ) + bus_speed = PCIX_100MHZ; + else if ((slot_num = ((slot_avail1 & SLOT_66MHZ_PCIX) >> 7) ) != 0 ) + bus_speed = PCIX_66MHZ; + else if ((slot_num = (slot_avail2 & SLOT_66MHZ)) != 0 ) + bus_speed = PCI_66MHZ; + else if ((slot_num = (slot_avail1 & SLOT_33MHZ)) != 0 ) + bus_speed = PCI_33MHZ; + else bus_speed = PCI_SPEED_UNKNOWN; + } + + *value = bus_speed; + dbg("Max bus speed = %d\n", bus_speed); + DBG_LEAVE_ROUTINE + return retval; +} + +static int hpc_get_cur_bus_speed (struct slot *slot, enum pci_bus_speed *value) +{ + struct php_ctlr_state_s *php_ctlr = (struct php_ctlr_state_s *) slot->ctrl->hpc_ctlr_handle; + enum pci_bus_speed bus_speed = PCI_SPEED_UNKNOWN; + u16 sec_bus_status; + int retval = 0; + u8 pi; + + DBG_ENTER_ROUTINE + + if (!slot->ctrl->hpc_ctlr_handle) { + err("%s: Invalid HPC controller handle!\n", __FUNCTION__); + return -1; + } + + if (slot->hp_slot >= php_ctlr->num_slots) { + err("%s: Invalid HPC slot number!\n", __FUNCTION__); + return -1; + } + + pi = readb(php_ctlr->creg + PROG_INTERFACE); + sec_bus_status = readw(php_ctlr->creg + SEC_BUS_CONFIG); + + if (pi == 2) { + switch (sec_bus_status & 0x000f) { + case 0: + bus_speed = PCI_SPEED_33MHz; + break; + case 1: + bus_speed = PCI_SPEED_66MHz; + break; + case 2: + bus_speed = PCI_SPEED_66MHz_PCIX; + break; + case 3: + bus_speed = PCI_SPEED_100MHz_PCIX; + break; + case 4: + bus_speed = PCI_SPEED_133MHz_PCIX; + break; + case 5: + bus_speed = PCI_SPEED_66MHz_PCIX_ECC; + break; + case 6: + bus_speed = PCI_SPEED_100MHz_PCIX_ECC; + break; + case 7: + bus_speed = PCI_SPEED_133MHz_PCIX_ECC; + break; + case 8: + bus_speed = PCI_SPEED_66MHz_PCIX_266; + break; + case 9: + bus_speed = PCI_SPEED_100MHz_PCIX_266; + break; + case 0xa: + bus_speed = PCI_SPEED_133MHz_PCIX_266; + break; + case 0xb: + bus_speed = PCI_SPEED_66MHz_PCIX_533; + break; + case 0xc: + bus_speed = PCI_SPEED_100MHz_PCIX_533; + break; + case 0xd: + bus_speed = PCI_SPEED_133MHz_PCIX_533; + break; + case 0xe: + case 0xf: + default: + bus_speed = PCI_SPEED_UNKNOWN; + break; + } + } else { + /* In the case where pi is undefined, default it to 1 */ + switch (sec_bus_status & 0x0007) { + case 0: + bus_speed = PCI_SPEED_33MHz; + break; + case 1: + bus_speed = PCI_SPEED_66MHz; + break; + case 2: + bus_speed = PCI_SPEED_66MHz_PCIX; + break; + case 3: + bus_speed = PCI_SPEED_100MHz_PCIX; + break; + case 4: + bus_speed = PCI_SPEED_133MHz_PCIX; + break; + case 5: + bus_speed = PCI_SPEED_UNKNOWN; /* Reserved */ + break; + case 6: + bus_speed = PCI_SPEED_UNKNOWN; /* Reserved */ + break; + case 7: + bus_speed = PCI_SPEED_UNKNOWN; /* Reserved */ + break; + default: + bus_speed = PCI_SPEED_UNKNOWN; + break; + } + } + + *value = bus_speed; + dbg("Current bus speed = %d\n", bus_speed); + DBG_LEAVE_ROUTINE + 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, + .enable_all_slots = hpc_enable_all_slots, + .pwr_on_all_slots = hpc_pwr_on_all_slots, + .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, + .check_cmd_status = hpc_check_cmd_status, +}; + +int shpc_init(struct controller * ctrl, + struct pci_dev * pdev, + php_intr_callback_t attention_button_callback, + php_intr_callback_t switch_change_callback, + php_intr_callback_t presence_change_callback, + php_intr_callback_t power_fault_callback) +{ + struct php_ctlr_state_s *php_ctlr, *p; + void *instance_id = ctrl; + int rc; + u8 hp_slot; + static int first = 1; + u32 shpc_cap_offset, shpc_base_offset; + u32 tempdword, slot_reg; + u16 vendor_id, device_id; + u8 i; + + DBG_ENTER_ROUTINE + + spin_lock_init(&list_lock); + php_ctlr = (struct php_ctlr_state_s *) kmalloc(sizeof(struct php_ctlr_state_s), GFP_KERNEL); + + if (!php_ctlr) { /* allocate controller state data */ + err("%s: HPC controller memory allocation error!\n", __FUNCTION__); + goto abort; + } + + memset(php_ctlr, 0, sizeof(struct php_ctlr_state_s)); + + php_ctlr->pci_dev = pdev; /* save pci_dev in context */ + + rc = pci_read_config_word(pdev, PCI_VENDOR_ID, &vendor_id); + dbg("%s: Vendor ID: %x\n",__FUNCTION__, vendor_id); + if (rc) { + err("%s: unable to read PCI configuration data\n", __FUNCTION__); + goto abort_free_ctlr; + } + + rc = pci_read_config_word(pdev, PCI_DEVICE_ID, &device_id); + dbg("%s: Device ID: %x\n",__FUNCTION__, device_id); + if (rc) { + err("%s: unable to read PCI configuration data\n", __FUNCTION__); + goto abort_free_ctlr; + } + + if ((vendor_id == PCI_VENDOR_ID_AMD) || (device_id == PCI_DEVICE_ID_AMD_GOLAM_7450)) { + shpc_base_offset = 0; /* amd shpc driver doesn't use this; assume 0 */ + } else { + if ((shpc_cap_offset = pci_find_capability(pdev, PCI_CAP_ID_SHPC)) == 0) { + err("%s : shpc_cap_offset == 0\n", __FUNCTION__); + goto abort_free_ctlr; + } + dbg("%s: shpc_cap_offset = %x\n", __FUNCTION__, shpc_cap_offset); + + rc = pci_write_config_byte(pdev, (u8)shpc_cap_offset + DWORD_SELECT , BASE_OFFSET); + if (rc) { + err("%s : pci_word_config_byte failed\n", __FUNCTION__); + goto abort_free_ctlr; + } + + rc = pci_read_config_dword(pdev, (u8)shpc_cap_offset + DWORD_DATA, &shpc_base_offset); + if (rc) { + err("%s : pci_read_config_dword failed\n", __FUNCTION__); + goto abort_free_ctlr; + } + + for (i = 0; i <= 14; i++) { + rc = pci_write_config_byte(pdev, (u8)shpc_cap_offset + DWORD_SELECT , i); + if (rc) { + err("%s : pci_word_config_byte failed\n", __FUNCTION__); + goto abort_free_ctlr; + } + + rc = pci_read_config_dword(pdev, (u8)shpc_cap_offset + DWORD_DATA, &tempdword); + if (rc) { + err("%s : pci_read_config_dword failed\n", __FUNCTION__); + goto abort_free_ctlr; + } + dbg("%s: offset %d: tempdword %x\n", __FUNCTION__,i, tempdword); + } + } + + if (first) { + spin_lock_init(&hpc_event_lock); + first = 0; + } + + dbg("pdev = %p: b:d:f:irq=0x%x:%x:%x:%x\n", pdev, pdev->bus->number, PCI_SLOT(pdev->devfn), + PCI_FUNC(pdev->devfn), pdev->irq); + for ( rc = 0; rc < DEVICE_COUNT_RESOURCE; rc++) + if (pci_resource_len(pdev, rc) > 0) + dbg("pci resource[%d] start=0x%lx(len=0x%lx), shpc_base_offset %x\n", rc, + pci_resource_start(pdev, rc), pci_resource_len(pdev, rc), shpc_base_offset); + + info("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 (pci_enable_device(pdev)) + goto abort_free_ctlr; + + if (!request_mem_region(pci_resource_start(pdev, 0) + shpc_base_offset, pci_resource_len(pdev, 0), MY_NAME)) { + err("%s: cannot reserve MMIO region\n", __FUNCTION__); + goto abort_free_ctlr; + } + + php_ctlr->creg = ioremap(pci_resource_start(pdev, 0) + shpc_base_offset, pci_resource_len(pdev, 0)); + if (!php_ctlr->creg) { + err("%s: cannot remap MMIO region %lx @ %lx\n", __FUNCTION__, pci_resource_len(pdev, 0), + pci_resource_start(pdev, 0) + shpc_base_offset); + release_mem_region(pci_resource_start(pdev, 0) + shpc_base_offset, pci_resource_len(pdev, 0)); + goto abort_free_ctlr; + } + dbg("%s: php_ctlr->creg %p\n", __FUNCTION__, php_ctlr->creg); + dbg("%s: physical addr %p\n", __FUNCTION__, (void*)pci_resource_start(pdev, 0)); + + init_MUTEX(&ctrl->crit_sect); + /* Setup wait queue */ + init_waitqueue_head(&ctrl->queue); + + /* Find the IRQ */ + php_ctlr->irq = pdev->irq; + dbg("HPC interrupt = %d\n", php_ctlr->irq); + + /* Save interrupt callback info */ + php_ctlr->attention_button_callback = attention_button_callback; + php_ctlr->switch_change_callback = switch_change_callback; + php_ctlr->presence_change_callback = presence_change_callback; + php_ctlr->power_fault_callback = power_fault_callback; + php_ctlr->callback_instance_id = instance_id; + + /* Return PCI Controller Info */ + php_ctlr->slot_device_offset = (readl(php_ctlr->creg + SLOT_CONFIG) & FIRST_DEV_NUM ) >> 8; + php_ctlr->num_slots = readl(php_ctlr->creg + SLOT_CONFIG) & SLOT_NUM; + dbg("%s: slot_device_offset %x\n", __FUNCTION__, php_ctlr->slot_device_offset); + dbg("%s: num_slots %x\n", __FUNCTION__, php_ctlr->num_slots); + + /* Mask Global Interrupt Mask & Command Complete Interrupt Mask */ + tempdword = readl(php_ctlr->creg + SERR_INTR_ENABLE); + dbg("%s: SERR_INTR_ENABLE = %x\n", __FUNCTION__, tempdword); + tempdword = 0x0003000f; + writel(tempdword, php_ctlr->creg + SERR_INTR_ENABLE); + tempdword = readl(php_ctlr->creg + SERR_INTR_ENABLE); + dbg("%s: SERR_INTR_ENABLE = %x\n", __FUNCTION__, 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 < php_ctlr->num_slots; hp_slot++) { + slot_reg = readl(php_ctlr->creg + SLOT1 + 4*hp_slot ); + dbg("%s: Default Logical Slot Register %d value %x\n", __FUNCTION__, + hp_slot, slot_reg); + tempdword = 0xffff3fff; + writel(tempdword, php_ctlr->creg + SLOT1 + (4*hp_slot)); + } + + if (shpchp_poll_mode) {/* Install interrupt polling code */ + /* Install and start the interrupt polling timer */ + init_timer(&php_ctlr->int_poll_timer); + start_int_poll_timer( php_ctlr, 10 ); /* start with 10 second delay */ + } else { + /* Installs the interrupt handler */ + rc = pci_enable_msi(pdev); + if (rc) { + info("Can't get msi for the hotplug controller\n"); + info("Use INTx for the hotplug controller\n"); + dbg("%s: rc = %x\n", __FUNCTION__, rc); + } else + php_ctlr->irq = pdev->irq; + + rc = request_irq(php_ctlr->irq, shpc_isr, SA_SHIRQ, MY_NAME, (void *) ctrl); + dbg("%s: request_irq %d for hpc%d (returns %d)\n", __FUNCTION__, php_ctlr->irq, ctlr_seq_num, rc); + if (rc) { + err("Can't get irq %d for the hotplug controller\n", php_ctlr->irq); + goto abort_free_ctlr; + } + /* Execute OSHP method here */ + } + dbg("%s: Before adding HPC to HPC list\n", __FUNCTION__); + + /* Add this HPC instance into the HPC list */ + spin_lock(&list_lock); + if (php_ctlr_list_head == 0) { + php_ctlr_list_head = php_ctlr; + p = php_ctlr_list_head; + p->pnext = NULL; + } else { + p = php_ctlr_list_head; + + while (p->pnext) + p = p->pnext; + + p->pnext = php_ctlr; + } + spin_unlock(&list_lock); + + + ctlr_seq_num++; + ctrl->hpc_ctlr_handle = php_ctlr; + ctrl->hpc_ops = &shpchp_hpc_ops; + + for (hp_slot = 0; hp_slot < php_ctlr->num_slots; hp_slot++) { + slot_reg = readl(php_ctlr->creg + SLOT1 + 4*hp_slot ); + dbg("%s: Default Logical Slot Register %d value %x\n", __FUNCTION__, + hp_slot, slot_reg); + tempdword = 0xe01f3fff; + writel(tempdword, php_ctlr->creg + SLOT1 + (4*hp_slot)); + } + if (!shpchp_poll_mode) { + /* Unmask all general input interrupts and SERR */ + tempdword = readl(php_ctlr->creg + SERR_INTR_ENABLE); + tempdword = 0x0000000a; + writel(tempdword, php_ctlr->creg + SERR_INTR_ENABLE); + tempdword = readl(php_ctlr->creg + SERR_INTR_ENABLE); + dbg("%s: SERR_INTR_ENABLE = %x\n", __FUNCTION__, tempdword); + } + + dbg("%s: Leaving shpc_init\n", __FUNCTION__); + DBG_LEAVE_ROUTINE + return 0; + + /* We end up here for the many possible ways to fail this API. */ +abort_free_ctlr: + kfree(php_ctlr); +abort: + DBG_LEAVE_ROUTINE + return -1; +} diff --git a/drivers/pci/hotplug/shpchp_pci.c b/drivers/pci/hotplug/shpchp_pci.c new file mode 100644 index 000000000000..90113e9cd69b --- /dev/null +++ b/drivers/pci/hotplug/shpchp_pci.c @@ -0,0 +1,810 @@ +/* + * 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>, <dely.l.sy@intel.com> + * + */ + +#include <linux/config.h> +#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 "../pci.h" +#include "shpchp.h" +#ifndef CONFIG_IA64 +#include "../../../arch/i386/pci/pci.h" /* horrible hack showing how processor dependant we are... */ +#endif + +int shpchp_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)); + + /* Still NULL ? Well then scan for it ! */ + if (func->pci_dev == NULL) { + num = pci_scan_slot(ctrl->pci_dev->subordinate, PCI_DEVFN(func->device, func->function)); + if (num) { + dbg("%s: subordiante %p number %x\n", __FUNCTION__, ctrl->pci_dev->subordinate, + ctrl->pci_dev->subordinate->number); + pci_bus_add_devices(ctrl->pci_dev->subordinate); + } + + 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 = pci_add_new_bus(func->pci_dev->bus, (func->pci_dev), bus); + pci_do_scan_bus(child); + + } + + return 0; +} + + +int shpchp_unconfigure_device(struct pci_func* func) +{ + int rc = 0; + int j; + + dbg("%s: bus/dev/func = %x/%x/%x\n", __FUNCTION__, func->bus, + func->device, func->function); + + for (j=0; j<8 ; j++) { + struct pci_dev* temp = pci_find_slot(func->bus, + (func->device << 3) | j); + if (temp) { + pci_remove_bus_device(temp); + } + } + return rc; +} + +/* + * shpchp_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 shpchp_set_irq (u8 bus_num, u8 dev_num, u8 int_pin, u8 irq_num) +{ +#if defined(CONFIG_X86) && !defined(CONFIG_X86_IO_APIC) && !defined(CONFIG_X86_64) + int rc; + u16 temp_word; + struct pci_dev fakedev; + struct pci_bus fakebus; + + fakedev.devfn = dev_num << 3; + fakedev.bus = &fakebus; + fakebus.number = bus_num; + dbg("%s: dev %d, bus %d, pin %d, num %d\n", + __FUNCTION__, dev_num, bus_num, int_pin, irq_num); + rc = pcibios_set_irq_routing(&fakedev, int_pin - 0x0a, irq_num); + dbg("%s: rc %d\n", __FUNCTION__, 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); +#endif + return 0; +} + +/* More PCI configuration routines; this time centered around hotplug controller */ + + +/* + * shpchp_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 shpchp_save_config(struct controller *ctrl, int busnumber, int num_ctlr_slots, int first_device_num) +{ + int 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; + int is_hot_plug = num_ctlr_slots || first_device_num; + struct pci_bus lpci_bus, *pci_bus; + + dbg("%s: num_ctlr_slots = %d, first_device_num = %d\n", __FUNCTION__, + num_ctlr_slots, first_device_num); + + memcpy(&lpci_bus, ctrl->pci_dev->subordinate, sizeof(lpci_bus)); + pci_bus = &lpci_bus; + + dbg("%s: num_ctlr_slots = %d, first_device_num = %d\n", __FUNCTION__, + num_ctlr_slots, first_device_num); + + /* Decide which slots are supported */ + if (is_hot_plug) { + /********************************* + * is_hot_plug is the slot mask + *********************************/ + FirstSupported = first_device_num; + LastSupported = FirstSupported + num_ctlr_slots - 1; + } else { + FirstSupported = 0; + LastSupported = 0x1F; + } + + dbg("FirstSupported = %d, LastSupported = %d\n", FirstSupported, + LastSupported); + + /* Save PCI configuration space for all devices in supported slots */ + pci_bus->number = busnumber; + for (device = FirstSupported; device <= LastSupported; device++) { + ID = 0xFFFFFFFF; + rc = pci_bus_read_config_dword(pci_bus, PCI_DEVFN(device, 0), + PCI_VENDOR_ID, &ID); + + if (ID != 0xFFFFFFFF) { /* device in slot */ + rc = pci_bus_read_config_byte(pci_bus, PCI_DEVFN(device, 0), + 0x0B, &class_code); + if (rc) + return rc; + + rc = pci_bus_read_config_byte(pci_bus, PCI_DEVFN(device, 0), + PCI_HEADER_TYPE, &header_type); + if (rc) + return rc; + + dbg("class_code = %x, header_type = %x\n", class_code, header_type); + + /* 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(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 = shpchp_save_config(ctrl, sub_bus, 0, 0); + if (rc) + return rc; + } + } + + index = 0; + new_slot = shpchp_slot_find(busnumber, device, index++); + + dbg("new_slot = %p\n", new_slot); + + while (new_slot && (new_slot->function != (u8) function)) { + new_slot = shpchp_slot_find(busnumber, device, index++); + dbg("new_slot = %p\n", new_slot); + } + if (!new_slot) { + /* Setup slot structure. */ + new_slot = shpchp_slot_create(busnumber); + dbg("new_slot = %p\n", new_slot); + + 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; + new_slot->pwr_save = 1; + /* 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); + dbg("new_slot->pci_dev = %p\n", new_slot->pci_dev); + + for (cloop = 0; cloop < 0x20; cloop++) { + rc = pci_bus_read_config_dword(pci_bus, + PCI_DEVFN(device, function), + cloop << 2, + (u32 *) &(new_slot->config_space [cloop])); + /* dbg("new_slot->config_space[%x] = %x\n", + cloop, 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(pci_bus, + PCI_DEVFN(device, function), + PCI_VENDOR_ID, &ID); + + if (ID == 0xFFFFFFFF) { /* nothing there. */ + function++; + dbg("Nothing there\n"); + } else { /* Something there */ + rc = pci_bus_read_config_byte(pci_bus, + PCI_DEVFN(device, function), + 0x0B, &class_code); + if (rc) + return rc; + + rc = pci_bus_read_config_byte(pci_bus, + PCI_DEVFN(device, function), + PCI_HEADER_TYPE, &header_type); + if (rc) + return rc; + + dbg("class_code = %x, header_type = %x\n", + class_code, header_type); + 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 = shpchp_slot_create(busnumber); + + if (new_slot == NULL) { + return(1); + } + dbg("new_slot = %p\n", new_slot); + + 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); +} + + +/* + * shpchp_save_slot_config + * + * Saves configuration info for all PCI devices in a given slot + * including subordinate busses. + * + * returns 0 if success + */ +int shpchp_save_slot_config(struct controller *ctrl, struct pci_func * new_slot) +{ + int 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; + struct pci_bus lpci_bus, *pci_bus; + memcpy(&lpci_bus, ctrl->pci_dev->subordinate, sizeof(lpci_bus)); + pci_bus = &lpci_bus; + pci_bus->number = new_slot->bus; + + ID = 0xFFFFFFFF; + + pci_bus_read_config_dword(pci_bus, PCI_DEVFN(new_slot->device, 0), + PCI_VENDOR_ID, &ID); + + if (ID != 0xFFFFFFFF) { /* device in slot */ + pci_bus_read_config_byte(pci_bus, PCI_DEVFN(new_slot->device, 0), + 0x0B, &class_code); + + pci_bus_read_config_byte(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(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 = shpchp_save_config(ctrl, sub_bus, 0, 0); + + if (rc) + return rc; + + } /* End of IF */ + + new_slot->status = 0; + + for (cloop = 0; cloop < 0x20; cloop++) { + pci_bus_read_config_dword(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(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(pci_bus, + PCI_DEVFN(new_slot->device, function), + 0x0B, &class_code); + + pci_bus_read_config_byte(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; +} + + +/* + * shpchp_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 + * if disable == 1(DISABLE_CARD), + * it loops for all functions of the slot and disables them. + * else, it just get resources of the function and return. + */ +int shpchp_save_used_resources(struct controller *ctrl, struct pci_func *func, int disable) +{ + u8 cloop; + u8 header_type; + u8 secondary_bus; + u8 temp_byte; + u16 command; + u16 save_command; + u16 w_base, w_length; + u32 temp_register; + u32 save_base; + u32 base, length; + u64 base64 = 0; + int index = 0; + unsigned int devfn; + struct pci_resource *mem_node = NULL; + struct pci_resource *p_mem_node = NULL; + struct pci_resource *t_mem_node; + struct pci_resource *io_node; + struct pci_resource *bus_node; + struct pci_bus lpci_bus, *pci_bus; + memcpy(&lpci_bus, ctrl->pci_dev->subordinate, sizeof(lpci_bus)); + pci_bus = &lpci_bus; + + if (disable) + func = shpchp_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); + + if (disable) { + /* 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 */ + dbg("Save_used_res of PCI bridge b:d=0x%x:%x, sc=0x%x\n", + func->bus, func->device, save_command); + if (disable) { + /* 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(struct pci_resource), + GFP_KERNEL); + if (!bus_node) + return -ENOMEM; + + bus_node->base = (ulong)secondary_bus; + bus_node->length = (ulong)(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, &temp_byte); + base = temp_byte; + pci_bus_read_config_byte(pci_bus, devfn, PCI_IO_LIMIT, &temp_byte); + length = temp_byte; + + if ((base <= length) && (!disable || (save_command & PCI_COMMAND_IO))) { + io_node = kmalloc(sizeof(struct pci_resource), + GFP_KERNEL); + if (!io_node) + return -ENOMEM; + + io_node->base = (ulong)(base & PCI_IO_RANGE_MASK) << 8; + io_node->length = (ulong)(length - 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) && (!disable || (save_command & PCI_COMMAND_MEMORY))) { + mem_node = kmalloc(sizeof(struct pci_resource), + GFP_KERNEL); + if (!mem_node) + return -ENOMEM; + + mem_node->base = (ulong)w_base << 16; + mem_node->length = (ulong)(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) && (!disable || (save_command & PCI_COMMAND_MEMORY))) { + p_mem_node = kmalloc(sizeof(struct pci_resource), + GFP_KERNEL); + if (!p_mem_node) + return -ENOMEM; + + p_mem_node->base = (ulong)w_base << 16; + p_mem_node->length = (ulong)(w_length - w_base + 0x10) << 16; + + p_mem_node->next = func->p_mem_head; + func->p_mem_head = p_mem_node; + } + } else if ((header_type & 0x7F) == PCI_HEADER_TYPE_NORMAL) { + dbg("Save_used_res of PCI adapter b:d=0x%x:%x, sc=0x%x\n", + func->bus, func->device, save_command); + + /* Figure out IO and memory base lengths */ + for (cloop = PCI_BASE_ADDRESS_0; cloop <= PCI_BASE_ADDRESS_5; 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, &temp_register); + + if (!disable) + pci_bus_write_config_dword(pci_bus, devfn, cloop, save_base); + + if (!temp_register) + continue; + + base = temp_register; + + if ((base & PCI_BASE_ADDRESS_SPACE_IO) && + (!disable || (save_command & PCI_COMMAND_IO))) { + /* IO base */ + /* set temp_register = amount of IO space requested */ + base = base & 0xFFFFFFFCL; + base = (~base) + 1; + + io_node = kmalloc(sizeof (struct pci_resource), + GFP_KERNEL); + if (!io_node) + return -ENOMEM; + + io_node->base = (ulong)save_base & PCI_BASE_ADDRESS_IO_MASK; + io_node->length = (ulong)base; + dbg("sur adapter: IO bar=0x%x(length=0x%x)\n", + io_node->base, io_node->length); + + io_node->next = func->io_head; + func->io_head = io_node; + } else { /* map Memory */ + int prefetchable = 1; + /* struct pci_resources **res_node; */ + char *res_type_str = "PMEM"; + u32 temp_register2; + + t_mem_node = kmalloc(sizeof (struct pci_resource), + GFP_KERNEL); + if (!t_mem_node) + return -ENOMEM; + + if (!(base & PCI_BASE_ADDRESS_MEM_PREFETCH) && + (!disable || (save_command & PCI_COMMAND_MEMORY))) { + prefetchable = 0; + mem_node = t_mem_node; + res_type_str++; + } else + p_mem_node = t_mem_node; + + base = base & 0xFFFFFFF0L; + base = (~base) + 1; + + switch (temp_register & PCI_BASE_ADDRESS_MEM_TYPE_MASK) { + case PCI_BASE_ADDRESS_MEM_TYPE_32: + if (prefetchable) { + p_mem_node->base = (ulong)save_base & PCI_BASE_ADDRESS_MEM_MASK; + p_mem_node->length = (ulong)base; + dbg("sur adapter: 32 %s bar=0x%x(length=0x%x)\n", + res_type_str, + p_mem_node->base, + p_mem_node->length); + + p_mem_node->next = func->p_mem_head; + func->p_mem_head = p_mem_node; + } else { + mem_node->base = (ulong)save_base & PCI_BASE_ADDRESS_MEM_MASK; + mem_node->length = (ulong)base; + dbg("sur adapter: 32 %s bar=0x%x(length=0x%x)\n", + res_type_str, + mem_node->base, + mem_node->length); + + mem_node->next = func->mem_head; + func->mem_head = mem_node; + } + break; + case PCI_BASE_ADDRESS_MEM_TYPE_64: + pci_bus_read_config_dword(pci_bus, devfn, cloop+4, &temp_register2); + base64 = temp_register2; + base64 = (base64 << 32) | save_base; + + if (temp_register2) { + dbg("sur adapter: 64 %s high dword of base64(0x%x:%x) masked to 0\n", + res_type_str, temp_register2, (u32)base64); + base64 &= 0x00000000FFFFFFFFL; + } + + if (prefetchable) { + p_mem_node->base = base64 & PCI_BASE_ADDRESS_MEM_MASK; + p_mem_node->length = base; + dbg("sur adapter: 64 %s base=0x%x(len=0x%x)\n", + res_type_str, + p_mem_node->base, + p_mem_node->length); + + p_mem_node->next = func->p_mem_head; + func->p_mem_head = p_mem_node; + } else { + mem_node->base = base64 & PCI_BASE_ADDRESS_MEM_MASK; + mem_node->length = base; + dbg("sur adapter: 64 %s base=0x%x(len=0x%x)\n", + res_type_str, + mem_node->base, + mem_node->length); + + mem_node->next = func->mem_head; + func->mem_head = mem_node; + } + cloop += 4; + break; + default: + dbg("asur: reserved BAR type=0x%x\n", + temp_register); + break; + } + } + } /* End of base register loop */ + } else { /* Some other unknown header type */ + dbg("Save_used_res of PCI unknown type b:d=0x%x:%x. skip.\n", + func->bus, func->device); + } + + /* find the next device in this slot */ + if (!disable) + break; + func = shpchp_slot_find(func->bus, func->device, index++); + } + + return 0; +} + +/** + * kfree_resource_list: release memory of all list members + * @res: resource list to free + */ +static inline void +return_resource_list(struct pci_resource **func, struct pci_resource **res) +{ + struct pci_resource *node; + struct pci_resource *t_node; + + node = *func; + *func = NULL; + while (node) { + t_node = node->next; + return_resource(res, node); + node = t_node; + } +} + +/* + * shpchp_return_board_resources + * + * this routine returns all resources allocated to a board to + * the available pool. + * + * returns 0 if success + */ +int shpchp_return_board_resources(struct pci_func * func, + struct resource_lists * resources) +{ + int rc; + dbg("%s\n", __FUNCTION__); + + if (!func) + return 1; + + return_resource_list(&(func->io_head),&(resources->io_head)); + return_resource_list(&(func->mem_head),&(resources->mem_head)); + return_resource_list(&(func->p_mem_head),&(resources->p_mem_head)); + return_resource_list(&(func->bus_head),&(resources->bus_head)); + + rc = shpchp_resource_sort_and_combine(&(resources->mem_head)); + rc |= shpchp_resource_sort_and_combine(&(resources->p_mem_head)); + rc |= shpchp_resource_sort_and_combine(&(resources->io_head)); + rc |= shpchp_resource_sort_and_combine(&(resources->bus_head)); + + return rc; +} + +/** + * kfree_resource_list: release memory of all list members + * @res: resource list to free + */ +static inline void +kfree_resource_list(struct pci_resource **r) +{ + struct pci_resource *res, *tres; + + res = *r; + *r = NULL; + + while (res) { + tres = res; + res = res->next; + kfree(tres); + } +} + +/** + * shpchp_destroy_resource_list: put node back in the resource list + * @resources: list to put nodes back + */ +void shpchp_destroy_resource_list(struct resource_lists *resources) +{ + kfree_resource_list(&(resources->io_head)); + kfree_resource_list(&(resources->mem_head)); + kfree_resource_list(&(resources->p_mem_head)); + kfree_resource_list(&(resources->bus_head)); +} + +/** + * shpchp_destroy_board_resources: put node back in the resource list + * @resources: list to put nodes back + */ +void shpchp_destroy_board_resources(struct pci_func * func) +{ + kfree_resource_list(&(func->io_head)); + kfree_resource_list(&(func->mem_head)); + kfree_resource_list(&(func->p_mem_head)); + kfree_resource_list(&(func->bus_head)); +} diff --git a/drivers/pci/hotplug/shpchp_sysfs.c b/drivers/pci/hotplug/shpchp_sysfs.c new file mode 100644 index 000000000000..9a1ee132d12c --- /dev/null +++ b/drivers/pci/hotplug/shpchp_sysfs.c @@ -0,0 +1,143 @@ +/* + * 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/config.h> +#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 "shpchp.h" + + +/* A few routines that create sysfs entries for the hot plug controller */ + +static ssize_t show_ctrl (struct device *dev, char *buf) +{ + struct pci_dev *pci_dev; + struct controller *ctrl; + char * out = buf; + int index; + struct pci_resource *res; + + pci_dev = container_of (dev, struct pci_dev, dev); + ctrl = pci_get_drvdata(pci_dev); + + 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 DEVICE_ATTR (ctrl, S_IRUGO, show_ctrl, NULL); + +static ssize_t show_dev (struct device *dev, char *buf) +{ + struct pci_dev *pci_dev; + struct controller *ctrl; + char * out = buf; + int index; + struct pci_resource *res; + struct pci_func *new_slot; + struct slot *slot; + + pci_dev = container_of (dev, struct pci_dev, dev); + ctrl = pci_get_drvdata(pci_dev); + + slot=ctrl->slot; + + while (slot) { + new_slot = shpchp_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 DEVICE_ATTR (dev, S_IRUGO, show_dev, NULL); + +void shpchp_create_ctrl_files (struct controller *ctrl) +{ + device_create_file (&ctrl->pci_dev->dev, &dev_attr_ctrl); + device_create_file (&ctrl->pci_dev->dev, &dev_attr_dev); +} diff --git a/drivers/pci/hotplug/shpchprm.h b/drivers/pci/hotplug/shpchprm.h new file mode 100644 index 000000000000..88aeb978c911 --- /dev/null +++ b/drivers/pci/hotplug/shpchprm.h @@ -0,0 +1,55 @@ +/* + * SHPCHPRM : SHPCHP Resource Manager for ACPI/non-ACPI platform + * + * 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>, <dely.l.sy@intel.com> + * + */ + +#ifndef _SHPCHPRM_H_ +#define _SHPCHPRM_H_ + +#ifdef CONFIG_HOTPLUG_PCI_SHPC_PHPRM_LEGACY +#include "shpchprm_legacy.h" +#else +#include "shpchprm_nonacpi.h" +#endif + +int shpchprm_init(enum php_ctlr_type ct); +void shpchprm_cleanup(void); +int shpchprm_print_pirt(void); +int shpchprm_find_available_resources(struct controller *ctrl); +int shpchprm_set_hpp(struct controller *ctrl, struct pci_func *func, u8 card_type); +void shpchprm_enable_card(struct controller *ctrl, struct pci_func *func, u8 card_type); +int shpchprm_get_physical_slot_number(struct controller *ctrl, u32 *sun, u8 busnum, u8 devnum); + +#ifdef DEBUG +#define RES_CHECK(this, bits) \ + { if (((this) & (bits - 1))) \ + printk("%s:%d ERR: potential res loss!\n", __FUNCTION__, __LINE__); } +#else +#define RES_CHECK(this, bits) +#endif + +#endif /* _SHPCHPRM_H_ */ diff --git a/drivers/pci/hotplug/shpchprm_acpi.c b/drivers/pci/hotplug/shpchprm_acpi.c new file mode 100644 index 000000000000..243a51d88b86 --- /dev/null +++ b/drivers/pci/hotplug/shpchprm_acpi.c @@ -0,0 +1,1713 @@ +/* + * SHPCHPRM ACPI: PHP Resource Manager for ACPI platform + * + * 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 <dely.l.sy@intel.com> + * + */ + +#include <linux/config.h> +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/types.h> +#include <linux/pci.h> +#include <linux/init.h> +#include <linux/acpi.h> +#include <linux/efi.h> +#include <asm/uaccess.h> +#include <asm/system.h> +#ifdef CONFIG_IA64 +#include <asm/iosapic.h> +#endif +#include <acpi/acpi.h> +#include <acpi/acpi_bus.h> +#include <acpi/actypes.h> +#include "shpchp.h" +#include "shpchprm.h" + +#define PCI_MAX_BUS 0x100 +#define ACPI_STA_DEVICE_PRESENT 0x01 + +#define METHOD_NAME__SUN "_SUN" +#define METHOD_NAME__HPP "_HPP" +#define METHOD_NAME_OSHP "OSHP" + +#define PHP_RES_BUS 0xA0 +#define PHP_RES_IO 0xA1 +#define PHP_RES_MEM 0xA2 +#define PHP_RES_PMEM 0xA3 + +#define BRIDGE_TYPE_P2P 0x00 +#define BRIDGE_TYPE_HOST 0x01 + +/* this should go to drivers/acpi/include/ */ +struct acpi__hpp { + u8 cache_line_size; + u8 latency_timer; + u8 enable_serr; + u8 enable_perr; +}; + +struct acpi_php_slot { + struct acpi_php_slot *next; + struct acpi_bridge *bridge; + acpi_handle handle; + int seg; + int bus; + int dev; + int fun; + u32 sun; + struct pci_resource *mem_head; + struct pci_resource *p_mem_head; + struct pci_resource *io_head; + struct pci_resource *bus_head; + void *slot_ops; /* _STA, _EJx, etc */ + struct slot *slot; +}; /* per func */ + +struct acpi_bridge { + struct acpi_bridge *parent; + struct acpi_bridge *next; + struct acpi_bridge *child; + acpi_handle handle; + int seg; + int pbus; /* pdev->bus->number */ + int pdevice; /* PCI_SLOT(pdev->devfn) */ + int pfunction; /* PCI_DEVFN(pdev->devfn) */ + int bus; /* pdev->subordinate->number */ + struct acpi__hpp *_hpp; + struct acpi_php_slot *slots; + struct pci_resource *tmem_head; /* total from crs */ + struct pci_resource *tp_mem_head; /* total from crs */ + struct pci_resource *tio_head; /* total from crs */ + struct pci_resource *tbus_head; /* total from crs */ + struct pci_resource *mem_head; /* available */ + struct pci_resource *p_mem_head; /* available */ + struct pci_resource *io_head; /* available */ + struct pci_resource *bus_head; /* available */ + int scanned; + int type; +}; + +static struct acpi_bridge *acpi_bridges_head; + +static u8 * acpi_path_name( acpi_handle handle) +{ + acpi_status status; + static u8 path_name[ACPI_PATHNAME_MAX]; + struct acpi_buffer ret_buf = { ACPI_PATHNAME_MAX, path_name }; + + memset(path_name, 0, sizeof (path_name)); + status = acpi_get_name(handle, ACPI_FULL_PATHNAME, &ret_buf); + + if (ACPI_FAILURE(status)) + return NULL; + else + return path_name; +} + +static void acpi_get__hpp ( struct acpi_bridge *ab); +static void acpi_run_oshp ( struct acpi_bridge *ab); + +static int acpi_add_slot_to_php_slots( + struct acpi_bridge *ab, + int bus_num, + acpi_handle handle, + u32 adr, + u32 sun + ) +{ + struct acpi_php_slot *aps; + static long samesun = -1; + + aps = (struct acpi_php_slot *) kmalloc (sizeof(struct acpi_php_slot), GFP_KERNEL); + if (!aps) { + err ("acpi_shpchprm: alloc for aps fail\n"); + return -1; + } + memset(aps, 0, sizeof(struct acpi_php_slot)); + + aps->handle = handle; + aps->bus = bus_num; + aps->dev = (adr >> 16) & 0xffff; + aps->fun = adr & 0xffff; + aps->sun = sun; + + aps->next = ab->slots; /* cling to the bridge */ + aps->bridge = ab; + ab->slots = aps; + + ab->scanned += 1; + if (!ab->_hpp) + acpi_get__hpp(ab); + + acpi_run_oshp(ab); + + if (sun != samesun) { + info("acpi_shpchprm: Slot sun(%x) at s:b:d:f=0x%02x:%02x:%02x:%02x\n", aps->sun, ab->seg, + aps->bus, aps->dev, aps->fun); + samesun = sun; + } + return 0; +} + +static void acpi_get__hpp ( struct acpi_bridge *ab) +{ + acpi_status status; + u8 nui[4]; + struct acpi_buffer ret_buf = { 0, NULL}; + union acpi_object *ext_obj, *package; + u8 *path_name = acpi_path_name(ab->handle); + int i, len = 0; + + /* get _hpp */ + status = acpi_evaluate_object(ab->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) { + err ("acpi_shpchprm:%s alloc for _HPP fail\n", path_name); + return; + } + status = acpi_evaluate_object(ab->handle, METHOD_NAME__HPP, NULL, &ret_buf); + if (ACPI_SUCCESS(status)) + break; + default: + if (ACPI_FAILURE(status)) { + err("acpi_shpchprm:%s _HPP fail=0x%x\n", path_name, status); + return; + } + } + + ext_obj = (union acpi_object *) ret_buf.pointer; + if (ext_obj->type != ACPI_TYPE_PACKAGE) { + err ("acpi_shpchprm:%s _HPP obj not a package\n", path_name); + 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: + err ("acpi_shpchprm:%s _HPP obj type incorrect\n", path_name); + goto free_and_return; + } + } + + ab->_hpp = kmalloc (sizeof (struct acpi__hpp), GFP_KERNEL); + if (!ab->_hpp) { + err ("acpi_shpchprm:%s alloc for _HPP failed\n", path_name); + goto free_and_return; + } + memset(ab->_hpp, 0, sizeof(struct acpi__hpp)); + + ab->_hpp->cache_line_size = nui[0]; + ab->_hpp->latency_timer = nui[1]; + ab->_hpp->enable_serr = nui[2]; + ab->_hpp->enable_perr = nui[3]; + + dbg(" _HPP: cache_line_size=0x%x\n", ab->_hpp->cache_line_size); + dbg(" _HPP: latency timer =0x%x\n", ab->_hpp->latency_timer); + dbg(" _HPP: enable SERR =0x%x\n", ab->_hpp->enable_serr); + dbg(" _HPP: enable PERR =0x%x\n", ab->_hpp->enable_perr); + +free_and_return: + kfree(ret_buf.pointer); +} + +static void acpi_run_oshp ( struct acpi_bridge *ab) +{ + acpi_status status; + u8 *path_name = acpi_path_name(ab->handle); + + /* run OSHP */ + status = acpi_evaluate_object(ab->handle, METHOD_NAME_OSHP, NULL, NULL); + if (ACPI_FAILURE(status)) { + err("acpi_pciehprm:%s OSHP fails=0x%x\n", path_name, status); + } else + dbg("acpi_pciehprm:%s OSHP passes =0x%x\n", path_name, status); + return; +} + +static acpi_status acpi_evaluate_crs( + acpi_handle handle, + struct acpi_resource **retbuf + ) +{ + acpi_status status; + struct acpi_buffer crsbuf; + u8 *path_name = acpi_path_name(handle); + + crsbuf.length = 0; + crsbuf.pointer = NULL; + + status = acpi_get_current_resources (handle, &crsbuf); + + switch (status) { + case AE_BUFFER_OVERFLOW: + break; /* found */ + case AE_NOT_FOUND: + dbg("acpi_shpchprm:%s _CRS not found\n", path_name); + return status; + default: + err ("acpi_shpchprm:%s _CRS fail=0x%x\n", path_name, status); + return status; + } + + crsbuf.pointer = kmalloc (crsbuf.length, GFP_KERNEL); + if (!crsbuf.pointer) { + err ("acpi_shpchprm: alloc %ld bytes for %s _CRS fail\n", (ulong)crsbuf.length, path_name); + return AE_NO_MEMORY; + } + + status = acpi_get_current_resources (handle, &crsbuf); + if (ACPI_FAILURE(status)) { + err("acpi_shpchprm: %s _CRS fail=0x%x.\n", path_name, status); + kfree(crsbuf.pointer); + return status; + } + + *retbuf = crsbuf.pointer; + + return status; +} + +static void free_pci_resource ( struct pci_resource *aprh) +{ + struct pci_resource *res, *next; + + for (res = aprh; res; res = next) { + next = res->next; + kfree(res); + } +} + +static void print_pci_resource ( struct pci_resource *aprh) +{ + struct pci_resource *res; + + for (res = aprh; res; res = res->next) + dbg(" base= 0x%x length= 0x%x\n", res->base, res->length); +} + +static void print_slot_resources( struct acpi_php_slot *aps) +{ + if (aps->bus_head) { + dbg(" BUS Resources:\n"); + print_pci_resource (aps->bus_head); + } + + if (aps->io_head) { + dbg(" IO Resources:\n"); + print_pci_resource (aps->io_head); + } + + if (aps->mem_head) { + dbg(" MEM Resources:\n"); + print_pci_resource (aps->mem_head); + } + + if (aps->p_mem_head) { + dbg(" PMEM Resources:\n"); + print_pci_resource (aps->p_mem_head); + } +} + +static void print_pci_resources( struct acpi_bridge *ab) +{ + if (ab->tbus_head) { + dbg(" Total BUS Resources:\n"); + print_pci_resource (ab->tbus_head); + } + if (ab->bus_head) { + dbg(" BUS Resources:\n"); + print_pci_resource (ab->bus_head); + } + + if (ab->tio_head) { + dbg(" Total IO Resources:\n"); + print_pci_resource (ab->tio_head); + } + if (ab->io_head) { + dbg(" IO Resources:\n"); + print_pci_resource (ab->io_head); + } + + if (ab->tmem_head) { + dbg(" Total MEM Resources:\n"); + print_pci_resource (ab->tmem_head); + } + if (ab->mem_head) { + dbg(" MEM Resources:\n"); + print_pci_resource (ab->mem_head); + } + + if (ab->tp_mem_head) { + dbg(" Total PMEM Resources:\n"); + print_pci_resource (ab->tp_mem_head); + } + if (ab->p_mem_head) { + dbg(" PMEM Resources:\n"); + print_pci_resource (ab->p_mem_head); + } + if (ab->_hpp) { + dbg(" _HPP: cache_line_size=0x%x\n", ab->_hpp->cache_line_size); + dbg(" _HPP: latency timer =0x%x\n", ab->_hpp->latency_timer); + dbg(" _HPP: enable SERR =0x%x\n", ab->_hpp->enable_serr); + dbg(" _HPP: enable PERR =0x%x\n", ab->_hpp->enable_perr); + } +} + +static int shpchprm_delete_resource( + struct pci_resource **aprh, + ulong base, + ulong size) +{ + struct pci_resource *res; + struct pci_resource *prevnode; + struct pci_resource *split_node; + ulong tbase; + + shpchp_resource_sort_and_combine(aprh); + + for (res = *aprh; res; res = res->next) { + if (res->base > base) + continue; + + if ((res->base + res->length) < (base + size)) + continue; + + if (res->base < base) { + tbase = base; + + if ((res->length - (tbase - res->base)) < size) + continue; + + split_node = (struct pci_resource *) kmalloc(sizeof(struct pci_resource), GFP_KERNEL); + if (!split_node) + return -ENOMEM; + + split_node->base = res->base; + split_node->length = tbase - res->base; + res->base = tbase; + res->length -= split_node->length; + + split_node->next = res->next; + res->next = split_node; + } + + if (res->length >= size) { + split_node = (struct pci_resource*) kmalloc(sizeof(struct pci_resource), GFP_KERNEL); + if (!split_node) + return -ENOMEM; + + split_node->base = res->base + size; + split_node->length = res->length - size; + res->length = size; + + split_node->next = res->next; + res->next = split_node; + } + + if (*aprh == res) { + *aprh = res->next; + } else { + prevnode = *aprh; + while (prevnode->next != res) + prevnode = prevnode->next; + + prevnode->next = res->next; + } + res->next = NULL; + kfree(res); + break; + } + + return 0; +} + +static int shpchprm_delete_resources( + struct pci_resource **aprh, + struct pci_resource *this + ) +{ + struct pci_resource *res; + + for (res = this; res; res = res->next) + shpchprm_delete_resource(aprh, res->base, res->length); + + return 0; +} + +static int shpchprm_add_resource( + struct pci_resource **aprh, + ulong base, + ulong size) +{ + struct pci_resource *res; + + for (res = *aprh; res; res = res->next) { + if ((res->base + res->length) == base) { + res->length += size; + size = 0L; + break; + } + if (res->next == *aprh) + break; + } + + if (size) { + res = kmalloc(sizeof(struct pci_resource), GFP_KERNEL); + if (!res) { + err ("acpi_shpchprm: alloc for res fail\n"); + return -ENOMEM; + } + memset(res, 0, sizeof (struct pci_resource)); + + res->base = base; + res->length = size; + res->next = *aprh; + *aprh = res; + } + + return 0; +} + +static int shpchprm_add_resources( + struct pci_resource **aprh, + struct pci_resource *this + ) +{ + struct pci_resource *res; + int rc = 0; + + for (res = this; res && !rc; res = res->next) + rc = shpchprm_add_resource(aprh, res->base, res->length); + + return rc; +} + +static void acpi_parse_io ( + struct acpi_bridge *ab, + union acpi_resource_data *data + ) +{ + struct acpi_resource_io *dataio; + dataio = (struct acpi_resource_io *) data; + + dbg("Io Resource\n"); + dbg(" %d bit decode\n", ACPI_DECODE_16 == dataio->io_decode ? 16:10); + dbg(" Range minimum base: %08X\n", dataio->min_base_address); + dbg(" Range maximum base: %08X\n", dataio->max_base_address); + dbg(" Alignment: %08X\n", dataio->alignment); + dbg(" Range Length: %08X\n", dataio->range_length); +} + +static void acpi_parse_fixed_io ( + struct acpi_bridge *ab, + union acpi_resource_data *data + ) +{ + struct acpi_resource_fixed_io *datafio; + datafio = (struct acpi_resource_fixed_io *) data; + + dbg("Fixed Io Resource\n"); + dbg(" Range base address: %08X", datafio->base_address); + dbg(" Range length: %08X", datafio->range_length); +} + +static void acpi_parse_address16_32 ( + struct acpi_bridge *ab, + union acpi_resource_data *data, + acpi_resource_type id + ) +{ + /* + * acpi_resource_address16 == acpi_resource_address32 + * acpi_resource_address16 *data16 = (acpi_resource_address16 *) data; + */ + struct acpi_resource_address32 *data32 = (struct acpi_resource_address32 *) data; + struct pci_resource **aprh, **tprh; + + if (id == ACPI_RSTYPE_ADDRESS16) + dbg("acpi_shpchprm:16-Bit Address Space Resource\n"); + else + dbg("acpi_shpchprm:32-Bit Address Space Resource\n"); + + switch (data32->resource_type) { + case ACPI_MEMORY_RANGE: + dbg(" Resource Type: Memory Range\n"); + aprh = &ab->mem_head; + tprh = &ab->tmem_head; + + switch (data32->attribute.memory.cache_attribute) { + case ACPI_NON_CACHEABLE_MEMORY: + dbg(" Type Specific: Noncacheable memory\n"); + break; + case ACPI_CACHABLE_MEMORY: + dbg(" Type Specific: Cacheable memory\n"); + break; + case ACPI_WRITE_COMBINING_MEMORY: + dbg(" Type Specific: Write-combining memory\n"); + break; + case ACPI_PREFETCHABLE_MEMORY: + aprh = &ab->p_mem_head; + dbg(" Type Specific: Prefetchable memory\n"); + break; + default: + dbg(" Type Specific: Invalid cache attribute\n"); + break; + } + + dbg(" Type Specific: Read%s\n", ACPI_READ_WRITE_MEMORY == data32->attribute.memory.read_write_attribute ? "/Write":" Only"); + break; + + case ACPI_IO_RANGE: + dbg(" Resource Type: I/O Range\n"); + aprh = &ab->io_head; + tprh = &ab->tio_head; + + switch (data32->attribute.io.range_attribute) { + case ACPI_NON_ISA_ONLY_RANGES: + dbg(" Type Specific: Non-ISA Io Addresses\n"); + break; + case ACPI_ISA_ONLY_RANGES: + dbg(" Type Specific: ISA Io Addresses\n"); + break; + case ACPI_ENTIRE_RANGE: + dbg(" Type Specific: ISA and non-ISA Io Addresses\n"); + break; + default: + dbg(" Type Specific: Invalid range attribute\n"); + break; + } + break; + + case ACPI_BUS_NUMBER_RANGE: + dbg(" Resource Type: Bus Number Range(fixed)\n"); + /* fixup to be compatible with the rest of php driver */ + data32->min_address_range++; + data32->address_length--; + aprh = &ab->bus_head; + tprh = &ab->tbus_head; + break; + default: + dbg(" Resource Type: Invalid resource type. Exiting.\n"); + return; + } + + dbg(" Resource %s\n", ACPI_CONSUMER == data32->producer_consumer ? "Consumer":"Producer"); + dbg(" %s decode\n", ACPI_SUB_DECODE == data32->decode ? "Subtractive":"Positive"); + dbg(" Min address is %s fixed\n", ACPI_ADDRESS_FIXED == data32->min_address_fixed ? "":"not"); + dbg(" Max address is %s fixed\n", ACPI_ADDRESS_FIXED == data32->max_address_fixed ? "":"not"); + dbg(" Granularity: %08X\n", data32->granularity); + dbg(" Address range min: %08X\n", data32->min_address_range); + dbg(" Address range max: %08X\n", data32->max_address_range); + dbg(" Address translation offset: %08X\n", data32->address_translation_offset); + dbg(" Address Length: %08X\n", data32->address_length); + + if (0xFF != data32->resource_source.index) { + dbg(" Resource Source Index: %X\n", data32->resource_source.index); + /* dbg(" Resource Source: %s\n", data32->resource_source.string_ptr); */ + } + + shpchprm_add_resource(aprh, data32->min_address_range, data32->address_length); +} + +static acpi_status acpi_parse_crs( + struct acpi_bridge *ab, + struct acpi_resource *crsbuf + ) +{ + acpi_status status = AE_OK; + struct acpi_resource *resource = crsbuf; + u8 count = 0; + u8 done = 0; + + while (!done) { + dbg("acpi_shpchprm: PCI bus 0x%x Resource structure %x.\n", ab->bus, count++); + switch (resource->id) { + case ACPI_RSTYPE_IRQ: + dbg("Irq -------- Resource\n"); + break; + case ACPI_RSTYPE_DMA: + dbg("DMA -------- Resource\n"); + break; + case ACPI_RSTYPE_START_DPF: + dbg("Start DPF -------- Resource\n"); + break; + case ACPI_RSTYPE_END_DPF: + dbg("End DPF -------- Resource\n"); + break; + case ACPI_RSTYPE_IO: + acpi_parse_io (ab, &resource->data); + break; + case ACPI_RSTYPE_FIXED_IO: + acpi_parse_fixed_io (ab, &resource->data); + break; + case ACPI_RSTYPE_VENDOR: + dbg("Vendor -------- Resource\n"); + break; + case ACPI_RSTYPE_END_TAG: + dbg("End_tag -------- Resource\n"); + done = 1; + break; + case ACPI_RSTYPE_MEM24: + dbg("Mem24 -------- Resource\n"); + break; + case ACPI_RSTYPE_MEM32: + dbg("Mem32 -------- Resource\n"); + break; + case ACPI_RSTYPE_FIXED_MEM32: + dbg("Fixed Mem32 -------- Resource\n"); + break; + case ACPI_RSTYPE_ADDRESS16: + acpi_parse_address16_32(ab, &resource->data, ACPI_RSTYPE_ADDRESS16); + break; + case ACPI_RSTYPE_ADDRESS32: + acpi_parse_address16_32(ab, &resource->data, ACPI_RSTYPE_ADDRESS32); + break; + case ACPI_RSTYPE_ADDRESS64: + info("Address64 -------- Resource unparsed\n"); + break; + case ACPI_RSTYPE_EXT_IRQ: + dbg("Ext Irq -------- Resource\n"); + break; + default: + dbg("Invalid -------- resource type 0x%x\n", resource->id); + break; + } + + resource = (struct acpi_resource *) ((char *)resource + resource->length); + } + + return status; +} + +static acpi_status acpi_get_crs( struct acpi_bridge *ab) +{ + acpi_status status; + struct acpi_resource *crsbuf; + + status = acpi_evaluate_crs(ab->handle, &crsbuf); + if (ACPI_SUCCESS(status)) { + status = acpi_parse_crs(ab, crsbuf); + kfree(crsbuf); + + shpchp_resource_sort_and_combine(&ab->bus_head); + shpchp_resource_sort_and_combine(&ab->io_head); + shpchp_resource_sort_and_combine(&ab->mem_head); + shpchp_resource_sort_and_combine(&ab->p_mem_head); + + shpchprm_add_resources (&ab->tbus_head, ab->bus_head); + shpchprm_add_resources (&ab->tio_head, ab->io_head); + shpchprm_add_resources (&ab->tmem_head, ab->mem_head); + shpchprm_add_resources (&ab->tp_mem_head, ab->p_mem_head); + } + + return status; +} + +/* find acpi_bridge downword from ab. */ +static struct acpi_bridge * +find_acpi_bridge_by_bus( + struct acpi_bridge *ab, + int seg, + int bus /* pdev->subordinate->number */ + ) +{ + struct acpi_bridge *lab = NULL; + + if (!ab) + return NULL; + + if ((ab->bus == bus) && (ab->seg == seg)) + return ab; + + if (ab->child) + lab = find_acpi_bridge_by_bus(ab->child, seg, bus); + + if (!lab) + if (ab->next) + lab = find_acpi_bridge_by_bus(ab->next, seg, bus); + + return lab; +} + +/* + * Build a device tree of ACPI PCI Bridges + */ +static void shpchprm_acpi_register_a_bridge ( + struct acpi_bridge **head, + struct acpi_bridge *pab, /* parent bridge to which child bridge is added */ + struct acpi_bridge *cab /* child bridge to add */ + ) +{ + struct acpi_bridge *lpab; + struct acpi_bridge *lcab; + + lpab = find_acpi_bridge_by_bus(*head, pab->seg, pab->bus); + if (!lpab) { + if (!(pab->type & BRIDGE_TYPE_HOST)) + warn("PCI parent bridge s:b(%x:%x) not in list.\n", pab->seg, pab->bus); + pab->next = *head; + *head = pab; + lpab = pab; + } + + if ((cab->type & BRIDGE_TYPE_HOST) && (pab == cab)) + return; + + lcab = find_acpi_bridge_by_bus(*head, cab->seg, cab->bus); + if (lcab) { + if ((pab->bus != lcab->parent->bus) || (lcab->bus != cab->bus)) + err("PCI child bridge s:b(%x:%x) in list with diff parent.\n", cab->seg, cab->bus); + return; + } else + lcab = cab; + + lcab->parent = lpab; + lcab->next = lpab->child; + lpab->child = lcab; +} + +static acpi_status shpchprm_acpi_build_php_slots_callback( + acpi_handle handle, + u32 Level, + void *context, + void **retval + ) +{ + ulong bus_num; + ulong seg_num; + ulong sun, adr; + ulong padr = 0; + acpi_handle phandle = NULL; + struct acpi_bridge *pab = (struct acpi_bridge *)context; + struct acpi_bridge *lab; + acpi_status status; + u8 *path_name = acpi_path_name(handle); + + /* get _SUN */ + status = acpi_evaluate_integer(handle, METHOD_NAME__SUN, NULL, &sun); + switch(status) { + case AE_NOT_FOUND: + return AE_OK; + default: + if (ACPI_FAILURE(status)) { + err("acpi_shpchprm:%s _SUN fail=0x%x\n", path_name, status); + return status; + } + } + + /* get _ADR. _ADR must exist if _SUN exists */ + status = acpi_evaluate_integer(handle, METHOD_NAME__ADR, NULL, &adr); + if (ACPI_FAILURE(status)) { + err("acpi_shpchprm:%s _ADR fail=0x%x\n", path_name, status); + return status; + } + + dbg("acpi_shpchprm:%s sun=0x%08x adr=0x%08x\n", path_name, (u32)sun, (u32)adr); + + status = acpi_get_parent(handle, &phandle); + if (ACPI_FAILURE(status)) { + err("acpi_shpchprm:%s get_parent fail=0x%x\n", path_name, status); + return (status); + } + + bus_num = pab->bus; + seg_num = pab->seg; + + if (pab->bus == bus_num) { + lab = pab; + } else { + dbg("WARN: pab is not parent\n"); + lab = find_acpi_bridge_by_bus(pab, seg_num, bus_num); + if (!lab) { + dbg("acpi_shpchprm: alloc new P2P bridge(%x) for sun(%08x)\n", (u32)bus_num, (u32)sun); + lab = (struct acpi_bridge *)kmalloc(sizeof(struct acpi_bridge), GFP_KERNEL); + if (!lab) { + err("acpi_shpchprm: alloc for ab fail\n"); + return AE_NO_MEMORY; + } + memset(lab, 0, sizeof(struct acpi_bridge)); + + lab->handle = phandle; + lab->pbus = pab->bus; + lab->pdevice = (int)(padr >> 16) & 0xffff; + lab->pfunction = (int)(padr & 0xffff); + lab->bus = (int)bus_num; + lab->scanned = 0; + lab->type = BRIDGE_TYPE_P2P; + + shpchprm_acpi_register_a_bridge (&acpi_bridges_head, pab, lab); + } else + dbg("acpi_shpchprm: found P2P bridge(%x) for sun(%08x)\n", (u32)bus_num, (u32)sun); + } + + acpi_add_slot_to_php_slots(lab, (int)bus_num, handle, (u32)adr, (u32)sun); + return (status); +} + +static int shpchprm_acpi_build_php_slots( + struct acpi_bridge *ab, + u32 depth + ) +{ + acpi_status status; + u8 *path_name = acpi_path_name(ab->handle); + + /* Walk down this pci bridge to get _SUNs if any behind P2P */ + status = acpi_walk_namespace ( ACPI_TYPE_DEVICE, + ab->handle, + depth, + shpchprm_acpi_build_php_slots_callback, + ab, + NULL ); + if (ACPI_FAILURE(status)) { + dbg("acpi_shpchprm:%s walk for _SUN on pci bridge seg:bus(%x:%x) fail=0x%x\n", path_name, ab->seg, ab->bus, status); + return -1; + } + + return 0; +} + +static void build_a_bridge( + struct acpi_bridge *pab, + struct acpi_bridge *ab + ) +{ + u8 *path_name = acpi_path_name(ab->handle); + + shpchprm_acpi_register_a_bridge (&acpi_bridges_head, pab, ab); + + switch (ab->type) { + case BRIDGE_TYPE_HOST: + dbg("acpi_shpchprm: Registered PCI HOST Bridge(%02x) on s:b:d:f(%02x:%02x:%02x:%02x) [%s]\n", + ab->bus, ab->seg, ab->pbus, ab->pdevice, ab->pfunction, path_name); + break; + case BRIDGE_TYPE_P2P: + dbg("acpi_shpchprm: Registered PCI P2P Bridge(%02x-%02x) on s:b:d:f(%02x:%02x:%02x:%02x) [%s]\n", + ab->pbus, ab->bus, ab->seg, ab->pbus, ab->pdevice, ab->pfunction, path_name); + break; + }; + + /* build any immediate PHP slots under this pci bridge */ + shpchprm_acpi_build_php_slots(ab, 1); +} + +static struct acpi_bridge * add_p2p_bridge( + acpi_handle handle, + struct acpi_bridge *pab, /* parent */ + ulong adr + ) +{ + struct acpi_bridge *ab; + struct pci_dev *pdev; + ulong devnum, funcnum; + u8 *path_name = acpi_path_name(handle); + + ab = (struct acpi_bridge *) kmalloc (sizeof(struct acpi_bridge), GFP_KERNEL); + if (!ab) { + err("acpi_shpchprm: alloc for ab fail\n"); + return NULL; + } + memset(ab, 0, sizeof(struct acpi_bridge)); + + devnum = (adr >> 16) & 0xffff; + funcnum = adr & 0xffff; + + pdev = pci_find_slot(pab->bus, PCI_DEVFN(devnum, funcnum)); + if (!pdev || !pdev->subordinate) { + err("acpi_shpchprm:%s is not a P2P Bridge\n", path_name); + kfree(ab); + return NULL; + } + + ab->handle = handle; + ab->seg = pab->seg; + ab->pbus = pab->bus; /* or pdev->bus->number */ + ab->pdevice = devnum; /* or PCI_SLOT(pdev->devfn) */ + ab->pfunction = funcnum; /* or PCI_FUNC(pdev->devfn) */ + ab->bus = pdev->subordinate->number; + ab->scanned = 0; + ab->type = BRIDGE_TYPE_P2P; + + dbg("acpi_shpchprm: P2P(%x-%x) on pci=b:d:f(%x:%x:%x) acpi=b:d:f(%x:%x:%x) [%s]\n", + pab->bus, ab->bus, pdev->bus->number, PCI_SLOT(pdev->devfn), PCI_FUNC(pdev->devfn), + pab->bus, (u32)devnum, (u32)funcnum, path_name); + + build_a_bridge(pab, ab); + + return ab; +} + +static acpi_status scan_p2p_bridge( + acpi_handle handle, + u32 Level, + void *context, + void **retval + ) +{ + struct acpi_bridge *pab = (struct acpi_bridge *)context; + struct acpi_bridge *ab; + acpi_status status; + ulong adr = 0; + u8 *path_name = acpi_path_name(handle); + ulong devnum, funcnum; + struct pci_dev *pdev; + + /* get device, function */ + status = acpi_evaluate_integer(handle, METHOD_NAME__ADR, NULL, &adr); + if (ACPI_FAILURE(status)) { + if (status != AE_NOT_FOUND) + err("acpi_shpchprm:%s _ADR fail=0x%x\n", path_name, status); + return AE_OK; + } + + devnum = (adr >> 16) & 0xffff; + funcnum = adr & 0xffff; + + pdev = pci_find_slot(pab->bus, PCI_DEVFN(devnum, funcnum)); + if (!pdev) + return AE_OK; + if (!pdev->subordinate) + return AE_OK; + + ab = add_p2p_bridge(handle, pab, adr); + if (ab) { + status = acpi_walk_namespace ( ACPI_TYPE_DEVICE, + handle, + (u32)1, + scan_p2p_bridge, + ab, + NULL); + if (ACPI_FAILURE(status)) + dbg("acpi_shpchprm:%s find_p2p fail=0x%x\n", path_name, status); + } + + return AE_OK; +} + +static struct acpi_bridge * add_host_bridge( + acpi_handle handle, + ulong segnum, + ulong busnum + ) +{ + ulong adr = 0; + acpi_status status; + struct acpi_bridge *ab; + u8 *path_name = acpi_path_name(handle); + + /* get device, function: host br adr is always 0000 though. */ + status = acpi_evaluate_integer(handle, METHOD_NAME__ADR, NULL, &adr); + if (ACPI_FAILURE(status)) { + err("acpi_shpchprm:%s _ADR fail=0x%x\n", path_name, status); + return NULL; + } + dbg("acpi_shpchprm: ROOT PCI seg(0x%x)bus(0x%x)dev(0x%x)func(0x%x) [%s]\n", (u32)segnum, (u32)busnum, + (u32)(adr >> 16) & 0xffff, (u32)adr & 0xffff, path_name); + + ab = (struct acpi_bridge *) kmalloc (sizeof(struct acpi_bridge), GFP_KERNEL); + if (!ab) { + err("acpi_shpchprm: alloc for ab fail\n"); + return NULL; + } + memset(ab, 0, sizeof(struct acpi_bridge)); + + ab->handle = handle; + ab->seg = (int)segnum; + ab->bus = ab->pbus = (int)busnum; + ab->pdevice = (int)(adr >> 16) & 0xffff; + ab->pfunction = (int)(adr & 0xffff); + ab->scanned = 0; + ab->type = BRIDGE_TYPE_HOST; + + /* get root pci bridge's current resources */ + status = acpi_get_crs(ab); + if (ACPI_FAILURE(status)) { + err("acpi_shpchprm:%s evaluate _CRS fail=0x%x\n", path_name, status); + kfree(ab); + return NULL; + } + build_a_bridge(ab, ab); + + return ab; +} + +static acpi_status acpi_scan_from_root_pci_callback ( + acpi_handle handle, + u32 Level, + void *context, + void **retval + ) +{ + ulong segnum = 0; + ulong busnum = 0; + acpi_status status; + struct acpi_bridge *ab; + u8 *path_name = acpi_path_name(handle); + + /* get bus number of this pci root bridge */ + status = acpi_evaluate_integer(handle, METHOD_NAME__SEG, NULL, &segnum); + if (ACPI_FAILURE(status)) { + if (status != AE_NOT_FOUND) { + err("acpi_shpchprm:%s evaluate _SEG fail=0x%x\n", path_name, status); + return status; + } + segnum = 0; + } + + /* get bus number of this pci root bridge */ + status = acpi_evaluate_integer(handle, METHOD_NAME__BBN, NULL, &busnum); + if (ACPI_FAILURE(status)) { + err("acpi_shpchprm:%s evaluate _BBN fail=0x%x\n", path_name, status); + return (status); + } + + ab = add_host_bridge(handle, segnum, busnum); + if (ab) { + status = acpi_walk_namespace ( ACPI_TYPE_DEVICE, + handle, + 1, + scan_p2p_bridge, + ab, + NULL); + if (ACPI_FAILURE(status)) + dbg("acpi_shpchprm:%s find_p2p fail=0x%x\n", path_name, status); + } + + return AE_OK; +} + +static int shpchprm_acpi_scan_pci (void) +{ + acpi_status status; + + /* + * TBD: traverse LDM device tree with the help of + * unified ACPI augmented for php device population. + */ + status = acpi_get_devices ( PCI_ROOT_HID_STRING, + acpi_scan_from_root_pci_callback, + NULL, + NULL ); + if (ACPI_FAILURE(status)) { + err("acpi_shpchprm:get_device PCI ROOT HID fail=0x%x\n", status); + return -1; + } + + return 0; +} + +int shpchprm_init(enum php_ctlr_type ctlr_type) +{ + int rc; + + if (ctlr_type != PCI) + return -ENODEV; + + dbg("shpchprm ACPI init <enter>\n"); + acpi_bridges_head = NULL; + + /* construct PCI bus:device tree of acpi_handles */ + rc = shpchprm_acpi_scan_pci(); + if (rc) + return rc; + + dbg("shpchprm ACPI init %s\n", (rc)?"fail":"success"); + return rc; +} + +static void free_a_slot(struct acpi_php_slot *aps) +{ + dbg(" free a php func of slot(0x%02x) on PCI b:d:f=0x%02x:%02x:%02x\n", aps->sun, aps->bus, aps->dev, aps->fun); + + free_pci_resource (aps->io_head); + free_pci_resource (aps->bus_head); + free_pci_resource (aps->mem_head); + free_pci_resource (aps->p_mem_head); + + kfree(aps); +} + +static void free_a_bridge( struct acpi_bridge *ab) +{ + struct acpi_php_slot *aps, *next; + + switch (ab->type) { + case BRIDGE_TYPE_HOST: + dbg("Free ACPI PCI HOST Bridge(%x) [%s] on s:b:d:f(%x:%x:%x:%x)\n", + ab->bus, acpi_path_name(ab->handle), ab->seg, ab->pbus, ab->pdevice, ab->pfunction); + break; + case BRIDGE_TYPE_P2P: + dbg("Free ACPI PCI P2P Bridge(%x-%x) [%s] on s:b:d:f(%x:%x:%x:%x)\n", + ab->pbus, ab->bus, acpi_path_name(ab->handle), ab->seg, ab->pbus, ab->pdevice, ab->pfunction); + break; + }; + + /* free slots first */ + for (aps = ab->slots; aps; aps = next) { + next = aps->next; + free_a_slot(aps); + } + + free_pci_resource (ab->io_head); + free_pci_resource (ab->tio_head); + free_pci_resource (ab->bus_head); + free_pci_resource (ab->tbus_head); + free_pci_resource (ab->mem_head); + free_pci_resource (ab->tmem_head); + free_pci_resource (ab->p_mem_head); + free_pci_resource (ab->tp_mem_head); + + kfree(ab); +} + +static void shpchprm_free_bridges ( struct acpi_bridge *ab) +{ + if (!ab) + return; + + if (ab->child) + shpchprm_free_bridges (ab->child); + + if (ab->next) + shpchprm_free_bridges (ab->next); + + free_a_bridge(ab); +} + +void shpchprm_cleanup(void) +{ + shpchprm_free_bridges (acpi_bridges_head); +} + +static int get_number_of_slots ( + struct acpi_bridge *ab, + int selfonly + ) +{ + struct acpi_php_slot *aps; + int prev_slot = -1; + int slot_num = 0; + + for ( aps = ab->slots; aps; aps = aps->next) + if (aps->dev != prev_slot) { + prev_slot = aps->dev; + slot_num++; + } + + if (ab->child) + slot_num += get_number_of_slots (ab->child, 0); + + if (selfonly) + return slot_num; + + if (ab->next) + slot_num += get_number_of_slots (ab->next, 0); + + return slot_num; +} + +static int print_acpi_resources (struct acpi_bridge *ab) +{ + struct acpi_php_slot *aps; + int i; + + switch (ab->type) { + case BRIDGE_TYPE_HOST: + dbg("PCI HOST Bridge (%x) [%s]\n", ab->bus, acpi_path_name(ab->handle)); + break; + case BRIDGE_TYPE_P2P: + dbg("PCI P2P Bridge (%x-%x) [%s]\n", ab->pbus, ab->bus, acpi_path_name(ab->handle)); + break; + }; + + print_pci_resources (ab); + + for ( i = -1, aps = ab->slots; aps; aps = aps->next) { + if (aps->dev == i) + continue; + dbg(" Slot sun(%x) s:b:d:f(%02x:%02x:%02x:%02x)\n", aps->sun, aps->seg, aps->bus, aps->dev, aps->fun); + print_slot_resources(aps); + i = aps->dev; + } + + if (ab->child) + print_acpi_resources (ab->child); + + if (ab->next) + print_acpi_resources (ab->next); + + return 0; +} + +int shpchprm_print_pirt(void) +{ + dbg("SHPCHPRM ACPI Slots\n"); + if (acpi_bridges_head) + print_acpi_resources (acpi_bridges_head); + return 0; +} + +static struct acpi_php_slot * get_acpi_slot ( + struct acpi_bridge *ab, + u32 sun + ) +{ + struct acpi_php_slot *aps = NULL; + + for ( aps = ab->slots; aps; aps = aps->next) + if (aps->sun == sun) + return aps; + + if (!aps && ab->child) { + aps = (struct acpi_php_slot *)get_acpi_slot (ab->child, sun); + if (aps) + return aps; + } + + if (!aps && ab->next) { + aps = (struct acpi_php_slot *)get_acpi_slot (ab->next, sun); + if (aps) + return aps; + } + + return aps; + +} + +#if 0 +static void * shpchprm_get_slot(struct slot *slot) +{ + struct acpi_bridge *ab = acpi_bridges_head; + struct acpi_php_slot *aps = get_acpi_slot (ab, slot->number); + + aps->slot = slot; + + dbg("Got acpi slot sun(%x): s:b:d:f(%x:%x:%x:%x)\n", aps->sun, aps->seg, aps->bus, aps->dev, aps->fun); + + return (void *)aps; +} +#endif + +static void shpchprm_dump_func_res( struct pci_func *fun) +{ + struct pci_func *func = fun; + + if (func->bus_head) { + dbg(": BUS Resources:\n"); + print_pci_resource (func->bus_head); + } + if (func->io_head) { + dbg(": IO Resources:\n"); + print_pci_resource (func->io_head); + } + if (func->mem_head) { + dbg(": MEM Resources:\n"); + print_pci_resource (func->mem_head); + } + if (func->p_mem_head) { + dbg(": PMEM Resources:\n"); + print_pci_resource (func->p_mem_head); + } +} + +static void shpchprm_dump_ctrl_res( struct controller *ctlr) +{ + struct controller *ctrl = ctlr; + + if (ctrl->bus_head) { + dbg(": BUS Resources:\n"); + print_pci_resource (ctrl->bus_head); + } + if (ctrl->io_head) { + dbg(": IO Resources:\n"); + print_pci_resource (ctrl->io_head); + } + if (ctrl->mem_head) { + dbg(": MEM Resources:\n"); + print_pci_resource (ctrl->mem_head); + } + if (ctrl->p_mem_head) { + dbg(": PMEM Resources:\n"); + print_pci_resource (ctrl->p_mem_head); + } +} + +static int shpchprm_get_used_resources ( + struct controller *ctrl, + struct pci_func *func + ) +{ + return shpchp_save_used_resources (ctrl, func, !DISABLE_CARD); +} + +static int configure_existing_function( + struct controller *ctrl, + struct pci_func *func + ) +{ + int rc; + + /* see how much resources the func has used. */ + rc = shpchprm_get_used_resources (ctrl, func); + + if (!rc) { + /* subtract the resources used by the func from ctrl resources */ + rc = shpchprm_delete_resources (&ctrl->bus_head, func->bus_head); + rc |= shpchprm_delete_resources (&ctrl->io_head, func->io_head); + rc |= shpchprm_delete_resources (&ctrl->mem_head, func->mem_head); + rc |= shpchprm_delete_resources (&ctrl->p_mem_head, func->p_mem_head); + if (rc) + warn("aCEF: cannot del used resources\n"); + } else + err("aCEF: cannot get used resources\n"); + + return rc; +} + +static int bind_pci_resources_to_slots ( struct controller *ctrl) +{ + struct pci_func *func, new_func; + int busn = ctrl->slot_bus; + int devn, funn; + u32 vid; + + for (devn = 0; devn < 32; devn++) { + for (funn = 0; funn < 8; funn++) { + /* + if (devn == ctrl->device && funn == ctrl->function) + continue; + */ + /* find out if this entry is for an occupied slot */ + vid = 0xFFFFFFFF; + pci_bus_read_config_dword(ctrl->pci_dev->subordinate, PCI_DEVFN(devn, funn), PCI_VENDOR_ID, &vid); + + if (vid != 0xFFFFFFFF) { + func = shpchp_slot_find(busn, devn, funn); + if (!func) { + memset(&new_func, 0, sizeof(struct pci_func)); + new_func.bus = busn; + new_func.device = devn; + new_func.function = funn; + new_func.is_a_board = 1; + configure_existing_function(ctrl, &new_func); + shpchprm_dump_func_res(&new_func); + } else { + configure_existing_function(ctrl, func); + shpchprm_dump_func_res(func); + } + dbg("aCCF:existing PCI 0x%x Func ResourceDump\n", ctrl->bus); + } + } + } + + return 0; +} + +static int bind_pci_resources( + struct controller *ctrl, + struct acpi_bridge *ab + ) +{ + int status = 0; + + if (ab->bus_head) { + dbg("bapr: BUS Resources add on PCI 0x%x\n", ab->bus); + status = shpchprm_add_resources (&ctrl->bus_head, ab->bus_head); + if (shpchprm_delete_resources (&ab->bus_head, ctrl->bus_head)) + warn("bapr: cannot sub BUS Resource on PCI 0x%x\n", ab->bus); + if (status) { + err("bapr: BUS Resource add on PCI 0x%x: fail=0x%x\n", ab->bus, status); + return status; + } + } else + info("bapr: No BUS Resource on PCI 0x%x.\n", ab->bus); + + if (ab->io_head) { + dbg("bapr: IO Resources add on PCI 0x%x\n", ab->bus); + status = shpchprm_add_resources (&ctrl->io_head, ab->io_head); + if (shpchprm_delete_resources (&ab->io_head, ctrl->io_head)) + warn("bapr: cannot sub IO Resource on PCI 0x%x\n", ab->bus); + if (status) { + err("bapr: IO Resource add on PCI 0x%x: fail=0x%x\n", ab->bus, status); + return status; + } + } else + info("bapr: No IO Resource on PCI 0x%x.\n", ab->bus); + + if (ab->mem_head) { + dbg("bapr: MEM Resources add on PCI 0x%x\n", ab->bus); + status = shpchprm_add_resources (&ctrl->mem_head, ab->mem_head); + if (shpchprm_delete_resources (&ab->mem_head, ctrl->mem_head)) + warn("bapr: cannot sub MEM Resource on PCI 0x%x\n", ab->bus); + if (status) { + err("bapr: MEM Resource add on PCI 0x%x: fail=0x%x\n", ab->bus, status); + return status; + } + } else + info("bapr: No MEM Resource on PCI 0x%x.\n", ab->bus); + + if (ab->p_mem_head) { + dbg("bapr: PMEM Resources add on PCI 0x%x\n", ab->bus); + status = shpchprm_add_resources (&ctrl->p_mem_head, ab->p_mem_head); + if (shpchprm_delete_resources (&ab->p_mem_head, ctrl->p_mem_head)) + warn("bapr: cannot sub PMEM Resource on PCI 0x%x\n", ab->bus); + if (status) { + err("bapr: PMEM Resource add on PCI 0x%x: fail=0x%x\n", ab->bus, status); + return status; + } + } else + info("bapr: No PMEM Resource on PCI 0x%x.\n", ab->bus); + + return status; +} + +static int no_pci_resources( struct acpi_bridge *ab) +{ + return !(ab->p_mem_head || ab->mem_head || ab->io_head || ab->bus_head); +} + +static int find_pci_bridge_resources ( + struct controller *ctrl, + struct acpi_bridge *ab + ) +{ + int rc = 0; + struct pci_func func; + + memset(&func, 0, sizeof(struct pci_func)); + + func.bus = ab->pbus; + func.device = ab->pdevice; + func.function = ab->pfunction; + func.is_a_board = 1; + + /* Get used resources for this PCI bridge */ + rc = shpchp_save_used_resources (ctrl, &func, !DISABLE_CARD); + + ab->io_head = func.io_head; + ab->mem_head = func.mem_head; + ab->p_mem_head = func.p_mem_head; + ab->bus_head = func.bus_head; + if (ab->bus_head) + shpchprm_delete_resource(&ab->bus_head, ctrl->bus, 1); + + return rc; +} + +static int get_pci_resources_from_bridge( + struct controller *ctrl, + struct acpi_bridge *ab + ) +{ + int rc = 0; + + dbg("grfb: Get Resources for PCI 0x%x from actual PCI bridge 0x%x.\n", ctrl->bus, ab->bus); + + rc = find_pci_bridge_resources (ctrl, ab); + + shpchp_resource_sort_and_combine(&ab->bus_head); + shpchp_resource_sort_and_combine(&ab->io_head); + shpchp_resource_sort_and_combine(&ab->mem_head); + shpchp_resource_sort_and_combine(&ab->p_mem_head); + + shpchprm_add_resources (&ab->tbus_head, ab->bus_head); + shpchprm_add_resources (&ab->tio_head, ab->io_head); + shpchprm_add_resources (&ab->tmem_head, ab->mem_head); + shpchprm_add_resources (&ab->tp_mem_head, ab->p_mem_head); + + return rc; +} + +static int get_pci_resources( + struct controller *ctrl, + struct acpi_bridge *ab + ) +{ + int rc = 0; + + if (no_pci_resources(ab)) { + dbg("spbr:PCI 0x%x has no resources. Get parent resources.\n", ab->bus); + rc = get_pci_resources_from_bridge(ctrl, ab); + } + + return rc; +} + +int shpchprm_get_physical_slot_number(struct controller *ctrl, u32 *sun, u8 busnum, u8 devnum) +{ + int offset = devnum - ctrl->slot_device_offset; + + dbg("%s: ctrl->slot_num_inc %d, offset %d\n", __FUNCTION__, ctrl->slot_num_inc, offset); + *sun = (u8) (ctrl->first_slot + ctrl->slot_num_inc *offset); + return 0; +} + +/* + * Get resources for this ctrl. + * 1. get total resources from ACPI _CRS or bridge (this ctrl) + * 2. find used resources of existing adapters + * 3. subtract used resources from total resources + */ +int shpchprm_find_available_resources( struct controller *ctrl) +{ + int rc = 0; + struct acpi_bridge *ab; + + ab = find_acpi_bridge_by_bus(acpi_bridges_head, ctrl->seg, ctrl->pci_dev->subordinate->number); + if (!ab) { + err("pfar:cannot locate acpi bridge of PCI 0x%x.\n", ctrl->pci_dev->subordinate->number); + return -1; + } + if (no_pci_resources(ab)) { + rc = get_pci_resources(ctrl, ab); + if (rc) { + err("pfar:cannot get pci resources of PCI 0x%x.\n",ctrl->pci_dev->subordinate->number); + return -1; + } + } + + rc = bind_pci_resources(ctrl, ab); + dbg("pfar:pre-Bind PCI 0x%x Ctrl Resource Dump\n", ctrl->pci_dev->subordinate->number); + shpchprm_dump_ctrl_res(ctrl); + + bind_pci_resources_to_slots (ctrl); + + dbg("pfar:post-Bind PCI 0x%x Ctrl Resource Dump\n", ctrl->pci_dev->subordinate->number); + shpchprm_dump_ctrl_res(ctrl); + + return rc; +} + +int shpchprm_set_hpp( + struct controller *ctrl, + struct pci_func *func, + u8 card_type + ) +{ + struct acpi_bridge *ab; + struct pci_bus lpci_bus, *pci_bus; + int rc = 0; + unsigned int devfn; + u8 cls= 0x08; /* default cache line size */ + u8 lt = 0x40; /* default latency timer */ + u8 ep = 0; + u8 es = 0; + + memcpy(&lpci_bus, ctrl->pci_bus, sizeof(lpci_bus)); + pci_bus = &lpci_bus; + pci_bus->number = func->bus; + devfn = PCI_DEVFN(func->device, func->function); + + ab = find_acpi_bridge_by_bus(acpi_bridges_head, ctrl->seg, ctrl->bus); + + if (ab) { + if (ab->_hpp) { + lt = (u8)ab->_hpp->latency_timer; + cls = (u8)ab->_hpp->cache_line_size; + ep = (u8)ab->_hpp->enable_perr; + es = (u8)ab->_hpp->enable_serr; + } else + dbg("_hpp: no _hpp for B/D/F=%#x/%#x/%#x. use default value\n", func->bus, func->device, func->function); + } else + dbg("_hpp: no acpi bridge for B/D/F = %#x/%#x/%#x. use default value\n", func->bus, func->device, func->function); + + + if (card_type == PCI_HEADER_TYPE_BRIDGE) { + /* set subordinate Latency Timer */ + rc |= pci_bus_write_config_byte(pci_bus, devfn, PCI_SEC_LATENCY_TIMER, lt); + } + + /* set base Latency Timer */ + rc |= pci_bus_write_config_byte(pci_bus, devfn, PCI_LATENCY_TIMER, lt); + dbg(" set latency timer =0x%02x: %x\n", lt, rc); + + rc |= pci_bus_write_config_byte(pci_bus, devfn, PCI_CACHE_LINE_SIZE, cls); + dbg(" set cache_line_size=0x%02x: %x\n", cls, rc); + + return rc; +} + +void shpchprm_enable_card( + struct controller *ctrl, + struct pci_func *func, + u8 card_type) +{ + u16 command, cmd, bcommand, bcmd; + struct pci_bus lpci_bus, *pci_bus; + struct acpi_bridge *ab; + unsigned int devfn; + int rc; + + memcpy(&lpci_bus, ctrl->pci_bus, sizeof(lpci_bus)); + pci_bus = &lpci_bus; + pci_bus->number = func->bus; + devfn = PCI_DEVFN(func->device, func->function); + + rc = pci_bus_read_config_word(pci_bus, devfn, PCI_COMMAND, &command); + + if (card_type == PCI_HEADER_TYPE_BRIDGE) { + rc = pci_bus_read_config_word(pci_bus, devfn, PCI_BRIDGE_CONTROL, &bcommand); + } + + cmd = command = command | PCI_COMMAND_MASTER | PCI_COMMAND_INVALIDATE + | PCI_COMMAND_IO | PCI_COMMAND_MEMORY; + bcmd = bcommand = bcommand | PCI_BRIDGE_CTL_NO_ISA; + + ab = find_acpi_bridge_by_bus(acpi_bridges_head, ctrl->seg, ctrl->bus); + if (ab) { + if (ab->_hpp) { + if (ab->_hpp->enable_perr) { + command |= PCI_COMMAND_PARITY; + bcommand |= PCI_BRIDGE_CTL_PARITY; + } else { + command &= ~PCI_COMMAND_PARITY; + bcommand &= ~PCI_BRIDGE_CTL_PARITY; + } + if (ab->_hpp->enable_serr) { + command |= PCI_COMMAND_SERR; + bcommand |= PCI_BRIDGE_CTL_SERR; + } else { + command &= ~PCI_COMMAND_SERR; + bcommand &= ~PCI_BRIDGE_CTL_SERR; + } + } else + dbg("no _hpp for B/D/F = %#x/%#x/%#x.\n", func->bus, func->device, func->function); + } else + dbg("no acpi bridge for B/D/F = %#x/%#x/%#x.\n", func->bus, func->device, func->function); + + if (command != cmd) { + rc = pci_bus_write_config_word(pci_bus, devfn, PCI_COMMAND, command); + } + if ((card_type == PCI_HEADER_TYPE_BRIDGE) && (bcommand != bcmd)) { + rc = pci_bus_write_config_word(pci_bus, devfn, PCI_BRIDGE_CONTROL, bcommand); + } +} + diff --git a/drivers/pci/hotplug/shpchprm_legacy.c b/drivers/pci/hotplug/shpchprm_legacy.c new file mode 100644 index 000000000000..37fa77a98289 --- /dev/null +++ b/drivers/pci/hotplug/shpchprm_legacy.c @@ -0,0 +1,439 @@ +/* + * SHPCHPRM Legacy: PHP Resource Manager for Non-ACPI/Legacy platform + * + * 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>,<dely.l.sy@intel.com> + * + */ + +#include <linux/config.h> +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/types.h> +#include <linux/pci.h> +#include <linux/init.h> +#include <asm/uaccess.h> +#ifdef CONFIG_IA64 +#include <asm/iosapic.h> +#endif +#include "shpchp.h" +#include "shpchprm.h" +#include "shpchprm_legacy.h" + +static void __iomem *shpchp_rom_start; +static u16 unused_IRQ; + +void shpchprm_cleanup(void) +{ + if (shpchp_rom_start) + iounmap(shpchp_rom_start); +} + +int shpchprm_print_pirt(void) +{ + return 0; +} + +int shpchprm_get_physical_slot_number(struct controller *ctrl, u32 *sun, u8 busnum, u8 devnum) +{ + int offset = devnum - ctrl->slot_device_offset; + + *sun = (u8) (ctrl->first_slot + ctrl->slot_num_inc * offset); + return 0; +} + +/* 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; +} + +/* + * shpchprm_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 shpchprm_find_available_resources(struct controller *ctrl) +{ + u8 populated_slot; + u8 bridged_slot; + void __iomem *one_slot; + struct pci_func *func = NULL; + int i = 10, index = 0; + u32 temp_dword, rc; + ulong temp_ulong; + struct pci_resource *mem_node; + struct pci_resource *p_mem_node; + struct pci_resource *io_node; + struct pci_resource *bus_node; + void __iomem *rom_resource_table; + struct pci_bus lpci_bus, *pci_bus; + u8 cfgspc_irq, temp; + + memcpy(&lpci_bus, ctrl->pci_bus, sizeof(lpci_bus)); + pci_bus = &lpci_bus; + rom_resource_table = detect_HRT_floating_pointer(shpchp_rom_start, shpchp_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) { + shpchp_disk_irq = temp; + break; + } + unused_IRQ = unused_IRQ >> 1; + temp++; + } + + dbg("shpchp_disk_irq= %d\n", shpchp_disk_irq); + unused_IRQ = unused_IRQ >> 1; + temp++; + + while (unused_IRQ) { + if (unused_IRQ & 1) { + shpchp_nic_irq = temp; + break; + } + unused_IRQ = unused_IRQ >> 1; + temp++; + } + + dbg("shpchp_nic_irq= %d\n", shpchp_nic_irq); + unused_IRQ = readl(rom_resource_table + PCIIRQ); + + temp = 0; + + pci_read_config_byte(ctrl->pci_dev, PCI_INTERRUPT_LINE, &cfgspc_irq); + + if (!shpchp_nic_irq) { + shpchp_nic_irq = cfgspc_irq; + } + + if (!shpchp_disk_irq) { + shpchp_disk_irq = cfgspc_irq; + } + + dbg("shpchp_disk_irq, shpchp_nic_irq= %d, %d\n", shpchp_disk_irq, shpchp_nic_irq); + + 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|MEMbase|length|PM 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->slot_bus) { + i--; + one_slot += sizeof(struct slot_rt); + continue; + } + /* find out if this entry is for an occupied slot */ + temp_dword = 0xFFFFFFFF; + pci_bus->number = primary_bus; + pci_bus_read_config_dword(pci_bus, dev_func, PCI_VENDOR_ID, &temp_dword); + + dbg("temp_D_word = %x\n", temp_dword); + + if (temp_dword != 0xFFFFFFFF) { + index = 0; + func = shpchp_slot_find(primary_bus, dev_func >> 3, 0); + + while (func && (func->function != (dev_func & 0x07))) { + dbg("func = %p b:d:f(%x:%x:%x)\n", func, primary_bus, dev_func >> 3, index); + func = shpchp_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; + } + dbg("slot populated =%s \n", populated_slot?"yes":"no"); + + /* If we've got a valid IO base, use it */ + + temp_ulong = io_base + io_length; + + if ((io_base) && (temp_ulong <= 0x10000)) { + io_node = (struct pci_resource *) kmalloc(sizeof(struct pci_resource), GFP_KERNEL); + if (!io_node) + return -ENOMEM; + + io_node->base = (ulong)io_base; + io_node->length = (ulong)io_length; + dbg("found io_node(base, length) = %x, %x\n", io_node->base, io_node->length); + + 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_ulong = mem_base + mem_length; + if ((mem_base) && (temp_ulong <= 0x10000)) { + mem_node = (struct pci_resource *) kmalloc(sizeof(struct pci_resource), GFP_KERNEL); + if (!mem_node) + return -ENOMEM; + + mem_node->base = (ulong)mem_base << 16; + mem_node->length = (ulong)(mem_length << 16); + dbg("found mem_node(base, length) = %x, %x\n", mem_node->base, mem_node->length); + + 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_ulong = pre_mem_base + pre_mem_length; + if ((pre_mem_base) && (temp_ulong <= 0x10000)) { + p_mem_node = (struct pci_resource *) kmalloc(sizeof(struct pci_resource), GFP_KERNEL); + if (!p_mem_node) + return -ENOMEM; + + p_mem_node->base = (ulong)pre_mem_base << 16; + p_mem_node->length = (ulong)pre_mem_length << 16; + dbg("found p_mem_node(base, length) = %x, %x\n", p_mem_node->base, p_mem_node->length); + + 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 = (struct pci_resource *) kmalloc(sizeof(struct pci_resource), GFP_KERNEL); + if (!bus_node) + return -ENOMEM; + + bus_node->base = (ulong)secondary_bus; + bus_node->length = (ulong)(max_bus - secondary_bus + 1); + dbg("found bus_node(base, length) = %x, %x\n", bus_node->base, bus_node->length); + + 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 &= shpchp_resource_sort_and_combine(&(ctrl->mem_head)); + rc &= shpchp_resource_sort_and_combine(&(ctrl->p_mem_head)); + rc &= shpchp_resource_sort_and_combine(&(ctrl->io_head)); + rc &= shpchp_resource_sort_and_combine(&(ctrl->bus_head)); + + return (rc); +} + +int shpchprm_set_hpp( + struct controller *ctrl, + struct pci_func *func, + u8 card_type) +{ + u32 rc; + u8 temp_byte; + struct pci_bus lpci_bus, *pci_bus; + unsigned int devfn; + memcpy(&lpci_bus, ctrl->pci_bus, sizeof(lpci_bus)); + pci_bus = &lpci_bus; + pci_bus->number = func->bus; + devfn = PCI_DEVFN(func->device, func->function); + + temp_byte = 0x40; /* hard coded value for LT */ + if (card_type == PCI_HEADER_TYPE_BRIDGE) { + /* set subordinate Latency Timer */ + rc = pci_bus_write_config_byte(pci_bus, devfn, PCI_SEC_LATENCY_TIMER, temp_byte); + if (rc) { + dbg("%s: set secondary LT error. b:d:f(%02x:%02x:%02x)\n", __FUNCTION__, func->bus, + func->device, func->function); + return rc; + } + } + + /* set base Latency Timer */ + rc = pci_bus_write_config_byte(pci_bus, devfn, PCI_LATENCY_TIMER, temp_byte); + if (rc) { + dbg("%s: set LT error. b:d:f(%02x:%02x:%02x)\n", __FUNCTION__, func->bus, func->device, func->function); + return rc; + } + + /* set Cache Line size */ + temp_byte = 0x08; /* hard coded value for CLS */ + rc = pci_bus_write_config_byte(pci_bus, devfn, PCI_CACHE_LINE_SIZE, temp_byte); + if (rc) { + dbg("%s: set CLS error. b:d:f(%02x:%02x:%02x)\n", __FUNCTION__, func->bus, func->device, func->function); + } + + /* set enable_perr */ + /* set enable_serr */ + + return rc; +} + +void shpchprm_enable_card( + struct controller *ctrl, + struct pci_func *func, + u8 card_type) +{ + u16 command, bcommand; + struct pci_bus lpci_bus, *pci_bus; + unsigned int devfn; + int rc; + + memcpy(&lpci_bus, ctrl->pci_bus, sizeof(lpci_bus)); + pci_bus = &lpci_bus; + pci_bus->number = func->bus; + devfn = PCI_DEVFN(func->device, func->function); + + rc = pci_bus_read_config_word(pci_bus, devfn, PCI_COMMAND, &command); + command |= PCI_COMMAND_PARITY | PCI_COMMAND_SERR + | PCI_COMMAND_MASTER | PCI_COMMAND_INVALIDATE + | PCI_COMMAND_IO | PCI_COMMAND_MEMORY; + rc = pci_bus_write_config_word(pci_bus, devfn, PCI_COMMAND, command); + + if (card_type == PCI_HEADER_TYPE_BRIDGE) { + rc = pci_bus_read_config_word(pci_bus, devfn, PCI_BRIDGE_CONTROL, &bcommand); + bcommand |= 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, bcommand); + } +} + +static int legacy_shpchprm_init_pci(void) +{ + shpchp_rom_start = ioremap(ROM_PHY_ADDR, ROM_PHY_LEN); + if (!shpchp_rom_start) { + err("Could not ioremap memory region for ROM\n"); + return -EIO; + } + + return 0; +} + +int shpchprm_init(enum php_ctlr_type ctrl_type) +{ + int retval; + + switch (ctrl_type) { + case PCI: + retval = legacy_shpchprm_init_pci(); + break; + default: + retval = -ENODEV; + break; + } + + return retval; +} diff --git a/drivers/pci/hotplug/shpchprm_legacy.h b/drivers/pci/hotplug/shpchprm_legacy.h new file mode 100644 index 000000000000..29ccea5e57e5 --- /dev/null +++ b/drivers/pci/hotplug/shpchprm_legacy.h @@ -0,0 +1,113 @@ +/* + * SHPCHPRM Legacy: PHP Resource Manager for Non-ACPI/Legacy platform using HRT + * + * 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>, <dely.l.sy@intel.com> + * + */ + +#ifndef _SHPCHPRM_LEGACY_H_ +#define _SHPCHPRM_LEGACY_H_ + +#define ROM_PHY_ADDR 0x0F0000 +#define ROM_PHY_LEN 0x00FFFF + +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 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 irq_info { + u8 bus, devfn; /* bus, device and function */ + struct { + u8 link; /* IRQ line ID, chipset dependent, 0=not routed */ + u16 bitmap; /* Available IRQs */ + } __attribute__ ((packed)) irq[4]; + u8 slot; /* slot number, 0=onboard */ + u8 rfu; +} __attribute__ ((packed)); + +struct irq_routing_table { + u32 signature; /* PIRQ_SIGNATURE should be here */ + u16 version; /* PIRQ_VERSION */ + u16 size; /* Table size in bytes */ + u8 rtr_bus, rtr_devfn; /* Where the interrupt router lies */ + u16 exclusive_irqs; /* IRQs devoted exclusively to PCI usage */ + u16 rtr_vendor, rtr_device; /* Vendor and device ID of interrupt router */ + u32 miniport_data; /* Crap */ + u8 rfu[11]; + u8 checksum; /* Modulo 256 checksum must give zero */ + struct irq_info slots[0]; +} __attribute__ ((packed)); + +#endif /* _SHPCHPRM_LEGACY_H_ */ diff --git a/drivers/pci/hotplug/shpchprm_nonacpi.c b/drivers/pci/hotplug/shpchprm_nonacpi.c new file mode 100644 index 000000000000..88f4d9f41886 --- /dev/null +++ b/drivers/pci/hotplug/shpchprm_nonacpi.c @@ -0,0 +1,434 @@ +/* + * SHPCHPRM NONACPI: PHP Resource Manager for Non-ACPI/Legacy platform + * + * 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>, <dely.l.sy@intel.com> + * + */ + +#include <linux/config.h> +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/types.h> +#include <linux/pci.h> +#include <linux/init.h> +#include <asm/uaccess.h> +#ifdef CONFIG_IA64 +#include <asm/iosapic.h> +#endif +#include "shpchp.h" +#include "shpchprm.h" +#include "shpchprm_nonacpi.h" + +void shpchprm_cleanup(void) +{ + return; +} + +int shpchprm_print_pirt(void) +{ + return 0; +} + +int shpchprm_get_physical_slot_number(struct controller *ctrl, u32 *sun, u8 busnum, u8 devnum) +{ + int offset = devnum - ctrl->slot_device_offset; + + dbg("%s: ctrl->slot_num_inc %d, offset %d\n", __FUNCTION__, ctrl->slot_num_inc, offset); + *sun = (u8) (ctrl->first_slot + ctrl->slot_num_inc * offset); + return 0; +} + +static void print_pci_resource ( struct pci_resource *aprh) +{ + struct pci_resource *res; + + for (res = aprh; res; res = res->next) + dbg(" base= 0x%x length= 0x%x\n", res->base, res->length); +} + + +static void phprm_dump_func_res( struct pci_func *fun) +{ + struct pci_func *func = fun; + + if (func->bus_head) { + dbg(": BUS Resources:\n"); + print_pci_resource (func->bus_head); + } + if (func->io_head) { + dbg(": IO Resources:\n"); + print_pci_resource (func->io_head); + } + if (func->mem_head) { + dbg(": MEM Resources:\n"); + print_pci_resource (func->mem_head); + } + if (func->p_mem_head) { + dbg(": PMEM Resources:\n"); + print_pci_resource (func->p_mem_head); + } +} + +static int phprm_get_used_resources ( + struct controller *ctrl, + struct pci_func *func + ) +{ + return shpchp_save_used_resources (ctrl, func, !DISABLE_CARD); +} + +static int phprm_delete_resource( + struct pci_resource **aprh, + ulong base, + ulong size) +{ + struct pci_resource *res; + struct pci_resource *prevnode; + struct pci_resource *split_node; + ulong tbase; + + shpchp_resource_sort_and_combine(aprh); + + for (res = *aprh; res; res = res->next) { + if (res->base > base) + continue; + + if ((res->base + res->length) < (base + size)) + continue; + + if (res->base < base) { + tbase = base; + + if ((res->length - (tbase - res->base)) < size) + continue; + + split_node = (struct pci_resource *) kmalloc(sizeof(struct pci_resource), GFP_KERNEL); + if (!split_node) + return -ENOMEM; + + split_node->base = res->base; + split_node->length = tbase - res->base; + res->base = tbase; + res->length -= split_node->length; + + split_node->next = res->next; + res->next = split_node; + } + + if (res->length >= size) { + split_node = (struct pci_resource*) kmalloc(sizeof(struct pci_resource), GFP_KERNEL); + if (!split_node) + return -ENOMEM; + + split_node->base = res->base + size; + split_node->length = res->length - size; + res->length = size; + + split_node->next = res->next; + res->next = split_node; + } + + if (*aprh == res) { + *aprh = res->next; + } else { + prevnode = *aprh; + while (prevnode->next != res) + prevnode = prevnode->next; + + prevnode->next = res->next; + } + res->next = NULL; + kfree(res); + break; + } + + return 0; +} + + +static int phprm_delete_resources( + struct pci_resource **aprh, + struct pci_resource *this + ) +{ + struct pci_resource *res; + + for (res = this; res; res = res->next) + phprm_delete_resource(aprh, res->base, res->length); + + return 0; +} + + +static int configure_existing_function( + struct controller *ctrl, + struct pci_func *func + ) +{ + int rc; + + /* see how much resources the func has used. */ + rc = phprm_get_used_resources (ctrl, func); + + if (!rc) { + /* subtract the resources used by the func from ctrl resources */ + rc = phprm_delete_resources (&ctrl->bus_head, func->bus_head); + rc |= phprm_delete_resources (&ctrl->io_head, func->io_head); + rc |= phprm_delete_resources (&ctrl->mem_head, func->mem_head); + rc |= phprm_delete_resources (&ctrl->p_mem_head, func->p_mem_head); + if (rc) + warn("aCEF: cannot del used resources\n"); + } else + err("aCEF: cannot get used resources\n"); + + return rc; +} + +static int bind_pci_resources_to_slots ( struct controller *ctrl) +{ + struct pci_func *func, new_func; + int busn = ctrl->slot_bus; + int devn, funn; + u32 vid; + + for (devn = 0; devn < 32; devn++) { + for (funn = 0; funn < 8; funn++) { + /* + if (devn == ctrl->device && funn == ctrl->function) + continue; + */ + /* find out if this entry is for an occupied slot */ + vid = 0xFFFFFFFF; + + pci_bus_read_config_dword(ctrl->pci_dev->subordinate, PCI_DEVFN(devn, funn), PCI_VENDOR_ID, &vid); + + if (vid != 0xFFFFFFFF) { + func = shpchp_slot_find(busn, devn, funn); + if (!func) { + memset(&new_func, 0, sizeof(struct pci_func)); + new_func.bus = busn; + new_func.device = devn; + new_func.function = funn; + new_func.is_a_board = 1; + configure_existing_function(ctrl, &new_func); + phprm_dump_func_res(&new_func); + } else { + configure_existing_function(ctrl, func); + phprm_dump_func_res(func); + } + dbg("aCCF:existing PCI 0x%x Func ResourceDump\n", ctrl->bus); + } + } + } + + return 0; +} + +static void phprm_dump_ctrl_res( struct controller *ctlr) +{ + struct controller *ctrl = ctlr; + + if (ctrl->bus_head) { + dbg(": BUS Resources:\n"); + print_pci_resource (ctrl->bus_head); + } + if (ctrl->io_head) { + dbg(": IO Resources:\n"); + print_pci_resource (ctrl->io_head); + } + if (ctrl->mem_head) { + dbg(": MEM Resources:\n"); + print_pci_resource (ctrl->mem_head); + } + if (ctrl->p_mem_head) { + dbg(": PMEM Resources:\n"); + print_pci_resource (ctrl->p_mem_head); + } +} + +/* + * phprm_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 shpchprm_find_available_resources(struct controller *ctrl) +{ + struct pci_func func; + u32 rc; + + memset(&func, 0, sizeof(struct pci_func)); + + func.bus = ctrl->bus; + func.device = ctrl->device; + func.function = ctrl->function; + func.is_a_board = 1; + + /* Get resources for this PCI bridge */ + rc = shpchp_save_used_resources (ctrl, &func, !DISABLE_CARD); + dbg("%s: shpchp_save_used_resources rc = %d\n", __FUNCTION__, rc); + + if (func.mem_head) + func.mem_head->next = ctrl->mem_head; + ctrl->mem_head = func.mem_head; + + if (func.p_mem_head) + func.p_mem_head->next = ctrl->p_mem_head; + ctrl->p_mem_head = func.p_mem_head; + + if (func.io_head) + func.io_head->next = ctrl->io_head; + ctrl->io_head = func.io_head; + + if(func.bus_head) + func.bus_head->next = ctrl->bus_head; + ctrl->bus_head = func.bus_head; + if (ctrl->bus_head) + phprm_delete_resource(&ctrl->bus_head, ctrl->pci_dev->subordinate->number, 1); + + dbg("%s:pre-Bind PCI 0x%x Ctrl Resource Dump\n", __FUNCTION__, ctrl->bus); + phprm_dump_ctrl_res(ctrl); + bind_pci_resources_to_slots (ctrl); + + dbg("%s:post-Bind PCI 0x%x Ctrl Resource Dump\n", __FUNCTION__, ctrl->bus); + phprm_dump_ctrl_res(ctrl); + + + /* If all of the following fail, we don't have any resources for hot plug add */ + rc = 1; + rc &= shpchp_resource_sort_and_combine(&(ctrl->mem_head)); + rc &= shpchp_resource_sort_and_combine(&(ctrl->p_mem_head)); + rc &= shpchp_resource_sort_and_combine(&(ctrl->io_head)); + rc &= shpchp_resource_sort_and_combine(&(ctrl->bus_head)); + + return (rc); +} + +int shpchprm_set_hpp( + struct controller *ctrl, + struct pci_func *func, + u8 card_type) +{ + u32 rc; + u8 temp_byte; + struct pci_bus lpci_bus, *pci_bus; + unsigned int devfn; + memcpy(&lpci_bus, ctrl->pci_bus, sizeof(lpci_bus)); + pci_bus = &lpci_bus; + pci_bus->number = func->bus; + devfn = PCI_DEVFN(func->device, func->function); + + temp_byte = 0x40; /* hard coded value for LT */ + if (card_type == PCI_HEADER_TYPE_BRIDGE) { + /* set subordinate Latency Timer */ + rc = pci_bus_write_config_byte(pci_bus, devfn, PCI_SEC_LATENCY_TIMER, temp_byte); + + if (rc) { + dbg("%s: set secondary LT error. b:d:f(%02x:%02x:%02x)\n", __FUNCTION__, func->bus, + func->device, func->function); + return rc; + } + } + + /* set base Latency Timer */ + rc = pci_bus_write_config_byte(pci_bus, devfn, PCI_LATENCY_TIMER, temp_byte); + + if (rc) { + dbg("%s: set LT error. b:d:f(%02x:%02x:%02x)\n", __FUNCTION__, func->bus, func->device, func->function); + return rc; + } + + /* set Cache Line size */ + temp_byte = 0x08; /* hard coded value for CLS */ + + rc = pci_bus_write_config_byte(pci_bus, devfn, PCI_CACHE_LINE_SIZE, temp_byte); + + if (rc) { + dbg("%s: set CLS error. b:d:f(%02x:%02x:%02x)\n", __FUNCTION__, func->bus, func->device, func->function); + } + + /* set enable_perr */ + /* set enable_serr */ + + return rc; +} + +void shpchprm_enable_card( + struct controller *ctrl, + struct pci_func *func, + u8 card_type) +{ + u16 command, bcommand; + struct pci_bus lpci_bus, *pci_bus; + unsigned int devfn; + int rc; + + memcpy(&lpci_bus, ctrl->pci_bus, sizeof(lpci_bus)); + pci_bus = &lpci_bus; + pci_bus->number = func->bus; + devfn = PCI_DEVFN(func->device, func->function); + + rc = pci_bus_read_config_word(pci_bus, devfn, PCI_COMMAND, &command); + + command |= PCI_COMMAND_PARITY | PCI_COMMAND_SERR + | PCI_COMMAND_MASTER | PCI_COMMAND_INVALIDATE + | PCI_COMMAND_IO | PCI_COMMAND_MEMORY; + + rc = pci_bus_write_config_word(pci_bus, devfn, PCI_COMMAND, command); + + if (card_type == PCI_HEADER_TYPE_BRIDGE) { + + rc = pci_bus_read_config_word(pci_bus, devfn, PCI_BRIDGE_CONTROL, &bcommand); + + bcommand |= 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, bcommand); + } +} + +static int legacy_shpchprm_init_pci(void) +{ + return 0; +} + +int shpchprm_init(enum php_ctlr_type ctrl_type) +{ + int retval; + + switch (ctrl_type) { + case PCI: + retval = legacy_shpchprm_init_pci(); + break; + default: + retval = -ENODEV; + break; + } + + return retval; +} diff --git a/drivers/pci/hotplug/shpchprm_nonacpi.h b/drivers/pci/hotplug/shpchprm_nonacpi.h new file mode 100644 index 000000000000..6bc8668023c3 --- /dev/null +++ b/drivers/pci/hotplug/shpchprm_nonacpi.h @@ -0,0 +1,56 @@ +/* + * SHPCHPRM NONACPI: PHP Resource Manager for Non-ACPI/Legacy platform + * + * 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>, <dely.l.sy@intel.com> + * + */ + +#ifndef _SHPCHPRM_NONACPI_H_ +#define _SHPCHPRM_NONACPI_H_ + +struct irq_info { + u8 bus, devfn; /* bus, device and function */ + struct { + u8 link; /* IRQ line ID, chipset dependent, 0=not routed */ + u16 bitmap; /* Available IRQs */ + } __attribute__ ((packed)) irq[4]; + u8 slot; /* slot number, 0=onboard */ + u8 rfu; +} __attribute__ ((packed)); + +struct irq_routing_table { + u32 signature; /* PIRQ_SIGNATURE should be here */ + u16 version; /* PIRQ_VERSION */ + u16 size; /* Table size in bytes */ + u8 rtr_bus, rtr_devfn; /* Where the interrupt router lies */ + u16 exclusive_irqs; /* IRQs devoted exclusively to PCI usage */ + u16 rtr_vendor, rtr_device; /* Vendor and device ID of interrupt router */ + u32 miniport_data; /* Crap */ + u8 rfu[11]; + u8 checksum; /* Modulo 256 checksum must give zero */ + struct irq_info slots[0]; +} __attribute__ ((packed)); + +#endif /* _SHPCHPRM_NONACPI_H_ */ |