diff options
Diffstat (limited to 'drivers/pci/hotplug')
48 files changed, 27857 insertions, 0 deletions
diff --git a/drivers/pci/hotplug/Kconfig b/drivers/pci/hotplug/Kconfig new file mode 100644 index 000000000..840a84bb5 --- /dev/null +++ b/drivers/pci/hotplug/Kconfig @@ -0,0 +1,161 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# PCI Hotplug support +# + +menuconfig HOTPLUG_PCI + bool "Support for PCI Hotplug" + depends on PCI && SYSFS + 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. + + When in doubt, say N. + +if HOTPLUG_PCI + +config HOTPLUG_PCI_COMPAQ + tristate "Compaq PCI Hotplug driver" + depends on 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 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 + bool "ACPI PCI Hotplug driver" + depends on HOTPLUG_PCI=y && ((!ACPI_DOCK && ACPI) || (ACPI_DOCK)) + help + Say Y here if you have a system that supports PCI Hotplug using + ACPI. + + When in doubt, say N. + +config HOTPLUG_PCI_ACPI_IBM + tristate "ACPI PCI Hotplug driver IBM extensions" + depends on HOTPLUG_PCI_ACPI + help + Say Y here if you have an IBM system that supports PCI Hotplug using + ACPI. + + To compile this driver as a module, choose M here: the + module will be called acpiphp_ibm. + + When in doubt, say N. + +config HOTPLUG_PCI_CPCI + bool "CompactPCI Hotplug driver" + help + Say Y here if you have a CompactPCI system card with CompactPCI + hotswap support per the PICMG 2.1 specification. + + When in doubt, say N. + +config HOTPLUG_PCI_CPCI_ZT5550 + tristate "Ziatech ZT5550 CompactPCI Hotplug driver" + depends on HOTPLUG_PCI_CPCI && X86 + help + Say Y here if you have an Performance Technologies (formerly Intel, + formerly just Ziatech) Ziatech ZT5550 CompactPCI system card. + + To compile this driver as a module, choose M here: the + module will be called cpcihp_zt5550. + + When in doubt, say N. + +config HOTPLUG_PCI_CPCI_GENERIC + tristate "Generic port I/O CompactPCI Hotplug driver" + depends on HOTPLUG_PCI_CPCI && X86 + help + Say Y here if you have a CompactPCI system card that exposes the #ENUM + hotswap signal as a bit in a system register that can be read through + standard port I/O. + + To compile this driver as a module, choose M here: the + module will be called cpcihp_generic. + + When in doubt, say N. + +config HOTPLUG_PCI_SHPC + bool "SHPC PCI Hotplug driver" + help + Say Y here if you have a motherboard with a SHPC PCI Hotplug + controller. + + When in doubt, say N. + +config HOTPLUG_PCI_POWERNV + tristate "PowerPC PowerNV PCI Hotplug driver" + depends on PPC_POWERNV && EEH + select OF_DYNAMIC + help + Say Y here if you run PowerPC PowerNV platform that supports + PCI Hotplug + + To compile this driver as a module, choose M here: the + module will be called pnv-php. + + When in doubt, say N. + +config HOTPLUG_PCI_RPA + tristate "RPA PCI Hotplug driver" + depends on PPC_PSERIES && EEH + help + Say Y here if you have a RPA system that supports PCI Hotplug. + + To compile this driver as a module, choose M here: the + module will be called rpaphp. + + When in doubt, say N. + +config HOTPLUG_PCI_RPA_DLPAR + tristate "RPA Dynamic Logical Partitioning for I/O slots" + depends on HOTPLUG_PCI_RPA + help + Say Y here if your system supports Dynamic Logical Partitioning + for I/O slots. + + To compile this driver as a module, choose M here: the + module will be called rpadlpar_io. + + When in doubt, say N. + +config HOTPLUG_PCI_S390 + bool "System z PCI Hotplug Support" + depends on S390 && 64BIT + help + Say Y here if you want to use the System z PCI Hotplug + driver for PCI devices. Without this driver it is not + possible to access stand-by PCI functions nor to deconfigure + PCI functions. + + When in doubt, say Y. + +endif # HOTPLUG_PCI diff --git a/drivers/pci/hotplug/Makefile b/drivers/pci/hotplug/Makefile new file mode 100644 index 000000000..519698322 --- /dev/null +++ b/drivers/pci/hotplug/Makefile @@ -0,0 +1,72 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Makefile for the Linux kernel pci hotplug controller drivers. +# + +obj-$(CONFIG_HOTPLUG_PCI) += pci_hotplug.o +obj-$(CONFIG_HOTPLUG_PCI_COMPAQ) += cpqphp.o +obj-$(CONFIG_HOTPLUG_PCI_IBM) += ibmphp.o + +# native drivers should be linked before acpiphp in order to allow the +# native driver to attempt to bind first. We can then fall back to +# generic support. + +obj-$(CONFIG_HOTPLUG_PCI_PCIE) += pciehp.o +obj-$(CONFIG_HOTPLUG_PCI_CPCI_ZT5550) += cpcihp_zt5550.o +obj-$(CONFIG_HOTPLUG_PCI_CPCI_GENERIC) += cpcihp_generic.o +obj-$(CONFIG_HOTPLUG_PCI_SHPC) += shpchp.o +obj-$(CONFIG_HOTPLUG_PCI_POWERNV) += pnv-php.o +obj-$(CONFIG_HOTPLUG_PCI_RPA) += rpaphp.o +obj-$(CONFIG_HOTPLUG_PCI_RPA_DLPAR) += rpadlpar_io.o +obj-$(CONFIG_HOTPLUG_PCI_ACPI) += acpiphp.o +obj-$(CONFIG_HOTPLUG_PCI_S390) += s390_pci_hpc.o + +# acpiphp_ibm extends acpiphp, so should be linked afterwards. + +obj-$(CONFIG_HOTPLUG_PCI_ACPI_IBM) += acpiphp_ibm.o + +pci_hotplug-objs := pci_hotplug_core.o + +ifdef CONFIG_HOTPLUG_PCI_CPCI +pci_hotplug-objs += cpci_hotplug_core.o \ + cpci_hotplug_pci.o +endif +ifdef CONFIG_ACPI +pci_hotplug-objs += acpi_pcihp.o +endif + +cpqphp-objs := cpqphp_core.o \ + cpqphp_ctrl.o \ + cpqphp_sysfs.o \ + cpqphp_pci.o +cpqphp-$(CONFIG_HOTPLUG_PCI_COMPAQ_NVRAM) += cpqphp_nvram.o +cpqphp-objs += $(cpqphp-y) + +ibmphp-objs := ibmphp_core.o \ + ibmphp_ebda.o \ + ibmphp_pci.o \ + ibmphp_res.o \ + ibmphp_hpc.o + +acpiphp-objs := acpiphp_core.o \ + acpiphp_glue.o + +pnv-php-objs := pnv_php.o + +rpaphp-objs := rpaphp_core.o \ + rpaphp_pci.o \ + rpaphp_slot.o + +rpadlpar_io-objs := rpadlpar_core.o \ + rpadlpar_sysfs.o + +pciehp-objs := pciehp_core.o \ + pciehp_ctrl.o \ + pciehp_pci.o \ + pciehp_hpc.o + +shpchp-objs := shpchp_core.o \ + shpchp_ctrl.o \ + shpchp_pci.o \ + shpchp_sysfs.o \ + shpchp_hpc.o diff --git a/drivers/pci/hotplug/TODO b/drivers/pci/hotplug/TODO new file mode 100644 index 000000000..88f217c82 --- /dev/null +++ b/drivers/pci/hotplug/TODO @@ -0,0 +1,66 @@ +Contributions are solicited in particular to remedy the following issues: + +cpcihp: + +* There are no implementations of the ->hardware_test, ->get_power and + ->set_power callbacks in struct cpci_hp_controller_ops. Why were they + introduced? Can they be removed from the struct? + +cpqphp: + +* The driver spawns a kthread cpqhp_event_thread() which is woken by the + hardirq handler cpqhp_ctrl_intr(). Convert this to threaded IRQ handling. + The kthread is also woken from the timer pushbutton_helper_thread(), + convert it to call irq_wake_thread(). Use pciehp as a template. + +* A large portion of cpqphp_ctrl.c and cpqphp_pci.c concerns resource + management. Doesn't this duplicate functionality in the core? + +ibmphp: + +* Implementations of hotplug_slot_ops callbacks such as get_adapter_present() + in ibmphp_core.c create a copy of the struct slot on the stack, then perform + the actual operation on that copy. Determine if this overhead is necessary, + delete it if not. The functions also perform a NULL pointer check on the + struct hotplug_slot, this seems superfluous. + +* Several functions access the pci_slot member in struct hotplug_slot even + though pci_hotplug.h declares it private. See get_max_bus_speed() for an + example. Either the pci_slot member should no longer be declared private + or ibmphp should store a pointer to its bus in struct slot. Probably the + former. + +* ibmphp_init_devno() takes a struct slot **, it could instead take a + struct slot *. + +* The return value of pci_hp_register() is not checked. + +* The various slot data structures are difficult to follow and need to be + simplified. A lot of functions are too large and too complex, they need + to be broken up into smaller, manageable pieces. Negative examples are + ebda_rsrc_controller() and configure_bridge(). + +* A large portion of ibmphp_res.c and ibmphp_pci.c concerns resource + management. Doesn't this duplicate functionality in the core? + +sgi_hotplug: + +* Several functions access the pci_slot member in struct hotplug_slot even + though pci_hotplug.h declares it private. See sn_hp_destroy() for an + example. Either the pci_slot member should no longer be declared private + or sgi_hotplug should store a pointer to it in struct slot. Probably the + former. + +shpchp: + +* There is only a single implementation of struct hpc_ops. Can the struct be + removed and its functions invoked directly? This has already been done in + pciehp with commit 82a9e79ef132 ("PCI: pciehp: remove hpc_ops"). Clarify + if there was a specific reason not to apply the same change to shpchp. + +* The ->get_mode1_ECC_cap callback in shpchp_hpc_ops is never invoked. + Why was it introduced? Can it be removed? + +* The hardirq handler shpc_isr() queues events on a workqueue. It can be + simplified by converting it to threaded IRQ handling. Use pciehp as a + template. diff --git a/drivers/pci/hotplug/acpi_pcihp.c b/drivers/pci/hotplug/acpi_pcihp.c new file mode 100644 index 000000000..4fedebf2f --- /dev/null +++ b/drivers/pci/hotplug/acpi_pcihp.c @@ -0,0 +1,212 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Common ACPI functions for hot plug platforms + * + * Copyright (C) 2006 Intel Corporation + * + * All rights reserved. + * + * Send feedback to <kristen.c.accardi@intel.com> + */ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/kernel.h> +#include <linux/types.h> +#include <linux/pci.h> +#include <linux/pci_hotplug.h> +#include <linux/acpi.h> +#include <linux/pci-acpi.h> +#include <linux/slab.h> + +#define MY_NAME "acpi_pcihp" + +#define dbg(fmt, arg...) do { if (debug_acpi) printk(KERN_DEBUG "%s: %s: " fmt, MY_NAME, __func__, ## arg); } while (0) +#define err(format, arg...) printk(KERN_ERR "%s: " format, MY_NAME, ## arg) +#define info(format, arg...) printk(KERN_INFO "%s: " format, MY_NAME, ## arg) +#define warn(format, arg...) printk(KERN_WARNING "%s: " format, MY_NAME, ## arg) + +#define METHOD_NAME__SUN "_SUN" +#define METHOD_NAME_OSHP "OSHP" + +static bool debug_acpi; + +/* acpi_run_oshp - get control of hotplug from the firmware + * + * @handle - the handle of the hotplug controller. + */ +static acpi_status acpi_run_oshp(acpi_handle handle) +{ + acpi_status status; + struct acpi_buffer string = { ACPI_ALLOCATE_BUFFER, NULL }; + + acpi_get_name(handle, ACPI_FULL_PATHNAME, &string); + + /* run OSHP */ + status = acpi_evaluate_object(handle, METHOD_NAME_OSHP, NULL, NULL); + if (ACPI_FAILURE(status)) + if (status != AE_NOT_FOUND) + printk(KERN_ERR "%s:%s OSHP fails=0x%x\n", + __func__, (char *)string.pointer, status); + else + dbg("%s:%s OSHP not found\n", + __func__, (char *)string.pointer); + else + pr_debug("%s:%s OSHP passes\n", __func__, + (char *)string.pointer); + + kfree(string.pointer); + return status; +} + +/** + * acpi_get_hp_hw_control_from_firmware + * @pdev: the pci_dev of the bridge that has a hotplug controller + * + * Attempt to take hotplug control from firmware. + */ +int acpi_get_hp_hw_control_from_firmware(struct pci_dev *pdev) +{ + const struct pci_host_bridge *host; + const struct acpi_pci_root *root; + acpi_status status; + acpi_handle chandle, handle; + struct acpi_buffer string = { ACPI_ALLOCATE_BUFFER, NULL }; + + /* + * If there's no ACPI host bridge (i.e., ACPI support is compiled + * into the kernel but the hardware platform doesn't support ACPI), + * there's nothing to do here. + */ + host = pci_find_host_bridge(pdev->bus); + root = acpi_pci_find_root(ACPI_HANDLE(&host->dev)); + if (!root) + return 0; + + /* + * If _OSC exists, it determines whether we're allowed to manage + * the SHPC. We executed it while enumerating the host bridge. + */ + if (root->osc_support_set) { + if (host->native_shpc_hotplug) + return 0; + return -ENODEV; + } + + /* + * In the absence of _OSC, we're always allowed to manage the SHPC. + * However, if an OSHP method is present, we must execute it so the + * firmware can transfer control to the OS, e.g., direct interrupts + * to the OS instead of to the firmware. + * + * N.B. The PCI Firmware Spec (r3.2, sec 4.8) does not endorse + * searching up the ACPI hierarchy, so the loops below are suspect. + */ + handle = ACPI_HANDLE(&pdev->dev); + if (!handle) { + /* + * This hotplug controller was not listed in the ACPI name + * space at all. Try to get ACPI handle of parent PCI bus. + */ + struct pci_bus *pbus; + for (pbus = pdev->bus; pbus; pbus = pbus->parent) { + handle = acpi_pci_get_bridge_handle(pbus); + if (handle) + break; + } + } + + while (handle) { + acpi_get_name(handle, ACPI_FULL_PATHNAME, &string); + pci_info(pdev, "Requesting control of SHPC hotplug via OSHP (%s)\n", + (char *)string.pointer); + status = acpi_run_oshp(handle); + if (ACPI_SUCCESS(status)) + goto got_one; + if (acpi_is_root_bridge(handle)) + break; + chandle = handle; + status = acpi_get_parent(chandle, &handle); + if (ACPI_FAILURE(status)) + break; + } + + pci_info(pdev, "Cannot get control of SHPC hotplug\n"); + kfree(string.pointer); + return -ENODEV; +got_one: + pci_info(pdev, "Gained control of SHPC hotplug (%s)\n", + (char *)string.pointer); + kfree(string.pointer); + return 0; +} +EXPORT_SYMBOL(acpi_get_hp_hw_control_from_firmware); + +static int pcihp_is_ejectable(acpi_handle handle) +{ + acpi_status status; + unsigned long long removable; + if (!acpi_has_method(handle, "_ADR")) + return 0; + if (acpi_has_method(handle, "_EJ0")) + return 1; + status = acpi_evaluate_integer(handle, "_RMV", NULL, &removable); + if (ACPI_SUCCESS(status) && removable) + return 1; + return 0; +} + +/** + * acpi_pci_check_ejectable - check if handle is ejectable ACPI PCI slot + * @pbus: the PCI bus of the PCI slot corresponding to 'handle' + * @handle: ACPI handle to check + * + * Return 1 if handle is ejectable PCI slot, 0 otherwise. + */ +int acpi_pci_check_ejectable(struct pci_bus *pbus, acpi_handle handle) +{ + acpi_handle bridge_handle, parent_handle; + + bridge_handle = acpi_pci_get_bridge_handle(pbus); + if (!bridge_handle) + return 0; + if ((ACPI_FAILURE(acpi_get_parent(handle, &parent_handle)))) + return 0; + if (bridge_handle != parent_handle) + return 0; + return pcihp_is_ejectable(handle); +} +EXPORT_SYMBOL_GPL(acpi_pci_check_ejectable); + +static acpi_status +check_hotplug(acpi_handle handle, u32 lvl, void *context, void **rv) +{ + int *found = (int *)context; + if (pcihp_is_ejectable(handle)) { + *found = 1; + return AE_CTRL_TERMINATE; + } + return AE_OK; +} + +/** + * acpi_pci_detect_ejectable - check if the PCI bus has ejectable slots + * @handle: handle of the PCI bus to scan + * + * Returns 1 if the PCI bus has ACPI based ejectable slots, 0 otherwise. + */ +int acpi_pci_detect_ejectable(acpi_handle handle) +{ + int found = 0; + + if (!handle) + return found; + + acpi_walk_namespace(ACPI_TYPE_DEVICE, handle, 1, + check_hotplug, NULL, (void *)&found, NULL); + return found; +} +EXPORT_SYMBOL_GPL(acpi_pci_detect_ejectable); + +module_param(debug_acpi, bool, 0644); +MODULE_PARM_DESC(debug_acpi, "Debugging mode for ACPI enabled or not"); diff --git a/drivers/pci/hotplug/acpiphp.h b/drivers/pci/hotplug/acpiphp.h new file mode 100644 index 000000000..1f8ab4377 --- /dev/null +++ b/drivers/pci/hotplug/acpiphp.h @@ -0,0 +1,188 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * ACPI PCI Hot Plug Controller Driver + * + * Copyright (C) 1995,2001 Compaq Computer Corporation + * Copyright (C) 2001 Greg Kroah-Hartman (greg@kroah.com) + * Copyright (C) 2001 IBM Corp. + * Copyright (C) 2002 Hiroshi Aono (h-aono@ap.jp.nec.com) + * Copyright (C) 2002,2003 Takayoshi Kochi (t-kochi@bq.jp.nec.com) + * Copyright (C) 2002,2003 NEC Corporation + * Copyright (C) 2003-2005 Matthew Wilcox (willy@infradead.org) + * Copyright (C) 2003-2005 Hewlett Packard + * + * All rights reserved. + * + * 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/mutex.h> +#include <linux/pci_hotplug.h> + +struct acpiphp_context; +struct acpiphp_bridge; +struct acpiphp_slot; + +/* + * struct slot - slot information for each *physical* slot + */ +struct slot { + struct hotplug_slot hotplug_slot; + struct acpiphp_slot *acpi_slot; + unsigned int sun; /* ACPI _SUN (Slot User Number) value */ +}; + +static inline const char *slot_name(struct slot *slot) +{ + return hotplug_slot_name(&slot->hotplug_slot); +} + +static inline struct slot *to_slot(struct hotplug_slot *hotplug_slot) +{ + return container_of(hotplug_slot, struct slot, hotplug_slot); +} + +/* + * struct acpiphp_bridge - PCI bridge information + * + * for each bridge device in ACPI namespace + */ +struct acpiphp_bridge { + struct list_head list; + struct list_head slots; + struct kref ref; + + struct acpiphp_context *context; + + int nr_slots; + + /* 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; + + bool is_going_away; +}; + + +/* + * struct acpiphp_slot - PCI slot information + * + * PCI slot information for each *physical* PCI slot + */ +struct acpiphp_slot { + struct list_head node; + struct pci_bus *bus; + struct list_head funcs; /* one slot may have different + objects (i.e. for each function) */ + struct slot *slot; + + u8 device; /* pci device# */ + 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_bridge *parent; + struct acpiphp_slot *slot; + + struct list_head sibling; + + u8 function; /* pci function# */ + u32 flags; /* see below */ +}; + +struct acpiphp_context { + struct acpi_hotplug_context hp; + struct acpiphp_func func; + struct acpiphp_bridge *bridge; + unsigned int refcount; +}; + +static inline struct acpiphp_context *to_acpiphp_context(struct acpi_hotplug_context *hp) +{ + return container_of(hp, struct acpiphp_context, hp); +} + +static inline struct acpiphp_context *func_to_context(struct acpiphp_func *func) +{ + return container_of(func, struct acpiphp_context, func); +} + +static inline struct acpi_device *func_to_acpi_device(struct acpiphp_func *func) +{ + return func_to_context(func)->hp.self; +} + +static inline acpi_handle func_to_handle(struct acpiphp_func *func) +{ + return func_to_acpi_device(func)->handle; +} + +struct acpiphp_root_context { + struct acpi_hotplug_context hp; + struct acpiphp_bridge *root_bridge; +}; + +static inline struct acpiphp_root_context *to_acpiphp_root_context(struct acpi_hotplug_context *hp) +{ + return container_of(hp, struct acpiphp_root_context, hp); +} + +/* + * 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; +}; + +/* ACPI _STA method value (ignore bit 4; battery present) */ +#define ACPI_STA_ALL (0x0000000f) + +/* slot flags */ + +#define SLOT_ENABLED (0x00000001) +#define SLOT_IS_GOING_AWAY (0x00000002) + +/* function flags */ + +#define FUNC_HAS_STA (0x00000001) +#define FUNC_HAS_EJ0 (0x00000002) + +/* function prototypes */ + +/* acpiphp_core.c */ +int acpiphp_register_attention(struct acpiphp_attention_info *info); +int acpiphp_unregister_attention(struct acpiphp_attention_info *info); +int acpiphp_register_hotplug_slot(struct acpiphp_slot *slot, unsigned int sun); +void acpiphp_unregister_hotplug_slot(struct acpiphp_slot *slot); + +int acpiphp_enable_slot(struct acpiphp_slot *slot); +int acpiphp_disable_slot(struct acpiphp_slot *slot); +u8 acpiphp_get_power_status(struct acpiphp_slot *slot); +u8 acpiphp_get_attention_status(struct acpiphp_slot *slot); +u8 acpiphp_get_latch_status(struct acpiphp_slot *slot); +u8 acpiphp_get_adapter_status(struct acpiphp_slot *slot); + +/* variables */ +extern bool acpiphp_disabled; + +#endif /* _ACPIPHP_H */ diff --git a/drivers/pci/hotplug/acpiphp_core.c b/drivers/pci/hotplug/acpiphp_core.c new file mode 100644 index 000000000..853e04ad2 --- /dev/null +++ b/drivers/pci/hotplug/acpiphp_core.c @@ -0,0 +1,312 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * ACPI PCI Hot Plug Controller Driver + * + * Copyright (C) 1995,2001 Compaq Computer Corporation + * Copyright (C) 2001 Greg Kroah-Hartman (greg@kroah.com) + * Copyright (C) 2001 IBM Corp. + * Copyright (C) 2002 Hiroshi Aono (h-aono@ap.jp.nec.com) + * Copyright (C) 2002,2003 Takayoshi Kochi (t-kochi@bq.jp.nec.com) + * Copyright (C) 2002,2003 NEC Corporation + * Copyright (C) 2003-2005 Matthew Wilcox (willy@infradead.org) + * Copyright (C) 2003-2005 Hewlett Packard + * + * All rights reserved. + * + * Send feedback to <kristen.c.accardi@intel.com> + * + */ + +#define pr_fmt(fmt) "acpiphp: " fmt + +#include <linux/init.h> +#include <linux/module.h> +#include <linux/moduleparam.h> + +#include <linux/kernel.h> +#include <linux/pci.h> +#include <linux/pci-acpi.h> +#include <linux/pci_hotplug.h> +#include <linux/slab.h> +#include <linux/smp.h> +#include "acpiphp.h" + +/* name size which is used for entries in pcihpfs */ +#define SLOT_NAME_SIZE 21 /* {_SUN} */ + +bool acpiphp_disabled; + +/* local variables */ +static struct acpiphp_attention_info *attention_info; + +#define DRIVER_VERSION "0.5" +#define DRIVER_AUTHOR "Greg Kroah-Hartman <gregkh@us.ibm.com>, Takayoshi Kochi <t-kochi@bq.jp.nec.com>, Matthew Wilcox <willy@infradead.org>" +#define DRIVER_DESC "ACPI Hot Plug PCI Controller Driver" + +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); +MODULE_PARM_DESC(disable, "disable acpiphp driver"); +module_param_named(disable, acpiphp_disabled, bool, 0444); + +static int enable_slot(struct hotplug_slot *slot); +static int disable_slot(struct hotplug_slot *slot); +static int set_attention_status(struct hotplug_slot *slot, u8 value); +static int get_power_status(struct hotplug_slot *slot, u8 *value); +static int get_attention_status(struct hotplug_slot *slot, u8 *value); +static int get_latch_status(struct hotplug_slot *slot, u8 *value); +static int get_adapter_status(struct hotplug_slot *slot, u8 *value); + +static const struct hotplug_slot_ops acpi_hotplug_slot_ops = { + .enable_slot = enable_slot, + .disable_slot = disable_slot, + .set_attention_status = set_attention_status, + .get_power_status = get_power_status, + .get_attention_status = get_attention_status, + .get_latch_status = get_latch_status, + .get_adapter_status = get_adapter_status, +}; + +/** + * acpiphp_register_attention - set attention LED callback + * @info: must be completely filled with LED callbacks + * + * Description: This is used to register a hardware specific ACPI + * driver that manipulates the attention LED. All the fields in + * info must be set. + */ +int acpiphp_register_attention(struct acpiphp_attention_info *info) +{ + int retval = -EINVAL; + + if (info && info->owner && info->set_attn && + info->get_attn && !attention_info) { + retval = 0; + attention_info = info; + } + return retval; +} +EXPORT_SYMBOL_GPL(acpiphp_register_attention); + + +/** + * 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; +} +EXPORT_SYMBOL_GPL(acpiphp_unregister_attention); + + +/** + * 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 = to_slot(hotplug_slot); + + pr_debug("%s - physical_slot = %s\n", __func__, slot_name(slot)); + + /* enable the specified slot */ + return acpiphp_enable_slot(slot->acpi_slot); +} + + +/** + * disable_slot - disable and power off a slot + * @hotplug_slot: slot to disable + * + * Actual tasks are done in acpiphp_disable_slot() + */ +static int disable_slot(struct hotplug_slot *hotplug_slot) +{ + struct slot *slot = to_slot(hotplug_slot); + + pr_debug("%s - physical_slot = %s\n", __func__, slot_name(slot)); + + /* 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; + + pr_debug("%s - physical_slot = %s\n", __func__, + hotplug_slot_name(hotplug_slot)); + + if (attention_info && try_module_get(attention_info->owner)) { + retval = attention_info->set_attn(hotplug_slot, status); + module_put(attention_info->owner); + } else + attention_info = NULL; + return retval; +} + + +/** + * get_power_status - get power status of a slot + * @hotplug_slot: slot to get status + * @value: pointer to store status + * + * Some platforms may not implement _STA method properly. + * In that case, the value returned may not be reliable. + */ +static int get_power_status(struct hotplug_slot *hotplug_slot, u8 *value) +{ + struct slot *slot = to_slot(hotplug_slot); + + pr_debug("%s - physical_slot = %s\n", __func__, slot_name(slot)); + + *value = acpiphp_get_power_status(slot->acpi_slot); + + return 0; +} + + +/** + * get_attention_status - get attention LED status + * @hotplug_slot: slot to get status from + * @value: returns with value of attention LED + * + * ACPI doesn't have known method to determine the state + * of the attention status LED, so we use a callback that + * was registered with us. This allows hardware specific + * ACPI implementations to determine its state. + */ +static int get_attention_status(struct hotplug_slot *hotplug_slot, u8 *value) +{ + int retval = -EINVAL; + + pr_debug("%s - physical_slot = %s\n", __func__, + hotplug_slot_name(hotplug_slot)); + + if (attention_info && try_module_get(attention_info->owner)) { + retval = attention_info->get_attn(hotplug_slot, value); + module_put(attention_info->owner); + } else + attention_info = NULL; + return retval; +} + + +/** + * get_latch_status - get latch status of a slot + * @hotplug_slot: slot to get status + * @value: pointer to store status + * + * ACPI doesn't provide any formal means to access latch status. + * Instead, we fake latch status from _STA. + */ +static int get_latch_status(struct hotplug_slot *hotplug_slot, u8 *value) +{ + struct slot *slot = to_slot(hotplug_slot); + + pr_debug("%s - physical_slot = %s\n", __func__, slot_name(slot)); + + *value = acpiphp_get_latch_status(slot->acpi_slot); + + return 0; +} + + +/** + * get_adapter_status - get adapter status of a slot + * @hotplug_slot: slot to get status + * @value: pointer to store status + * + * ACPI doesn't provide any formal means to access adapter status. + * Instead, we fake adapter status from _STA. + */ +static int get_adapter_status(struct hotplug_slot *hotplug_slot, u8 *value) +{ + struct slot *slot = to_slot(hotplug_slot); + + pr_debug("%s - physical_slot = %s\n", __func__, slot_name(slot)); + + *value = acpiphp_get_adapter_status(slot->acpi_slot); + + return 0; +} + +/* callback routine to initialize 'struct slot' for each slot */ +int acpiphp_register_hotplug_slot(struct acpiphp_slot *acpiphp_slot, + unsigned int sun) +{ + struct slot *slot; + int retval = -ENOMEM; + char name[SLOT_NAME_SIZE]; + + slot = kzalloc(sizeof(*slot), GFP_KERNEL); + if (!slot) + goto error; + + slot->hotplug_slot.ops = &acpi_hotplug_slot_ops; + + slot->acpi_slot = acpiphp_slot; + + acpiphp_slot->slot = slot; + slot->sun = sun; + snprintf(name, SLOT_NAME_SIZE, "%u", sun); + + retval = pci_hp_register(&slot->hotplug_slot, acpiphp_slot->bus, + acpiphp_slot->device, name); + if (retval == -EBUSY) + goto error_slot; + if (retval) { + pr_err("pci_hp_register failed with error %d\n", retval); + goto error_slot; + } + + pr_info("Slot [%s] registered\n", slot_name(slot)); + + return 0; +error_slot: + kfree(slot); +error: + return retval; +} + + +void acpiphp_unregister_hotplug_slot(struct acpiphp_slot *acpiphp_slot) +{ + struct slot *slot = acpiphp_slot->slot; + + pr_info("Slot [%s] unregistered\n", slot_name(slot)); + + pci_hp_deregister(&slot->hotplug_slot); + kfree(slot); +} + + +void __init acpiphp_init(void) +{ + pr_info(DRIVER_DESC " version: " DRIVER_VERSION "%s\n", + acpiphp_disabled ? ", disabled by user; please report a bug" + : ""); +} diff --git a/drivers/pci/hotplug/acpiphp_glue.c b/drivers/pci/hotplug/acpiphp_glue.c new file mode 100644 index 000000000..6efa3d8db --- /dev/null +++ b/drivers/pci/hotplug/acpiphp_glue.c @@ -0,0 +1,1060 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * ACPI PCI HotPlug glue functions to ACPI CA subsystem + * + * Copyright (C) 2002,2003 Takayoshi Kochi (t-kochi@bq.jp.nec.com) + * Copyright (C) 2002 Hiroshi Aono (h-aono@ap.jp.nec.com) + * Copyright (C) 2002,2003 NEC Corporation + * Copyright (C) 2003-2005 Matthew Wilcox (willy@infradead.org) + * Copyright (C) 2003-2005 Hewlett Packard + * Copyright (C) 2005 Rajesh Shah (rajesh.shah@intel.com) + * Copyright (C) 2005 Intel Corporation + * + * All rights reserved. + * + * Send feedback to <kristen.c.accardi@intel.com> + * + */ + +/* + * Lifetime rules for pci_dev: + * - The one in acpiphp_bridge has its refcount elevated by pci_get_slot() + * when the bridge is scanned and it loses a refcount when the bridge + * is removed. + * - When a P2P bridge is present, we elevate the refcount on the subordinate + * bus. It loses the refcount when the driver unloads. + */ + +#define pr_fmt(fmt) "acpiphp_glue: " fmt + +#include <linux/module.h> + +#include <linux/kernel.h> +#include <linux/pci.h> +#include <linux/pci_hotplug.h> +#include <linux/pci-acpi.h> +#include <linux/pm_runtime.h> +#include <linux/mutex.h> +#include <linux/slab.h> +#include <linux/acpi.h> + +#include "../pci.h" +#include "acpiphp.h" + +static LIST_HEAD(bridge_list); +static DEFINE_MUTEX(bridge_mutex); + +static int acpiphp_hotplug_notify(struct acpi_device *adev, u32 type); +static void acpiphp_post_dock_fixup(struct acpi_device *adev); +static void acpiphp_sanitize_bus(struct pci_bus *bus); +static void hotplug_event(u32 type, struct acpiphp_context *context); +static void free_bridge(struct kref *kref); + +/** + * acpiphp_init_context - Create hotplug context and grab a reference to it. + * @adev: ACPI device object to create the context for. + * + * Call under acpi_hp_context_lock. + */ +static struct acpiphp_context *acpiphp_init_context(struct acpi_device *adev) +{ + struct acpiphp_context *context; + + context = kzalloc(sizeof(*context), GFP_KERNEL); + if (!context) + return NULL; + + context->refcount = 1; + context->hp.notify = acpiphp_hotplug_notify; + context->hp.fixup = acpiphp_post_dock_fixup; + acpi_set_hp_context(adev, &context->hp); + return context; +} + +/** + * acpiphp_get_context - Get hotplug context and grab a reference to it. + * @adev: ACPI device object to get the context for. + * + * Call under acpi_hp_context_lock. + */ +static struct acpiphp_context *acpiphp_get_context(struct acpi_device *adev) +{ + struct acpiphp_context *context; + + if (!adev->hp) + return NULL; + + context = to_acpiphp_context(adev->hp); + context->refcount++; + return context; +} + +/** + * acpiphp_put_context - Drop a reference to ACPI hotplug context. + * @context: ACPI hotplug context to drop a reference to. + * + * The context object is removed if there are no more references to it. + * + * Call under acpi_hp_context_lock. + */ +static void acpiphp_put_context(struct acpiphp_context *context) +{ + if (--context->refcount) + return; + + WARN_ON(context->bridge); + context->hp.self->hp = NULL; + kfree(context); +} + +static inline void get_bridge(struct acpiphp_bridge *bridge) +{ + kref_get(&bridge->ref); +} + +static inline void put_bridge(struct acpiphp_bridge *bridge) +{ + kref_put(&bridge->ref, free_bridge); +} + +static struct acpiphp_context *acpiphp_grab_context(struct acpi_device *adev) +{ + struct acpiphp_context *context; + + acpi_lock_hp_context(); + + context = acpiphp_get_context(adev); + if (!context) + goto unlock; + + if (context->func.parent->is_going_away) { + acpiphp_put_context(context); + context = NULL; + goto unlock; + } + + get_bridge(context->func.parent); + acpiphp_put_context(context); + +unlock: + acpi_unlock_hp_context(); + return context; +} + +static void acpiphp_let_context_go(struct acpiphp_context *context) +{ + put_bridge(context->func.parent); +} + +static void free_bridge(struct kref *kref) +{ + struct acpiphp_context *context; + struct acpiphp_bridge *bridge; + struct acpiphp_slot *slot, *next; + struct acpiphp_func *func, *tmp; + + acpi_lock_hp_context(); + + bridge = container_of(kref, struct acpiphp_bridge, ref); + + list_for_each_entry_safe(slot, next, &bridge->slots, node) { + list_for_each_entry_safe(func, tmp, &slot->funcs, sibling) + acpiphp_put_context(func_to_context(func)); + + kfree(slot); + } + + context = bridge->context; + /* Root bridges will not have hotplug context. */ + if (context) { + /* Release the reference taken by acpiphp_enumerate_slots(). */ + put_bridge(context->func.parent); + context->bridge = NULL; + acpiphp_put_context(context); + } + + put_device(&bridge->pci_bus->dev); + pci_dev_put(bridge->pci_dev); + kfree(bridge); + + acpi_unlock_hp_context(); +} + +/** + * acpiphp_post_dock_fixup - Post-dock fixups for PCI devices. + * @adev: ACPI device object corresponding to a PCI device. + * + * TBD - figure out a way to only call fixups for systems that require them. + */ +static void acpiphp_post_dock_fixup(struct acpi_device *adev) +{ + struct acpiphp_context *context = acpiphp_grab_context(adev); + struct pci_bus *bus; + u32 buses; + + if (!context) + return; + + bus = context->func.slot->bus; + if (!bus->self) + goto out; + + /* fixup bad _DCK function that rewrites + * secondary bridge on slot + */ + pci_read_config_dword(bus->self, PCI_PRIMARY_BUS, &buses); + + if (((buses >> 8) & 0xff) != bus->busn_res.start) { + buses = (buses & 0xff000000) + | ((unsigned int)(bus->primary) << 0) + | ((unsigned int)(bus->busn_res.start) << 8) + | ((unsigned int)(bus->busn_res.end) << 16); + pci_write_config_dword(bus->self, PCI_PRIMARY_BUS, buses); + } + + out: + acpiphp_let_context_go(context); +} + +/** + * acpiphp_add_context - Add ACPIPHP context to an ACPI device object. + * @handle: ACPI handle of the object to add a context to. + * @lvl: Not used. + * @data: The object's parent ACPIPHP bridge. + * @rv: Not used. + */ +static acpi_status acpiphp_add_context(acpi_handle handle, u32 lvl, void *data, + void **rv) +{ + struct acpi_device *adev = acpi_fetch_acpi_dev(handle); + struct acpiphp_bridge *bridge = data; + struct acpiphp_context *context; + struct acpiphp_slot *slot; + struct acpiphp_func *newfunc; + acpi_status status = AE_OK; + unsigned long long adr; + int device, function; + struct pci_bus *pbus = bridge->pci_bus; + struct pci_dev *pdev = bridge->pci_dev; + u32 val; + + if (!adev) + return AE_OK; + + status = acpi_evaluate_integer(handle, "_ADR", NULL, &adr); + if (ACPI_FAILURE(status)) { + if (status != AE_NOT_FOUND) + acpi_handle_warn(handle, + "can't evaluate _ADR (%#x)\n", status); + return AE_OK; + } + + device = (adr >> 16) & 0xffff; + function = adr & 0xffff; + + acpi_lock_hp_context(); + context = acpiphp_init_context(adev); + if (!context) { + acpi_unlock_hp_context(); + acpi_handle_err(handle, "No hotplug context\n"); + return AE_NOT_EXIST; + } + newfunc = &context->func; + newfunc->function = function; + newfunc->parent = bridge; + acpi_unlock_hp_context(); + + /* + * If this is a dock device, its _EJ0 should be executed by the dock + * notify handler after calling _DCK. + */ + if (!is_dock_device(adev) && acpi_has_method(handle, "_EJ0")) + newfunc->flags = FUNC_HAS_EJ0; + + if (acpi_has_method(handle, "_STA")) + newfunc->flags |= FUNC_HAS_STA; + + /* search for objects that share the same slot */ + list_for_each_entry(slot, &bridge->slots, node) + if (slot->device == device) + goto slot_found; + + slot = kzalloc(sizeof(struct acpiphp_slot), GFP_KERNEL); + if (!slot) { + acpi_lock_hp_context(); + acpiphp_put_context(context); + acpi_unlock_hp_context(); + return AE_NO_MEMORY; + } + + slot->bus = bridge->pci_bus; + slot->device = device; + INIT_LIST_HEAD(&slot->funcs); + + list_add_tail(&slot->node, &bridge->slots); + + /* + * Expose slots to user space for functions that have _EJ0 or _RMV or + * are located in dock stations. Do not expose them for devices handled + * by the native PCIe hotplug (PCIeHP) or standard PCI hotplug + * (SHPCHP), because that code is supposed to expose slots to user + * space in those cases. + */ + if ((acpi_pci_check_ejectable(pbus, handle) || is_dock_device(adev)) + && !(pdev && hotplug_is_native(pdev))) { + unsigned long long sun; + int retval; + + bridge->nr_slots++; + status = acpi_evaluate_integer(handle, "_SUN", NULL, &sun); + if (ACPI_FAILURE(status)) + sun = bridge->nr_slots; + + pr_debug("found ACPI PCI Hotplug slot %llu at PCI %04x:%02x:%02x\n", + sun, pci_domain_nr(pbus), pbus->number, device); + + retval = acpiphp_register_hotplug_slot(slot, sun); + if (retval) { + slot->slot = NULL; + bridge->nr_slots--; + if (retval == -EBUSY) + pr_warn("Slot %llu already registered by another hotplug driver\n", sun); + else + pr_warn("acpiphp_register_hotplug_slot failed (err code = 0x%x)\n", retval); + } + /* Even if the slot registration fails, we can still use it. */ + } + + slot_found: + newfunc->slot = slot; + list_add_tail(&newfunc->sibling, &slot->funcs); + + if (pci_bus_read_dev_vendor_id(pbus, PCI_DEVFN(device, function), + &val, 60*1000)) + slot->flags |= SLOT_ENABLED; + + return AE_OK; +} + +static void cleanup_bridge(struct acpiphp_bridge *bridge) +{ + struct acpiphp_slot *slot; + struct acpiphp_func *func; + + list_for_each_entry(slot, &bridge->slots, node) { + list_for_each_entry(func, &slot->funcs, sibling) { + struct acpi_device *adev = func_to_acpi_device(func); + + acpi_lock_hp_context(); + adev->hp->notify = NULL; + adev->hp->fixup = NULL; + acpi_unlock_hp_context(); + } + slot->flags |= SLOT_IS_GOING_AWAY; + if (slot->slot) + acpiphp_unregister_hotplug_slot(slot); + } + + mutex_lock(&bridge_mutex); + list_del(&bridge->list); + mutex_unlock(&bridge_mutex); + + acpi_lock_hp_context(); + bridge->is_going_away = true; + acpi_unlock_hp_context(); +} + +/** + * acpiphp_max_busnr - return the highest reserved bus number under the given bus. + * @bus: bus to start search with + */ +static unsigned char acpiphp_max_busnr(struct pci_bus *bus) +{ + struct pci_bus *tmp; + unsigned char max, n; + + /* + * pci_bus_max_busnr will return the highest + * reserved busnr for all these children. + * that is equivalent to the bus->subordinate + * value. We don't want to use the parent's + * bus->subordinate value because it could have + * padding in it. + */ + max = bus->busn_res.start; + + list_for_each_entry(tmp, &bus->children, node) { + n = pci_bus_max_busnr(tmp); + if (n > max) + max = n; + } + return max; +} + +static void acpiphp_set_acpi_region(struct acpiphp_slot *slot) +{ + struct acpiphp_func *func; + + list_for_each_entry(func, &slot->funcs, sibling) { + /* _REG is optional, we don't care about if there is failure */ + acpi_evaluate_reg(func_to_handle(func), + ACPI_ADR_SPACE_PCI_CONFIG, + ACPI_REG_CONNECT); + } +} + +static void check_hotplug_bridge(struct acpiphp_slot *slot, struct pci_dev *dev) +{ + struct acpiphp_func *func; + + /* quirk, or pcie could set it already */ + if (dev->is_hotplug_bridge) + return; + + list_for_each_entry(func, &slot->funcs, sibling) { + if (PCI_FUNC(dev->devfn) == func->function) { + dev->is_hotplug_bridge = 1; + break; + } + } +} + +static int acpiphp_rescan_slot(struct acpiphp_slot *slot) +{ + struct acpiphp_func *func; + + list_for_each_entry(func, &slot->funcs, sibling) { + struct acpi_device *adev = func_to_acpi_device(func); + + acpi_bus_scan(adev->handle); + if (acpi_device_enumerated(adev)) + acpi_device_set_power(adev, ACPI_STATE_D0); + } + return pci_scan_slot(slot->bus, PCI_DEVFN(slot->device, 0)); +} + +static void acpiphp_native_scan_bridge(struct pci_dev *bridge) +{ + struct pci_bus *bus = bridge->subordinate; + struct pci_dev *dev; + int max; + + if (!bus) + return; + + max = bus->busn_res.start; + /* Scan already configured non-hotplug bridges */ + for_each_pci_bridge(dev, bus) { + if (!hotplug_is_native(dev)) + max = pci_scan_bridge(bus, dev, max, 0); + } + + /* Scan non-hotplug bridges that need to be reconfigured */ + for_each_pci_bridge(dev, bus) { + if (hotplug_is_native(dev)) + continue; + + max = pci_scan_bridge(bus, dev, max, 1); + if (dev->subordinate) { + pcibios_resource_survey_bus(dev->subordinate); + pci_bus_size_bridges(dev->subordinate); + pci_bus_assign_resources(dev->subordinate); + } + } +} + +/** + * enable_slot - enable, configure a slot + * @slot: slot to be enabled + * @bridge: true if enable is for the whole bridge (not a single slot) + * + * This function should be called per *physical slot*, + * not per each slot object in ACPI namespace. + */ +static void enable_slot(struct acpiphp_slot *slot, bool bridge) +{ + struct pci_dev *dev; + struct pci_bus *bus = slot->bus; + struct acpiphp_func *func; + + if (bridge && bus->self && hotplug_is_native(bus->self)) { + /* + * If native hotplug is used, it will take care of hotplug + * slot management and resource allocation for hotplug + * bridges. However, ACPI hotplug may still be used for + * non-hotplug bridges to bring in additional devices such + * as a Thunderbolt host controller. + */ + for_each_pci_bridge(dev, bus) { + if (PCI_SLOT(dev->devfn) == slot->device) + acpiphp_native_scan_bridge(dev); + } + } else { + LIST_HEAD(add_list); + int max, pass; + + acpiphp_rescan_slot(slot); + max = acpiphp_max_busnr(bus); + for (pass = 0; pass < 2; pass++) { + for_each_pci_bridge(dev, bus) { + if (PCI_SLOT(dev->devfn) != slot->device) + continue; + + max = pci_scan_bridge(bus, dev, max, pass); + if (pass && dev->subordinate) { + check_hotplug_bridge(slot, dev); + pcibios_resource_survey_bus(dev->subordinate); + __pci_bus_size_bridges(dev->subordinate, + &add_list); + } + } + } + __pci_bus_assign_resources(bus, &add_list, NULL); + } + + acpiphp_sanitize_bus(bus); + pcie_bus_configure_settings(bus); + acpiphp_set_acpi_region(slot); + + list_for_each_entry(dev, &bus->devices, bus_list) { + /* Assume that newly added devices are powered on already. */ + if (!pci_dev_is_added(dev)) + dev->current_state = PCI_D0; + } + + pci_bus_add_devices(bus); + + slot->flags |= SLOT_ENABLED; + list_for_each_entry(func, &slot->funcs, sibling) { + dev = pci_get_slot(bus, PCI_DEVFN(slot->device, + func->function)); + if (!dev) { + /* Do not set SLOT_ENABLED flag if some funcs + are not added. */ + slot->flags &= ~SLOT_ENABLED; + continue; + } + pci_dev_put(dev); + } +} + +/** + * disable_slot - disable a slot + * @slot: ACPI PHP slot + */ +static void disable_slot(struct acpiphp_slot *slot) +{ + struct pci_bus *bus = slot->bus; + struct pci_dev *dev, *prev; + struct acpiphp_func *func; + + /* + * enable_slot() enumerates all functions in this device via + * pci_scan_slot(), whether they have associated ACPI hotplug + * methods (_EJ0, etc.) or not. Therefore, we remove all functions + * here. + */ + list_for_each_entry_safe_reverse(dev, prev, &bus->devices, bus_list) + if (PCI_SLOT(dev->devfn) == slot->device) + pci_stop_and_remove_bus_device(dev); + + list_for_each_entry(func, &slot->funcs, sibling) + acpi_bus_trim(func_to_acpi_device(func)); + + slot->flags &= ~SLOT_ENABLED; +} + +static bool slot_no_hotplug(struct acpiphp_slot *slot) +{ + struct pci_bus *bus = slot->bus; + struct pci_dev *dev; + + list_for_each_entry(dev, &bus->devices, bus_list) { + if (PCI_SLOT(dev->devfn) == slot->device && dev->ignore_hotplug) + return true; + } + return false; +} + +/** + * get_slot_status - get ACPI slot status + * @slot: ACPI PHP slot + * + * If a slot has _STA for each function and if any one of them + * returned non-zero status, return it. + * + * If a slot doesn't have _STA and if any one of its functions' + * configuration space is configured, return 0x0f as a _STA. + * + * Otherwise return 0. + */ +static unsigned int get_slot_status(struct acpiphp_slot *slot) +{ + unsigned long long sta = 0; + struct acpiphp_func *func; + u32 dvid; + + list_for_each_entry(func, &slot->funcs, sibling) { + if (func->flags & FUNC_HAS_STA) { + acpi_status status; + + status = acpi_evaluate_integer(func_to_handle(func), + "_STA", NULL, &sta); + if (ACPI_SUCCESS(status) && sta) + break; + } else { + if (pci_bus_read_dev_vendor_id(slot->bus, + PCI_DEVFN(slot->device, func->function), + &dvid, 0)) { + sta = ACPI_STA_ALL; + break; + } + } + } + + if (!sta) { + /* + * Check for the slot itself since it may be that the + * ACPI slot is a device below PCIe upstream port so in + * that case it may not even be reachable yet. + */ + if (pci_bus_read_dev_vendor_id(slot->bus, + PCI_DEVFN(slot->device, 0), &dvid, 0)) { + sta = ACPI_STA_ALL; + } + } + + return (unsigned int)sta; +} + +static inline bool device_status_valid(unsigned int sta) +{ + /* + * ACPI spec says that _STA may return bit 0 clear with bit 3 set + * if the device is valid but does not require a device driver to be + * loaded (Section 6.3.7 of ACPI 5.0A). + */ + unsigned int mask = ACPI_STA_DEVICE_ENABLED | ACPI_STA_DEVICE_FUNCTIONING; + return (sta & mask) == mask; +} + +/** + * trim_stale_devices - remove PCI devices that are not responding. + * @dev: PCI device to start walking the hierarchy from. + */ +static void trim_stale_devices(struct pci_dev *dev) +{ + struct acpi_device *adev = ACPI_COMPANION(&dev->dev); + struct pci_bus *bus = dev->subordinate; + bool alive = dev->ignore_hotplug; + + if (adev) { + acpi_status status; + unsigned long long sta; + + status = acpi_evaluate_integer(adev->handle, "_STA", NULL, &sta); + alive = alive || (ACPI_SUCCESS(status) && device_status_valid(sta)); + } + if (!alive) + alive = pci_device_is_present(dev); + + if (!alive) { + pci_dev_set_disconnected(dev, NULL); + if (pci_has_subordinate(dev)) + pci_walk_bus(dev->subordinate, pci_dev_set_disconnected, + NULL); + + pci_stop_and_remove_bus_device(dev); + if (adev) + acpi_bus_trim(adev); + } else if (bus) { + struct pci_dev *child, *tmp; + + /* The device is a bridge. so check the bus below it. */ + pm_runtime_get_sync(&dev->dev); + list_for_each_entry_safe_reverse(child, tmp, &bus->devices, bus_list) + trim_stale_devices(child); + + pm_runtime_put(&dev->dev); + } +} + +/** + * acpiphp_check_bridge - re-enumerate devices + * @bridge: where to begin re-enumeration + * + * Iterate over all slots under this bridge and make sure that if a + * card is present they are enabled, and if not they are disabled. + */ +static void acpiphp_check_bridge(struct acpiphp_bridge *bridge) +{ + struct acpiphp_slot *slot; + + /* Bail out if the bridge is going away. */ + if (bridge->is_going_away) + return; + + if (bridge->pci_dev) + pm_runtime_get_sync(&bridge->pci_dev->dev); + + list_for_each_entry(slot, &bridge->slots, node) { + struct pci_bus *bus = slot->bus; + struct pci_dev *dev, *tmp; + + if (slot_no_hotplug(slot)) { + ; /* do nothing */ + } else if (device_status_valid(get_slot_status(slot))) { + /* remove stale devices if any */ + list_for_each_entry_safe_reverse(dev, tmp, + &bus->devices, bus_list) + if (PCI_SLOT(dev->devfn) == slot->device) + trim_stale_devices(dev); + + /* configure all functions */ + enable_slot(slot, true); + } else { + disable_slot(slot); + } + } + + if (bridge->pci_dev) + pm_runtime_put(&bridge->pci_dev->dev); +} + +/* + * Remove devices for which we could not assign resources, call + * arch specific code to fix-up the bus + */ +static void acpiphp_sanitize_bus(struct pci_bus *bus) +{ + struct pci_dev *dev, *tmp; + int i; + unsigned long type_mask = IORESOURCE_IO | IORESOURCE_MEM; + + list_for_each_entry_safe_reverse(dev, tmp, &bus->devices, bus_list) { + for (i = 0; i < PCI_BRIDGE_RESOURCES; i++) { + struct resource *res = &dev->resource[i]; + if ((res->flags & type_mask) && !res->start && + res->end) { + /* Could not assign a required resources + * for this device, remove it */ + pci_stop_and_remove_bus_device(dev); + break; + } + } + } +} + +/* + * ACPI event handlers + */ + +void acpiphp_check_host_bridge(struct acpi_device *adev) +{ + struct acpiphp_bridge *bridge = NULL; + + acpi_lock_hp_context(); + if (adev->hp) { + bridge = to_acpiphp_root_context(adev->hp)->root_bridge; + if (bridge) + get_bridge(bridge); + } + acpi_unlock_hp_context(); + if (bridge) { + pci_lock_rescan_remove(); + + acpiphp_check_bridge(bridge); + + pci_unlock_rescan_remove(); + put_bridge(bridge); + } +} + +static int acpiphp_disable_and_eject_slot(struct acpiphp_slot *slot); + +static void hotplug_event(u32 type, struct acpiphp_context *context) +{ + acpi_handle handle = context->hp.self->handle; + struct acpiphp_func *func = &context->func; + struct acpiphp_slot *slot = func->slot; + struct acpiphp_bridge *bridge; + + acpi_lock_hp_context(); + bridge = context->bridge; + if (bridge) + get_bridge(bridge); + + acpi_unlock_hp_context(); + + pci_lock_rescan_remove(); + + switch (type) { + case ACPI_NOTIFY_BUS_CHECK: + /* bus re-enumerate */ + acpi_handle_debug(handle, "Bus check in %s()\n", __func__); + if (bridge) + acpiphp_check_bridge(bridge); + else if (!(slot->flags & SLOT_IS_GOING_AWAY)) + enable_slot(slot, false); + + break; + + case ACPI_NOTIFY_DEVICE_CHECK: + /* device check */ + acpi_handle_debug(handle, "Device check in %s()\n", __func__); + if (bridge) { + acpiphp_check_bridge(bridge); + } else if (!(slot->flags & SLOT_IS_GOING_AWAY)) { + /* + * Check if anything has changed in the slot and rescan + * from the parent if that's the case. + */ + if (acpiphp_rescan_slot(slot)) + acpiphp_check_bridge(func->parent); + } + break; + + case ACPI_NOTIFY_EJECT_REQUEST: + /* request device eject */ + acpi_handle_debug(handle, "Eject request in %s()\n", __func__); + acpiphp_disable_and_eject_slot(slot); + break; + } + + pci_unlock_rescan_remove(); + if (bridge) + put_bridge(bridge); +} + +static int acpiphp_hotplug_notify(struct acpi_device *adev, u32 type) +{ + struct acpiphp_context *context; + + context = acpiphp_grab_context(adev); + if (!context) + return -ENODATA; + + hotplug_event(type, context); + acpiphp_let_context_go(context); + return 0; +} + +/** + * acpiphp_enumerate_slots - Enumerate PCI slots for a given bus. + * @bus: PCI bus to enumerate the slots for. + * + * A "slot" is an object associated with a PCI device number. All functions + * (PCI devices) with the same bus and device number belong to the same slot. + */ +void acpiphp_enumerate_slots(struct pci_bus *bus) +{ + struct acpiphp_bridge *bridge; + struct acpi_device *adev; + acpi_handle handle; + acpi_status status; + + if (acpiphp_disabled) + return; + + adev = ACPI_COMPANION(bus->bridge); + if (!adev) + return; + + handle = adev->handle; + bridge = kzalloc(sizeof(struct acpiphp_bridge), GFP_KERNEL); + if (!bridge) + return; + + INIT_LIST_HEAD(&bridge->slots); + kref_init(&bridge->ref); + bridge->pci_dev = pci_dev_get(bus->self); + bridge->pci_bus = bus; + + /* + * Grab a ref to the subordinate PCI bus in case the bus is + * removed via PCI core logical hotplug. The ref pins the bus + * (which we access during module unload). + */ + get_device(&bus->dev); + + acpi_lock_hp_context(); + if (pci_is_root_bus(bridge->pci_bus)) { + struct acpiphp_root_context *root_context; + + root_context = kzalloc(sizeof(*root_context), GFP_KERNEL); + if (!root_context) + goto err; + + root_context->root_bridge = bridge; + acpi_set_hp_context(adev, &root_context->hp); + } else { + struct acpiphp_context *context; + + /* + * This bridge should have been registered as a hotplug function + * under its parent, so the context should be there, unless the + * parent is going to be handled by pciehp, in which case this + * bridge is not interesting to us either. + */ + context = acpiphp_get_context(adev); + if (!context) + goto err; + + bridge->context = context; + context->bridge = bridge; + /* Get a reference to the parent bridge. */ + get_bridge(context->func.parent); + } + acpi_unlock_hp_context(); + + /* Must be added to the list prior to calling acpiphp_add_context(). */ + mutex_lock(&bridge_mutex); + list_add(&bridge->list, &bridge_list); + mutex_unlock(&bridge_mutex); + + /* register all slot objects under this bridge */ + status = acpi_walk_namespace(ACPI_TYPE_DEVICE, handle, 1, + acpiphp_add_context, NULL, bridge, NULL); + if (ACPI_FAILURE(status)) { + acpi_handle_err(handle, "failed to register slots\n"); + cleanup_bridge(bridge); + put_bridge(bridge); + } + return; + + err: + acpi_unlock_hp_context(); + put_device(&bus->dev); + pci_dev_put(bridge->pci_dev); + kfree(bridge); +} + +static void acpiphp_drop_bridge(struct acpiphp_bridge *bridge) +{ + if (pci_is_root_bus(bridge->pci_bus)) { + struct acpiphp_root_context *root_context; + struct acpi_device *adev; + + acpi_lock_hp_context(); + adev = ACPI_COMPANION(bridge->pci_bus->bridge); + root_context = to_acpiphp_root_context(adev->hp); + adev->hp = NULL; + acpi_unlock_hp_context(); + kfree(root_context); + } + cleanup_bridge(bridge); + put_bridge(bridge); +} + +/** + * acpiphp_remove_slots - Remove slot objects associated with a given bus. + * @bus: PCI bus to remove the slot objects for. + */ +void acpiphp_remove_slots(struct pci_bus *bus) +{ + struct acpiphp_bridge *bridge; + + if (acpiphp_disabled) + return; + + mutex_lock(&bridge_mutex); + list_for_each_entry(bridge, &bridge_list, list) + if (bridge->pci_bus == bus) { + mutex_unlock(&bridge_mutex); + acpiphp_drop_bridge(bridge); + return; + } + + mutex_unlock(&bridge_mutex); +} + +/** + * acpiphp_enable_slot - power on slot + * @slot: ACPI PHP slot + */ +int acpiphp_enable_slot(struct acpiphp_slot *slot) +{ + pci_lock_rescan_remove(); + + if (slot->flags & SLOT_IS_GOING_AWAY) { + pci_unlock_rescan_remove(); + return -ENODEV; + } + + /* configure all functions */ + if (!(slot->flags & SLOT_ENABLED)) + enable_slot(slot, false); + + pci_unlock_rescan_remove(); + return 0; +} + +/** + * acpiphp_disable_and_eject_slot - power off and eject slot + * @slot: ACPI PHP slot + */ +static int acpiphp_disable_and_eject_slot(struct acpiphp_slot *slot) +{ + struct acpiphp_func *func; + + if (slot->flags & SLOT_IS_GOING_AWAY) + return -ENODEV; + + /* unconfigure all functions */ + disable_slot(slot); + + list_for_each_entry(func, &slot->funcs, sibling) + if (func->flags & FUNC_HAS_EJ0) { + acpi_handle handle = func_to_handle(func); + + if (ACPI_FAILURE(acpi_evaluate_ej0(handle))) + acpi_handle_err(handle, "_EJ0 failed\n"); + + break; + } + + return 0; +} + +int acpiphp_disable_slot(struct acpiphp_slot *slot) +{ + int ret; + + /* + * Acquire acpi_scan_lock to ensure that the execution of _EJ0 in + * acpiphp_disable_and_eject_slot() will be synchronized properly. + */ + acpi_scan_lock_acquire(); + pci_lock_rescan_remove(); + ret = acpiphp_disable_and_eject_slot(slot); + pci_unlock_rescan_remove(); + acpi_scan_lock_release(); + return ret; +} + +/* + * slot enabled: 1 + * slot disabled: 0 + */ +u8 acpiphp_get_power_status(struct acpiphp_slot *slot) +{ + return (slot->flags & SLOT_ENABLED); +} + +/* + * latch open: 1 + * latch closed: 0 + */ +u8 acpiphp_get_latch_status(struct acpiphp_slot *slot) +{ + return !(get_slot_status(slot) & ACPI_STA_DEVICE_UI); +} + +/* + * adapter presence : 1 + * absence : 0 + */ +u8 acpiphp_get_adapter_status(struct acpiphp_slot *slot) +{ + return !!get_slot_status(slot); +} diff --git a/drivers/pci/hotplug/acpiphp_ibm.c b/drivers/pci/hotplug/acpiphp_ibm.c new file mode 100644 index 000000000..8f3a0a33f --- /dev/null +++ b/drivers/pci/hotplug/acpiphp_ibm.c @@ -0,0 +1,490 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * ACPI PCI Hot Plug IBM Extension + * + * Copyright (C) 2004 Vernon Mauery <vernux@us.ibm.com> + * Copyright (C) 2004 IBM Corp. + * + * All rights reserved. + * + * Send feedback to <vernux@us.ibm.com> + * + */ + +#define pr_fmt(fmt) "acpiphp_ibm: " fmt + +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/sysfs.h> +#include <linux/kobject.h> +#include <linux/moduleparam.h> +#include <linux/pci.h> +#include <linux/uaccess.h> + +#include "acpiphp.h" +#include "../pci.h" + +#define DRIVER_VERSION "1.0.1" +#define DRIVER_AUTHOR "Irene Zubarev <zubarev@us.ibm.com>, Vernon Mauery <vernux@us.ibm.com>" +#define DRIVER_DESC "ACPI Hot Plug PCI Controller Driver IBM extension" + + +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); +MODULE_VERSION(DRIVER_VERSION); + +#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) (to_slot(A)->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 file *filp, struct kobject *kobj, + struct bin_attribute *bin_attr, + char *buffer, loff_t pos, size_t size); +static acpi_status __init ibm_find_acpi_device(acpi_handle handle, + u32 lvl, void *context, void **rv); +static int __init ibm_acpiphp_init(void); +static void __exit ibm_acpiphp_exit(void); + +static acpi_handle ibm_acpi_handle; +static struct notification ibm_note; +static struct bin_attribute ibm_apci_table_attr __ro_after_init = { + .attr = { + .name = "apci_table", + .mode = S_IRUGO, + }, + .read = ibm_read_apci_table, + .write = NULL, +}; +static struct acpiphp_attention_info ibm_attention_info = +{ + .set_attn = ibm_set_attention_status, + .get_attn = ibm_get_attention_status, + .owner = THIS_MODULE, +}; + +/** + * ibm_slot_from_id - workaround for bad ibm hardware + * @id: the slot number that linux refers to the slot by + * + * Description: This method returns the aCPI slot descriptor + * corresponding to the Linux slot number. This descriptor + * has info about the aPCI slot id and attention status. + * This descriptor must be freed using kfree when done. + */ +static union apci_descriptor *ibm_slot_from_id(int id) +{ + int ind = 0, size; + union apci_descriptor *ret = NULL, *des; + char *table; + + size = ibm_get_table_from_acpi(&table); + if (size < 0) + return NULL; + 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); + if (ret) + memcpy(ret, des, sizeof(union apci_descriptor)); + } + kfree(table); + return ret; +} + +/** + * ibm_set_attention_status - callback method to set the attention LED + * @slot: the hotplug_slot to work with + * @status: what to set the LED to (0 or 1) + * + * Description: This method is registered with the acpiphp module as a + * callback to do the device specific task of setting the LED status. + */ +static int ibm_set_attention_status(struct hotplug_slot *slot, u8 status) +{ + union acpi_object args[2]; + struct acpi_object_list params = { .pointer = args, .count = 2 }; + acpi_status stat; + unsigned long long rc; + union apci_descriptor *ibm_slot; + int id = hpslot_to_sun(slot); + + ibm_slot = ibm_slot_from_id(id); + if (!ibm_slot) { + pr_err("APLS null ACPI descriptor for slot %d\n", id); + return -ENODEV; + } + + pr_debug("%s: set slot %d (%d) attention status to %d\n", __func__, + ibm_slot->slot.slot_num, ibm_slot->slot.slot_id, + (status ? 1 : 0)); + + args[0].type = ACPI_TYPE_INTEGER; + args[0].integer.value = ibm_slot->slot.slot_id; + args[1].type = ACPI_TYPE_INTEGER; + args[1].integer.value = (status) ? 1 : 0; + + kfree(ibm_slot); + + stat = acpi_evaluate_integer(ibm_acpi_handle, "APLS", ¶ms, &rc); + if (ACPI_FAILURE(stat)) { + pr_err("APLS evaluation failed: 0x%08x\n", stat); + return -ENODEV; + } else if (!rc) { + pr_err("APLS method failed: 0x%08llx\n", rc); + return -ERANGE; + } + return 0; +} + +/** + * ibm_get_attention_status - callback method to get attention LED status + * @slot: the hotplug_slot to work with + * @status: returns what the LED is set to (0 or 1) + * + * Description: This method is registered with the acpiphp module as a + * callback to do the device specific task of getting the LED status. + * + * Because there is no direct method of getting the LED status directly + * from an ACPI call, we read the aPCI table and parse out our + * slot descriptor to read the status from that. + */ +static int ibm_get_attention_status(struct hotplug_slot *slot, u8 *status) +{ + union apci_descriptor *ibm_slot; + int id = hpslot_to_sun(slot); + + ibm_slot = ibm_slot_from_id(id); + if (!ibm_slot) { + pr_err("APLS null ACPI descriptor for slot %d\n", id); + return -ENODEV; + } + + if (ibm_slot->slot.attn & 0xa0 || ibm_slot->slot.status[1] & 0x08) + *status = 1; + else + *status = 0; + + pr_debug("%s: get slot %d (%d) attention status is %d\n", __func__, + ibm_slot->slot.slot_num, ibm_slot->slot.slot_id, + *status); + + kfree(ibm_slot); + return 0; +} + +/** + * ibm_handle_events - listens for ACPI events for the IBM37D0 device + * @handle: an ACPI handle to the device that caused the event + * @event: the event info (device specific) + * @context: passed context (our notification struct) + * + * Description: This method is registered as a callback with the ACPI + * subsystem it is called when this device has an event to notify the OS of. + * + * The events actually come from the device as two events that get + * synthesized into one event with data by this function. The event + * ID comes first and then the slot number that caused it. We report + * this as one event to the OS. + * + * From section 5.6.2.2 of the ACPI 2.0 spec, I understand that the OSPM will + * only re-enable the interrupt that causes this event AFTER this method + * has returned, thereby enforcing serial access for the notification struct. + */ +static void ibm_handle_events(acpi_handle handle, u32 event, void *context) +{ + u8 detail = event & 0x0f; + u8 subevent = event & 0xf0; + struct notification *note = context; + + pr_debug("%s: Received notification %02x\n", __func__, event); + + if (subevent == 0x80) { + pr_debug("%s: generating bus event\n", __func__); + acpi_bus_generate_netlink_event(note->device->pnp.device_class, + dev_name(¬e->device->dev), + 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)) { + pr_err("%s: APCI evaluation failed\n", __func__); + return -ENODEV; + } + + package = (union acpi_object *) buffer.pointer; + if (!(package) || + (package->type != ACPI_TYPE_PACKAGE) || + !(package->package.elements)) { + pr_err("%s: Invalid APCI object\n", __func__); + goto read_table_done; + } + + for (size = 0, i = 0; i < package->package.count; i++) { + if (package->package.elements[i].type != ACPI_TYPE_BUFFER) { + pr_err("%s: Invalid APCI element %d\n", __func__, i); + goto read_table_done; + } + size += package->package.elements[i].buffer.length; + } + + if (bufp == NULL) + goto read_table_done; + + lbuf = kzalloc(size, GFP_KERNEL); + pr_debug("%s: element count: %i, ASL table size: %i, &table = 0x%p\n", + __func__, package->package.count, size, lbuf); + + if (lbuf) { + *bufp = lbuf; + } else { + size = -ENOMEM; + goto read_table_done; + } + + size = 0; + for (i = 0; i < package->package.count; i++) { + memcpy(&lbuf[size], + package->package.elements[i].buffer.pointer, + package->package.elements[i].buffer.length); + size += package->package.elements[i].buffer.length; + } + +read_table_done: + kfree(buffer.pointer); + return size; +} + +/** + * ibm_read_apci_table - callback for the sysfs apci_table file + * @filp: the open sysfs file + * @kobj: the kobject this binary attribute is a part of + * @bin_attr: struct bin_attribute for this file + * @buffer: the kernel space buffer to fill + * @pos: the offset into the file + * @size: the number of bytes requested + * + * Description: Gets registered with sysfs as the reader callback + * to be executed when /sys/bus/pci/slots/apci_table gets read. + * + * Since we don't get notified on open and close for this file, + * things get really tricky here... + * our solution is to only allow reading the table in all at once. + */ +static ssize_t ibm_read_apci_table(struct file *filp, struct kobject *kobj, + struct bin_attribute *bin_attr, + char *buffer, loff_t pos, size_t size) +{ + int bytes_read = -EINVAL; + char *table = NULL; + + pr_debug("%s: pos = %d, size = %zd\n", __func__, (int)pos, size); + + if (pos == 0) { + bytes_read = ibm_get_table_from_acpi(&table); + if (bytes_read > 0 && bytes_read <= size) + memcpy(buffer, table, bytes_read); + kfree(table); + } + return bytes_read; +} + +/** + * ibm_find_acpi_device - callback to find our ACPI device + * @handle: the ACPI handle of the device we are inspecting + * @lvl: depth into the namespace tree + * @context: a pointer to our handle to fill when we find the device + * @rv: a return value to fill if desired + * + * Description: Used as a callback when calling acpi_walk_namespace + * to find our device. When this method returns non-zero + * acpi_walk_namespace quits its search and returns our value. + */ +static acpi_status __init ibm_find_acpi_device(acpi_handle handle, + u32 lvl, void *context, void **rv) +{ + acpi_handle *phandle = (acpi_handle *)context; + unsigned long long current_status = 0; + acpi_status status; + struct acpi_device_info *info; + int retval = 0; + + status = acpi_get_object_info(handle, &info); + if (ACPI_FAILURE(status)) { + pr_err("%s: Failed to get device information status=0x%x\n", + __func__, status); + return retval; + } + + acpi_bus_get_status_handle(handle, ¤t_status); + + if (current_status && (info->valid & ACPI_VALID_HID) && + (!strcmp(info->hardware_id.string, IBM_HARDWARE_ID1) || + !strcmp(info->hardware_id.string, IBM_HARDWARE_ID2))) { + pr_debug("found hardware: %s, handle: %p\n", + info->hardware_id.string, handle); + *phandle = handle; + /* returning non-zero causes the search to stop + * and returns this value to the caller of + * acpi_walk_namespace, but it also causes some warnings + * in the acpi debug code to print... + */ + retval = FOUND_APCI; + } + kfree(info); + return retval; +} + +static int __init ibm_acpiphp_init(void) +{ + int retval = 0; + acpi_status status; + struct acpi_device *device; + struct kobject *sysdir = &pci_slots_kset->kobj; + + pr_debug("%s\n", __func__); + + if (acpi_walk_namespace(ACPI_TYPE_DEVICE, ACPI_ROOT_OBJECT, + ACPI_UINT32_MAX, ibm_find_acpi_device, NULL, + &ibm_acpi_handle, NULL) != FOUND_APCI) { + pr_err("%s: acpi_walk_namespace failed\n", __func__); + retval = -ENODEV; + goto init_return; + } + pr_debug("%s: found IBM aPCI device\n", __func__); + device = acpi_fetch_acpi_dev(ibm_acpi_handle); + if (!device) { + pr_err("%s: acpi_fetch_acpi_dev failed\n", __func__); + retval = -ENODEV; + goto init_return; + } + if (acpiphp_register_attention(&ibm_attention_info)) { + retval = -ENODEV; + goto init_return; + } + + ibm_note.device = device; + status = acpi_install_notify_handler(ibm_acpi_handle, + ACPI_DEVICE_NOTIFY, ibm_handle_events, + &ibm_note); + if (ACPI_FAILURE(status)) { + pr_err("%s: Failed to register notification handler\n", + __func__); + retval = -EBUSY; + goto init_cleanup; + } + + ibm_apci_table_attr.size = ibm_get_table_from_acpi(NULL); + retval = sysfs_create_bin_file(sysdir, &ibm_apci_table_attr); + + return retval; + +init_cleanup: + acpiphp_unregister_attention(&ibm_attention_info); +init_return: + return retval; +} + +static void __exit ibm_acpiphp_exit(void) +{ + acpi_status status; + struct kobject *sysdir = &pci_slots_kset->kobj; + + pr_debug("%s\n", __func__); + + if (acpiphp_unregister_attention(&ibm_attention_info)) + pr_err("%s: attention info deregistration failed", __func__); + + status = acpi_remove_notify_handler( + ibm_acpi_handle, + ACPI_DEVICE_NOTIFY, + ibm_handle_events); + if (ACPI_FAILURE(status)) + pr_err("%s: Notification handler removal failed\n", __func__); + /* remove the /sys entries */ + sysfs_remove_bin_file(sysdir, &ibm_apci_table_attr); +} + +module_init(ibm_acpiphp_init); +module_exit(ibm_acpiphp_exit); diff --git a/drivers/pci/hotplug/cpci_hotplug.h b/drivers/pci/hotplug/cpci_hotplug.h new file mode 100644 index 000000000..3fdd1b9bd --- /dev/null +++ b/drivers/pci/hotplug/cpci_hotplug.h @@ -0,0 +1,104 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * 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. + * + * Send feedback to <scottm@somanetworks.com> + */ + +#ifndef _CPCI_HOTPLUG_H +#define _CPCI_HOTPLUG_H + +#include <linux/types.h> +#include <linux/pci.h> +#include <linux/pci_hotplug.h> + +/* PICMG 2.1 R2.0 HS CSR bits: */ +#define HS_CSR_INS 0x0080 +#define HS_CSR_EXT 0x0040 +#define HS_CSR_PI 0x0030 +#define HS_CSR_LOO 0x0008 +#define HS_CSR_PIE 0x0004 +#define HS_CSR_EIM 0x0002 +#define HS_CSR_DHA 0x0001 + +struct slot { + u8 number; + unsigned int devfn; + struct pci_bus *bus; + struct pci_dev *dev; + unsigned int latch_status:1; + unsigned int adapter_status:1; + unsigned int extracting; + struct hotplug_slot hotplug_slot; + struct list_head slot_list; +}; + +struct cpci_hp_controller_ops { + int (*query_enum)(void); + int (*enable_irq)(void); + int (*disable_irq)(void); + int (*check_irq)(void *dev_id); + int (*hardware_test)(struct slot *slot, u32 value); + u8 (*get_power)(struct slot *slot); + int (*set_power)(struct slot *slot, int value); +}; + +struct cpci_hp_controller { + unsigned int irq; + unsigned long irq_flags; + char *devname; + void *dev_id; + char *name; + struct cpci_hp_controller_ops *ops; +}; + +static inline const char *slot_name(struct slot *slot) +{ + return hotplug_slot_name(&slot->hotplug_slot); +} + +static inline struct slot *to_slot(struct hotplug_slot *hotplug_slot) +{ + return container_of(hotplug_slot, struct slot, hotplug_slot); +} + +int cpci_hp_register_controller(struct cpci_hp_controller *controller); +int cpci_hp_unregister_controller(struct cpci_hp_controller *controller); +int cpci_hp_register_bus(struct pci_bus *bus, u8 first, u8 last); +int cpci_hp_unregister_bus(struct pci_bus *bus); +int cpci_hp_start(void); +int cpci_hp_stop(void); + +/* Global variables */ +extern int cpci_debug; + +/* + * Internal function prototypes, these functions should not be used by + * board/chassis drivers. + */ +u8 cpci_get_attention_status(struct slot *slot); +u8 cpci_get_latch_status(struct slot *slot); +u8 cpci_get_adapter_status(struct slot *slot); +u16 cpci_get_hs_csr(struct slot *slot); +int cpci_set_attention_status(struct slot *slot, int status); +int cpci_check_and_clear_ins(struct slot *slot); +int cpci_check_ext(struct slot *slot); +int cpci_clear_ext(struct slot *slot); +int cpci_led_on(struct slot *slot); +int cpci_led_off(struct slot *slot); +int cpci_configure_slot(struct slot *slot); +int cpci_unconfigure_slot(struct slot *slot); + +#ifdef CONFIG_HOTPLUG_PCI_CPCI +int cpci_hotplug_init(int debug); +#else +static inline int cpci_hotplug_init(int debug) { return 0; } +#endif + +#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 000000000..d0559d2fa --- /dev/null +++ b/drivers/pci/hotplug/cpci_hotplug_core.c @@ -0,0 +1,644 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * CompactPCI Hot Plug Driver + * + * Copyright (C) 2002,2005 SOMA Networks, Inc. + * Copyright (C) 2001 Greg Kroah-Hartman (greg@kroah.com) + * Copyright (C) 2001 IBM Corp. + * + * All rights reserved. + * + * Send feedback to <scottm@somanetworks.com> + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/sched/signal.h> +#include <linux/slab.h> +#include <linux/pci.h> +#include <linux/pci_hotplug.h> +#include <linux/init.h> +#include <linux/interrupt.h> +#include <linux/atomic.h> +#include <linux/delay.h> +#include <linux/kthread.h> +#include "cpci_hotplug.h" + +#define DRIVER_AUTHOR "Scott Murray <scottm@somanetworks.com>" +#define DRIVER_DESC "CompactPCI Hot Plug Core" + +#define MY_NAME "cpci_hotplug" + +#define dbg(format, arg...) \ + do { \ + if (cpci_debug) \ + printk(KERN_DEBUG "%s: " format "\n", \ + MY_NAME, ## arg); \ + } while (0) +#define err(format, arg...) printk(KERN_ERR "%s: " format "\n", MY_NAME, ## arg) +#define info(format, arg...) printk(KERN_INFO "%s: " format "\n", MY_NAME, ## arg) +#define warn(format, arg...) printk(KERN_WARNING "%s: " format "\n", MY_NAME, ## arg) + +/* local variables */ +static DECLARE_RWSEM(list_rwsem); +static LIST_HEAD(slot_list); +static int slots; +static atomic_t extracting; +int cpci_debug; +static struct cpci_hp_controller *controller; +static struct task_struct *cpci_thread; +static int thread_finished; + +static int enable_slot(struct hotplug_slot *slot); +static int disable_slot(struct hotplug_slot *slot); +static int set_attention_status(struct hotplug_slot *slot, u8 value); +static int get_power_status(struct hotplug_slot *slot, u8 *value); +static int get_attention_status(struct hotplug_slot *slot, u8 *value); +static int get_adapter_status(struct hotplug_slot *slot, u8 *value); +static int get_latch_status(struct hotplug_slot *slot, u8 *value); + +static const struct hotplug_slot_ops cpci_hotplug_slot_ops = { + .enable_slot = enable_slot, + .disable_slot = disable_slot, + .set_attention_status = set_attention_status, + .get_power_status = get_power_status, + .get_attention_status = get_attention_status, + .get_adapter_status = get_adapter_status, + .get_latch_status = get_latch_status, +}; + +static int +enable_slot(struct hotplug_slot *hotplug_slot) +{ + struct slot *slot = to_slot(hotplug_slot); + int retval = 0; + + dbg("%s - physical_slot = %s", __func__, slot_name(slot)); + + if (controller->ops->set_power) + retval = controller->ops->set_power(slot, 1); + return retval; +} + +static int +disable_slot(struct hotplug_slot *hotplug_slot) +{ + struct slot *slot = to_slot(hotplug_slot); + int retval = 0; + + dbg("%s - physical_slot = %s", __func__, slot_name(slot)); + + down_write(&list_rwsem); + + /* Unconfigure device */ + dbg("%s - unconfiguring slot %s", __func__, slot_name(slot)); + retval = cpci_unconfigure_slot(slot); + if (retval) { + err("%s - could not unconfigure slot %s", + __func__, slot_name(slot)); + goto disable_error; + } + dbg("%s - finished unconfiguring slot %s", __func__, slot_name(slot)); + + /* Clear EXT (by setting it) */ + if (cpci_clear_ext(slot)) { + err("%s - could not clear EXT for slot %s", + __func__, slot_name(slot)); + retval = -ENODEV; + goto disable_error; + } + cpci_led_on(slot); + + if (controller->ops->set_power) { + retval = controller->ops->set_power(slot, 0); + if (retval) + goto disable_error; + } + + slot->adapter_status = 0; + + if (slot->extracting) { + slot->extracting = 0; + atomic_dec(&extracting); + } +disable_error: + up_write(&list_rwsem); + return retval; +} + +static u8 +cpci_get_power_status(struct slot *slot) +{ + u8 power = 1; + + if (controller->ops->get_power) + power = controller->ops->get_power(slot); + return power; +} + +static int +get_power_status(struct hotplug_slot *hotplug_slot, u8 *value) +{ + struct slot *slot = to_slot(hotplug_slot); + + *value = cpci_get_power_status(slot); + return 0; +} + +static int +get_attention_status(struct hotplug_slot *hotplug_slot, u8 *value) +{ + struct slot *slot = to_slot(hotplug_slot); + + *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(to_slot(hotplug_slot), status); +} + +static int +get_adapter_status(struct hotplug_slot *hotplug_slot, u8 *value) +{ + struct slot *slot = to_slot(hotplug_slot); + + *value = slot->adapter_status; + return 0; +} + +static int +get_latch_status(struct hotplug_slot *hotplug_slot, u8 *value) +{ + struct slot *slot = to_slot(hotplug_slot); + + *value = slot->latch_status; + return 0; +} + +static void release_slot(struct slot *slot) +{ + pci_dev_put(slot->dev); + kfree(slot); +} + +#define SLOT_NAME_SIZE 6 + +int +cpci_hp_register_bus(struct pci_bus *bus, u8 first, u8 last) +{ + struct slot *slot; + char name[SLOT_NAME_SIZE]; + int status; + int i; + + if (!(controller && bus)) + return -ENODEV; + + /* + * Create a structure for each slot, and register that slot + * with the pci_hotplug subsystem. + */ + for (i = first; i <= last; ++i) { + slot = kzalloc(sizeof(struct slot), GFP_KERNEL); + if (!slot) { + status = -ENOMEM; + goto error; + } + + slot->bus = bus; + slot->number = i; + slot->devfn = PCI_DEVFN(i, 0); + + snprintf(name, SLOT_NAME_SIZE, "%02x:%02x", bus->number, i); + + slot->hotplug_slot.ops = &cpci_hotplug_slot_ops; + + dbg("registering slot %s", name); + status = pci_hp_register(&slot->hotplug_slot, bus, i, name); + if (status) { + err("pci_hp_register failed with error %d", status); + goto error_slot; + } + dbg("slot registered with name: %s", slot_name(slot)); + + /* Add slot to our internal list */ + down_write(&list_rwsem); + list_add(&slot->slot_list, &slot_list); + slots++; + up_write(&list_rwsem); + } + return 0; +error_slot: + kfree(slot); +error: + return status; +} +EXPORT_SYMBOL_GPL(cpci_hp_register_bus); + +int +cpci_hp_unregister_bus(struct pci_bus *bus) +{ + struct slot *slot; + struct slot *tmp; + int status = 0; + + down_write(&list_rwsem); + if (!slots) { + up_write(&list_rwsem); + return -1; + } + list_for_each_entry_safe(slot, tmp, &slot_list, slot_list) { + if (slot->bus == bus) { + list_del(&slot->slot_list); + slots--; + + dbg("deregistering slot %s", slot_name(slot)); + pci_hp_deregister(&slot->hotplug_slot); + release_slot(slot); + } + } + up_write(&list_rwsem); + return status; +} +EXPORT_SYMBOL_GPL(cpci_hp_unregister_bus); + +/* This is the interrupt mode interrupt handler */ +static irqreturn_t +cpci_hp_intr(int irq, void *data) +{ + dbg("entered cpci_hp_intr"); + + /* Check to see if it was our interrupt */ + if ((controller->irq_flags & IRQF_SHARED) && + !controller->ops->check_irq(controller->dev_id)) { + dbg("exited cpci_hp_intr, not our interrupt"); + return IRQ_NONE; + } + + /* Disable ENUM interrupt */ + controller->ops->disable_irq(); + + /* Trigger processing by the event thread */ + wake_up_process(cpci_thread); + return IRQ_HANDLED; +} + +/* + * According to PICMG 2.1 R2.0, section 6.3.2, upon + * initialization, the system driver shall clear the + * INS bits of the cold-inserted devices. + */ +static int +init_slots(int clear_ins) +{ + struct slot *slot; + struct pci_dev *dev; + + dbg("%s - enter", __func__); + down_read(&list_rwsem); + if (!slots) { + up_read(&list_rwsem); + return -1; + } + list_for_each_entry(slot, &slot_list, slot_list) { + dbg("%s - looking at slot %s", __func__, slot_name(slot)); + if (clear_ins && cpci_check_and_clear_ins(slot)) + dbg("%s - cleared INS for slot %s", + __func__, slot_name(slot)); + dev = pci_get_slot(slot->bus, PCI_DEVFN(slot->number, 0)); + if (dev) { + slot->adapter_status = 1; + slot->latch_status = 1; + slot->dev = dev; + } + } + up_read(&list_rwsem); + dbg("%s - exit", __func__); + return 0; +} + +static int +check_slots(void) +{ + struct slot *slot; + int extracted; + int inserted; + u16 hs_csr; + + down_read(&list_rwsem); + if (!slots) { + up_read(&list_rwsem); + err("no slots registered, shutting down"); + return -1; + } + extracted = inserted = 0; + list_for_each_entry(slot, &slot_list, slot_list) { + dbg("%s - looking at slot %s", __func__, slot_name(slot)); + if (cpci_check_and_clear_ins(slot)) { + /* + * Some broken hardware (e.g. PLX 9054AB) asserts + * ENUM# twice... + */ + if (slot->dev) { + warn("slot %s already inserted", + slot_name(slot)); + inserted++; + continue; + } + + /* Process insertion */ + dbg("%s - slot %s inserted", __func__, slot_name(slot)); + + /* GSM, debug */ + hs_csr = cpci_get_hs_csr(slot); + dbg("%s - slot %s HS_CSR (1) = %04x", + __func__, slot_name(slot), hs_csr); + + /* Configure device */ + dbg("%s - configuring slot %s", + __func__, slot_name(slot)); + if (cpci_configure_slot(slot)) { + err("%s - could not configure slot %s", + __func__, slot_name(slot)); + continue; + } + dbg("%s - finished configuring slot %s", + __func__, slot_name(slot)); + + /* GSM, debug */ + hs_csr = cpci_get_hs_csr(slot); + dbg("%s - slot %s HS_CSR (2) = %04x", + __func__, slot_name(slot), hs_csr); + + slot->latch_status = 1; + slot->adapter_status = 1; + + cpci_led_off(slot); + + /* GSM, debug */ + hs_csr = cpci_get_hs_csr(slot); + dbg("%s - slot %s HS_CSR (3) = %04x", + __func__, slot_name(slot), hs_csr); + + inserted++; + } else if (cpci_check_ext(slot)) { + /* Process extraction request */ + dbg("%s - slot %s extracted", + __func__, slot_name(slot)); + + /* GSM, debug */ + hs_csr = cpci_get_hs_csr(slot); + dbg("%s - slot %s HS_CSR = %04x", + __func__, slot_name(slot), hs_csr); + + if (!slot->extracting) { + slot->latch_status = 0; + slot->extracting = 1; + atomic_inc(&extracting); + } + extracted++; + } else if (slot->extracting) { + hs_csr = cpci_get_hs_csr(slot); + if (hs_csr == 0xffff) { + /* + * Hmmm, we're likely hosed at this point, should we + * bother trying to tell the driver or not? + */ + err("card in slot %s was improperly removed", + slot_name(slot)); + slot->adapter_status = 0; + slot->extracting = 0; + atomic_dec(&extracting); + } + } + } + up_read(&list_rwsem); + dbg("inserted=%d, extracted=%d, extracting=%d", + inserted, extracted, atomic_read(&extracting)); + if (inserted || extracted) + return extracted; + else if (!atomic_read(&extracting)) { + err("cannot find ENUM# source, shutting down"); + return -1; + } + return 0; +} + +/* This is the interrupt mode worker thread body */ +static int +event_thread(void *data) +{ + int rc; + + dbg("%s - event thread started", __func__); + while (1) { + dbg("event thread sleeping"); + set_current_state(TASK_INTERRUPTIBLE); + schedule(); + if (kthread_should_stop()) + break; + do { + rc = check_slots(); + if (rc > 0) { + /* Give userspace a chance to handle extraction */ + msleep(500); + } else if (rc < 0) { + dbg("%s - error checking slots", __func__); + thread_finished = 1; + goto out; + } + } while (atomic_read(&extracting) && !kthread_should_stop()); + if (kthread_should_stop()) + break; + + /* Re-enable ENUM# interrupt */ + dbg("%s - re-enabling irq", __func__); + controller->ops->enable_irq(); + } + out: + return 0; +} + +/* This is the polling mode worker thread body */ +static int +poll_thread(void *data) +{ + int rc; + + while (1) { + if (kthread_should_stop() || signal_pending(current)) + break; + if (controller->ops->query_enum()) { + do { + rc = check_slots(); + if (rc > 0) { + /* Give userspace a chance to handle extraction */ + msleep(500); + } else if (rc < 0) { + dbg("%s - error checking slots", __func__); + thread_finished = 1; + goto out; + } + } while (atomic_read(&extracting) && !kthread_should_stop()); + } + msleep(100); + } + out: + return 0; +} + +static int +cpci_start_thread(void) +{ + if (controller->irq) + cpci_thread = kthread_run(event_thread, NULL, "cpci_hp_eventd"); + else + cpci_thread = kthread_run(poll_thread, NULL, "cpci_hp_polld"); + if (IS_ERR(cpci_thread)) { + err("Can't start up our thread"); + return PTR_ERR(cpci_thread); + } + thread_finished = 0; + return 0; +} + +static void +cpci_stop_thread(void) +{ + kthread_stop(cpci_thread); + thread_finished = 1; +} + +int +cpci_hp_register_controller(struct cpci_hp_controller *new_controller) +{ + int status = 0; + + if (controller) + return -1; + if (!(new_controller && new_controller->ops)) + return -EINVAL; + if (new_controller->irq) { + if (!(new_controller->ops->enable_irq && + new_controller->ops->disable_irq)) + status = -EINVAL; + if (request_irq(new_controller->irq, + cpci_hp_intr, + new_controller->irq_flags, + MY_NAME, + new_controller->dev_id)) { + err("Can't get irq %d for the hotplug cPCI controller", + new_controller->irq); + status = -ENODEV; + } + dbg("%s - acquired controller irq %d", + __func__, new_controller->irq); + } + if (!status) + controller = new_controller; + return status; +} +EXPORT_SYMBOL_GPL(cpci_hp_register_controller); + +static void +cleanup_slots(void) +{ + struct slot *slot; + struct slot *tmp; + + /* + * Unregister all of our slots with the pci_hotplug subsystem, + * and free up all memory that we had allocated. + */ + down_write(&list_rwsem); + if (!slots) + goto cleanup_null; + list_for_each_entry_safe(slot, tmp, &slot_list, slot_list) { + list_del(&slot->slot_list); + pci_hp_deregister(&slot->hotplug_slot); + release_slot(slot); + } +cleanup_null: + up_write(&list_rwsem); +} + +int +cpci_hp_unregister_controller(struct cpci_hp_controller *old_controller) +{ + int status = 0; + + if (controller) { + if (!thread_finished) + cpci_stop_thread(); + if (controller->irq) + free_irq(controller->irq, controller->dev_id); + controller = NULL; + cleanup_slots(); + } else + status = -ENODEV; + return status; +} +EXPORT_SYMBOL_GPL(cpci_hp_unregister_controller); + +int +cpci_hp_start(void) +{ + static int first = 1; + int status; + + dbg("%s - enter", __func__); + if (!controller) + return -ENODEV; + + down_read(&list_rwsem); + if (list_empty(&slot_list)) { + up_read(&list_rwsem); + return -ENODEV; + } + up_read(&list_rwsem); + + status = init_slots(first); + if (first) + first = 0; + if (status) + return status; + + status = cpci_start_thread(); + if (status) + return status; + dbg("%s - thread started", __func__); + + if (controller->irq) { + /* Start enum interrupt processing */ + dbg("%s - enabling irq", __func__); + controller->ops->enable_irq(); + } + dbg("%s - exit", __func__); + return 0; +} +EXPORT_SYMBOL_GPL(cpci_hp_start); + +int +cpci_hp_stop(void) +{ + if (!controller) + return -ENODEV; + if (controller->irq) { + /* Stop enum interrupt processing */ + dbg("%s - disabling irq", __func__); + controller->ops->disable_irq(); + } + cpci_stop_thread(); + return 0; +} +EXPORT_SYMBOL_GPL(cpci_hp_stop); + +int __init +cpci_hotplug_init(int debug) +{ + cpci_debug = debug; + return 0; +} diff --git a/drivers/pci/hotplug/cpci_hotplug_pci.c b/drivers/pci/hotplug/cpci_hotplug_pci.c new file mode 100644 index 000000000..6c48066ac --- /dev/null +++ b/drivers/pci/hotplug/cpci_hotplug_pci.c @@ -0,0 +1,312 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * CompactPCI Hot Plug Driver PCI functions + * + * Copyright (C) 2002,2005 by SOMA Networks, Inc. + * + * All rights reserved. + * + * Send feedback to <scottm@somanetworks.com> + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/pci.h> +#include <linux/pci_hotplug.h> +#include <linux/proc_fs.h> +#include "../pci.h" +#include "cpci_hotplug.h" + +#define MY_NAME "cpci_hotplug" + +#define dbg(format, arg...) \ + do { \ + if (cpci_debug) \ + printk(KERN_DEBUG "%s: " format "\n", \ + MY_NAME, ## arg); \ + } while (0) +#define err(format, arg...) printk(KERN_ERR "%s: " format "\n", MY_NAME, ## arg) +#define info(format, arg...) printk(KERN_INFO "%s: " format "\n", MY_NAME, ## arg) +#define warn(format, arg...) printk(KERN_WARNING "%s: " format "\n", MY_NAME, ## arg) + + +u8 cpci_get_attention_status(struct slot *slot) +{ + int hs_cap; + u16 hs_csr; + + hs_cap = pci_bus_find_capability(slot->bus, + slot->devfn, + PCI_CAP_ID_CHSWP); + if (!hs_cap) + return 0; + + if (pci_bus_read_config_word(slot->bus, + slot->devfn, + hs_cap + 2, + &hs_csr)) + return 0; + + return hs_csr & 0x0008 ? 1 : 0; +} + +int cpci_set_attention_status(struct slot *slot, int status) +{ + int hs_cap; + u16 hs_csr; + + hs_cap = pci_bus_find_capability(slot->bus, + slot->devfn, + PCI_CAP_ID_CHSWP); + if (!hs_cap) + return 0; + if (pci_bus_read_config_word(slot->bus, + slot->devfn, + hs_cap + 2, + &hs_csr)) + return 0; + if (status) + hs_csr |= HS_CSR_LOO; + else + hs_csr &= ~HS_CSR_LOO; + if (pci_bus_write_config_word(slot->bus, + slot->devfn, + hs_cap + 2, + hs_csr)) + return 0; + return 1; +} + +u16 cpci_get_hs_csr(struct slot *slot) +{ + int hs_cap; + u16 hs_csr; + + hs_cap = pci_bus_find_capability(slot->bus, + slot->devfn, + PCI_CAP_ID_CHSWP); + if (!hs_cap) + return 0xFFFF; + if (pci_bus_read_config_word(slot->bus, + slot->devfn, + hs_cap + 2, + &hs_csr)) + return 0xFFFF; + return hs_csr; +} + +int cpci_check_and_clear_ins(struct slot *slot) +{ + int hs_cap; + u16 hs_csr; + int ins = 0; + + hs_cap = pci_bus_find_capability(slot->bus, + slot->devfn, + PCI_CAP_ID_CHSWP); + if (!hs_cap) + return 0; + if (pci_bus_read_config_word(slot->bus, + slot->devfn, + hs_cap + 2, + &hs_csr)) + return 0; + if (hs_csr & HS_CSR_INS) { + /* Clear INS (by setting it) */ + if (pci_bus_write_config_word(slot->bus, + slot->devfn, + hs_cap + 2, + hs_csr)) + ins = 0; + else + ins = 1; + } + return ins; +} + +int cpci_check_ext(struct slot *slot) +{ + int hs_cap; + u16 hs_csr; + int ext = 0; + + hs_cap = pci_bus_find_capability(slot->bus, + slot->devfn, + PCI_CAP_ID_CHSWP); + if (!hs_cap) + return 0; + if (pci_bus_read_config_word(slot->bus, + slot->devfn, + hs_cap + 2, + &hs_csr)) + return 0; + if (hs_csr & HS_CSR_EXT) + ext = 1; + return ext; +} + +int cpci_clear_ext(struct slot *slot) +{ + int hs_cap; + u16 hs_csr; + + hs_cap = pci_bus_find_capability(slot->bus, + slot->devfn, + PCI_CAP_ID_CHSWP); + if (!hs_cap) + return -ENODEV; + if (pci_bus_read_config_word(slot->bus, + slot->devfn, + hs_cap + 2, + &hs_csr)) + return -ENODEV; + if (hs_csr & HS_CSR_EXT) { + /* Clear EXT (by setting it) */ + if (pci_bus_write_config_word(slot->bus, + slot->devfn, + hs_cap + 2, + hs_csr)) + return -ENODEV; + } + return 0; +} + +int cpci_led_on(struct slot *slot) +{ + int hs_cap; + u16 hs_csr; + + hs_cap = pci_bus_find_capability(slot->bus, + slot->devfn, + PCI_CAP_ID_CHSWP); + if (!hs_cap) + return -ENODEV; + if (pci_bus_read_config_word(slot->bus, + slot->devfn, + hs_cap + 2, + &hs_csr)) + return -ENODEV; + if ((hs_csr & HS_CSR_LOO) != HS_CSR_LOO) { + hs_csr |= HS_CSR_LOO; + if (pci_bus_write_config_word(slot->bus, + slot->devfn, + hs_cap + 2, + hs_csr)) { + err("Could not set LOO for slot %s", slot_name(slot)); + return -ENODEV; + } + } + return 0; +} + +int cpci_led_off(struct slot *slot) +{ + int hs_cap; + u16 hs_csr; + + hs_cap = pci_bus_find_capability(slot->bus, + slot->devfn, + PCI_CAP_ID_CHSWP); + if (!hs_cap) + return -ENODEV; + if (pci_bus_read_config_word(slot->bus, + slot->devfn, + hs_cap + 2, + &hs_csr)) + return -ENODEV; + if (hs_csr & HS_CSR_LOO) { + hs_csr &= ~HS_CSR_LOO; + if (pci_bus_write_config_word(slot->bus, + slot->devfn, + hs_cap + 2, + hs_csr)) { + err("Could not clear LOO for slot %s", slot_name(slot)); + return -ENODEV; + } + } + return 0; +} + + +/* + * Device configuration functions + */ + +int cpci_configure_slot(struct slot *slot) +{ + struct pci_dev *dev; + struct pci_bus *parent; + int ret = 0; + + dbg("%s - enter", __func__); + + pci_lock_rescan_remove(); + + if (slot->dev == NULL) { + dbg("pci_dev null, finding %02x:%02x:%x", + slot->bus->number, PCI_SLOT(slot->devfn), PCI_FUNC(slot->devfn)); + slot->dev = pci_get_slot(slot->bus, slot->devfn); + } + + /* Still NULL? Well then scan for it! */ + if (slot->dev == NULL) { + int n; + dbg("pci_dev still null"); + + /* + * This will generate pci_dev structures for all functions, but + * we will only call this case when lookup fails. + */ + n = pci_scan_slot(slot->bus, slot->devfn); + dbg("%s: pci_scan_slot returned %d", __func__, n); + slot->dev = pci_get_slot(slot->bus, slot->devfn); + if (slot->dev == NULL) { + err("Could not find PCI device for slot %02x", slot->number); + ret = -ENODEV; + goto out; + } + } + parent = slot->dev->bus; + + for_each_pci_bridge(dev, parent) { + if (PCI_SLOT(dev->devfn) == PCI_SLOT(slot->devfn)) + pci_hp_add_bridge(dev); + } + + pci_assign_unassigned_bridge_resources(parent->self); + + pci_bus_add_devices(parent); + + out: + pci_unlock_rescan_remove(); + dbg("%s - exit", __func__); + return ret; +} + +int cpci_unconfigure_slot(struct slot *slot) +{ + struct pci_dev *dev, *temp; + + dbg("%s - enter", __func__); + if (!slot->dev) { + err("No device for slot %02x\n", slot->number); + return -ENODEV; + } + + pci_lock_rescan_remove(); + + list_for_each_entry_safe(dev, temp, &slot->bus->devices, bus_list) { + if (PCI_SLOT(dev->devfn) != PCI_SLOT(slot->devfn)) + continue; + pci_dev_get(dev); + pci_stop_and_remove_bus_device(dev); + pci_dev_put(dev); + } + pci_dev_put(slot->dev); + slot->dev = NULL; + + pci_unlock_rescan_remove(); + + dbg("%s - exit", __func__); + return 0; +} diff --git a/drivers/pci/hotplug/cpcihp_generic.c b/drivers/pci/hotplug/cpcihp_generic.c new file mode 100644 index 000000000..17d71edf0 --- /dev/null +++ b/drivers/pci/hotplug/cpcihp_generic.c @@ -0,0 +1,207 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * 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 generic CompactPCI hotplug driver should allow using the PCI hotplug + * mechanism on any CompactPCI board that exposes the #ENUM signal as a bit + * in a system register that can be read through standard port I/O. + * + * Send feedback to <scottm@somanetworks.com> + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/errno.h> +#include <linux/pci.h> +#include <linux/string.h> +#include "cpci_hotplug.h" + +#define DRIVER_VERSION "0.1" +#define DRIVER_AUTHOR "Scott Murray <scottm@somanetworks.com>" +#define DRIVER_DESC "Generic port I/O CompactPCI Hot Plug Driver" + +#if !defined(MODULE) +#define MY_NAME "cpcihp_generic" +#else +#define MY_NAME THIS_MODULE->name +#endif + +#define dbg(format, arg...) \ + do { \ + if (debug) \ + printk(KERN_DEBUG "%s: " format "\n", \ + MY_NAME, ## arg); \ + } while (0) +#define err(format, arg...) printk(KERN_ERR "%s: " format "\n", MY_NAME, ## arg) +#define info(format, arg...) printk(KERN_INFO "%s: " format "\n", MY_NAME, ## arg) +#define warn(format, arg...) printk(KERN_WARNING "%s: " format "\n", MY_NAME, ## arg) + +/* local variables */ +static bool debug; +static char *bridge; +static u8 bridge_busnr; +static u8 bridge_slot; +static struct pci_bus *bus; +static u8 first_slot; +static u8 last_slot; +static u16 port; +static unsigned int enum_bit; +static u8 enum_mask; + +static struct cpci_hp_controller_ops generic_hpc_ops; +static struct cpci_hp_controller generic_hpc; + +static int __init validate_parameters(void) +{ + char *str; + char *p; + unsigned long tmp; + + if (!bridge) { + info("not configured, disabling."); + return -EINVAL; + } + str = bridge; + if (!*str) + return -EINVAL; + + tmp = simple_strtoul(str, &p, 16); + if (p == str || tmp > 0xff) { + err("Invalid hotplug bus bridge device bus number"); + return -EINVAL; + } + bridge_busnr = (u8) tmp; + dbg("bridge_busnr = 0x%02x", bridge_busnr); + if (*p != ':') { + err("Invalid hotplug bus bridge device"); + return -EINVAL; + } + str = p + 1; + tmp = simple_strtoul(str, &p, 16); + if (p == str || tmp > 0x1f) { + err("Invalid hotplug bus bridge device slot number"); + return -EINVAL; + } + bridge_slot = (u8) tmp; + dbg("bridge_slot = 0x%02x", bridge_slot); + + dbg("first_slot = 0x%02x", first_slot); + dbg("last_slot = 0x%02x", last_slot); + if (!(first_slot && last_slot)) { + err("Need to specify first_slot and last_slot"); + return -EINVAL; + } + if (last_slot < first_slot) { + err("first_slot must be less than last_slot"); + return -EINVAL; + } + + dbg("port = 0x%04x", port); + dbg("enum_bit = 0x%02x", enum_bit); + if (enum_bit > 7) { + err("Invalid #ENUM bit"); + return -EINVAL; + } + enum_mask = 1 << enum_bit; + return 0; +} + +static int query_enum(void) +{ + u8 value; + + value = inb_p(port); + return ((value & enum_mask) == enum_mask); +} + +static int __init cpcihp_generic_init(void) +{ + int status; + struct resource *r; + struct pci_dev *dev; + + info(DRIVER_DESC " version: " DRIVER_VERSION); + status = validate_parameters(); + if (status) + return status; + + r = request_region(port, 1, "#ENUM hotswap signal register"); + if (!r) + return -EBUSY; + + dev = pci_get_domain_bus_and_slot(0, bridge_busnr, + PCI_DEVFN(bridge_slot, 0)); + if (!dev || dev->hdr_type != PCI_HEADER_TYPE_BRIDGE) { + err("Invalid bridge device %s", bridge); + pci_dev_put(dev); + return -EINVAL; + } + bus = dev->subordinate; + pci_dev_put(dev); + + memset(&generic_hpc, 0, sizeof(struct cpci_hp_controller)); + generic_hpc_ops.query_enum = query_enum; + generic_hpc.ops = &generic_hpc_ops; + + status = cpci_hp_register_controller(&generic_hpc); + if (status != 0) { + err("Could not register cPCI hotplug controller"); + return -ENODEV; + } + dbg("registered controller"); + + status = cpci_hp_register_bus(bus, first_slot, last_slot); + if (status != 0) { + err("Could not register cPCI hotplug bus"); + goto init_bus_register_error; + } + dbg("registered bus"); + + status = cpci_hp_start(); + if (status != 0) { + err("Could not started cPCI hotplug system"); + goto init_start_error; + } + dbg("started cpci hp system"); + return 0; +init_start_error: + cpci_hp_unregister_bus(bus); +init_bus_register_error: + cpci_hp_unregister_controller(&generic_hpc); + err("status = %d", status); + return status; + +} + +static void __exit cpcihp_generic_exit(void) +{ + cpci_hp_stop(); + cpci_hp_unregister_bus(bus); + cpci_hp_unregister_controller(&generic_hpc); + release_region(port, 1); +} + +module_init(cpcihp_generic_init); +module_exit(cpcihp_generic_exit); + +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); +module_param(debug, bool, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(debug, "Debugging mode enabled or not"); +module_param(bridge, charp, 0); +MODULE_PARM_DESC(bridge, "Hotswap bus bridge device, <bus>:<slot> (bus and slot are in hexadecimal)"); +module_param(first_slot, byte, 0); +MODULE_PARM_DESC(first_slot, "Hotswap bus first slot number"); +module_param(last_slot, byte, 0); +MODULE_PARM_DESC(last_slot, "Hotswap bus last slot number"); +module_param_hw(port, ushort, ioport, 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 000000000..ae63e5a39 --- /dev/null +++ b/drivers/pci/hotplug/cpcihp_zt5550.c @@ -0,0 +1,309 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * 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. + * + * Send feedback to <scottm@somanetworks.com> + */ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/init.h> +#include <linux/errno.h> +#include <linux/pci.h> +#include <linux/interrupt.h> +#include <linux/signal.h> /* IRQF_SHARED */ +#include "cpci_hotplug.h" +#include "cpcihp_zt5550.h" + +#define DRIVER_VERSION "0.2" +#define DRIVER_AUTHOR "Scott Murray <scottm@somanetworks.com>" +#define DRIVER_DESC "ZT5550 CompactPCI Hot Plug Driver" + +#define MY_NAME "cpcihp_zt5550" + +#define dbg(format, arg...) \ + do { \ + if (debug) \ + printk(KERN_DEBUG "%s: " format "\n", \ + MY_NAME, ## arg); \ + } while (0) +#define err(format, arg...) printk(KERN_ERR "%s: " format "\n", MY_NAME, ## arg) +#define info(format, arg...) printk(KERN_INFO "%s: " format "\n", MY_NAME, ## arg) +#define warn(format, arg...) printk(KERN_WARNING "%s: " format "\n", MY_NAME, ## arg) + +/* local variables */ +static bool debug; +static bool poll; +static struct cpci_hp_controller_ops zt5550_hpc_ops; +static struct cpci_hp_controller zt5550_hpc; + +/* Primary cPCI bus bridge device */ +static struct pci_dev *bus0_dev; +static struct pci_bus *bus0; + +/* Host controller device */ +static struct pci_dev *hc_dev; + +/* Host controller register addresses */ +static void __iomem *hc_registers; +static void __iomem *csr_hc_index; +static void __iomem *csr_hc_data; +static void __iomem *csr_int_status; +static void __iomem *csr_int_mask; + + +static int zt5550_hc_config(struct pci_dev *pdev) +{ + int ret; + + /* Since we know that no boards exist with two HC chips, treat it as an error */ + if (hc_dev) { + err("too many host controller devices?"); + return -EBUSY; + } + + ret = pci_enable_device(pdev); + if (ret) { + err("cannot enable %s\n", pci_name(pdev)); + return ret; + } + + hc_dev = pdev; + dbg("hc_dev = %p", hc_dev); + dbg("pci resource start %llx", (unsigned long long)pci_resource_start(hc_dev, 1)); + dbg("pci resource len %llx", (unsigned long long)pci_resource_len(hc_dev, 1)); + + if (!request_mem_region(pci_resource_start(hc_dev, 1), + pci_resource_len(hc_dev, 1), MY_NAME)) { + err("cannot reserve MMIO region"); + ret = -ENOMEM; + goto exit_disable_device; + } + + hc_registers = + ioremap(pci_resource_start(hc_dev, 1), pci_resource_len(hc_dev, 1)); + if (!hc_registers) { + err("cannot remap MMIO region %llx @ %llx", + (unsigned long long)pci_resource_len(hc_dev, 1), + (unsigned long long)pci_resource_start(hc_dev, 1)); + ret = -ENODEV; + goto exit_release_region; + } + + csr_hc_index = hc_registers + CSR_HCINDEX; + csr_hc_data = hc_registers + CSR_HCDATA; + csr_int_status = hc_registers + CSR_INTSTAT; + csr_int_mask = hc_registers + CSR_INTMASK; + + /* + * Disable host control, fault and serial interrupts + */ + dbg("disabling host control, fault and serial interrupts"); + writeb((u8) HC_INT_MASK_REG, csr_hc_index); + writeb((u8) ALL_INDEXED_INTS_MASK, csr_hc_data); + dbg("disabled host control, fault and serial interrupts"); + + /* + * Disable timer0, timer1 and ENUM interrupts + */ + dbg("disabling timer0, timer1 and ENUM interrupts"); + writeb((u8) ALL_DIRECT_INTS_MASK, csr_int_mask); + dbg("disabled timer0, timer1 and ENUM interrupts"); + return 0; + +exit_release_region: + release_mem_region(pci_resource_start(hc_dev, 1), + pci_resource_len(hc_dev, 1)); +exit_disable_device: + pci_disable_device(hc_dev); + return ret; +} + +static int zt5550_hc_cleanup(void) +{ + if (!hc_dev) + return -ENODEV; + + iounmap(hc_registers); + release_mem_region(pci_resource_start(hc_dev, 1), + pci_resource_len(hc_dev, 1)); + pci_disable_device(hc_dev); + return 0; +} + +static int zt5550_hc_query_enum(void) +{ + u8 value; + + value = inb_p(ENUM_PORT); + return ((value & ENUM_MASK) == ENUM_MASK); +} + +static int zt5550_hc_check_irq(void *dev_id) +{ + int ret; + u8 reg; + + ret = 0; + if (dev_id == zt5550_hpc.dev_id) { + reg = readb(csr_int_status); + if (reg) + ret = 1; + } + return ret; +} + +static int zt5550_hc_enable_irq(void) +{ + u8 reg; + + if (hc_dev == NULL) + return -ENODEV; + + reg = readb(csr_int_mask); + reg = reg & ~ENUM_INT_MASK; + writeb(reg, csr_int_mask); + return 0; +} + +static int zt5550_hc_disable_irq(void) +{ + u8 reg; + + if (hc_dev == NULL) + return -ENODEV; + + reg = readb(csr_int_mask); + reg = reg | ENUM_INT_MASK; + writeb(reg, csr_int_mask); + return 0; +} + +static int zt5550_hc_init_one(struct pci_dev *pdev, const struct pci_device_id *ent) +{ + int status; + + status = zt5550_hc_config(pdev); + if (status != 0) + return status; + + dbg("returned from zt5550_hc_config"); + + memset(&zt5550_hpc, 0, sizeof(struct cpci_hp_controller)); + zt5550_hpc_ops.query_enum = zt5550_hc_query_enum; + zt5550_hpc.ops = &zt5550_hpc_ops; + if (!poll) { + zt5550_hpc.irq = hc_dev->irq; + zt5550_hpc.irq_flags = IRQF_SHARED; + zt5550_hpc.dev_id = hc_dev; + + zt5550_hpc_ops.enable_irq = zt5550_hc_enable_irq; + zt5550_hpc_ops.disable_irq = zt5550_hc_disable_irq; + zt5550_hpc_ops.check_irq = zt5550_hc_check_irq; + } else { + info("using ENUM# polling mode"); + } + + status = cpci_hp_register_controller(&zt5550_hpc); + if (status != 0) { + err("could not register cPCI hotplug controller"); + goto init_hc_error; + } + dbg("registered controller"); + + /* Look for first device matching cPCI bus's bridge vendor and device IDs */ + bus0_dev = pci_get_device(PCI_VENDOR_ID_DEC, + PCI_DEVICE_ID_DEC_21154, NULL); + if (!bus0_dev) { + 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 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 const 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 = zt5550_hc_remove_one, +}; + +static int __init zt5550_init(void) +{ + struct resource *r; + int rc; + + info(DRIVER_DESC " version: " DRIVER_VERSION); + r = request_region(ENUM_PORT, 1, "#ENUM hotswap signal register"); + if (!r) + return -EBUSY; + + rc = pci_register_driver(&zt5550_hc_driver); + if (rc < 0) + release_region(ENUM_PORT, 1); + return rc; +} + +static void __exit +zt5550_exit(void) +{ + pci_unregister_driver(&zt5550_hc_driver); + release_region(ENUM_PORT, 1); +} + +module_init(zt5550_init); +module_exit(zt5550_exit); + +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); +module_param(debug, bool, 0644); +MODULE_PARM_DESC(debug, "Debugging mode enabled or not"); +module_param(poll, bool, 0644); +MODULE_PARM_DESC(poll, "#ENUM polling mode enabled or not"); diff --git a/drivers/pci/hotplug/cpcihp_zt5550.h b/drivers/pci/hotplug/cpcihp_zt5550.h new file mode 100644 index 000000000..5ea10df83 --- /dev/null +++ b/drivers/pci/hotplug/cpcihp_zt5550.h @@ -0,0 +1,60 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * 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. + * + * 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 000000000..2f7b49ea9 --- /dev/null +++ b/drivers/pci/hotplug/cpqphp.h @@ -0,0 +1,729 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * 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. + * + * Send feedback to <greg@kroah.com> + * + */ +#ifndef _CPQPHP_H +#define _CPQPHP_H + +#include <linux/interrupt.h> +#include <linux/io.h> /* for read? and write? functions */ +#include <linux/delay.h> /* for delays */ +#include <linux/mutex.h> +#include <linux/sched/signal.h> /* for signal_pending() */ + +#define MY_NAME "cpqphp" + +#define dbg(fmt, arg...) do { if (cpqhp_debug) printk(KERN_DEBUG "%s: " fmt, MY_NAME, ## arg); } while (0) +#define err(format, arg...) printk(KERN_ERR "%s: " format, MY_NAME, ## arg) +#define info(format, arg...) printk(KERN_INFO "%s: " format, MY_NAME, ## arg) +#define warn(format, arg...) printk(KERN_WARNING "%s: " format, MY_NAME, ## arg) + + + +struct smbios_system_slot { + u8 type; + u8 length; + u16 handle; + u8 name_string_num; + u8 slot_type; + u8 slot_width; + u8 slot_current_usage; + u8 slot_length; + u16 slot_number; + u8 properties1; + u8 properties2; +} __attribute__ ((packed)); + +/* offsets to the smbios generic type based on the above structure layout */ +enum smbios_system_slot_offsets { + SMBIOS_SLOT_GENERIC_TYPE = offsetof(struct smbios_system_slot, type), + SMBIOS_SLOT_GENERIC_LENGTH = offsetof(struct smbios_system_slot, length), + SMBIOS_SLOT_GENERIC_HANDLE = offsetof(struct smbios_system_slot, handle), + SMBIOS_SLOT_NAME_STRING_NUM = offsetof(struct smbios_system_slot, name_string_num), + SMBIOS_SLOT_TYPE = offsetof(struct smbios_system_slot, slot_type), + SMBIOS_SLOT_WIDTH = offsetof(struct smbios_system_slot, slot_width), + SMBIOS_SLOT_CURRENT_USAGE = offsetof(struct smbios_system_slot, slot_current_usage), + SMBIOS_SLOT_LENGTH = offsetof(struct smbios_system_slot, slot_length), + SMBIOS_SLOT_NUMBER = offsetof(struct smbios_system_slot, slot_number), + SMBIOS_SLOT_PROPERTIES1 = offsetof(struct smbios_system_slot, properties1), + SMBIOS_SLOT_PROPERTIES2 = offsetof(struct smbios_system_slot, properties2), +}; + +struct smbios_generic { + u8 type; + u8 length; + u16 handle; +} __attribute__ ((packed)); + +/* offsets to the smbios generic type based on the above structure layout */ +enum smbios_generic_offsets { + SMBIOS_GENERIC_TYPE = offsetof(struct smbios_generic, type), + SMBIOS_GENERIC_LENGTH = offsetof(struct smbios_generic, length), + SMBIOS_GENERIC_HANDLE = offsetof(struct smbios_generic, handle), +}; + +struct smbios_entry_point { + char anchor[4]; + u8 ep_checksum; + u8 ep_length; + u8 major_version; + u8 minor_version; + u16 max_size_entry; + u8 ep_rev; + u8 reserved[5]; + char int_anchor[5]; + u8 int_checksum; + u16 st_length; + u32 st_address; + u16 number_of_entrys; + u8 bcd_rev; +} __attribute__ ((packed)); + +/* offsets to the smbios entry point based on the above structure layout */ +enum smbios_entry_point_offsets { + ANCHOR = offsetof(struct smbios_entry_point, anchor[0]), + EP_CHECKSUM = offsetof(struct smbios_entry_point, ep_checksum), + EP_LENGTH = offsetof(struct smbios_entry_point, ep_length), + MAJOR_VERSION = offsetof(struct smbios_entry_point, major_version), + MINOR_VERSION = offsetof(struct smbios_entry_point, minor_version), + MAX_SIZE_ENTRY = offsetof(struct smbios_entry_point, max_size_entry), + EP_REV = offsetof(struct smbios_entry_point, ep_rev), + INT_ANCHOR = offsetof(struct smbios_entry_point, int_anchor[0]), + INT_CHECKSUM = offsetof(struct smbios_entry_point, int_checksum), + ST_LENGTH = offsetof(struct smbios_entry_point, st_length), + ST_ADDRESS = offsetof(struct smbios_entry_point, st_address), + NUMBER_OF_ENTRYS = offsetof(struct smbios_entry_point, number_of_entrys), + BCD_REV = offsetof(struct smbios_entry_point, bcd_rev), +}; + +struct ctrl_reg { /* offset */ + u8 slot_RST; /* 0x00 */ + u8 slot_enable; /* 0x01 */ + u16 misc; /* 0x02 */ + u32 led_control; /* 0x04 */ + u32 int_input_clear; /* 0x08 */ + u32 int_mask; /* 0x0a */ + u8 reserved0; /* 0x10 */ + u8 reserved1; /* 0x11 */ + u8 reserved2; /* 0x12 */ + u8 gen_output_AB; /* 0x13 */ + u32 non_int_input; /* 0x14 */ + u32 reserved3; /* 0x18 */ + u32 reserved4; /* 0x1a */ + u32 reserved5; /* 0x20 */ + u8 reserved6; /* 0x24 */ + u8 reserved7; /* 0x25 */ + u16 reserved8; /* 0x26 */ + u8 slot_mask; /* 0x28 */ + u8 reserved9; /* 0x29 */ + u8 reserved10; /* 0x2a */ + u8 reserved11; /* 0x2b */ + u8 slot_SERR; /* 0x2c */ + u8 slot_power; /* 0x2d */ + u8 reserved12; /* 0x2e */ + u8 reserved13; /* 0x2f */ + u8 next_curr_freq; /* 0x30 */ + u8 reset_freq_mode; /* 0x31 */ +} __attribute__ ((packed)); + +/* offsets to the controller registers based on the above structure layout */ +enum ctrl_offsets { + SLOT_RST = offsetof(struct ctrl_reg, slot_RST), + SLOT_ENABLE = offsetof(struct ctrl_reg, slot_enable), + MISC = offsetof(struct ctrl_reg, misc), + LED_CONTROL = offsetof(struct ctrl_reg, led_control), + INT_INPUT_CLEAR = offsetof(struct ctrl_reg, int_input_clear), + INT_MASK = offsetof(struct ctrl_reg, int_mask), + CTRL_RESERVED0 = offsetof(struct ctrl_reg, reserved0), + CTRL_RESERVED1 = offsetof(struct ctrl_reg, reserved1), + CTRL_RESERVED2 = offsetof(struct ctrl_reg, reserved1), + GEN_OUTPUT_AB = offsetof(struct ctrl_reg, gen_output_AB), + NON_INT_INPUT = offsetof(struct ctrl_reg, non_int_input), + CTRL_RESERVED3 = offsetof(struct ctrl_reg, reserved3), + CTRL_RESERVED4 = offsetof(struct ctrl_reg, reserved4), + CTRL_RESERVED5 = offsetof(struct ctrl_reg, reserved5), + CTRL_RESERVED6 = offsetof(struct ctrl_reg, reserved6), + CTRL_RESERVED7 = offsetof(struct ctrl_reg, reserved7), + CTRL_RESERVED8 = offsetof(struct ctrl_reg, reserved8), + SLOT_MASK = offsetof(struct ctrl_reg, slot_mask), + CTRL_RESERVED9 = offsetof(struct ctrl_reg, reserved9), + CTRL_RESERVED10 = offsetof(struct ctrl_reg, reserved10), + CTRL_RESERVED11 = offsetof(struct ctrl_reg, reserved11), + SLOT_SERR = offsetof(struct ctrl_reg, slot_SERR), + SLOT_POWER = offsetof(struct ctrl_reg, slot_power), + NEXT_CURR_FREQ = offsetof(struct ctrl_reg, next_curr_freq), + RESET_FREQ_MODE = offsetof(struct ctrl_reg, reset_freq_mode), +}; + +struct hrt { + char sig0; + char sig1; + char sig2; + char sig3; + u16 unused_IRQ; + u16 PCIIRQ; + u8 number_of_entries; + u8 revision; + u16 reserved1; + u32 reserved2; +} __attribute__ ((packed)); + +/* offsets to the hotplug resource table registers based on the above + * structure layout + */ +enum hrt_offsets { + SIG0 = offsetof(struct hrt, sig0), + SIG1 = offsetof(struct hrt, sig1), + SIG2 = offsetof(struct hrt, sig2), + SIG3 = offsetof(struct hrt, sig3), + UNUSED_IRQ = offsetof(struct hrt, unused_IRQ), + PCIIRQ = offsetof(struct hrt, PCIIRQ), + NUMBER_OF_ENTRIES = offsetof(struct hrt, number_of_entries), + REVISION = offsetof(struct hrt, revision), + HRT_RESERVED1 = offsetof(struct hrt, reserved1), + HRT_RESERVED2 = offsetof(struct hrt, reserved2), +}; + +struct slot_rt { + u8 dev_func; + u8 primary_bus; + u8 secondary_bus; + u8 max_bus; + u16 io_base; + u16 io_length; + u16 mem_base; + u16 mem_length; + u16 pre_mem_base; + u16 pre_mem_length; +} __attribute__ ((packed)); + +/* offsets to the hotplug slot resource table registers based on the above + * structure layout + */ +enum slot_rt_offsets { + DEV_FUNC = offsetof(struct slot_rt, dev_func), + PRIMARY_BUS = offsetof(struct slot_rt, primary_bus), + SECONDARY_BUS = offsetof(struct slot_rt, secondary_bus), + MAX_BUS = offsetof(struct slot_rt, max_bus), + IO_BASE = offsetof(struct slot_rt, io_base), + IO_LENGTH = offsetof(struct slot_rt, io_length), + MEM_BASE = offsetof(struct slot_rt, mem_base), + MEM_LENGTH = offsetof(struct slot_rt, mem_length), + PRE_MEM_BASE = offsetof(struct slot_rt, pre_mem_base), + PRE_MEM_LENGTH = offsetof(struct slot_rt, pre_mem_length), +}; + +struct pci_func { + struct pci_func *next; + u8 bus; + u8 device; + u8 function; + u8 is_a_board; + u16 status; + u8 configured; + u8 switch_save; + u8 presence_save; + u32 base_length[0x06]; + u8 base_type[0x06]; + u16 reserved2; + u32 config_space[0x20]; + struct pci_resource *mem_head; + struct pci_resource *p_mem_head; + struct pci_resource *io_head; + struct pci_resource *bus_head; + struct timer_list *p_task_event; + struct pci_dev *pci_dev; +}; + +struct slot { + struct slot *next; + u8 bus; + u8 device; + u8 number; + u8 is_a_board; + u8 configured; + u8 state; + u8 switch_save; + u8 presence_save; + u32 capabilities; + u16 reserved2; + struct timer_list task_event; + u8 hp_slot; + struct controller *ctrl; + void __iomem *p_sm_slot; + struct hotplug_slot hotplug_slot; +}; + +struct pci_resource { + struct pci_resource *next; + u32 base; + u32 length; +}; + +struct event_info { + u32 event_type; + u8 hp_slot; +}; + +struct controller { + struct controller *next; + u32 ctrl_int_comp; + struct mutex crit_sect; /* critical section mutex */ + void __iomem *hpc_reg; /* cookie for our pci controller location */ + struct pci_resource *mem_head; + struct pci_resource *p_mem_head; + struct pci_resource *io_head; + struct pci_resource *bus_head; + struct pci_dev *pci_dev; + struct pci_bus *pci_bus; + struct event_info event_queue[10]; + struct slot *slot; + u8 next_event; + u8 interrupt; + u8 cfgspc_irq; + u8 bus; /* bus number for the pci hotplug controller */ + u8 rev; + u8 slot_device_offset; + u8 first_slot; + u8 add_support; + u8 push_flag; + u8 push_button; /* 0 = no pushbutton, 1 = pushbutton present */ + u8 slot_switch_type; /* 0 = no switch, 1 = switch present */ + u8 defeature_PHP; /* 0 = PHP not supported, 1 = PHP supported */ + u8 alternate_base_address; /* 0 = not supported, 1 = supported */ + u8 pci_config_space; /* Index/data access to working registers 0 = not supported, 1 = supported */ + u8 pcix_speed_capability; /* PCI-X */ + u8 pcix_support; /* PCI-X */ + u16 vendor_id; + struct work_struct int_task_event; + wait_queue_head_t queue; /* sleep & wake process */ + struct dentry *dentry; /* debugfs dentry */ +}; + +struct irq_mapping { + u8 barber_pole; + u8 valid_INT; + u8 interrupt[4]; +}; + +struct resource_lists { + struct pci_resource *mem_head; + struct pci_resource *p_mem_head; + struct pci_resource *io_head; + struct pci_resource *bus_head; + struct irq_mapping *irqs; +}; + +#define ROM_PHY_ADDR 0x0F0000 +#define ROM_PHY_LEN 0x00ffff + +#define PCI_HPC_ID 0xA0F7 +#define PCI_SUB_HPC_ID 0xA2F7 +#define PCI_SUB_HPC_ID2 0xA2F8 +#define PCI_SUB_HPC_ID3 0xA2F9 +#define PCI_SUB_HPC_ID_INTC 0xA2FA +#define PCI_SUB_HPC_ID4 0xA2FD + +#define INT_BUTTON_IGNORE 0 +#define INT_PRESENCE_ON 1 +#define INT_PRESENCE_OFF 2 +#define INT_SWITCH_CLOSE 3 +#define INT_SWITCH_OPEN 4 +#define INT_POWER_FAULT 5 +#define INT_POWER_FAULT_CLEAR 6 +#define INT_BUTTON_PRESS 7 +#define INT_BUTTON_RELEASE 8 +#define INT_BUTTON_CANCEL 9 + +#define STATIC_STATE 0 +#define BLINKINGON_STATE 1 +#define BLINKINGOFF_STATE 2 +#define POWERON_STATE 3 +#define POWEROFF_STATE 4 + +#define PCISLOT_INTERLOCK_CLOSED 0x00000001 +#define PCISLOT_ADAPTER_PRESENT 0x00000002 +#define PCISLOT_POWERED 0x00000004 +#define PCISLOT_66_MHZ_OPERATION 0x00000008 +#define PCISLOT_64_BIT_OPERATION 0x00000010 +#define PCISLOT_REPLACE_SUPPORTED 0x00000020 +#define PCISLOT_ADD_SUPPORTED 0x00000040 +#define PCISLOT_INTERLOCK_SUPPORTED 0x00000080 +#define PCISLOT_66_MHZ_SUPPORTED 0x00000100 +#define PCISLOT_64_BIT_SUPPORTED 0x00000200 + +#define PCI_TO_PCI_BRIDGE_CLASS 0x00060400 + +#define INTERLOCK_OPEN 0x00000002 +#define ADD_NOT_SUPPORTED 0x00000003 +#define CARD_FUNCTIONING 0x00000005 +#define ADAPTER_NOT_SAME 0x00000006 +#define NO_ADAPTER_PRESENT 0x00000009 +#define NOT_ENOUGH_RESOURCES 0x0000000B +#define DEVICE_TYPE_NOT_SUPPORTED 0x0000000C +#define POWER_FAILURE 0x0000000E + +#define REMOVE_NOT_SUPPORTED 0x00000003 + + +/* + * error Messages + */ +#define msg_initialization_err "Initialization failure, error=%d\n" +#define msg_HPC_rev_error "Unsupported revision of the PCI hot plug controller found.\n" +#define msg_HPC_non_compaq_or_intel "The PCI hot plug controller is not supported by this driver.\n" +#define msg_HPC_not_supported "this system is not supported by this version of cpqphpd. Upgrade to a newer version of cpqphpd\n" +#define msg_unable_to_save "unable to store PCI hot plug add resource information. This system must be rebooted before adding any PCI devices.\n" +#define msg_button_on "PCI slot #%d - powering on due to button press.\n" +#define msg_button_off "PCI slot #%d - powering off due to button press.\n" +#define msg_button_cancel "PCI slot #%d - action canceled due to button press.\n" +#define msg_button_ignore "PCI slot #%d - button press ignored. (action in progress...)\n" + + +/* debugfs functions for the hotplug controller info */ +void cpqhp_initialize_debugfs(void); +void cpqhp_shutdown_debugfs(void); +void cpqhp_create_debugfs_files(struct controller *ctrl); +void cpqhp_remove_debugfs_files(struct controller *ctrl); + +/* controller functions */ +void cpqhp_pushbutton_thread(struct timer_list *t); +irqreturn_t cpqhp_ctrl_intr(int IRQ, void *data); +int cpqhp_find_available_resources(struct controller *ctrl, + void __iomem *rom_start); +int cpqhp_event_start_thread(void); +void cpqhp_event_stop_thread(void); +struct pci_func *cpqhp_slot_create(unsigned char busnumber); +struct pci_func *cpqhp_slot_find(unsigned char bus, unsigned char device, + unsigned char index); +int cpqhp_process_SI(struct controller *ctrl, struct pci_func *func); +int cpqhp_process_SS(struct controller *ctrl, struct pci_func *func); +int cpqhp_hardware_test(struct controller *ctrl, int test_num); + +/* resource functions */ +int cpqhp_resource_sort_and_combine(struct pci_resource **head); + +/* pci functions */ +int cpqhp_set_irq(u8 bus_num, u8 dev_num, u8 int_pin, u8 irq_num); +int cpqhp_get_bus_dev(struct controller *ctrl, u8 *bus_num, u8 *dev_num, + u8 slot); +int cpqhp_save_config(struct controller *ctrl, int busnumber, int is_hot_plug); +int cpqhp_save_base_addr_length(struct controller *ctrl, struct pci_func *func); +int cpqhp_save_used_resources(struct controller *ctrl, struct pci_func *func); +int cpqhp_configure_board(struct controller *ctrl, struct pci_func *func); +int cpqhp_save_slot_config(struct controller *ctrl, struct pci_func *new_slot); +int cpqhp_valid_replace(struct controller *ctrl, struct pci_func *func); +void cpqhp_destroy_board_resources(struct pci_func *func); +int cpqhp_return_board_resources(struct pci_func *func, + struct resource_lists *resources); +void cpqhp_destroy_resource_list(struct resource_lists *resources); +int cpqhp_configure_device(struct controller *ctrl, struct pci_func *func); +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]; +extern struct irq_routing_table *cpqhp_routing_table; + +/* these can be gotten rid of, but for debugging they are purty */ +extern u8 cpqhp_nic_irq; +extern u8 cpqhp_disk_irq; + + +/* inline functions */ + +static inline const char *slot_name(struct slot *slot) +{ + return hotplug_slot_name(&slot->hotplug_slot); +} + +static inline struct slot *to_slot(struct hotplug_slot *hotplug_slot) +{ + return container_of(hotplug_slot, struct slot, hotplug_slot); +} + +/* + * return_resource + * + * Puts node back in the resource list pointed to by head + */ +static inline void return_resource(struct pci_resource **head, + struct pci_resource *node) +{ + if (!node || !head) + return; + node->next = *head; + *head = node; +} + +static inline void set_SOGO(struct controller *ctrl) +{ + u16 misc; + + misc = readw(ctrl->hpc_reg + MISC); + misc = (misc | 0x0001) & 0xFFFB; + writew(misc, ctrl->hpc_reg + MISC); +} + + +static inline void amber_LED_on(struct controller *ctrl, u8 slot) +{ + u32 led_control; + + led_control = readl(ctrl->hpc_reg + LED_CONTROL); + led_control |= (0x01010000L << slot); + writel(led_control, ctrl->hpc_reg + LED_CONTROL); +} + + +static inline void amber_LED_off(struct controller *ctrl, u8 slot) +{ + u32 led_control; + + led_control = readl(ctrl->hpc_reg + LED_CONTROL); + led_control &= ~(0x01010000L << slot); + writel(led_control, ctrl->hpc_reg + LED_CONTROL); +} + + +static inline int read_amber_LED(struct controller *ctrl, u8 slot) +{ + u32 led_control; + + led_control = readl(ctrl->hpc_reg + LED_CONTROL); + led_control &= (0x01010000L << slot); + + return led_control ? 1 : 0; +} + + +static inline void green_LED_on(struct controller *ctrl, u8 slot) +{ + u32 led_control; + + led_control = readl(ctrl->hpc_reg + LED_CONTROL); + led_control |= 0x0101L << slot; + writel(led_control, ctrl->hpc_reg + LED_CONTROL); +} + +static inline void green_LED_off(struct controller *ctrl, u8 slot) +{ + u32 led_control; + + led_control = readl(ctrl->hpc_reg + LED_CONTROL); + led_control &= ~(0x0101L << slot); + writel(led_control, ctrl->hpc_reg + LED_CONTROL); +} + + +static inline void green_LED_blink(struct controller *ctrl, u8 slot) +{ + u32 led_control; + + led_control = readl(ctrl->hpc_reg + LED_CONTROL); + led_control &= ~(0x0101L << slot); + led_control |= (0x0001L << slot); + writel(led_control, ctrl->hpc_reg + LED_CONTROL); +} + + +static inline void slot_disable(struct controller *ctrl, u8 slot) +{ + u8 slot_enable; + + slot_enable = readb(ctrl->hpc_reg + SLOT_ENABLE); + slot_enable &= ~(0x01 << slot); + writeb(slot_enable, ctrl->hpc_reg + SLOT_ENABLE); +} + + +static inline void slot_enable(struct controller *ctrl, u8 slot) +{ + u8 slot_enable; + + slot_enable = readb(ctrl->hpc_reg + SLOT_ENABLE); + slot_enable |= (0x01 << slot); + writeb(slot_enable, ctrl->hpc_reg + SLOT_ENABLE); +} + + +static inline u8 is_slot_enabled(struct controller *ctrl, u8 slot) +{ + u8 slot_enable; + + slot_enable = readb(ctrl->hpc_reg + SLOT_ENABLE); + slot_enable &= (0x01 << slot); + return slot_enable ? 1 : 0; +} + + +static inline u8 read_slot_enable(struct controller *ctrl) +{ + return readb(ctrl->hpc_reg + SLOT_ENABLE); +} + + +/** + * get_controller_speed - find the current frequency/mode of controller. + * + * @ctrl: controller to get frequency/mode for. + * + * Returns controller speed. + */ +static inline u8 get_controller_speed(struct controller *ctrl) +{ + u8 curr_freq; + u16 misc; + + if (ctrl->pcix_support) { + curr_freq = readb(ctrl->hpc_reg + NEXT_CURR_FREQ); + if ((curr_freq & 0xB0) == 0xB0) + return PCI_SPEED_133MHz_PCIX; + if ((curr_freq & 0xA0) == 0xA0) + return PCI_SPEED_100MHz_PCIX; + if ((curr_freq & 0x90) == 0x90) + return PCI_SPEED_66MHz_PCIX; + if (curr_freq & 0x10) + return PCI_SPEED_66MHz; + + return PCI_SPEED_33MHz; + } + + misc = readw(ctrl->hpc_reg + MISC); + return (misc & 0x0800) ? PCI_SPEED_66MHz : PCI_SPEED_33MHz; +} + + +/** + * get_adapter_speed - find the max supported frequency/mode of adapter. + * + * @ctrl: hotplug controller. + * @hp_slot: hotplug slot where adapter is installed. + * + * Returns adapter speed. + */ +static inline u8 get_adapter_speed(struct controller *ctrl, u8 hp_slot) +{ + u32 temp_dword = readl(ctrl->hpc_reg + NON_INT_INPUT); + dbg("slot: %d, PCIXCAP: %8x\n", hp_slot, temp_dword); + if (ctrl->pcix_support) { + if (temp_dword & (0x10000 << hp_slot)) + return PCI_SPEED_133MHz_PCIX; + if (temp_dword & (0x100 << hp_slot)) + return PCI_SPEED_66MHz_PCIX; + } + + if (temp_dword & (0x01 << hp_slot)) + return PCI_SPEED_66MHz; + + return PCI_SPEED_33MHz; +} + +static inline void enable_slot_power(struct controller *ctrl, u8 slot) +{ + u8 slot_power; + + slot_power = readb(ctrl->hpc_reg + SLOT_POWER); + slot_power |= (0x01 << slot); + writeb(slot_power, ctrl->hpc_reg + SLOT_POWER); +} + +static inline void disable_slot_power(struct controller *ctrl, u8 slot) +{ + u8 slot_power; + + slot_power = readb(ctrl->hpc_reg + SLOT_POWER); + slot_power &= ~(0x01 << slot); + writeb(slot_power, ctrl->hpc_reg + SLOT_POWER); +} + + +static inline int cpq_get_attention_status(struct controller *ctrl, struct slot *slot) +{ + u8 hp_slot; + + hp_slot = slot->device - ctrl->slot_device_offset; + + return read_amber_LED(ctrl, hp_slot); +} + + +static inline int get_slot_enabled(struct controller *ctrl, struct slot *slot) +{ + u8 hp_slot; + + hp_slot = slot->device - ctrl->slot_device_offset; + + return is_slot_enabled(ctrl, hp_slot); +} + + +static inline int cpq_get_latch_status(struct controller *ctrl, + struct slot *slot) +{ + u32 status; + u8 hp_slot; + + hp_slot = slot->device - ctrl->slot_device_offset; + dbg("%s: slot->device = %d, ctrl->slot_device_offset = %d\n", + __func__, slot->device, ctrl->slot_device_offset); + + status = (readl(ctrl->hpc_reg + INT_INPUT_CLEAR) & (0x01L << hp_slot)); + + return (status == 0) ? 1 : 0; +} + + +static inline int get_presence_status(struct controller *ctrl, + struct slot *slot) +{ + int presence_save = 0; + u8 hp_slot; + u32 tempdword; + + hp_slot = slot->device - ctrl->slot_device_offset; + + tempdword = readl(ctrl->hpc_reg + INT_INPUT_CLEAR); + presence_save = (int) ((((~tempdword) >> 23) | ((~tempdword) >> 15)) + >> hp_slot) & 0x02; + + return presence_save; +} + +static inline int wait_for_ctrl_irq(struct controller *ctrl) +{ + DECLARE_WAITQUEUE(wait, current); + int retval = 0; + + dbg("%s - start\n", __func__); + add_wait_queue(&ctrl->queue, &wait); + /* Sleep for up to 1 second to wait for the LED to change. */ + msleep_interruptible(1000); + remove_wait_queue(&ctrl->queue, &wait); + if (signal_pending(current)) + retval = -EINTR; + + dbg("%s - end\n", __func__); + return retval; +} + +#include <asm/pci_x86.h> +static inline int cpqhp_routing_table_length(void) +{ + BUG_ON(cpqhp_routing_table == NULL); + return ((cpqhp_routing_table->size - sizeof(struct irq_routing_table)) / + sizeof(struct irq_info)); +} + +#endif diff --git a/drivers/pci/hotplug/cpqphp_core.c b/drivers/pci/hotplug/cpqphp_core.c new file mode 100644 index 000000000..c94b40e64 --- /dev/null +++ b/drivers/pci/hotplug/cpqphp_core.c @@ -0,0 +1,1407 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * 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. + * + * Send feedback to <greg@kroah.com> + * + * Jan 12, 2003 - Added 66/100/133MHz PCI-X support, + * Torben Mathiasen <torben.mathiasen@hp.com> + */ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/kernel.h> +#include <linux/types.h> +#include <linux/proc_fs.h> +#include <linux/slab.h> +#include <linux/workqueue.h> +#include <linux/pci.h> +#include <linux/pci_hotplug.h> +#include <linux/init.h> +#include <linux/interrupt.h> + +#include <linux/uaccess.h> + +#include "cpqphp.h" +#include "cpqphp_nvram.h" + + +/* Global variables */ +int cpqhp_debug; +int cpqhp_legacy_mode; +struct controller *cpqhp_ctrl_list; /* = NULL */ +struct pci_func *cpqhp_slot_list[256]; +struct irq_routing_table *cpqhp_routing_table; + +/* local variables */ +static void __iomem *smbios_table; +static void __iomem *smbios_start; +static void __iomem *cpqhp_rom_start; +static bool power_mode; +static bool debug; +static int initialized; + +#define DRIVER_VERSION "0.9.8" +#define DRIVER_AUTHOR "Dan Zink <dan.zink@compaq.com>, Greg Kroah-Hartman <greg@kroah.com>" +#define DRIVER_DESC "Compaq Hot Plug PCI Controller Driver" + +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); + +module_param(power_mode, bool, 0644); +MODULE_PARM_DESC(power_mode, "Power mode enabled or not"); + +module_param(debug, bool, 0644); +MODULE_PARM_DESC(debug, "Debugging mode enabled or not"); + +#define CPQHPC_MODULE_MINOR 208 + +static inline int is_slot64bit(struct slot *slot) +{ + return (readb(slot->p_sm_slot + SMBIOS_SLOT_WIDTH) == 0x06) ? 1 : 0; +} + +static inline int is_slot66mhz(struct slot *slot) +{ + return (readb(slot->p_sm_slot + SMBIOS_SLOT_TYPE) == 0x0E) ? 1 : 0; +} + +/** + * detect_SMBIOS_pointer - find the System Management BIOS Table in mem region. + * @begin: begin pointer for region to be scanned. + * @end: end pointer for region to be scanned. + * + * Returns pointer to the head of the SMBIOS tables (or %NULL). + */ +static void __iomem *detect_SMBIOS_pointer(void __iomem *begin, void __iomem *end) +{ + void __iomem *fp; + void __iomem *endp; + u8 temp1, temp2, temp3, temp4; + int status = 0; + + endp = (end - sizeof(u32) + 1); + + for (fp = begin; fp <= endp; fp += 16) { + temp1 = readb(fp); + temp2 = readb(fp+1); + temp3 = readb(fp+2); + temp4 = readb(fp+3); + if (temp1 == '_' && + temp2 == 'S' && + temp3 == 'M' && + temp4 == '_') { + status = 1; + break; + } + } + + if (!status) + fp = NULL; + + dbg("Discovered SMBIOS Entry point at %p\n", fp); + + return fp; +} + +/** + * init_SERR - Initializes the per slot SERR generation. + * @ctrl: controller to use + * + * For unexpected switch opens + */ +static int init_SERR(struct controller *ctrl) +{ + u32 tempdword; + u32 number_of_slots; + + 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) { + writeb(0, ctrl->hpc_reg + SLOT_SERR); + tempdword++; + number_of_slots--; + } + + return 0; +} + +static int init_cpqhp_routing_table(void) +{ + int len; + + cpqhp_routing_table = pcibios_get_irq_routing_table(); + if (cpqhp_routing_table == NULL) + return -ENOMEM; + + len = cpqhp_routing_table_length(); + if (len == 0) { + kfree(cpqhp_routing_table); + cpqhp_routing_table = NULL; + return -1; + } + + return 0; +} + +/* nice debugging output */ +static void pci_print_IRQ_route(void) +{ + int len; + int loop; + u8 tbus, tdevice, tslot; + + len = cpqhp_routing_table_length(); + + dbg("bus dev func slot\n"); + for (loop = 0; loop < len; ++loop) { + tbus = cpqhp_routing_table->slots[loop].bus; + tdevice = cpqhp_routing_table->slots[loop].devfn; + tslot = cpqhp_routing_table->slots[loop].slot; + dbg("%d %d %d %d\n", tbus, tdevice >> 3, tdevice & 0x7, tslot); + + } +} + + +/** + * get_subsequent_smbios_entry: get the next entry from bios table. + * @smbios_start: where to start in the SMBIOS table + * @smbios_table: location of the SMBIOS table + * @curr: %NULL or pointer to previously returned structure + * + * Gets the first entry if previous == NULL; + * otherwise, returns the next entry. + * Uses global SMBIOS Table pointer. + * + * Returns a pointer to an SMBIOS structure or NULL if none found. + */ +static void __iomem *get_subsequent_smbios_entry(void __iomem *smbios_start, + void __iomem *smbios_table, + void __iomem *curr) +{ + u8 bail = 0; + u8 previous_byte = 1; + void __iomem *p_temp; + void __iomem *p_max; + + if (!smbios_table || !curr) + return NULL; + + /* set p_max to the end of the table */ + p_max = smbios_start + readw(smbios_table + ST_LENGTH); + + p_temp = curr; + p_temp += readb(curr + SMBIOS_GENERIC_LENGTH); + + while ((p_temp < p_max) && !bail) { + /* Look for the double NULL terminator + * The first condition is the previous byte + * and the second is the curr + */ + if (!previous_byte && !(readb(p_temp))) + bail = 1; + + previous_byte = readb(p_temp); + p_temp++; + } + + if (p_temp < p_max) + return p_temp; + else + return NULL; +} + + +/** + * get_SMBIOS_entry - return the requested SMBIOS entry or %NULL + * @smbios_start: where to start in the SMBIOS table + * @smbios_table: location of the SMBIOS table + * @type: SMBIOS structure type to be returned + * @previous: %NULL or pointer to previously returned structure + * + * Gets the first entry of the specified type if previous == %NULL; + * Otherwise, returns the next entry of the given type. + * Uses global SMBIOS Table pointer. + * Uses get_subsequent_smbios_entry. + * + * Returns a pointer to an SMBIOS structure or %NULL if none found. + */ +static void __iomem *get_SMBIOS_entry(void __iomem *smbios_start, + void __iomem *smbios_table, + u8 type, + void __iomem *previous) +{ + if (!smbios_table) + return NULL; + + if (!previous) + previous = smbios_start; + else + previous = get_subsequent_smbios_entry(smbios_start, + smbios_table, previous); + + while (previous) + if (readb(previous + SMBIOS_GENERIC_TYPE) != type) + previous = get_subsequent_smbios_entry(smbios_start, + smbios_table, previous); + else + break; + + return previous; +} + +static int ctrl_slot_cleanup(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); + old_slot = next_slot; + } + + cpqhp_remove_debugfs_files(ctrl); + + /* Free IRQ associated with hot plug device */ + free_irq(ctrl->interrupt, ctrl); + /* Unmap the memory */ + iounmap(ctrl->hpc_reg); + /* Finally reclaim PCI mem */ + release_mem_region(pci_resource_start(ctrl->pci_dev, 0), + pci_resource_len(ctrl->pci_dev, 0)); + + return 0; +} + + +/** + * get_slot_mapping - determine logical slot mapping for PCI device + * + * Won't work for more than one PCI-PCI bridge in a slot. + * + * @bus: pointer to the PCI bus structure + * @bus_num: bus number of PCI device + * @dev_num: device number of PCI device + * @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) +{ + u32 work; + long len; + long loop; + + u8 tbus, tdevice, tslot, bridgeSlot; + + dbg("%s: %p, %d, %d, %p\n", __func__, bus, bus_num, dev_num, slot); + + bridgeSlot = 0xFF; + + len = cpqhp_routing_table_length(); + for (loop = 0; loop < len; ++loop) { + tbus = cpqhp_routing_table->slots[loop].bus; + tdevice = cpqhp_routing_table->slots[loop].devfn >> 3; + tslot = cpqhp_routing_table->slots[loop].slot; + + if ((tbus == bus_num) && (tdevice == dev_num)) { + *slot = tslot; + return 0; + } else { + /* Did not get a match on the target PCI device. Check + * if the current IRQ table entry is a PCI-to-PCI + * bridge device. If so, and it's secondary bus + * matches the bus number for the target device, I need + * to save the bridge's slot number. If I can not find + * an entry for the target device, I will have to + * assume it's on the other side of the bridge, and + * assign it the bridge's slot. + */ + bus->number = tbus; + pci_bus_read_config_dword(bus, PCI_DEVFN(tdevice, 0), + PCI_CLASS_REVISION, &work); + + if ((work >> 8) == PCI_TO_PCI_BRIDGE_CLASS) { + pci_bus_read_config_dword(bus, + PCI_DEVFN(tdevice, 0), + PCI_PRIMARY_BUS, &work); + // See if bridge's secondary bus matches target bus. + if (((work >> 8) & 0x000000FF) == (long) bus_num) + bridgeSlot = tslot; + } + } + + } + + /* If we got here, we didn't find an entry in the IRQ mapping table for + * the target PCI device. If we did determine that the target device + * is on the other side of a PCI-to-PCI bridge, return the slot number + * for the bridge. + */ + if (bridgeSlot != 0xFF) { + *slot = bridgeSlot; + return 0; + } + /* Couldn't find an entry in the routing table for this PCI device */ + return -1; +} + + +/** + * cpqhp_set_attention_status - Turns the Amber LED for a slot on or off + * @ctrl: struct controller to use + * @func: PCI device/function info + * @status: LED control flag: 1 = LED on, 0 = LED off + */ +static int +cpqhp_set_attention_status(struct controller *ctrl, struct pci_func *func, + u32 status) +{ + u8 hp_slot; + + if (func == NULL) + return 1; + + hp_slot = func->device - ctrl->slot_device_offset; + + /* Wait for exclusive access to hardware */ + mutex_lock(&ctrl->crit_sect); + + if (status == 1) + amber_LED_on(ctrl, hp_slot); + else if (status == 0) + amber_LED_off(ctrl, hp_slot); + else { + /* Done with exclusive hardware access */ + mutex_unlock(&ctrl->crit_sect); + return 1; + } + + set_SOGO(ctrl); + + /* Wait for SOBS to be unset */ + wait_for_ctrl_irq(ctrl); + + /* Done with exclusive hardware access */ + mutex_unlock(&ctrl->crit_sect); + + return 0; +} + + +/** + * set_attention_status - Turns the Amber LED for a slot on or off + * @hotplug_slot: slot to change LED on + * @status: LED control flag + */ +static int set_attention_status(struct hotplug_slot *hotplug_slot, u8 status) +{ + struct pci_func *slot_func; + struct slot *slot = to_slot(hotplug_slot); + struct controller *ctrl = slot->ctrl; + u8 bus; + u8 devfn; + u8 device; + u8 function; + + dbg("%s - physical_slot = %s\n", __func__, slot_name(slot)); + + if (cpqhp_get_bus_dev(ctrl, &bus, &devfn, slot->number) == -1) + return -ENODEV; + + device = devfn >> 3; + function = devfn & 0x7; + dbg("bus, dev, fn = %d, %d, %d\n", bus, device, function); + + slot_func = cpqhp_slot_find(bus, device, function); + if (!slot_func) + return -ENODEV; + + return cpqhp_set_attention_status(ctrl, slot_func, status); +} + + +static int process_SI(struct hotplug_slot *hotplug_slot) +{ + struct pci_func *slot_func; + struct slot *slot = to_slot(hotplug_slot); + struct controller *ctrl = slot->ctrl; + u8 bus; + u8 devfn; + u8 device; + u8 function; + + dbg("%s - physical_slot = %s\n", __func__, slot_name(slot)); + + if (cpqhp_get_bus_dev(ctrl, &bus, &devfn, slot->number) == -1) + return -ENODEV; + + device = devfn >> 3; + function = devfn & 0x7; + dbg("bus, dev, fn = %d, %d, %d\n", bus, device, function); + + slot_func = cpqhp_slot_find(bus, device, function); + if (!slot_func) + return -ENODEV; + + slot_func->bus = bus; + slot_func->device = device; + slot_func->function = function; + slot_func->configured = 0; + dbg("board_added(%p, %p)\n", slot_func, ctrl); + return cpqhp_process_SI(ctrl, slot_func); +} + + +static int process_SS(struct hotplug_slot *hotplug_slot) +{ + struct pci_func *slot_func; + struct slot *slot = to_slot(hotplug_slot); + struct controller *ctrl = slot->ctrl; + u8 bus; + u8 devfn; + u8 device; + u8 function; + + dbg("%s - physical_slot = %s\n", __func__, slot_name(slot)); + + if (cpqhp_get_bus_dev(ctrl, &bus, &devfn, slot->number) == -1) + return -ENODEV; + + device = devfn >> 3; + function = devfn & 0x7; + dbg("bus, dev, fn = %d, %d, %d\n", bus, device, function); + + slot_func = cpqhp_slot_find(bus, device, function); + if (!slot_func) + return -ENODEV; + + dbg("In %s, slot_func = %p, ctrl = %p\n", __func__, slot_func, ctrl); + return cpqhp_process_SS(ctrl, slot_func); +} + + +static int hardware_test(struct hotplug_slot *hotplug_slot, u32 value) +{ + struct slot *slot = to_slot(hotplug_slot); + struct controller *ctrl = slot->ctrl; + + dbg("%s - physical_slot = %s\n", __func__, slot_name(slot)); + + return cpqhp_hardware_test(ctrl, value); +} + + +static int get_power_status(struct hotplug_slot *hotplug_slot, u8 *value) +{ + struct slot *slot = to_slot(hotplug_slot); + struct controller *ctrl = slot->ctrl; + + dbg("%s - physical_slot = %s\n", __func__, slot_name(slot)); + + *value = get_slot_enabled(ctrl, slot); + return 0; +} + +static int get_attention_status(struct hotplug_slot *hotplug_slot, u8 *value) +{ + struct slot *slot = to_slot(hotplug_slot); + struct controller *ctrl = slot->ctrl; + + dbg("%s - physical_slot = %s\n", __func__, slot_name(slot)); + + *value = cpq_get_attention_status(ctrl, slot); + return 0; +} + +static int get_latch_status(struct hotplug_slot *hotplug_slot, u8 *value) +{ + struct slot *slot = to_slot(hotplug_slot); + struct controller *ctrl = slot->ctrl; + + dbg("%s - physical_slot = %s\n", __func__, slot_name(slot)); + + *value = cpq_get_latch_status(ctrl, slot); + + return 0; +} + +static int get_adapter_status(struct hotplug_slot *hotplug_slot, u8 *value) +{ + struct slot *slot = to_slot(hotplug_slot); + struct controller *ctrl = slot->ctrl; + + dbg("%s - physical_slot = %s\n", __func__, slot_name(slot)); + + *value = get_presence_status(ctrl, slot); + + return 0; +} + +static const struct hotplug_slot_ops cpqphp_hotplug_slot_ops = { + .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, +}; + +#define SLOT_NAME_SIZE 10 + +static int ctrl_slot_setup(struct controller *ctrl, + void __iomem *smbios_start, + void __iomem *smbios_table) +{ + struct slot *slot; + struct pci_bus *bus = ctrl->pci_bus; + u8 number_of_slots; + u8 slot_device; + u8 slot_number; + u8 ctrl_slot; + u32 tempdword; + char name[SLOT_NAME_SIZE]; + void __iomem *slot_entry = NULL; + int result; + + dbg("%s\n", __func__); + + tempdword = readl(ctrl->hpc_reg + INT_INPUT_CLEAR); + + number_of_slots = readb(ctrl->hpc_reg + SLOT_MASK) & 0x0F; + slot_device = readb(ctrl->hpc_reg + SLOT_MASK) >> 4; + slot_number = ctrl->first_slot; + + while (number_of_slots) { + slot = kzalloc(sizeof(*slot), GFP_KERNEL); + if (!slot) { + result = -ENOMEM; + goto error; + } + + slot->ctrl = ctrl; + slot->bus = ctrl->bus; + slot->device = slot_device; + slot->number = slot_number; + dbg("slot->number = %u\n", slot->number); + + slot_entry = get_SMBIOS_entry(smbios_start, smbios_table, 9, + slot_entry); + + while (slot_entry && (readw(slot_entry + SMBIOS_SLOT_NUMBER) != + slot->number)) { + slot_entry = get_SMBIOS_entry(smbios_start, + smbios_table, 9, slot_entry); + } + + slot->p_sm_slot = slot_entry; + + timer_setup(&slot->task_event, cpqhp_pushbutton_thread, 0); + slot->task_event.expires = jiffies + 5 * HZ; + + /*FIXME: these capabilities aren't used but if they are + * they need to be correctly implemented + */ + slot->capabilities |= PCISLOT_REPLACE_SUPPORTED; + slot->capabilities |= PCISLOT_INTERLOCK_SUPPORTED; + + if (is_slot64bit(slot)) + slot->capabilities |= PCISLOT_64_BIT_SUPPORTED; + if (is_slot66mhz(slot)) + slot->capabilities |= PCISLOT_66_MHZ_SUPPORTED; + if (bus->cur_bus_speed == PCI_SPEED_66MHz) + slot->capabilities |= PCISLOT_66_MHZ_OPERATION; + + ctrl_slot = + slot_device - (readb(ctrl->hpc_reg + SLOT_MASK) >> 4); + + /* Check presence */ + slot->capabilities |= + ((((~tempdword) >> 23) | + ((~tempdword) >> 15)) >> ctrl_slot) & 0x02; + /* Check the switch state */ + slot->capabilities |= + ((~tempdword & 0xFF) >> ctrl_slot) & 0x01; + /* Check the slot enable */ + slot->capabilities |= + ((read_slot_enable(ctrl) << 2) >> ctrl_slot) & 0x04; + + /* register this slot with the hotplug pci core */ + snprintf(name, SLOT_NAME_SIZE, "%u", slot->number); + slot->hotplug_slot.ops = &cpqphp_hotplug_slot_ops; + + dbg("registering bus %d, dev %d, number %d, ctrl->slot_device_offset %d, slot %d\n", + slot->bus, slot->device, + slot->number, ctrl->slot_device_offset, + slot_number); + result = pci_hp_register(&slot->hotplug_slot, + ctrl->pci_dev->bus, + slot->device, + name); + if (result) { + err("pci_hp_register failed with error %d\n", result); + goto error_slot; + } + + slot->next = ctrl->slot; + ctrl->slot = slot; + + number_of_slots--; + slot_device++; + slot_number++; + } + + return 0; +error_slot: + kfree(slot); +error: + return result; +} + +static int one_time_init(void) +{ + int loop; + int retval = 0; + + if (initialized) + return 0; + + power_mode = 0; + + retval = init_cpqhp_routing_table(); + if (retval) + goto error; + + if (cpqhp_debug) + pci_print_IRQ_route(); + + 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 int cpqhpc_probe(struct pci_dev *pdev, const struct pci_device_id *ent) +{ + u8 num_of_slots = 0; + u8 hp_slot = 0; + u8 device; + u8 bus_cap; + u16 temp_word; + u16 vendor_id; + u16 subsystem_vid; + u16 subsystem_deviceid; + u32 rc; + struct controller *ctrl; + struct pci_func *func; + struct pci_bus *bus; + int err; + + err = pci_enable_device(pdev); + if (err) { + printk(KERN_ERR MY_NAME ": cannot enable PCI device %s (%d)\n", + pci_name(pdev), err); + return err; + } + + bus = pdev->subordinate; + if (!bus) { + pci_notice(pdev, "the device is not a bridge, skipping\n"); + rc = -ENODEV; + goto err_disable_device; + } + + /* Need to read VID early b/c it's used to differentiate CPQ and INTC + * discovery + */ + vendor_id = pdev->vendor; + if ((vendor_id != PCI_VENDOR_ID_COMPAQ) && + (vendor_id != PCI_VENDOR_ID_INTEL)) { + err(msg_HPC_non_compaq_or_intel); + rc = -ENODEV; + goto err_disable_device; + } + dbg("Vendor ID: %x\n", vendor_id); + + dbg("revision: %d\n", pdev->revision); + if ((vendor_id == PCI_VENDOR_ID_COMPAQ) && (!pdev->revision)) { + err(msg_HPC_rev_error); + rc = -ENODEV; + goto err_disable_device; + } + + /* Check for the proper subsystem IDs + * Intel uses a different SSID programming model than Compaq. + * For Intel, each SSID bit identifies a PHP capability. + * Also Intel HPCs may have RID=0. + */ + if ((pdev->revision <= 2) && (vendor_id != PCI_VENDOR_ID_INTEL)) { + err(msg_HPC_not_supported); + rc = -ENODEV; + goto err_disable_device; + } + + /* TODO: This code can be made to support non-Compaq or Intel + * subsystem IDs + */ + subsystem_vid = pdev->subsystem_vendor; + dbg("Subsystem Vendor ID: %x\n", subsystem_vid); + if ((subsystem_vid != PCI_VENDOR_ID_COMPAQ) && (subsystem_vid != PCI_VENDOR_ID_INTEL)) { + err(msg_HPC_non_compaq_or_intel); + rc = -ENODEV; + goto err_disable_device; + } + + ctrl = kzalloc(sizeof(struct controller), GFP_KERNEL); + if (!ctrl) { + rc = -ENOMEM; + goto err_disable_device; + } + + subsystem_deviceid = pdev->subsystem_device; + + info("Hot Plug Subsystem Device ID: %x\n", subsystem_deviceid); + + /* Set Vendor ID, so it can be accessed later from other + * functions + */ + ctrl->vendor_id = vendor_id; + + switch (subsystem_vid) { + case PCI_VENDOR_ID_COMPAQ: + if (pdev->revision >= 0x13) { /* CIOBX */ + ctrl->push_flag = 1; + ctrl->slot_switch_type = 1; + ctrl->push_button = 1; + ctrl->pci_config_space = 1; + ctrl->defeature_PHP = 1; + ctrl->pcix_support = 1; + ctrl->pcix_speed_capability = 1; + pci_read_config_byte(pdev, 0x41, &bus_cap); + if (bus_cap & 0x80) { + dbg("bus max supports 133MHz PCI-X\n"); + bus->max_bus_speed = PCI_SPEED_133MHz_PCIX; + break; + } + if (bus_cap & 0x40) { + dbg("bus max supports 100MHz PCI-X\n"); + bus->max_bus_speed = PCI_SPEED_100MHz_PCIX; + break; + } + if (bus_cap & 0x20) { + dbg("bus max supports 66MHz PCI-X\n"); + bus->max_bus_speed = PCI_SPEED_66MHz_PCIX; + break; + } + if (bus_cap & 0x10) { + dbg("bus max supports 66MHz PCI\n"); + bus->max_bus_speed = PCI_SPEED_66MHz; + break; + } + + break; + } + + switch (subsystem_deviceid) { + case PCI_SUB_HPC_ID: + /* Original 6500/7000 implementation */ + ctrl->slot_switch_type = 1; + bus->max_bus_speed = 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; + bus->max_bus_speed = 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; + bus->max_bus_speed = 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; + bus->max_bus_speed = 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; + bus->max_bus_speed = 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) + bus->max_bus_speed = PCI_SPEED_66MHz; + else + bus->max_bus_speed = PCI_SPEED_33MHz; + + /* Check for push button */ + if (subsystem_deviceid & 0x0002) + ctrl->push_button = 0; + else + ctrl->push_button = 1; + + /* Check for slot switch type (0=mechanical, 1=not mechanical) */ + if (subsystem_deviceid & 0x0004) + ctrl->slot_switch_type = 0; + else + 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; + else + ctrl->alternate_base_address = 0; + + /* PCI Config Space Index (0=not supported, 1=supported) */ + if (subsystem_deviceid & 0x0020) + ctrl->pci_config_space = 1; + else + ctrl->pci_config_space = 0; + + /* PCI-X support */ + if (subsystem_deviceid & 0x0080) { + ctrl->pcix_support = 1; + 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; + } + + /* 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", bus->max_bus_speed); + 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 = kmemdup(pdev->bus, sizeof(*ctrl->pci_bus), GFP_KERNEL); + if (!ctrl->pci_bus) { + err("out of memory\n"); + rc = -ENOMEM; + goto err_free_ctrl; + } + + ctrl->bus = pdev->bus->number; + ctrl->rev = pdev->revision; + dbg("bus device function rev: %d %d %d %d\n", ctrl->bus, + PCI_SLOT(pdev->devfn), PCI_FUNC(pdev->devfn), ctrl->rev); + + mutex_init(&ctrl->crit_sect); + init_waitqueue_head(&ctrl->queue); + + /* initialize our threads if they haven't already been started up */ + rc = one_time_init(); + if (rc) + goto err_free_bus; + + dbg("pdev = %p\n", pdev); + dbg("pci resource start %llx\n", (unsigned long long)pci_resource_start(pdev, 0)); + dbg("pci resource len %llx\n", (unsigned long long)pci_resource_len(pdev, 0)); + + if (!request_mem_region(pci_resource_start(pdev, 0), + pci_resource_len(pdev, 0), MY_NAME)) { + err("cannot reserve MMIO region\n"); + rc = -ENOMEM; + goto err_free_bus; + } + + ctrl->hpc_reg = ioremap(pci_resource_start(pdev, 0), + pci_resource_len(pdev, 0)); + if (!ctrl->hpc_reg) { + err("cannot remap MMIO region %llx @ %llx\n", + (unsigned long long)pci_resource_len(pdev, 0), + (unsigned long long)pci_resource_start(pdev, 0)); + rc = -ENODEV; + goto err_free_mem_region; + } + + /* Check for 66Mhz operation */ + bus->cur_bus_speed = get_controller_speed(ctrl); + + + /******************************************************** + * + * Save configuration headers for this and + * subordinate PCI buses + * + ********************************************************/ + + /* find the physical slot number of the first hot plug slot */ + + /* Get slot won't work for devices behind bridges, but + * in this case it will always be called for the "base" + * bus/dev/func of a slot. + * CS: this is leveraging the PCIIRQ routing code from the kernel + * (pci-pc.c: get_irq_routing_table) */ + rc = get_slot_mapping(ctrl->pci_bus, pdev->bus->number, + (readb(ctrl->hpc_reg + SLOT_MASK) >> 4), + &(ctrl->first_slot)); + dbg("get_slot_mapping: first_slot = %d, returned = %d\n", + ctrl->first_slot, rc); + if (rc) { + err(msg_initialization_err, rc); + goto err_iounmap; + } + + /* Store PCI Config Space for all devices on this bus */ + rc = cpqhp_save_config(ctrl, ctrl->bus, readb(ctrl->hpc_reg + SLOT_MASK)); + if (rc) { + err("%s: unable to save PCI configuration data, error %d\n", + __func__, rc); + goto err_iounmap; + } + + /* + * Get IO, memory, and IRQ resources for new devices + */ + /* The next line is required for cpqhp_find_available_resources */ + ctrl->interrupt = pdev->irq; + if (ctrl->interrupt < 0x10) { + cpqhp_legacy_mode = 1; + dbg("System seems to be configured for Full Table Mapped MPS mode\n"); + } + + ctrl->cfgspc_irq = 0; + pci_read_config_byte(pdev, PCI_INTERRUPT_LINE, &ctrl->cfgspc_irq); + + rc = cpqhp_find_available_resources(ctrl, cpqhp_rom_start); + ctrl->add_support = !rc; + if (rc) { + dbg("cpqhp_find_available_resources = 0x%x\n", rc); + err("unable to locate PCI configuration resources for hot plug add.\n"); + goto err_iounmap; + } + + /* + * Finish setting up the hot plug ctrl device + */ + ctrl->slot_device_offset = readb(ctrl->hpc_reg + SLOT_MASK) >> 4; + dbg("NumSlots %d\n", ctrl->slot_device_offset); + + ctrl->next_event = 0; + + /* Setup the slot information structures */ + rc = ctrl_slot_setup(ctrl, smbios_start, smbios_table); + if (rc) { + err(msg_initialization_err, 6); + err("%s: unable to save PCI configuration data, error %d\n", + __func__, rc); + goto err_iounmap; + } + + /* Mask all general input interrupts */ + writel(0xFFFFFFFFL, ctrl->hpc_reg + INT_MASK); + + /* set up the interrupt */ + dbg("HPC interrupt = %d\n", ctrl->interrupt); + if (request_irq(ctrl->interrupt, cpqhp_ctrl_intr, + IRQF_SHARED, MY_NAME, ctrl)) { + err("Can't get irq %d for the hotplug pci controller\n", + ctrl->interrupt); + rc = -ENODEV; + goto err_iounmap; + } + + /* Enable Shift Out interrupt and clear it, also enable SERR on power + * fault + */ + temp_word = readw(ctrl->hpc_reg + MISC); + temp_word |= 0x4006; + writew(temp_word, ctrl->hpc_reg + MISC); + + /* Changed 05/05/97 to clear all interrupts at start */ + writel(0xFFFFFFFFL, ctrl->hpc_reg + INT_INPUT_CLEAR); + + ctrl->ctrl_int_comp = readl(ctrl->hpc_reg + INT_INPUT_CLEAR); + + writel(0x0L, ctrl->hpc_reg + INT_MASK); + + if (!cpqhp_ctrl_list) { + cpqhp_ctrl_list = ctrl; + ctrl->next = NULL; + } else { + ctrl->next = cpqhp_ctrl_list; + cpqhp_ctrl_list = ctrl; + } + + /* turn off empty slots here unless command line option "ON" set + * Wait for exclusive access to hardware + */ + mutex_lock(&ctrl->crit_sect); + + num_of_slots = readb(ctrl->hpc_reg + SLOT_MASK) & 0x0F; + + /* find first device number for the ctrl */ + device = readb(ctrl->hpc_reg + SLOT_MASK) >> 4; + + while (num_of_slots) { + dbg("num_of_slots: %d\n", num_of_slots); + func = cpqhp_slot_find(ctrl->bus, device, 0); + if (!func) + break; + + hp_slot = func->device - ctrl->slot_device_offset; + dbg("hp_slot: %d\n", hp_slot); + + /* We have to save the presence info for these slots */ + temp_word = ctrl->ctrl_int_comp >> 16; + func->presence_save = (temp_word >> hp_slot) & 0x01; + func->presence_save |= (temp_word >> (hp_slot + 7)) & 0x02; + + if (ctrl->ctrl_int_comp & (0x1L << hp_slot)) + func->switch_save = 0; + else + func->switch_save = 0x10; + + if (!power_mode) + if (!func->is_a_board) { + green_LED_off(ctrl, hp_slot); + slot_disable(ctrl, hp_slot); + } + + device++; + num_of_slots--; + } + + if (!power_mode) { + set_SOGO(ctrl); + /* Wait for SOBS to be unset */ + wait_for_ctrl_irq(ctrl); + } + + rc = init_SERR(ctrl); + if (rc) { + err("init_SERR failed\n"); + mutex_unlock(&ctrl->crit_sect); + goto err_free_irq; + } + + /* Done with exclusive hardware access */ + mutex_unlock(&ctrl->crit_sect); + + cpqhp_create_debugfs_files(ctrl); + + return 0; + +err_free_irq: + free_irq(ctrl->interrupt, ctrl); +err_iounmap: + iounmap(ctrl->hpc_reg); +err_free_mem_region: + release_mem_region(pci_resource_start(pdev, 0), pci_resource_len(pdev, 0)); +err_free_bus: + kfree(ctrl->pci_bus); +err_free_ctrl: + kfree(ctrl); +err_disable_device: + pci_disable_device(pdev); + return rc; +} + +static 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; + + compaq_nvram_store(cpqhp_rom_start); + + ctrl = cpqhp_ctrl_list; + + while (ctrl) { + if (ctrl->hpc_reg) { + u16 misc; + rc = read_slot_enable(ctrl); + + writeb(0, ctrl->hpc_reg + SLOT_SERR); + writel(0xFFFFFFC0L | ~rc, ctrl->hpc_reg + INT_MASK); + + misc = readw(ctrl->hpc_reg + MISC); + misc &= 0xFFFD; + writew(misc, ctrl->hpc_reg + MISC); + } + + ctrl_slot_cleanup(ctrl); + + res = ctrl->io_head; + while (res) { + tres = res; + res = res->next; + kfree(tres); + } + + res = ctrl->mem_head; + while (res) { + tres = res; + res = res->next; + kfree(tres); + } + + res = ctrl->p_mem_head; + while (res) { + tres = res; + res = res->next; + kfree(tres); + } + + res = ctrl->bus_head; + while (res) { + tres = res; + res = res->next; + kfree(tres); + } + + kfree(ctrl->pci_bus); + + tctrl = ctrl; + ctrl = ctrl->next; + kfree(tctrl); + } + + for (loop = 0; loop < 256; loop++) { + next = cpqhp_slot_list[loop]; + while (next != NULL) { + res = next->io_head; + while (res) { + tres = res; + res = res->next; + kfree(tres); + } + + res = next->mem_head; + while (res) { + tres = res; + res = res->next; + kfree(tres); + } + + res = next->p_mem_head; + while (res) { + tres = res; + res = res->next; + kfree(tres); + } + + res = next->bus_head; + while (res) { + tres = res; + res = res->next; + kfree(tres); + } + + TempSlot = next; + next = next->next; + kfree(TempSlot); + } + } + + /* Stop the notification mechanism */ + if (initialized) + cpqhp_event_stop_thread(); + + /* unmap the rom address */ + if (cpqhp_rom_start) + iounmap(cpqhp_rom_start); + if (smbios_start) + iounmap(smbios_start); +} + +static const struct pci_device_id hpcd_pci_tbl[] = { + { + /* handle any PCI Hotplug controller */ + .class = ((PCI_CLASS_SYSTEM_PCI_HOTPLUG << 8) | 0x00), + .class_mask = ~0, + + /* no matter who makes it */ + .vendor = PCI_ANY_ID, + .device = PCI_ANY_ID, + .subvendor = PCI_ANY_ID, + .subdevice = PCI_ANY_ID, + + }, { /* end: all zeroes */ } +}; + +MODULE_DEVICE_TABLE(pci, hpcd_pci_tbl); + +static struct pci_driver cpqhpc_driver = { + .name = "compaq_pci_hotplug", + .id_table = hpcd_pci_tbl, + .probe = cpqhpc_probe, + /* remove: cpqhpc_remove_one, */ +}; + +static int __init cpqhpc_init(void) +{ + int result; + + cpqhp_debug = debug; + + info(DRIVER_DESC " version: " DRIVER_VERSION "\n"); + cpqhp_initialize_debugfs(); + result = pci_register_driver(&cpqhpc_driver); + dbg("pci_register_driver = %d\n", result); + return result; +} + +static void __exit cpqhpc_cleanup(void) +{ + dbg("unload_cpqphpd()\n"); + unload_cpqphpd(); + + dbg("pci_unregister_driver\n"); + pci_unregister_driver(&cpqhpc_driver); + cpqhp_shutdown_debugfs(); +} + +module_init(cpqhpc_init); +module_exit(cpqhpc_cleanup); diff --git a/drivers/pci/hotplug/cpqphp_ctrl.c b/drivers/pci/hotplug/cpqphp_ctrl.c new file mode 100644 index 000000000..e429ecddc --- /dev/null +++ b/drivers/pci/hotplug/cpqphp_ctrl.c @@ -0,0 +1,2911 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * 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. + * + * Send feedback to <greg@kroah.com> + * + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/types.h> +#include <linux/slab.h> +#include <linux/workqueue.h> +#include <linux/interrupt.h> +#include <linux/delay.h> +#include <linux/wait.h> +#include <linux/pci.h> +#include <linux/pci_hotplug.h> +#include <linux/kthread.h> +#include "cpqphp.h" + +static u32 configure_new_device(struct controller *ctrl, struct pci_func *func, + u8 behind_bridge, struct resource_lists *resources); +static int configure_new_function(struct controller *ctrl, struct pci_func *func, + u8 behind_bridge, struct resource_lists *resources); +static void interrupt_event_handler(struct controller *ctrl); + + +static struct task_struct *cpqhp_event_thread; +static struct timer_list *pushbutton_pending; /* = NULL */ + +/* delay is in jiffies to wait for */ +static void long_delay(int delay) +{ + /* + * XXX(hch): if someone is bored please convert all callers + * to call msleep_interruptible directly. They really want + * to specify timeouts in natural units and spend a lot of + * effort converting them to jiffies.. + */ + msleep_interruptible(jiffies_to_msecs(delay)); +} + + +/* FIXME: The following line needs to be somewhere else... */ +#define WRONG_BUS_FREQUENCY 0x07 +static u8 handle_switch_change(u8 change, struct controller *ctrl) +{ + int hp_slot; + u8 rc = 0; + u16 temp_word; + struct pci_func *func; + struct event_info *taskInfo; + + if (!change) + return 0; + + /* Switch Change */ + dbg("cpqsbd: Switch interrupt received.\n"); + + for (hp_slot = 0; hp_slot < 6; hp_slot++) { + if (change & (0x1L << hp_slot)) { + /* + * this one changed. + */ + func = cpqhp_slot_find(ctrl->bus, + (hp_slot + ctrl->slot_device_offset), 0); + + /* this is the structure that tells the worker thread + * what to do + */ + taskInfo = &(ctrl->event_queue[ctrl->next_event]); + ctrl->next_event = (ctrl->next_event + 1) % 10; + taskInfo->hp_slot = hp_slot; + + rc++; + + temp_word = ctrl->ctrl_int_comp >> 16; + func->presence_save = (temp_word >> hp_slot) & 0x01; + func->presence_save |= (temp_word >> (hp_slot + 7)) & 0x02; + + if (ctrl->ctrl_int_comp & (0x1L << hp_slot)) { + /* + * Switch opened + */ + + func->switch_save = 0; + + taskInfo->event_type = INT_SWITCH_OPEN; + } else { + /* + * Switch closed + */ + + func->switch_save = 0x10; + + taskInfo->event_type = INT_SWITCH_CLOSE; + } + } + } + + return rc; +} + +/** + * cpqhp_find_slot - find the struct slot of given device + * @ctrl: scan lots of this controller + * @device: the device id to find + */ +static struct slot *cpqhp_find_slot(struct controller *ctrl, u8 device) +{ + struct slot *slot = ctrl->slot; + + while (slot && (slot->device != device)) + slot = slot->next; + + return slot; +} + + +static u8 handle_presence_change(u16 change, struct controller *ctrl) +{ + int hp_slot; + u8 rc = 0; + u8 temp_byte; + u16 temp_word; + struct pci_func *func; + struct event_info *taskInfo; + struct slot *p_slot; + + if (!change) + return 0; + + /* + * Presence Change + */ + dbg("cpqsbd: Presence/Notify input change.\n"); + dbg(" Changed bits are 0x%4.4x\n", change); + + for (hp_slot = 0; hp_slot < 6; hp_slot++) { + if (change & (0x0101 << hp_slot)) { + /* + * this one changed. + */ + func = cpqhp_slot_find(ctrl->bus, + (hp_slot + ctrl->slot_device_offset), 0); + + taskInfo = &(ctrl->event_queue[ctrl->next_event]); + ctrl->next_event = (ctrl->next_event + 1) % 10; + taskInfo->hp_slot = hp_slot; + + rc++; + + p_slot = cpqhp_find_slot(ctrl, hp_slot + (readb(ctrl->hpc_reg + SLOT_MASK) >> 4)); + if (!p_slot) + return 0; + + /* If the switch closed, must be a button + * If not in button mode, nevermind + */ + if (func->switch_save && (ctrl->push_button == 1)) { + temp_word = ctrl->ctrl_int_comp >> 16; + temp_byte = (temp_word >> hp_slot) & 0x01; + temp_byte |= (temp_word >> (hp_slot + 7)) & 0x02; + + if (temp_byte != func->presence_save) { + /* + * button Pressed (doesn't do anything) + */ + dbg("hp_slot %d button pressed\n", hp_slot); + taskInfo->event_type = INT_BUTTON_PRESS; + } else { + /* + * button Released - TAKE ACTION!!!! + */ + dbg("hp_slot %d button released\n", hp_slot); + taskInfo->event_type = INT_BUTTON_RELEASE; + + /* Cancel if we are still blinking */ + if ((p_slot->state == BLINKINGON_STATE) + || (p_slot->state == BLINKINGOFF_STATE)) { + taskInfo->event_type = INT_BUTTON_CANCEL; + dbg("hp_slot %d button cancel\n", hp_slot); + } else if ((p_slot->state == POWERON_STATE) + || (p_slot->state == POWEROFF_STATE)) { + /* info(msg_button_ignore, p_slot->number); */ + taskInfo->event_type = INT_BUTTON_IGNORE; + dbg("hp_slot %d button ignore\n", hp_slot); + } + } + } else { + /* Switch is open, assume a presence change + * Save the presence state + */ + temp_word = ctrl->ctrl_int_comp >> 16; + func->presence_save = (temp_word >> hp_slot) & 0x01; + func->presence_save |= (temp_word >> (hp_slot + 7)) & 0x02; + + if ((!(ctrl->ctrl_int_comp & (0x010000 << hp_slot))) || + (!(ctrl->ctrl_int_comp & (0x01000000 << hp_slot)))) { + /* Present */ + taskInfo->event_type = INT_PRESENCE_ON; + } else { + /* Not Present */ + taskInfo->event_type = INT_PRESENCE_OFF; + } + } + } + } + + return rc; +} + + +static u8 handle_power_fault(u8 change, struct controller *ctrl) +{ + int hp_slot; + u8 rc = 0; + struct pci_func *func; + struct event_info *taskInfo; + + if (!change) + return 0; + + /* + * power fault + */ + + info("power fault interrupt\n"); + + for (hp_slot = 0; hp_slot < 6; hp_slot++) { + if (change & (0x01 << hp_slot)) { + /* + * this one changed. + */ + func = cpqhp_slot_find(ctrl->bus, + (hp_slot + ctrl->slot_device_offset), 0); + + taskInfo = &(ctrl->event_queue[ctrl->next_event]); + ctrl->next_event = (ctrl->next_event + 1) % 10; + taskInfo->hp_slot = hp_slot; + + rc++; + + if (ctrl->ctrl_int_comp & (0x00000100 << hp_slot)) { + /* + * power fault Cleared + */ + func->status = 0x00; + + taskInfo->event_type = INT_POWER_FAULT_CLEAR; + } else { + /* + * power fault + */ + taskInfo->event_type = INT_POWER_FAULT; + + if (ctrl->rev < 4) { + amber_LED_on(ctrl, hp_slot); + green_LED_off(ctrl, hp_slot); + set_SOGO(ctrl); + + /* this is a fatal condition, we want + * to crash the machine to protect from + * data corruption. simulated_NMI + * shouldn't ever return */ + /* FIXME + simulated_NMI(hp_slot, ctrl); */ + + /* The following code causes a software + * crash just in case simulated_NMI did + * return */ + /*FIXME + panic(msg_power_fault); */ + } else { + /* set power fault status for this board */ + func->status = 0xFF; + info("power fault bit %x set\n", hp_slot); + } + } + } + } + + return rc; +} + + +/** + * sort_by_size - sort nodes on the list by their length, smallest first. + * @head: list to sort + */ +static int sort_by_size(struct pci_resource **head) +{ + struct pci_resource *current_res; + struct pci_resource *next_res; + int out_of_order = 1; + + if (!(*head)) + return 1; + + if (!((*head)->next)) + return 0; + + while (out_of_order) { + out_of_order = 0; + + /* Special case for swapping list head */ + if (((*head)->next) && + ((*head)->length > (*head)->next->length)) { + out_of_order++; + current_res = *head; + *head = (*head)->next; + current_res->next = (*head)->next; + (*head)->next = current_res; + } + + current_res = *head; + + while (current_res->next && current_res->next->next) { + if (current_res->next->length > current_res->next->next->length) { + out_of_order++; + next_res = current_res->next; + current_res->next = current_res->next->next; + current_res = current_res->next; + next_res->next = current_res->next; + current_res->next = next_res; + } else + current_res = current_res->next; + } + } /* End of out_of_order loop */ + + return 0; +} + + +/** + * sort_by_max_size - sort nodes on the list by their length, largest first. + * @head: list to sort + */ +static int sort_by_max_size(struct pci_resource **head) +{ + struct pci_resource *current_res; + struct pci_resource *next_res; + int out_of_order = 1; + + if (!(*head)) + return 1; + + if (!((*head)->next)) + return 0; + + while (out_of_order) { + out_of_order = 0; + + /* Special case for swapping list head */ + if (((*head)->next) && + ((*head)->length < (*head)->next->length)) { + out_of_order++; + current_res = *head; + *head = (*head)->next; + current_res->next = (*head)->next; + (*head)->next = current_res; + } + + current_res = *head; + + while (current_res->next && current_res->next->next) { + if (current_res->next->length < current_res->next->next->length) { + out_of_order++; + next_res = current_res->next; + current_res->next = current_res->next->next; + current_res = current_res->next; + next_res->next = current_res->next; + current_res->next = next_res; + } else + current_res = current_res->next; + } + } /* End of out_of_order loop */ + + return 0; +} + + +/** + * do_pre_bridge_resource_split - find node of resources that are unused + * @head: new list head + * @orig_head: original list head + * @alignment: max node size (?) + */ +static struct pci_resource *do_pre_bridge_resource_split(struct pci_resource **head, + struct pci_resource **orig_head, u32 alignment) +{ + struct pci_resource *prevnode = NULL; + struct pci_resource *node; + struct pci_resource *split_node; + u32 rc; + u32 temp_dword; + dbg("do_pre_bridge_resource_split\n"); + + if (!(*head) || !(*orig_head)) + return NULL; + + rc = cpqhp_resource_sort_and_combine(head); + + if (rc) + return NULL; + + if ((*head)->base != (*orig_head)->base) + return NULL; + + if ((*head)->length == (*orig_head)->length) + return NULL; + + + /* If we got here, there the bridge requires some of the resource, but + * we may be able to split some off of the front + */ + + node = *head; + + if (node->length & (alignment - 1)) { + /* this one isn't an aligned length, so we'll make a new entry + * and split it up. + */ + split_node = kmalloc(sizeof(*split_node), GFP_KERNEL); + + if (!split_node) + return NULL; + + temp_dword = (node->length | (alignment-1)) + 1 - alignment; + + split_node->base = node->base; + split_node->length = temp_dword; + + node->length -= temp_dword; + node->base += split_node->length; + + /* Put it in the list */ + *head = split_node; + split_node->next = node; + } + + if (node->length < alignment) + return NULL; + + /* Now unlink it */ + if (*head == node) { + *head = node->next; + } else { + prevnode = *head; + while (prevnode->next != node) + prevnode = prevnode->next; + + prevnode->next = node->next; + } + node->next = NULL; + + return node; +} + + +/** + * do_bridge_resource_split - find one node of resources that aren't in use + * @head: list head + * @alignment: max node size (?) + */ +static struct pci_resource *do_bridge_resource_split(struct pci_resource **head, u32 alignment) +{ + struct pci_resource *prevnode = NULL; + struct pci_resource *node; + u32 rc; + u32 temp_dword; + + rc = cpqhp_resource_sort_and_combine(head); + + if (rc) + return NULL; + + node = *head; + + while (node->next) { + prevnode = node; + node = node->next; + kfree(prevnode); + } + + if (node->length < alignment) + goto error; + + if (node->base & (alignment - 1)) { + /* Short circuit if adjusted size is too small */ + temp_dword = (node->base | (alignment-1)) + 1; + if ((node->length - (temp_dword - node->base)) < alignment) + goto error; + + node->length -= (temp_dword - node->base); + node->base = temp_dword; + } + + if (node->length & (alignment - 1)) + /* There's stuff in use after this node */ + goto error; + + return node; +error: + kfree(node); + return NULL; +} + + +/** + * get_io_resource - find first node of given size not in ISA aliasing window. + * @head: list to search + * @size: size of node to find, must be a power of two. + * + * Description: This function sorts the resource list by size and then + * returns 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; + + if (temp) + temp->next = max->next; + } + + max->next = NULL; + break; + } + + return max; +} + + +/** + * get_resource - find resource of given size and split up larger ones. + * @head: the list to search for resources + * @size: the size limit to use + * + * Description: This function sorts the resource list by size and then + * returns the first node of "size" length. If it finds a node + * larger than "size" it will split it up. + * + * size must be a power of two. + */ +static struct pci_resource *get_resource(struct pci_resource **head, u32 size) +{ + struct pci_resource *prevnode; + struct pci_resource *node; + struct pci_resource *split_node; + u32 temp_dword; + + if (cpqhp_resource_sort_and_combine(head)) + return NULL; + + if (sort_by_size(head)) + return NULL; + + for (node = *head; node; node = node->next) { + dbg("%s: req_size =%x node=%p, base=%x, length=%x\n", + __func__, size, node, node->base, node->length); + if (node->length < size) + continue; + + if (node->base & (size - 1)) { + dbg("%s: not aligned\n", __func__); + /* this one isn't base aligned properly + * so we'll make a new entry and split it up + */ + temp_dword = (node->base | (size-1)) + 1; + + /* Short circuit if adjusted size is too small */ + if ((node->length - (temp_dword - node->base)) < size) + continue; + + split_node = kmalloc(sizeof(*split_node), GFP_KERNEL); + + if (!split_node) + return NULL; + + split_node->base = node->base; + split_node->length = temp_dword - node->base; + node->base = temp_dword; + node->length -= split_node->length; + + split_node->next = node->next; + node->next = split_node; + } /* End of non-aligned base */ + + /* Don't need to check if too small since we already did */ + if (node->length > size) { + dbg("%s: too big\n", __func__); + /* this one is longer than we need + * so we'll make a new entry and split it up + */ + split_node = kmalloc(sizeof(*split_node), GFP_KERNEL); + + if (!split_node) + return NULL; + + split_node->base = node->base + size; + split_node->length = node->length - size; + node->length = size; + + /* Put it in the list */ + split_node->next = node->next; + node->next = split_node; + } /* End of too big on top end */ + + dbg("%s: got one!!!\n", __func__); + /* If we got here, then it is the right size + * Now take it out of the list */ + if (*head == node) { + *head = node->next; + } else { + prevnode = *head; + while (prevnode->next != node) + prevnode = prevnode->next; + + prevnode->next = node->next; + } + node->next = NULL; + break; + } + return node; +} + + +/** + * cpqhp_resource_sort_and_combine - sort nodes by base addresses and clean up + * @head: the list to sort and clean up + * + * Description: Sorts all of the nodes in the list in ascending order by + * their base addresses. Also does garbage collection by + * combining adjacent nodes. + * + * Returns %0 if success. + */ +int cpqhp_resource_sort_and_combine(struct pci_resource **head) +{ + struct pci_resource *node1; + struct pci_resource *node2; + int out_of_order = 1; + + dbg("%s: head = %p, *head = %p\n", __func__, head, *head); + + if (!(*head)) + return 1; + + dbg("*head->next = %p\n", (*head)->next); + + if (!(*head)->next) + return 0; /* only one item on the list, already sorted! */ + + dbg("*head->base = 0x%x\n", (*head)->base); + dbg("*head->next->base = 0x%x\n", (*head)->next->base); + while (out_of_order) { + out_of_order = 0; + + /* Special case for swapping list head */ + if (((*head)->next) && + ((*head)->base > (*head)->next->base)) { + node1 = *head; + (*head) = (*head)->next; + node1->next = (*head)->next; + (*head)->next = node1; + out_of_order++; + } + + node1 = (*head); + + while (node1->next && node1->next->next) { + if (node1->next->base > node1->next->next->base) { + out_of_order++; + node2 = node1->next; + node1->next = node1->next->next; + node1 = node1->next; + node2->next = node1->next; + node1->next = node2; + } else + node1 = node1->next; + } + } /* End of out_of_order loop */ + + node1 = *head; + + while (node1 && node1->next) { + if ((node1->base + node1->length) == node1->next->base) { + /* Combine */ + dbg("8..\n"); + node1->length += node1->next->length; + node2 = node1->next; + node1->next = node1->next->next; + kfree(node2); + } else + node1 = node1->next; + } + + return 0; +} + + +irqreturn_t cpqhp_ctrl_intr(int IRQ, void *data) +{ + struct controller *ctrl = data; + u8 schedule_flag = 0; + u8 reset; + u16 misc; + u32 Diff; + + + misc = readw(ctrl->hpc_reg + MISC); + /* + * Check to see if it was our interrupt + */ + if (!(misc & 0x000C)) + return IRQ_NONE; + + if (misc & 0x0004) { + /* + * Serial Output interrupt Pending + */ + + /* Clear the interrupt */ + misc |= 0x0004; + writew(misc, ctrl->hpc_reg + MISC); + + /* Read to clear posted writes */ + misc = readw(ctrl->hpc_reg + MISC); + + dbg("%s - waking up\n", __func__); + wake_up_interruptible(&ctrl->queue); + } + + if (misc & 0x0008) { + /* General-interrupt-input interrupt Pending */ + Diff = readl(ctrl->hpc_reg + INT_INPUT_CLEAR) ^ ctrl->ctrl_int_comp; + + ctrl->ctrl_int_comp = readl(ctrl->hpc_reg + INT_INPUT_CLEAR); + + /* Clear the interrupt */ + writel(Diff, ctrl->hpc_reg + INT_INPUT_CLEAR); + + /* Read it back to clear any posted writes */ + readl(ctrl->hpc_reg + INT_INPUT_CLEAR); + + if (!Diff) + /* Clear all interrupts */ + writel(0xFFFFFFFF, ctrl->hpc_reg + INT_INPUT_CLEAR); + + schedule_flag += handle_switch_change((u8)(Diff & 0xFFL), ctrl); + schedule_flag += handle_presence_change((u16)((Diff & 0xFFFF0000L) >> 16), ctrl); + schedule_flag += handle_power_fault((u8)((Diff & 0xFF00L) >> 8), ctrl); + } + + reset = readb(ctrl->hpc_reg + RESET_FREQ_MODE); + if (reset & 0x40) { + /* Bus reset has completed */ + reset &= 0xCF; + writeb(reset, ctrl->hpc_reg + RESET_FREQ_MODE); + reset = readb(ctrl->hpc_reg + RESET_FREQ_MODE); + wake_up_interruptible(&ctrl->queue); + } + + if (schedule_flag) { + wake_up_process(cpqhp_event_thread); + dbg("Waking even thread"); + } + return IRQ_HANDLED; +} + + +/** + * cpqhp_slot_create - Creates a node and adds it to the proper bus. + * @busnumber: bus where new node is to be located + * + * Returns pointer to the new node or %NULL if unsuccessful. + */ +struct pci_func *cpqhp_slot_create(u8 busnumber) +{ + struct pci_func *new_slot; + struct pci_func *next; + + new_slot = kzalloc(sizeof(*new_slot), GFP_KERNEL); + if (new_slot == NULL) + return new_slot; + + new_slot->next = NULL; + new_slot->configured = 1; + + if (cpqhp_slot_list[busnumber] == NULL) { + cpqhp_slot_list[busnumber] = new_slot; + } else { + next = cpqhp_slot_list[busnumber]; + while (next->next != NULL) + next = next->next; + next->next = new_slot; + } + return new_slot; +} + + +/** + * slot_remove - Removes a node from the linked list of slots. + * @old_slot: slot to remove + * + * Returns %0 if successful, !0 otherwise. + */ +static int slot_remove(struct pci_func *old_slot) +{ + struct pci_func *next; + + if (old_slot == NULL) + return 1; + + next = cpqhp_slot_list[old_slot->bus]; + if (next == NULL) + return 1; + + if (next == old_slot) { + cpqhp_slot_list[old_slot->bus] = old_slot->next; + cpqhp_destroy_board_resources(old_slot); + kfree(old_slot); + return 0; + } + + while ((next->next != old_slot) && (next->next != NULL)) + next = next->next; + + if (next->next == old_slot) { + next->next = old_slot->next; + cpqhp_destroy_board_resources(old_slot); + kfree(old_slot); + return 0; + } else + return 2; +} + + +/** + * bridge_slot_remove - Removes a node from the linked list of slots. + * @bridge: bridge to remove + * + * Returns %0 if successful, !0 otherwise. + */ +static int bridge_slot_remove(struct pci_func *bridge) +{ + u8 subordinateBus, secondaryBus; + u8 tempBus; + struct pci_func *next; + + secondaryBus = (bridge->config_space[0x06] >> 8) & 0xFF; + subordinateBus = (bridge->config_space[0x06] >> 16) & 0xFF; + + for (tempBus = secondaryBus; tempBus <= subordinateBus; tempBus++) { + next = cpqhp_slot_list[tempBus]; + + while (!slot_remove(next)) + next = cpqhp_slot_list[tempBus]; + } + + next = cpqhp_slot_list[bridge->bus]; + + if (next == NULL) + return 1; + + if (next == bridge) { + cpqhp_slot_list[bridge->bus] = bridge->next; + goto out; + } + + while ((next->next != bridge) && (next->next != NULL)) + next = next->next; + + if (next->next != bridge) + return 2; + next->next = bridge->next; +out: + kfree(bridge); + return 0; +} + + +/** + * cpqhp_slot_find - Looks for a node by bus, and device, multiple functions accessed + * @bus: bus to find + * @device: device to find + * @index: is %0 for first function found, %1 for the second... + * + * Returns pointer to the node if successful, %NULL otherwise. + */ +struct pci_func *cpqhp_slot_find(u8 bus, u8 device, u8 index) +{ + int found = -1; + struct pci_func *func; + + func = cpqhp_slot_list[bus]; + + if ((func == NULL) || ((func->device == device) && (index == 0))) + return func; + + if (func->device == device) + found++; + + while (func->next != NULL) { + func = func->next; + + if (func->device == device) + found++; + + if (found == index) + return func; + } + + return NULL; +} + + +/* DJZ: I don't think is_bridge will work as is. + * FIXME */ +static int is_bridge(struct pci_func *func) +{ + /* Check the header type */ + if (((func->config_space[0x03] >> 16) & 0xFF) == 0x01) + return 1; + else + return 0; +} + + +/** + * set_controller_speed - set the frequency and/or mode of a specific controller segment. + * @ctrl: controller to change frequency/mode for. + * @adapter_speed: the speed of the adapter we want to match. + * @hp_slot: the slot number where the adapter is installed. + * + * Returns %0 if we successfully change frequency and/or mode to match the + * adapter speed. + */ +static u8 set_controller_speed(struct controller *ctrl, u8 adapter_speed, u8 hp_slot) +{ + struct slot *slot; + struct pci_bus *bus = ctrl->pci_bus; + u8 reg; + u8 slot_power = readb(ctrl->hpc_reg + SLOT_POWER); + u16 reg16; + u32 leds = readl(ctrl->hpc_reg + LED_CONTROL); + + if (bus->cur_bus_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 (get_presence_status(ctrl, slot) == 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 (bus->cur_bus_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 ((bus->cur_bus_speed > adapter_speed) && (!ctrl->pcix_speed_capability)) + return 1; + + /* But we allow the adapter to run at a lower rate if possible */ + if ((bus->cur_bus_speed < adapter_speed) && (!ctrl->pcix_speed_capability)) + return 0; + + /* We try to set the max speed supported by both the adapter and + * controller + */ + if (bus->max_bus_speed < adapter_speed) { + if (bus->cur_bus_speed == bus->max_bus_speed) + return 0; + adapter_speed = bus->max_bus_speed; + } + + 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); + + /* Re-enable 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 (((bus->cur_bus_speed == PCI_SPEED_66MHz) && (adapter_speed == PCI_SPEED_66MHz_PCIX)) || + ((bus->cur_bus_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); + + bus->cur_bus_speed = adapter_speed; + slot = cpqhp_find_slot(ctrl, hp_slot + ctrl->slot_device_offset); + + info("Successfully changed frequency/mode for adapter in slot %d\n", + slot->number); + return 0; +} + +/* the following routines constitute the bulk of the + * hotplug controller logic + */ + + +/** + * board_replaced - Called after a board has been replaced in the system. + * @func: PCI device/function information + * @ctrl: hotplug controller + * + * This is only used if we don't have resources for hot add. + * Turns power on for the board. + * Checks to see if board is the same. + * If board is same, reconfigures it. + * If board isn't same, turns it back off. + */ +static u32 board_replaced(struct pci_func *func, struct controller *ctrl) +{ + struct pci_bus *bus = ctrl->pci_bus; + u8 hp_slot; + u8 temp_byte; + u8 adapter_speed; + u32 rc = 0; + + hp_slot = func->device - ctrl->slot_device_offset; + + /* + * The switch is open. + */ + if (readl(ctrl->hpc_reg + INT_INPUT_CLEAR) & (0x01L << hp_slot)) + rc = INTERLOCK_OPEN; + /* + * The board is already on + */ + else if (is_slot_enabled(ctrl, hp_slot)) + rc = CARD_FUNCTIONING; + else { + mutex_lock(&ctrl->crit_sect); + + /* turn on board without attaching to the bus */ + enable_slot_power(ctrl, hp_slot); + + set_SOGO(ctrl); + + /* Wait for SOBS to be unset */ + wait_for_ctrl_irq(ctrl); + + /* Change bits in slot power register to force another shift out + * NOTE: this is to work around the timer bug */ + temp_byte = readb(ctrl->hpc_reg + SLOT_POWER); + writeb(0x00, ctrl->hpc_reg + SLOT_POWER); + writeb(temp_byte, ctrl->hpc_reg + SLOT_POWER); + + set_SOGO(ctrl); + + /* Wait for SOBS to be unset */ + wait_for_ctrl_irq(ctrl); + + adapter_speed = get_adapter_speed(ctrl, hp_slot); + if (bus->cur_bus_speed != adapter_speed) + if (set_controller_speed(ctrl, adapter_speed, hp_slot)) + rc = WRONG_BUS_FREQUENCY; + + /* turn off board without attaching to the bus */ + disable_slot_power(ctrl, hp_slot); + + set_SOGO(ctrl); + + /* Wait for SOBS to be unset */ + wait_for_ctrl_irq(ctrl); + + mutex_unlock(&ctrl->crit_sect); + + if (rc) + return rc; + + mutex_lock(&ctrl->crit_sect); + + slot_enable(ctrl, hp_slot); + green_LED_blink(ctrl, hp_slot); + + amber_LED_off(ctrl, hp_slot); + + set_SOGO(ctrl); + + /* Wait for SOBS to be unset */ + wait_for_ctrl_irq(ctrl); + + mutex_unlock(&ctrl->crit_sect); + + /* Wait for ~1 second because of hot plug spec */ + long_delay(1*HZ); + + /* Check for a power fault */ + if (func->status == 0xFF) { + /* power fault occurred, but it was benign */ + rc = POWER_FAILURE; + func->status = 0; + } else + rc = cpqhp_valid_replace(ctrl, func); + + if (!rc) { + /* It must be the same board */ + + rc = cpqhp_configure_board(ctrl, func); + + /* If configuration fails, turn it off + * Get slot won't work for devices behind + * bridges, but in this case it will always be + * called for the "base" bus/dev/func of an + * adapter. + */ + + mutex_lock(&ctrl->crit_sect); + + amber_LED_on(ctrl, hp_slot); + green_LED_off(ctrl, hp_slot); + slot_disable(ctrl, hp_slot); + + set_SOGO(ctrl); + + /* Wait for SOBS to be unset */ + wait_for_ctrl_irq(ctrl); + + mutex_unlock(&ctrl->crit_sect); + + if (rc) + return rc; + else + return 1; + + } else { + /* Something is wrong + + * Get slot won't work for devices behind bridges, but + * in this case it will always be called for the "base" + * bus/dev/func of an adapter. + */ + + mutex_lock(&ctrl->crit_sect); + + amber_LED_on(ctrl, hp_slot); + green_LED_off(ctrl, hp_slot); + slot_disable(ctrl, hp_slot); + + set_SOGO(ctrl); + + /* Wait for SOBS to be unset */ + wait_for_ctrl_irq(ctrl); + + mutex_unlock(&ctrl->crit_sect); + } + + } + return rc; + +} + + +/** + * board_added - Called after a board has been added to the system. + * @func: PCI device/function info + * @ctrl: hotplug controller + * + * Turns power on for the board. + * Configures board. + */ +static u32 board_added(struct pci_func *func, struct controller *ctrl) +{ + u8 hp_slot; + u8 temp_byte; + u8 adapter_speed; + int index; + u32 temp_register = 0xFFFFFFFF; + u32 rc = 0; + struct pci_func *new_slot = NULL; + struct pci_bus *bus = ctrl->pci_bus; + struct resource_lists res_lists; + + hp_slot = func->device - ctrl->slot_device_offset; + dbg("%s: func->device, slot_offset, hp_slot = %d, %d ,%d\n", + __func__, func->device, ctrl->slot_device_offset, hp_slot); + + mutex_lock(&ctrl->crit_sect); + + /* turn on board without attaching to the bus */ + enable_slot_power(ctrl, hp_slot); + + set_SOGO(ctrl); + + /* Wait for SOBS to be unset */ + wait_for_ctrl_irq(ctrl); + + /* Change bits in slot power register to force another shift out + * NOTE: this is to work around the timer bug + */ + temp_byte = readb(ctrl->hpc_reg + SLOT_POWER); + writeb(0x00, ctrl->hpc_reg + SLOT_POWER); + writeb(temp_byte, ctrl->hpc_reg + SLOT_POWER); + + set_SOGO(ctrl); + + /* Wait for SOBS to be unset */ + wait_for_ctrl_irq(ctrl); + + adapter_speed = get_adapter_speed(ctrl, hp_slot); + if (bus->cur_bus_speed != adapter_speed) + if (set_controller_speed(ctrl, adapter_speed, hp_slot)) + rc = WRONG_BUS_FREQUENCY; + + /* turn off board without attaching to the bus */ + disable_slot_power(ctrl, hp_slot); + + set_SOGO(ctrl); + + /* Wait for SOBS to be unset */ + wait_for_ctrl_irq(ctrl); + + mutex_unlock(&ctrl->crit_sect); + + if (rc) + return rc; + + cpqhp_find_slot(ctrl, hp_slot + ctrl->slot_device_offset); + + /* turn on board and blink green LED */ + + dbg("%s: before down\n", __func__); + mutex_lock(&ctrl->crit_sect); + dbg("%s: after down\n", __func__); + + dbg("%s: before slot_enable\n", __func__); + slot_enable(ctrl, hp_slot); + + dbg("%s: before green_LED_blink\n", __func__); + green_LED_blink(ctrl, hp_slot); + + dbg("%s: before amber_LED_blink\n", __func__); + amber_LED_off(ctrl, hp_slot); + + dbg("%s: before set_SOGO\n", __func__); + set_SOGO(ctrl); + + /* Wait for SOBS to be unset */ + dbg("%s: before wait_for_ctrl_irq\n", __func__); + wait_for_ctrl_irq(ctrl); + dbg("%s: after wait_for_ctrl_irq\n", __func__); + + dbg("%s: before up\n", __func__); + mutex_unlock(&ctrl->crit_sect); + dbg("%s: after up\n", __func__); + + /* Wait for ~1 second because of hot plug spec */ + dbg("%s: before long_delay\n", __func__); + long_delay(1*HZ); + dbg("%s: after long_delay\n", __func__); + + dbg("%s: func status = %x\n", __func__, func->status); + /* Check for a power fault */ + if (func->status == 0xFF) { + /* power fault occurred, but it was benign */ + temp_register = 0xFFFFFFFF; + dbg("%s: temp register set to %x by power fault\n", __func__, temp_register); + rc = POWER_FAILURE; + func->status = 0; + } else { + /* Get vendor/device ID u32 */ + ctrl->pci_bus->number = func->bus; + rc = pci_bus_read_config_dword(ctrl->pci_bus, PCI_DEVFN(func->device, func->function), PCI_VENDOR_ID, &temp_register); + dbg("%s: pci_read_config_dword returns %d\n", __func__, rc); + dbg("%s: temp_register is %x\n", __func__, temp_register); + + if (rc != 0) { + /* Something's wrong here */ + temp_register = 0xFFFFFFFF; + dbg("%s: temp register set to %x by error\n", __func__, temp_register); + } + /* Preset return code. It will be changed later if things go okay. */ + rc = NO_ADAPTER_PRESENT; + } + + /* All F's is an empty slot or an invalid board */ + if (temp_register != 0xFFFFFFFF) { + res_lists.io_head = ctrl->io_head; + res_lists.mem_head = ctrl->mem_head; + res_lists.p_mem_head = ctrl->p_mem_head; + res_lists.bus_head = ctrl->bus_head; + res_lists.irqs = NULL; + + rc = configure_new_device(ctrl, func, 0, &res_lists); + + dbg("%s: back from configure_new_device\n", __func__); + ctrl->io_head = res_lists.io_head; + ctrl->mem_head = res_lists.mem_head; + ctrl->p_mem_head = res_lists.p_mem_head; + ctrl->bus_head = res_lists.bus_head; + + cpqhp_resource_sort_and_combine(&(ctrl->mem_head)); + cpqhp_resource_sort_and_combine(&(ctrl->p_mem_head)); + cpqhp_resource_sort_and_combine(&(ctrl->io_head)); + cpqhp_resource_sort_and_combine(&(ctrl->bus_head)); + + if (rc) { + mutex_lock(&ctrl->crit_sect); + + amber_LED_on(ctrl, hp_slot); + green_LED_off(ctrl, hp_slot); + slot_disable(ctrl, hp_slot); + + set_SOGO(ctrl); + + /* Wait for SOBS to be unset */ + wait_for_ctrl_irq(ctrl); + + mutex_unlock(&ctrl->crit_sect); + return rc; + } else { + cpqhp_save_slot_config(ctrl, func); + } + + + func->status = 0; + func->switch_save = 0x10; + func->is_a_board = 0x01; + + /* next, we will instantiate the linux pci_dev structures (with + * appropriate driver notification, if already present) */ + dbg("%s: configure linux pci_dev structure\n", __func__); + index = 0; + do { + new_slot = cpqhp_slot_find(ctrl->bus, func->device, index++); + if (new_slot && !new_slot->pci_dev) + cpqhp_configure_device(ctrl, new_slot); + } while (new_slot); + + mutex_lock(&ctrl->crit_sect); + + green_LED_on(ctrl, hp_slot); + + set_SOGO(ctrl); + + /* Wait for SOBS to be unset */ + wait_for_ctrl_irq(ctrl); + + mutex_unlock(&ctrl->crit_sect); + } else { + mutex_lock(&ctrl->crit_sect); + + amber_LED_on(ctrl, hp_slot); + green_LED_off(ctrl, hp_slot); + slot_disable(ctrl, hp_slot); + + set_SOGO(ctrl); + + /* Wait for SOBS to be unset */ + wait_for_ctrl_irq(ctrl); + + mutex_unlock(&ctrl->crit_sect); + + return rc; + } + return 0; +} + + +/** + * remove_board - Turns off slot and LEDs + * @func: PCI device/function info + * @replace_flag: whether replacing or adding a new device + * @ctrl: target controller + */ +static u32 remove_board(struct pci_func *func, u32 replace_flag, struct controller *ctrl) +{ + int index; + u8 skip = 0; + u8 device; + u8 hp_slot; + u8 temp_byte; + struct resource_lists res_lists; + struct pci_func *temp_func; + + if (cpqhp_unconfigure_device(func)) + return 1; + + device = func->device; + + hp_slot = func->device - ctrl->slot_device_offset; + dbg("In %s, hp_slot = %d\n", __func__, hp_slot); + + /* When we get here, it is safe to change base address registers. + * We will attempt to save the base address register lengths */ + if (replace_flag || !ctrl->add_support) + 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) + cpqhp_save_used_resources(ctrl, func); + } + /* Change status to shutdown */ + if (func->is_a_board) + func->status = 0x01; + func->configured = 0; + + mutex_lock(&ctrl->crit_sect); + + green_LED_off(ctrl, hp_slot); + slot_disable(ctrl, hp_slot); + + set_SOGO(ctrl); + + /* turn off SERR for slot */ + temp_byte = readb(ctrl->hpc_reg + SLOT_SERR); + temp_byte &= ~(0x01 << hp_slot); + writeb(temp_byte, ctrl->hpc_reg + SLOT_SERR); + + /* Wait for SOBS to be unset */ + wait_for_ctrl_irq(ctrl); + + mutex_unlock(&ctrl->crit_sect); + + if (!replace_flag && ctrl->add_support) { + while (func) { + res_lists.io_head = ctrl->io_head; + res_lists.mem_head = ctrl->mem_head; + res_lists.p_mem_head = ctrl->p_mem_head; + res_lists.bus_head = ctrl->bus_head; + + cpqhp_return_board_resources(func, &res_lists); + + ctrl->io_head = res_lists.io_head; + ctrl->mem_head = res_lists.mem_head; + ctrl->p_mem_head = res_lists.p_mem_head; + ctrl->bus_head = res_lists.bus_head; + + cpqhp_resource_sort_and_combine(&(ctrl->mem_head)); + cpqhp_resource_sort_and_combine(&(ctrl->p_mem_head)); + cpqhp_resource_sort_and_combine(&(ctrl->io_head)); + cpqhp_resource_sort_and_combine(&(ctrl->bus_head)); + + if (is_bridge(func)) { + bridge_slot_remove(func); + } else + slot_remove(func); + + func = cpqhp_slot_find(ctrl->bus, device, 0); + } + + /* Setup slot structure with entry for empty slot */ + func = cpqhp_slot_create(ctrl->bus); + + if (func == NULL) + return 1; + + func->bus = ctrl->bus; + func->device = device; + func->function = 0; + func->configured = 0; + func->switch_save = 0x10; + func->is_a_board = 0; + func->p_task_event = NULL; + } + + return 0; +} + +static void pushbutton_helper_thread(struct timer_list *t) +{ + pushbutton_pending = t; + + wake_up_process(cpqhp_event_thread); +} + + +/* this is the main worker thread */ +static int event_thread(void *data) +{ + struct controller *ctrl; + + while (1) { + dbg("!!!!event_thread sleeping\n"); + set_current_state(TASK_INTERRUPTIBLE); + schedule(); + + if (kthread_should_stop()) + break; + /* Do stuff here */ + if (pushbutton_pending) + cpqhp_pushbutton_thread(pushbutton_pending); + else + for (ctrl = cpqhp_ctrl_list; ctrl; ctrl = ctrl->next) + interrupt_event_handler(ctrl); + } + dbg("event_thread signals exit\n"); + return 0; +} + +int cpqhp_event_start_thread(void) +{ + cpqhp_event_thread = kthread_run(event_thread, NULL, "phpd_event"); + if (IS_ERR(cpqhp_event_thread)) { + err("Can't start up our event thread\n"); + return PTR_ERR(cpqhp_event_thread); + } + + return 0; +} + + +void cpqhp_event_stop_thread(void) +{ + kthread_stop(cpqhp_event_thread); +} + + +static void interrupt_event_handler(struct controller *ctrl) +{ + int loop; + int change = 1; + struct pci_func *func; + u8 hp_slot; + struct slot *p_slot; + + while (change) { + change = 0; + + for (loop = 0; loop < 10; loop++) { + /* dbg("loop %d\n", loop); */ + if (ctrl->event_queue[loop].event_type != 0) { + hp_slot = ctrl->event_queue[loop].hp_slot; + + func = cpqhp_slot_find(ctrl->bus, (hp_slot + ctrl->slot_device_offset), 0); + if (!func) + return; + + p_slot = cpqhp_find_slot(ctrl, hp_slot + ctrl->slot_device_offset); + if (!p_slot) + return; + + dbg("hp_slot %d, func %p, p_slot %p\n", + hp_slot, func, p_slot); + + if (ctrl->event_queue[loop].event_type == INT_BUTTON_PRESS) { + dbg("button pressed\n"); + } else if (ctrl->event_queue[loop].event_type == + INT_BUTTON_CANCEL) { + dbg("button cancel\n"); + del_timer(&p_slot->task_event); + + mutex_lock(&ctrl->crit_sect); + + if (p_slot->state == BLINKINGOFF_STATE) { + /* slot is on */ + dbg("turn on green LED\n"); + green_LED_on(ctrl, hp_slot); + } else if (p_slot->state == BLINKINGON_STATE) { + /* slot is off */ + dbg("turn off green LED\n"); + green_LED_off(ctrl, hp_slot); + } + + info(msg_button_cancel, p_slot->number); + + p_slot->state = STATIC_STATE; + + amber_LED_off(ctrl, hp_slot); + + set_SOGO(ctrl); + + /* Wait for SOBS to be unset */ + wait_for_ctrl_irq(ctrl); + + mutex_unlock(&ctrl->crit_sect); + } + /*** button Released (No action on press...) */ + else if (ctrl->event_queue[loop].event_type == INT_BUTTON_RELEASE) { + dbg("button release\n"); + + if (is_slot_enabled(ctrl, hp_slot)) { + dbg("slot is on\n"); + p_slot->state = BLINKINGOFF_STATE; + info(msg_button_off, p_slot->number); + } else { + dbg("slot is off\n"); + p_slot->state = BLINKINGON_STATE; + info(msg_button_on, p_slot->number); + } + mutex_lock(&ctrl->crit_sect); + + dbg("blink green LED and turn off amber\n"); + + amber_LED_off(ctrl, hp_slot); + green_LED_blink(ctrl, hp_slot); + + set_SOGO(ctrl); + + /* Wait for SOBS to be unset */ + wait_for_ctrl_irq(ctrl); + + mutex_unlock(&ctrl->crit_sect); + timer_setup(&p_slot->task_event, + pushbutton_helper_thread, + 0); + 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 */ + + 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"); + } + + ctrl->event_queue[loop].event_type = 0; + + change = 1; + } + } /* End of FOR loop */ + } +} + + +/** + * cpqhp_pushbutton_thread - handle pushbutton events + * @t: pointer to struct timer_list which holds all timer-related callbacks + * + * Scheduled procedure to handle blocking stuff for the pushbuttons. + * Handles all pending events and exits. + */ +void cpqhp_pushbutton_thread(struct timer_list *t) +{ + u8 hp_slot; + struct pci_func *func; + struct slot *p_slot = from_timer(p_slot, t, task_event); + struct controller *ctrl = (struct controller *) p_slot->ctrl; + + pushbutton_pending = NULL; + hp_slot = p_slot->hp_slot; + + if (is_slot_enabled(ctrl, hp_slot)) { + p_slot->state = POWEROFF_STATE; + /* power Down board */ + func = cpqhp_slot_find(p_slot->bus, p_slot->device, 0); + dbg("In power_down_board, func = %p, ctrl = %p\n", func, ctrl); + if (!func) { + dbg("Error! func NULL in %s\n", __func__); + return; + } + + if (cpqhp_process_SS(ctrl, func) != 0) { + amber_LED_on(ctrl, hp_slot); + green_LED_on(ctrl, hp_slot); + + set_SOGO(ctrl); + + /* Wait for SOBS to be unset */ + wait_for_ctrl_irq(ctrl); + } + + p_slot->state = STATIC_STATE; + } else { + p_slot->state = POWERON_STATE; + /* slot is off */ + + func = cpqhp_slot_find(p_slot->bus, p_slot->device, 0); + dbg("In add_board, func = %p, ctrl = %p\n", func, ctrl); + if (!func) { + dbg("Error! func NULL in %s\n", __func__); + return; + } + + if (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; + } +} + + +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; + + tempdword = 0; + + device = func->device; + hp_slot = device - ctrl->slot_device_offset; + p_slot = cpqhp_find_slot(ctrl, device); + + /* Check to see if the interlock is closed */ + tempdword = readl(ctrl->hpc_reg + INT_INPUT_CLEAR); + + if (tempdword & (0x01 << hp_slot)) + return 1; + + if (func->is_a_board) { + rc = board_replaced(func, ctrl); + } else { + /* add board */ + slot_remove(func); + + func = cpqhp_slot_create(ctrl->bus); + if (func == NULL) + return 1; + + func->bus = ctrl->bus; + func->device = device; + func->function = 0; + func->configured = 0; + func->is_a_board = 1; + + /* We have to save the presence info for these slots */ + temp_word = ctrl->ctrl_int_comp >> 16; + func->presence_save = (temp_word >> hp_slot) & 0x01; + func->presence_save |= (temp_word >> (hp_slot + 7)) & 0x02; + + if (ctrl->ctrl_int_comp & (0x1L << hp_slot)) { + func->switch_save = 0; + } else { + func->switch_save = 0x10; + } + + rc = board_added(func, ctrl); + if (rc) { + if (is_bridge(func)) { + bridge_slot_remove(func); + } else + slot_remove(func); + + /* Setup slot structure with entry for empty slot */ + func = cpqhp_slot_create(ctrl->bus); + + if (func == NULL) + return 1; + + func->bus = ctrl->bus; + func->device = device; + func->function = 0; + func->configured = 0; + func->is_a_board = 0; + + /* We have to save the presence info for these slots */ + temp_word = ctrl->ctrl_int_comp >> 16; + func->presence_save = (temp_word >> hp_slot) & 0x01; + func->presence_save |= + (temp_word >> (hp_slot + 7)) & 0x02; + + if (ctrl->ctrl_int_comp & (0x1L << hp_slot)) { + func->switch_save = 0; + } else { + func->switch_save = 0x10; + } + } + } + + if (rc) + dbg("%s: rc = %d\n", __func__, rc); + + 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; + + device = func->device; + func = cpqhp_slot_find(ctrl->bus, device, index++); + p_slot = cpqhp_find_slot(ctrl, device); + + /* 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; + } + + return rc; +} + +/** + * switch_leds - switch the leds, go from one site to the other. + * @ctrl: controller to use + * @num_of_slots: number of slots to use + * @work_LED: LED control value + * @direction: 1 to start from the left side, 0 to start right. + */ +static void switch_leds(struct controller *ctrl, const int num_of_slots, + u32 *work_LED, const int direction) +{ + int loop; + + for (loop = 0; loop < num_of_slots; loop++) { + if (direction) + *work_LED = *work_LED >> 1; + else + *work_LED = *work_LED << 1; + writel(*work_LED, ctrl->hpc_reg + LED_CONTROL); + + set_SOGO(ctrl); + + /* Wait for SOGO interrupt */ + wait_for_ctrl_irq(ctrl); + + /* Get ready for next iteration */ + long_delay((2*HZ)/10); + } +} + +/** + * cpqhp_hardware_test - runs hardware tests + * @ctrl: target controller + * @test_num: the number written to the "test" file in sysfs. + * + * For hot plug ctrl folks to play with. + */ +int cpqhp_hardware_test(struct controller *ctrl, int test_num) +{ + u32 save_LED; + u32 work_LED; + int loop; + int num_of_slots; + + num_of_slots = readb(ctrl->hpc_reg + SLOT_MASK) & 0x0f; + + switch (test_num) { + case 1: + /* Do stuff here! */ + + /* Do that funky LED thing */ + /* so we can restore them later */ + save_LED = readl(ctrl->hpc_reg + LED_CONTROL); + work_LED = 0x01010101; + switch_leds(ctrl, num_of_slots, &work_LED, 0); + switch_leds(ctrl, num_of_slots, &work_LED, 1); + switch_leds(ctrl, num_of_slots, &work_LED, 0); + switch_leds(ctrl, num_of_slots, &work_LED, 1); + + work_LED = 0x01010000; + writel(work_LED, ctrl->hpc_reg + LED_CONTROL); + switch_leds(ctrl, num_of_slots, &work_LED, 0); + switch_leds(ctrl, num_of_slots, &work_LED, 1); + work_LED = 0x00000101; + writel(work_LED, ctrl->hpc_reg + LED_CONTROL); + switch_leds(ctrl, num_of_slots, &work_LED, 0); + switch_leds(ctrl, num_of_slots, &work_LED, 1); + + work_LED = 0x01010000; + writel(work_LED, ctrl->hpc_reg + LED_CONTROL); + for (loop = 0; loop < num_of_slots; loop++) { + set_SOGO(ctrl); + + /* Wait for SOGO interrupt */ + wait_for_ctrl_irq(ctrl); + + /* Get ready for next iteration */ + long_delay((3*HZ)/10); + work_LED = work_LED >> 16; + writel(work_LED, ctrl->hpc_reg + LED_CONTROL); + + set_SOGO(ctrl); + + /* Wait for SOGO interrupt */ + wait_for_ctrl_irq(ctrl); + + /* Get ready for next iteration */ + long_delay((3*HZ)/10); + work_LED = work_LED << 16; + writel(work_LED, ctrl->hpc_reg + LED_CONTROL); + work_LED = work_LED << 1; + writel(work_LED, ctrl->hpc_reg + LED_CONTROL); + } + + /* put it back the way it was */ + writel(save_LED, ctrl->hpc_reg + LED_CONTROL); + + set_SOGO(ctrl); + + /* Wait for SOBS to be unset */ + wait_for_ctrl_irq(ctrl); + break; + case 2: + /* Do other stuff here! */ + break; + case 3: + /* and more... */ + break; + } + return 0; +} + + +/** + * configure_new_device - Configures the PCI header information of one board. + * @ctrl: pointer to controller structure + * @func: pointer to function structure + * @behind_bridge: 1 if this is a recursive call, 0 if not + * @resources: pointer to set of resource lists + * + * Returns 0 if success. + */ +static u32 configure_new_device(struct controller *ctrl, struct pci_func *func, + u8 behind_bridge, struct resource_lists *resources) +{ + u8 temp_byte, function, max_functions, stop_it; + int rc; + u32 ID; + struct pci_func *new_slot; + int index; + + new_slot = func; + + dbg("%s\n", __func__); + /* Check for Multi-function device */ + ctrl->pci_bus->number = func->bus; + rc = pci_bus_read_config_byte(ctrl->pci_bus, PCI_DEVFN(func->device, func->function), 0x0E, &temp_byte); + if (rc) { + dbg("%s: rc = %d\n", __func__, rc); + return rc; + } + + if (temp_byte & 0x80) /* Multi-function device */ + max_functions = 8; + else + max_functions = 1; + + function = 0; + + do { + rc = configure_new_function(ctrl, new_slot, behind_bridge, resources); + + if (rc) { + dbg("configure_new_function failed %d\n", rc); + index = 0; + + while (new_slot) { + new_slot = cpqhp_slot_find(new_slot->bus, new_slot->device, index++); + + if (new_slot) + cpqhp_return_board_resources(new_slot, resources); + } + + return rc; + } + + function++; + + stop_it = 0; + + /* The following loop skips to the next present function + * and creates a board structure */ + + while ((function < max_functions) && (!stop_it)) { + pci_bus_read_config_dword(ctrl->pci_bus, PCI_DEVFN(func->device, function), 0x00, &ID); + + if (PCI_POSSIBLE_ERROR(ID)) { + function++; + } else { + /* 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) { + /* 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 buses 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 buses 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 */ + 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); + + /* Copy the memory resources and fill in the bridge's memory + * range registers. + */ + 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); + + memcpy(hold_p_mem_node, p_mem_node, sizeof(struct pci_resource)); + p_mem_node->next = NULL; + + /* set Pre Mem base and Limit registers */ + temp_word = p_mem_node->base >> 16; + rc = pci_bus_write_config_word(pci_bus, devfn, PCI_PREF_MEMORY_BASE, temp_word); + + temp_word = (p_mem_node->base + p_mem_node->length - 1) >> 16; + rc = pci_bus_write_config_word(pci_bus, devfn, PCI_PREF_MEMORY_LIMIT, temp_word); + + /* Adjust this to compensate for extra adjustment in first loop + */ + irqs.barber_pole--; + + rc = 0; + + /* Here we actually find the devices and configure them */ + for (device = 0; (device <= 0x1F) && !rc; device++) { + irqs.barber_pole = (irqs.barber_pole + 1) & 0x03; + + ID = 0xFFFFFFFF; + pci_bus->number = hold_bus_node->base; + pci_bus_read_config_dword(pci_bus, PCI_DEVFN(device, 0), 0x00, &ID); + pci_bus->number = func->bus; + + if (!PCI_POSSIBLE_ERROR(ID)) { /* 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, + cloop + 1, 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 (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 (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); + if (!io_node) + return -ENOMEM; + 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 */ + base = io_node->base; + io_node->next = func->io_head; + func->io_head = io_node; + } 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 { + /* Reserved bits or 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 + * already 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, 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 000000000..7a65d427a --- /dev/null +++ b/drivers/pci/hotplug/cpqphp_nvram.c @@ -0,0 +1,650 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * 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. + * + * Send feedback to <greg@kroah.com> + * + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/types.h> +#include <linux/proc_fs.h> +#include <linux/slab.h> +#include <linux/workqueue.h> +#include <linux/pci.h> +#include <linux/pci_hotplug.h> +#include <linux/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; + +/* lock for ordering int15_bios_call() */ +static DEFINE_SPINLOCK(int15_lock); + + +/* This is a series of function that deals with + * setting & getting the hotplug resource table in some environment variable. + */ + +/* + * We really shouldn't be doing this unless there is a _very_ good reason to!!! + * greg k-h + */ + + +static u32 add_byte(u32 **p_buffer, u8 value, u32 *used, u32 *avail) +{ + u8 **tByte; + + if ((*used + 1) > *avail) + return(1); + + *((u8 *)*p_buffer) = value; + tByte = (u8 **)p_buffer; + (*tByte)++; + *used += 1; + return(0); +} + + +static u32 add_dword(u32 **p_buffer, u32 value, u32 *used, u32 *avail) +{ + if ((*used + 4) > *avail) + return(1); + + **p_buffer = value; + (*p_buffer)++; + *used += 4; + return(0); +} + + +/* + * check_for_compaq_ROM + * + * this routine verifies that the ROM OEM string is 'COMPAQ' + * + * returns 0 for non-Compaq ROM, 1 for Compaq ROM + */ +static int check_for_compaq_ROM(void __iomem *rom_start) +{ + u8 temp1, temp2, temp3, temp4, temp5, temp6; + int result = 0; + + temp1 = readb(rom_start + 0xffea + 0); + temp2 = readb(rom_start + 0xffea + 1); + temp3 = readb(rom_start + 0xffea + 2); + temp4 = readb(rom_start + 0xffea + 3); + temp5 = readb(rom_start + 0xffea + 4); + temp6 = readb(rom_start + 0xffea + 5); + if ((temp1 == 'C') && + (temp2 == 'O') && + (temp3 == 'M') && + (temp4 == 'P') && + (temp5 == 'A') && + (temp6 == 'Q')) { + result = 1; + } + dbg("%s - returned %d\n", __func__, result); + return result; +} + + +static u32 access_EV(u16 operation, u8 *ev_name, u8 *buffer, u32 *buf_size) +{ + unsigned long flags; + int op = operation; + int ret_val; + + if (!compaq_int15_entry_point) + return -ENODEV; + + spin_lock_irqsave(&int15_lock, flags); + __asm__ ( + "xorl %%ebx,%%ebx\n" \ + "xorl %%edx,%%edx\n" \ + "pushf\n" \ + "push %%cs\n" \ + "cli\n" \ + "call *%6\n" + : "=c" (*buf_size), "=a" (ret_val) + : "a" (op), "c" (*buf_size), "S" (ev_name), + "D" (buffer), "m" (compaq_int15_entry_point) + : "%ebx", "%edx"); + spin_unlock_irqrestore(&int15_lock, flags); + + return((ret_val & 0xFF00) >> 8); +} + + +/* + * load_HRT + * + * Read the hot plug Resource Table from NVRAM + */ +static int load_HRT(void __iomem *rom_start) +{ + u32 available; + u32 temp_dword; + u8 temp_byte = 0xFF; + u32 rc; + + if (!check_for_compaq_ROM(rom_start)) + return -ENODEV; + + available = 1024; + + /* Now load the EV */ + temp_dword = available; + + rc = access_EV(READ_EV, "CQTHPS", evbuffer, &temp_dword); + + evbuffer_length = temp_dword; + + /* We're maintaining the resource lists so write FF to invalidate old + * info + */ + temp_dword = 1; + + rc = access_EV(WRITE_EV, "CQTHPS", &temp_byte, &temp_dword); + + return rc; +} + + +/* + * store_HRT + * + * Save the hot plug Resource Table in NVRAM + */ +static u32 store_HRT(void __iomem *rom_start) +{ + u32 *buffer; + u32 *pFill; + u32 usedbytes; + u32 available; + u32 temp_dword; + u32 rc; + u8 loop; + u8 numCtrl = 0; + struct controller *ctrl; + struct pci_resource *resNode; + struct ev_hrt_header *p_EV_header; + struct ev_hrt_ctrl *p_ev_ctrl; + + available = 1024; + + if (!check_for_compaq_ROM(rom_start)) + return(1); + + buffer = (u32 *) evbuffer; + + if (!buffer) + return(1); + + pFill = buffer; + usedbytes = 0; + + p_EV_header = (struct ev_hrt_header *) pFill; + + ctrl = cpqhp_ctrl_list; + + /* The revision of this structure */ + rc = add_byte(&pFill, 1 + ctrl->push_flag, &usedbytes, &available); + if (rc) + return(rc); + + /* The number of controllers */ + rc = add_byte(&pFill, 1, &usedbytes, &available); + if (rc) + return(rc); + + while (ctrl) { + p_ev_ctrl = (struct ev_hrt_ctrl *) pFill; + + numCtrl++; + + /* The bus number */ + rc = add_byte(&pFill, ctrl->bus, &usedbytes, &available); + if (rc) + return(rc); + + /* The device Number */ + rc = add_byte(&pFill, PCI_SLOT(ctrl->pci_dev->devfn), &usedbytes, &available); + if (rc) + return(rc); + + /* The function Number */ + rc = add_byte(&pFill, PCI_FUNC(ctrl->pci_dev->devfn), &usedbytes, &available); + if (rc) + return(rc); + + /* Skip the number of available entries */ + rc = add_dword(&pFill, 0, &usedbytes, &available); + if (rc) + return(rc); + + /* Figure out memory Available */ + + resNode = ctrl->mem_head; + + loop = 0; + + while (resNode) { + loop++; + + /* base */ + rc = add_dword(&pFill, resNode->base, &usedbytes, &available); + if (rc) + return(rc); + + /* length */ + rc = add_dword(&pFill, resNode->length, &usedbytes, &available); + if (rc) + return(rc); + + resNode = resNode->next; + } + + /* Fill in the number of entries */ + p_ev_ctrl->mem_avail = loop; + + /* Figure out prefetchable memory Available */ + + resNode = ctrl->p_mem_head; + + loop = 0; + + while (resNode) { + loop++; + + /* base */ + rc = add_dword(&pFill, resNode->base, &usedbytes, &available); + if (rc) + return(rc); + + /* length */ + rc = add_dword(&pFill, resNode->length, &usedbytes, &available); + if (rc) + return(rc); + + resNode = resNode->next; + } + + /* Fill in the number of entries */ + p_ev_ctrl->p_mem_avail = loop; + + /* Figure out IO Available */ + + resNode = ctrl->io_head; + + loop = 0; + + while (resNode) { + loop++; + + /* base */ + rc = add_dword(&pFill, resNode->base, &usedbytes, &available); + if (rc) + return(rc); + + /* length */ + rc = add_dword(&pFill, resNode->length, &usedbytes, &available); + if (rc) + return(rc); + + resNode = resNode->next; + } + + /* Fill in the number of entries */ + p_ev_ctrl->io_avail = loop; + + /* Figure out bus Available */ + + resNode = ctrl->bus_head; + + loop = 0; + + while (resNode) { + loop++; + + /* base */ + rc = add_dword(&pFill, resNode->base, &usedbytes, &available); + if (rc) + return(rc); + + /* length */ + rc = add_dword(&pFill, resNode->length, &usedbytes, &available); + if (rc) + return(rc); + + resNode = resNode->next; + } + + /* Fill in the number of entries */ + p_ev_ctrl->bus_avail = loop; + + ctrl = ctrl->next; + } + + p_EV_header->num_of_ctrl = numCtrl; + + /* Now store the EV */ + + temp_dword = usedbytes; + + rc = access_EV(WRITE_EV, "CQTHPS", (u8 *) buffer, &temp_dword); + + dbg("usedbytes = 0x%x, length = 0x%x\n", usedbytes, temp_dword); + + evbuffer_length = temp_dword; + + if (rc) { + err(msg_unable_to_save); + return(1); + } + + return(0); +} + + +void compaq_nvram_init(void __iomem *rom_start) +{ + if (rom_start) + compaq_int15_entry_point = (rom_start + ROM_INT15_PHY_ADDR - ROM_PHY_ADDR); + + dbg("int15 entry = %p\n", compaq_int15_entry_point); +} + + +int compaq_nvram_load(void __iomem *rom_start, struct controller *ctrl) +{ + u8 bus, device, function; + u8 nummem, numpmem, numio, numbus; + u32 rc; + u8 *p_byte; + struct pci_resource *mem_node; + struct pci_resource *p_mem_node; + struct pci_resource *io_node; + struct pci_resource *bus_node; + struct ev_hrt_ctrl *p_ev_ctrl; + struct ev_hrt_header *p_EV_header; + + if (!evbuffer_init) { + /* Read the resource list information in from NVRAM */ + if (load_HRT(rom_start)) + memset(evbuffer, 0, 1024); + + evbuffer_init = 1; + } + + /* If we saved information in NVRAM, use it now */ + p_EV_header = (struct ev_hrt_header *) evbuffer; + + /* The following code is for systems where version 1.0 of this + * driver has been loaded, but doesn't support the hardware. + * In that case, the driver would incorrectly store something + * in NVRAM. + */ + if ((p_EV_header->Version == 2) || + ((p_EV_header->Version == 1) && !ctrl->push_flag)) { + p_byte = &(p_EV_header->next); + + p_ev_ctrl = (struct ev_hrt_ctrl *) &(p_EV_header->next); + + p_byte += 3; + + if (p_byte > ((u8 *)p_EV_header + evbuffer_length)) + return 2; + + bus = p_ev_ctrl->bus; + device = p_ev_ctrl->device; + function = p_ev_ctrl->function; + + while ((bus != ctrl->bus) || + (device != PCI_SLOT(ctrl->pci_dev->devfn)) || + (function != PCI_FUNC(ctrl->pci_dev->devfn))) { + nummem = p_ev_ctrl->mem_avail; + numpmem = p_ev_ctrl->p_mem_avail; + numio = p_ev_ctrl->io_avail; + numbus = p_ev_ctrl->bus_avail; + + p_byte += 4; + + if (p_byte > ((u8 *)p_EV_header + evbuffer_length)) + return 2; + + /* Skip forward to the next entry */ + p_byte += (nummem + numpmem + numio + numbus) * 8; + + if (p_byte > ((u8 *)p_EV_header + evbuffer_length)) + return 2; + + p_ev_ctrl = (struct ev_hrt_ctrl *) p_byte; + + p_byte += 3; + + if (p_byte > ((u8 *)p_EV_header + evbuffer_length)) + return 2; + + bus = p_ev_ctrl->bus; + device = p_ev_ctrl->device; + function = p_ev_ctrl->function; + } + + nummem = p_ev_ctrl->mem_avail; + numpmem = p_ev_ctrl->p_mem_avail; + numio = p_ev_ctrl->io_avail; + numbus = p_ev_ctrl->bus_avail; + + p_byte += 4; + + if (p_byte > ((u8 *)p_EV_header + evbuffer_length)) + return 2; + + while (nummem--) { + mem_node = kmalloc(sizeof(struct pci_resource), GFP_KERNEL); + + if (!mem_node) + break; + + mem_node->base = *(u32 *)p_byte; + dbg("mem base = %8.8x\n", mem_node->base); + p_byte += 4; + + if (p_byte > ((u8 *)p_EV_header + evbuffer_length)) { + kfree(mem_node); + return 2; + } + + mem_node->length = *(u32 *)p_byte; + dbg("mem length = %8.8x\n", mem_node->length); + p_byte += 4; + + if (p_byte > ((u8 *)p_EV_header + evbuffer_length)) { + kfree(mem_node); + return 2; + } + + mem_node->next = ctrl->mem_head; + ctrl->mem_head = mem_node; + } + + while (numpmem--) { + p_mem_node = kmalloc(sizeof(struct pci_resource), GFP_KERNEL); + + if (!p_mem_node) + break; + + p_mem_node->base = *(u32 *)p_byte; + dbg("pre-mem base = %8.8x\n", p_mem_node->base); + p_byte += 4; + + if (p_byte > ((u8 *)p_EV_header + evbuffer_length)) { + kfree(p_mem_node); + return 2; + } + + p_mem_node->length = *(u32 *)p_byte; + dbg("pre-mem length = %8.8x\n", p_mem_node->length); + p_byte += 4; + + if (p_byte > ((u8 *)p_EV_header + evbuffer_length)) { + kfree(p_mem_node); + return 2; + } + + p_mem_node->next = ctrl->p_mem_head; + ctrl->p_mem_head = p_mem_node; + } + + while (numio--) { + io_node = kmalloc(sizeof(struct pci_resource), GFP_KERNEL); + + if (!io_node) + break; + + io_node->base = *(u32 *)p_byte; + dbg("io base = %8.8x\n", io_node->base); + p_byte += 4; + + if (p_byte > ((u8 *)p_EV_header + evbuffer_length)) { + kfree(io_node); + return 2; + } + + io_node->length = *(u32 *)p_byte; + dbg("io length = %8.8x\n", io_node->length); + p_byte += 4; + + if (p_byte > ((u8 *)p_EV_header + evbuffer_length)) { + kfree(io_node); + return 2; + } + + io_node->next = ctrl->io_head; + ctrl->io_head = io_node; + } + + while (numbus--) { + bus_node = kmalloc(sizeof(struct pci_resource), GFP_KERNEL); + + if (!bus_node) + break; + + bus_node->base = *(u32 *)p_byte; + p_byte += 4; + + if (p_byte > ((u8 *)p_EV_header + evbuffer_length)) { + kfree(bus_node); + return 2; + } + + bus_node->length = *(u32 *)p_byte; + p_byte += 4; + + if (p_byte > ((u8 *)p_EV_header + evbuffer_length)) { + kfree(bus_node); + return 2; + } + + bus_node->next = ctrl->bus_head; + ctrl->bus_head = bus_node; + } + + /* If all of the following fail, we don't have any resources for + * hot plug add + */ + rc = 1; + rc &= cpqhp_resource_sort_and_combine(&(ctrl->mem_head)); + rc &= cpqhp_resource_sort_and_combine(&(ctrl->p_mem_head)); + rc &= cpqhp_resource_sort_and_combine(&(ctrl->io_head)); + rc &= cpqhp_resource_sort_and_combine(&(ctrl->bus_head)); + + if (rc) + return(rc); + } else { + if ((evbuffer[0] != 0) && (!ctrl->push_flag)) + return 1; + } + + return 0; +} + + +int compaq_nvram_store(void __iomem *rom_start) +{ + int rc = 1; + + if (rom_start == NULL) + return -ENODEV; + + if (evbuffer_init) { + rc = store_HRT(rom_start); + if (rc) + err(msg_unable_to_save); + } + return rc; +} + diff --git a/drivers/pci/hotplug/cpqphp_nvram.h b/drivers/pci/hotplug/cpqphp_nvram.h new file mode 100644 index 000000000..70e879b6a --- /dev/null +++ b/drivers/pci/hotplug/cpqphp_nvram.h @@ -0,0 +1,40 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Compaq Hot Plug Controller Driver + * + * Copyright (C) 1995,2001 Compaq Computer Corporation + * Copyright (C) 2001 Greg Kroah-Hartman (greg@kroah.com) + * + * All rights reserved. + * + * 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) { } + +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 + +void compaq_nvram_init(void __iomem *rom_start); +int compaq_nvram_load(void __iomem *rom_start, struct controller *ctrl); +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 000000000..3b248426a --- /dev/null +++ b/drivers/pci/hotplug/cpqphp_pci.c @@ -0,0 +1,1562 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * 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. + * + * Send feedback to <greg@kroah.com> + * + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/types.h> +#include <linux/slab.h> +#include <linux/workqueue.h> +#include <linux/proc_fs.h> +#include <linux/pci.h> +#include <linux/pci_hotplug.h> +#include "../pci.h" +#include "cpqphp.h" +#include "cpqphp_nvram.h" + + +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) +{ + struct pci_bus *child; + int num; + + pci_lock_rescan_remove(); + + if (func->pci_dev == NULL) + func->pci_dev = pci_get_domain_bus_and_slot(0, 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_get_domain_bus_and_slot(0, func->bus, + PCI_DEVFN(func->device, + func->function)); + if (func->pci_dev == NULL) { + dbg("ERROR: pci_dev still null\n"); + goto out; + } + } + + if (func->pci_dev->hdr_type == PCI_HEADER_TYPE_BRIDGE) { + pci_hp_add_bridge(func->pci_dev); + child = func->pci_dev->subordinate; + if (child) + pci_bus_add_devices(child); + } + + pci_dev_put(func->pci_dev); + + out: + pci_unlock_rescan_remove(); + return 0; +} + + +int cpqhp_unconfigure_device(struct pci_func *func) +{ + int j; + + dbg("%s: bus/dev/func = %x/%x/%x\n", __func__, func->bus, func->device, func->function); + + pci_lock_rescan_remove(); + for (j = 0; j < 8 ; j++) { + struct pci_dev *temp = pci_get_domain_bus_and_slot(0, + func->bus, + PCI_DEVFN(func->device, + j)); + if (temp) { + pci_dev_put(temp); + pci_stop_and_remove_bus_device(temp); + } + } + pci_unlock_rescan_remove(); + return 0; +} + +static int PCI_RefinedAccessConfig(struct pci_bus *bus, unsigned int devfn, u8 offset, u32 *value) +{ + u32 vendID = 0; + + if (pci_bus_read_config_dword(bus, devfn, PCI_VENDOR_ID, &vendID) == -1) + return -1; + if (vendID == 0xffffffff) + return -1; + return pci_bus_read_config_dword(bus, devfn, offset, value); +} + + +/* + * cpqhp_set_irq + * + * @bus_num: bus number of PCI device + * @dev_num: device number of PCI device + * @slot: pointer to u8 where slot number will be returned + */ +int cpqhp_set_irq(u8 bus_num, u8 dev_num, u8 int_pin, u8 irq_num) +{ + int rc = 0; + + if (cpqhp_legacy_mode) { + struct pci_dev *fakedev; + struct pci_bus *fakebus; + u16 temp_word; + + fakedev = kmalloc(sizeof(*fakedev), GFP_KERNEL); + fakebus = kmalloc(sizeof(*fakebus), GFP_KERNEL); + if (!fakedev || !fakebus) { + kfree(fakedev); + kfree(fakebus); + return -ENOMEM; + } + + fakedev->devfn = dev_num << 3; + fakedev->bus = fakebus; + fakebus->number = bus_num; + dbg("%s: dev %d, bus %d, pin %d, num %d\n", + __func__, dev_num, bus_num, int_pin, irq_num); + rc = pcibios_set_irq_routing(fakedev, int_pin - 1, irq_num); + kfree(fakedev); + kfree(fakebus); + dbg("%s: rc %d\n", __func__, rc); + if (!rc) + return !rc; + + /* set the Edge Level Control Register (ELCR) */ + temp_word = inb(0x4d0); + temp_word |= inb(0x4d1) << 8; + + temp_word |= 0x01 << irq_num; + + /* This should only be for x86 as it sets the Edge Level + * Control Register + */ + outb((u8)(temp_word & 0xFF), 0x4d0); + outb((u8)((temp_word & 0xFF00) >> 8), 0x4d1); + rc = 0; + } + + return rc; +} + + +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); + /* XXX: no recursion, wtf? */ + dbg("Recurse on bus_num %d tdevice %d\n", tbus, tdevice); + return 0; + } + } + + return -1; +} + + +static int PCI_GetBusDevHelper(struct controller *ctrl, u8 *bus_num, u8 *dev_num, u8 slot, u8 nobridge) +{ + int loop, len; + u32 work; + u8 tbus, tdevice, tslot; + + len = cpqhp_routing_table_length(); + for (loop = 0; loop < len; ++loop) { + tbus = cpqhp_routing_table->slots[loop].bus; + tdevice = cpqhp_routing_table->slots[loop].devfn; + tslot = cpqhp_routing_table->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)) + 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; + return 0; + } + } else + return 0; + } + } + return -1; +} + + +int cpqhp_get_bus_dev(struct controller *ctrl, u8 *bus_num, u8 *dev_num, u8 slot) +{ + /* plain (bridges allowed) */ + return PCI_GetBusDevHelper(ctrl, bus_num, dev_num, slot, 0); +} + + +/* 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 buses, 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; + u16 devfn; + + /* 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) { + 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; + } + continue; + } + + 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) { + /* 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; + devfn = (new_slot->device << 3) | new_slot->function; + new_slot->pci_dev = pci_get_domain_bus_and_slot(0, + new_slot->bus, devfn); + + 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; + } + + pci_dev_put(new_slot->pci_dev); + + 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) { + function++; + continue; + } + 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 FOR loop */ + + return 0; +} + + +/* + * cpqhp_save_slot_config + * + * Saves configuration info for all PCI devices in a given slot + * including subordinate buses. + * + * 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 = 0; + int cloop; + 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) + return 2; + + 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; + + while (function < max_functions) { + if ((header_type & 0x7F) == PCI_HEADER_TYPE_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; + + } + + 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) + function++; + else { + 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++; + } + } + + } + + 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_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 this register is implemented */ + if (base) { + 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) { + /* 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 this register is implemented */ + if (base) { + 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) { + /* 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 this register is implemented */ + if (base) { + 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 */ + /* Standard header */ + } else if ((header_type & 0x7F) == 0x00) { + /* 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 this register is implemented */ + if (base) { + 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 */ + } + + /* 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_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) { + /* In order to continue checking, we must program the + * bus registers in the bridge to respond to accesses + * for its 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 this register is implemented */ + if (base) { + if (base & 0x01L) { + /* IO base + * set base = amount of IO + * space requested + */ + base = base & 0xFFFFFFFE; + base = (~base) + 1; + + type = 1; + } else { + /* memory base */ + base = base & 0xFFFFFFF0; + base = (~base) + 1; + + type = 0; + } + } else { + base = 0x0L; + type = 0; + } + + /* Check information in slot structure */ + if (func->base_length[(cloop - 0x10) >> 2] != base) + return(ADAPTER_NOT_SAME); + + if (func->base_type[(cloop - 0x10) >> 2] != type) + return(ADAPTER_NOT_SAME); + + } /* End of base register loop */ + + } /* End of (type 0 config space) else */ + else { + /* this is not a type 0 or 1 config space header so + * we don't know how to do it + */ + return(DEVICE_TYPE_NOT_SUPPORTED); + } + + /* Get the next function */ + func = cpqhp_slot_find(func->bus, func->device, index++); + } + + + return 0; +} + + +/* + * cpqhp_find_available_resources + * + * Finds available memory, IO, and IRQ resources for programming + * devices which may be added to the system + * this function is for hot plug ADD! + * + * returns 0 if success + */ +int cpqhp_find_available_resources(struct controller *ctrl, void __iomem *rom_start) +{ + u8 temp; + u8 populated_slot; + u8 bridged_slot; + void __iomem *one_slot; + void __iomem *rom_resource_table; + struct pci_func *func = NULL; + int i = 10, index; + u32 temp_dword, rc; + struct pci_resource *mem_node; + struct pci_resource *p_mem_node; + struct pci_resource *io_node; + struct pci_resource *bus_node; + + rom_resource_table = detect_HRT_floating_pointer(rom_start, rom_start+0xffff); + dbg("rom_resource_table = %p\n", rom_resource_table); + + if (rom_resource_table == NULL) + return -ENODEV; + + /* Sum all resources and setup resource maps */ + unused_IRQ = readl(rom_resource_table + UNUSED_IRQ); + dbg("unused_IRQ = %x\n", unused_IRQ); + + temp = 0; + while (unused_IRQ) { + if (unused_IRQ & 1) { + cpqhp_disk_irq = temp; + break; + } + unused_IRQ = unused_IRQ >> 1; + temp++; + } + + dbg("cpqhp_disk_irq= %d\n", cpqhp_disk_irq); + unused_IRQ = unused_IRQ >> 1; + temp++; + + while (unused_IRQ) { + if (unused_IRQ & 1) { + cpqhp_nic_irq = temp; + break; + } + unused_IRQ = unused_IRQ >> 1; + temp++; + } + + dbg("cpqhp_nic_irq= %d\n", cpqhp_nic_irq); + unused_IRQ = readl(rom_resource_table + PCIIRQ); + + temp = 0; + + if (!cpqhp_nic_irq) + cpqhp_nic_irq = ctrl->cfgspc_irq; + + if (!cpqhp_disk_irq) + cpqhp_disk_irq = ctrl->cfgspc_irq; + + dbg("cpqhp_disk_irq, cpqhp_nic_irq= %d, %d\n", cpqhp_disk_irq, cpqhp_nic_irq); + + rc = compaq_nvram_load(rom_start, ctrl); + if (rc) + return rc; + + one_slot = rom_resource_table + sizeof(struct hrt); + + i = readb(rom_resource_table + NUMBER_OF_ENTRIES); + dbg("number_of_entries = %d\n", i); + + if (!readb(one_slot + SECONDARY_BUS)) + return 1; + + dbg("dev|IO base|length|Mem base|length|Pre base|length|PB SB MB\n"); + + while (i && readb(one_slot + SECONDARY_BUS)) { + u8 dev_func = readb(one_slot + DEV_FUNC); + u8 primary_bus = readb(one_slot + PRIMARY_BUS); + u8 secondary_bus = readb(one_slot + SECONDARY_BUS); + u8 max_bus = readb(one_slot + MAX_BUS); + u16 io_base = readw(one_slot + IO_BASE); + u16 io_length = readw(one_slot + IO_LENGTH); + u16 mem_base = readw(one_slot + MEM_BASE); + u16 mem_length = readw(one_slot + MEM_LENGTH); + u16 pre_mem_base = readw(one_slot + PRE_MEM_BASE); + u16 pre_mem_length = readw(one_slot + PRE_MEM_LENGTH); + + dbg("%2.2x | %4.4x | %4.4x | %4.4x | %4.4x | %4.4x | %4.4x |%2.2x %2.2x %2.2x\n", + dev_func, io_base, io_length, mem_base, mem_length, pre_mem_base, pre_mem_length, + primary_bus, secondary_bus, max_bus); + + /* If this entry isn't for our controller's bus, ignore it */ + if (primary_bus != ctrl->bus) { + i--; + one_slot += sizeof(struct slot_rt); + continue; + } + /* find out if this entry is for an occupied slot */ + ctrl->pci_bus->number = primary_bus; + pci_bus_read_config_dword(ctrl->pci_bus, dev_func, PCI_VENDOR_ID, &temp_dword); + dbg("temp_D_word = %x\n", temp_dword); + + if (temp_dword != 0xFFFFFFFF) { + index = 0; + func = cpqhp_slot_find(primary_bus, dev_func >> 3, 0); + + while (func && (func->function != (dev_func & 0x07))) { + dbg("func = %p (bus, dev, fun) = (%d, %d, %d)\n", func, primary_bus, dev_func >> 3, index); + func = cpqhp_slot_find(primary_bus, dev_func >> 3, index++); + } + + /* If we can't find a match, skip this table entry */ + if (!func) { + i--; + one_slot += sizeof(struct slot_rt); + continue; + } + /* this may not work and shouldn't be used */ + if (secondary_bus != primary_bus) + bridged_slot = 1; + else + bridged_slot = 0; + + populated_slot = 1; + } else { + populated_slot = 0; + bridged_slot = 0; + } + + + /* If we've got a valid IO base, use it */ + + temp_dword = io_base + io_length; + + if ((io_base) && (temp_dword < 0x10000)) { + io_node = kmalloc(sizeof(*io_node), GFP_KERNEL); + if (!io_node) + return -ENOMEM; + + io_node->base = io_base; + io_node->length = io_length; + + dbg("found io_node(base, length) = %x, %x\n", + io_node->base, io_node->length); + dbg("populated slot =%d \n", populated_slot); + if (!populated_slot) { + io_node->next = ctrl->io_head; + ctrl->io_head = io_node; + } else { + io_node->next = func->io_head; + func->io_head = io_node; + } + } + + /* If we've got a valid memory base, use it */ + temp_dword = mem_base + mem_length; + if ((mem_base) && (temp_dword < 0x10000)) { + mem_node = kmalloc(sizeof(*mem_node), GFP_KERNEL); + if (!mem_node) + return -ENOMEM; + + mem_node->base = mem_base << 16; + + mem_node->length = mem_length << 16; + + dbg("found mem_node(base, length) = %x, %x\n", + mem_node->base, mem_node->length); + dbg("populated slot =%d \n", populated_slot); + if (!populated_slot) { + mem_node->next = ctrl->mem_head; + ctrl->mem_head = mem_node; + } else { + mem_node->next = func->mem_head; + func->mem_head = mem_node; + } + } + + /* If we've got a valid prefetchable memory base, and + * the base + length isn't greater than 0xFFFF + */ + temp_dword = pre_mem_base + pre_mem_length; + if ((pre_mem_base) && (temp_dword < 0x10000)) { + p_mem_node = kmalloc(sizeof(*p_mem_node), GFP_KERNEL); + if (!p_mem_node) + return -ENOMEM; + + p_mem_node->base = pre_mem_base << 16; + + p_mem_node->length = pre_mem_length << 16; + dbg("found p_mem_node(base, length) = %x, %x\n", + p_mem_node->base, p_mem_node->length); + dbg("populated slot =%d \n", populated_slot); + + if (!populated_slot) { + p_mem_node->next = ctrl->p_mem_head; + ctrl->p_mem_head = p_mem_node; + } else { + p_mem_node->next = func->p_mem_head; + func->p_mem_head = p_mem_node; + } + } + + /* If we've got a valid bus number, use it + * The second condition is to ignore bus numbers on + * populated slots that don't have PCI-PCI bridges + */ + if (secondary_bus && (secondary_bus != primary_bus)) { + bus_node = kmalloc(sizeof(*bus_node), GFP_KERNEL); + if (!bus_node) + return -ENOMEM; + + bus_node->base = secondary_bus; + bus_node->length = max_bus - secondary_bus + 1; + dbg("found bus_node(base, length) = %x, %x\n", + bus_node->base, bus_node->length); + dbg("populated slot =%d \n", populated_slot); + if (!populated_slot) { + bus_node->next = ctrl->bus_head; + ctrl->bus_head = bus_node; + } else { + bus_node->next = func->bus_head; + func->bus_head = bus_node; + } + } + + i--; + one_slot += sizeof(struct slot_rt); + } + + /* If all of the following fail, we don't have any resources for + * hot plug add + */ + rc = 1; + rc &= cpqhp_resource_sort_and_combine(&(ctrl->mem_head)); + rc &= cpqhp_resource_sort_and_combine(&(ctrl->p_mem_head)); + rc &= cpqhp_resource_sort_and_combine(&(ctrl->io_head)); + rc &= cpqhp_resource_sort_and_combine(&(ctrl->bus_head)); + + return rc; +} + + +/* + * cpqhp_return_board_resources + * + * this routine returns all resources allocated to a board to + * the available pool. + * + * returns 0 if success + */ +int cpqhp_return_board_resources(struct pci_func *func, struct resource_lists *resources) +{ + int rc = 0; + struct pci_resource *node; + struct pci_resource *t_node; + dbg("%s\n", __func__); + + if (!func) + return 1; + + node = func->io_head; + func->io_head = NULL; + while (node) { + t_node = node->next; + return_resource(&(resources->io_head), node); + node = t_node; + } + + node = func->mem_head; + func->mem_head = NULL; + while (node) { + t_node = node->next; + return_resource(&(resources->mem_head), node); + node = t_node; + } + + node = func->p_mem_head; + func->p_mem_head = NULL; + while (node) { + t_node = node->next; + return_resource(&(resources->p_mem_head), node); + node = t_node; + } + + node = func->bus_head; + func->bus_head = NULL; + while (node) { + t_node = node->next; + return_resource(&(resources->bus_head), node); + node = t_node; + } + + rc |= cpqhp_resource_sort_and_combine(&(resources->mem_head)); + rc |= cpqhp_resource_sort_and_combine(&(resources->p_mem_head)); + rc |= cpqhp_resource_sort_and_combine(&(resources->io_head)); + rc |= cpqhp_resource_sort_and_combine(&(resources->bus_head)); + + return rc; +} + + +/* + * cpqhp_destroy_resource_list + * + * Puts node back in the resource list pointed to by head + */ +void cpqhp_destroy_resource_list(struct resource_lists *resources) +{ + struct pci_resource *res, *tres; + + res = resources->io_head; + resources->io_head = NULL; + + while (res) { + tres = res; + res = res->next; + kfree(tres); + } + + res = resources->mem_head; + resources->mem_head = NULL; + + while (res) { + tres = res; + res = res->next; + kfree(tres); + } + + res = resources->p_mem_head; + resources->p_mem_head = NULL; + + while (res) { + tres = res; + res = res->next; + kfree(tres); + } + + res = resources->bus_head; + resources->bus_head = NULL; + + while (res) { + tres = res; + res = res->next; + kfree(tres); + } +} + + +/* + * cpqhp_destroy_board_resources + * + * Puts node back in the resource list pointed to by head + */ +void cpqhp_destroy_board_resources(struct pci_func *func) +{ + struct pci_resource *res, *tres; + + res = func->io_head; + func->io_head = NULL; + + while (res) { + tres = res; + res = res->next; + kfree(tres); + } + + res = func->mem_head; + func->mem_head = NULL; + + while (res) { + tres = res; + res = res->next; + kfree(tres); + } + + res = func->p_mem_head; + func->p_mem_head = NULL; + + while (res) { + tres = res; + res = res->next; + kfree(tres); + } + + res = func->bus_head; + func->bus_head = NULL; + + while (res) { + tres = res; + res = res->next; + kfree(tres); + } +} diff --git a/drivers/pci/hotplug/cpqphp_sysfs.c b/drivers/pci/hotplug/cpqphp_sysfs.c new file mode 100644 index 000000000..fed1360ee --- /dev/null +++ b/drivers/pci/hotplug/cpqphp_sysfs.c @@ -0,0 +1,208 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * 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. + * + * Send feedback to <greg@kroah.com> + * + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/types.h> +#include <linux/proc_fs.h> +#include <linux/workqueue.h> +#include <linux/pci.h> +#include <linux/pci_hotplug.h> +#include <linux/mutex.h> +#include <linux/debugfs.h> +#include "cpqphp.h" + +static DEFINE_MUTEX(cpqphp_mutex); +static int show_ctrl(struct controller *ctrl, char *buf) +{ + char *out = buf; + int index; + struct pci_resource *res; + + out += sprintf(buf, "Free resources: memory\n"); + index = 11; + res = ctrl->mem_head; + while (res && index--) { + out += sprintf(out, "start = %8.8x, length = %8.8x\n", res->base, res->length); + res = res->next; + } + out += sprintf(out, "Free resources: prefetchable memory\n"); + index = 11; + res = ctrl->p_mem_head; + while (res && index--) { + out += sprintf(out, "start = %8.8x, length = %8.8x\n", res->base, res->length); + res = res->next; + } + out += sprintf(out, "Free resources: IO\n"); + index = 11; + res = ctrl->io_head; + while (res && index--) { + out += sprintf(out, "start = %8.8x, length = %8.8x\n", res->base, res->length); + res = res->next; + } + out += sprintf(out, "Free resources: bus numbers\n"); + index = 11; + res = ctrl->bus_head; + while (res && index--) { + out += sprintf(out, "start = %8.8x, length = %8.8x\n", res->base, res->length); + res = res->next; + } + + return out - buf; +} + +static int show_dev(struct controller *ctrl, char *buf) +{ + char *out = buf; + int index; + struct pci_resource *res; + struct pci_func *new_slot; + struct slot *slot; + + slot = ctrl->slot; + + while (slot) { + new_slot = cpqhp_slot_find(slot->bus, slot->device, 0); + if (!new_slot) + break; + out += sprintf(out, "assigned resources: memory\n"); + index = 11; + res = new_slot->mem_head; + while (res && index--) { + out += sprintf(out, "start = %8.8x, length = %8.8x\n", res->base, res->length); + res = res->next; + } + out += sprintf(out, "assigned resources: prefetchable memory\n"); + index = 11; + res = new_slot->p_mem_head; + while (res && index--) { + out += sprintf(out, "start = %8.8x, length = %8.8x\n", res->base, res->length); + res = res->next; + } + out += sprintf(out, "assigned resources: IO\n"); + index = 11; + res = new_slot->io_head; + while (res && index--) { + out += sprintf(out, "start = %8.8x, length = %8.8x\n", res->base, res->length); + res = res->next; + } + out += sprintf(out, "assigned resources: bus numbers\n"); + index = 11; + res = new_slot->bus_head; + while (res && index--) { + out += sprintf(out, "start = %8.8x, length = %8.8x\n", res->base, res->length); + res = res->next; + } + slot = slot->next; + } + + return out - buf; +} + +static int spew_debug_info(struct controller *ctrl, char *data, int size) +{ + int used; + + used = size - show_ctrl(ctrl, data); + used = (size - used) - show_dev(ctrl, &data[used]); + return used; +} + +struct ctrl_dbg { + int size; + char *data; + struct controller *ctrl; +}; + +#define MAX_OUTPUT (4*PAGE_SIZE) + +static int open(struct inode *inode, struct file *file) +{ + struct controller *ctrl = inode->i_private; + struct ctrl_dbg *dbg; + int retval = -ENOMEM; + + mutex_lock(&cpqphp_mutex); + dbg = kmalloc(sizeof(*dbg), GFP_KERNEL); + if (!dbg) + goto exit; + dbg->data = kmalloc(MAX_OUTPUT, GFP_KERNEL); + if (!dbg->data) { + kfree(dbg); + goto exit; + } + dbg->size = spew_debug_info(ctrl, dbg->data, MAX_OUTPUT); + file->private_data = dbg; + retval = 0; +exit: + mutex_unlock(&cpqphp_mutex); + return retval; +} + +static loff_t lseek(struct file *file, loff_t off, int whence) +{ + struct ctrl_dbg *dbg = file->private_data; + return fixed_size_llseek(file, off, whence, dbg->size); +} + +static ssize_t read(struct file *file, char __user *buf, + size_t nbytes, loff_t *ppos) +{ + struct ctrl_dbg *dbg = file->private_data; + return simple_read_from_buffer(buf, nbytes, ppos, dbg->data, dbg->size); +} + +static int release(struct inode *inode, struct file *file) +{ + struct ctrl_dbg *dbg = file->private_data; + + kfree(dbg->data); + kfree(dbg); + return 0; +} + +static const struct file_operations debug_ops = { + .owner = THIS_MODULE, + .open = open, + .llseek = lseek, + .read = read, + .release = release, +}; + +static struct dentry *root; + +void cpqhp_initialize_debugfs(void) +{ + if (!root) + root = debugfs_create_dir("cpqhp", NULL); +} + +void cpqhp_shutdown_debugfs(void) +{ + debugfs_remove(root); +} + +void cpqhp_create_debugfs_files(struct controller *ctrl) +{ + ctrl->dentry = debugfs_create_file(dev_name(&ctrl->pci_dev->dev), + S_IRUGO, root, ctrl, &debug_ops); +} + +void cpqhp_remove_debugfs_files(struct controller *ctrl) +{ + debugfs_remove(ctrl->dentry); + ctrl->dentry = NULL; +} + diff --git a/drivers/pci/hotplug/ibmphp.h b/drivers/pci/hotplug/ibmphp.h new file mode 100644 index 000000000..0399c60d2 --- /dev/null +++ b/drivers/pci/hotplug/ibmphp.h @@ -0,0 +1,750 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +#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. + * + * Send feedback to <gregkh@us.ibm.com> + * + */ + +#include <linux/pci_hotplug.h> + +extern int ibmphp_debug; + +#if !defined(MODULE) + #define MY_NAME "ibmphpd" +#else + #define MY_NAME THIS_MODULE->name +#endif +#define debug(fmt, arg...) do { if (ibmphp_debug == 1) printk(KERN_DEBUG "%s: " fmt, MY_NAME, ## arg); } while (0) +#define debug_pci(fmt, arg...) do { if (ibmphp_debug) printk(KERN_DEBUG "%s: " fmt, MY_NAME, ## arg); } while (0) +#define err(format, arg...) printk(KERN_ERR "%s: " format, MY_NAME, ## arg) +#define info(format, arg...) printk(KERN_INFO "%s: " format, MY_NAME, ## arg) +#define warn(format, arg...) printk(KERN_WARNING "%s: " format, MY_NAME, ## arg) + + +/* EBDA stuff */ + +/*********************************************************** +* SLOT CAPABILITY * +***********************************************************/ + +#define EBDA_SLOT_133_MAX 0x20 +#define EBDA_SLOT_100_MAX 0x10 +#define EBDA_SLOT_66_MAX 0x02 +#define EBDA_SLOT_PCIX_CAP 0x08 + + +/************************************************************ +* RESOURCE TYPE * +************************************************************/ + +#define EBDA_RSRC_TYPE_MASK 0x03 +#define EBDA_IO_RSRC_TYPE 0x00 +#define EBDA_MEM_RSRC_TYPE 0x01 +#define EBDA_PFM_RSRC_TYPE 0x03 +#define EBDA_RES_RSRC_TYPE 0x02 + + +/************************************************************* +* IO RESTRICTION TYPE * +*************************************************************/ + +#define EBDA_IO_RESTRI_MASK 0x0c +#define EBDA_NO_RESTRI 0x00 +#define EBDA_AVO_VGA_ADDR 0x04 +#define EBDA_AVO_VGA_ADDR_AND_ALIA 0x08 +#define EBDA_AVO_ISA_ADDR 0x0c + + +/************************************************************** +* DEVICE TYPE DEF * +**************************************************************/ + +#define EBDA_DEV_TYPE_MASK 0x10 +#define EBDA_PCI_DEV 0x10 +#define EBDA_NON_PCI_DEV 0x00 + + +/*************************************************************** +* PRIMARY DEF DEFINITION * +***************************************************************/ + +#define EBDA_PRI_DEF_MASK 0x20 +#define EBDA_PRI_PCI_BUS_INFO 0x20 +#define EBDA_NORM_DEV_RSRC_INFO 0x00 + + +//-------------------------------------------------------------- +// RIO TABLE DATA STRUCTURE +//-------------------------------------------------------------- + +struct rio_table_hdr { + u8 ver_num; + u8 scal_count; + u8 riodev_count; + u16 offset; +}; + +//------------------------------------------------------------- +// SCALABILITY DETAIL +//------------------------------------------------------------- + +struct scal_detail { + u8 node_id; + u32 cbar; + u8 port0_node_connect; + u8 port0_port_connect; + u8 port1_node_connect; + u8 port1_port_connect; + u8 port2_node_connect; + u8 port2_port_connect; + u8 chassis_num; +// struct list_head scal_detail_list; +}; + +//-------------------------------------------------------------- +// RIO DETAIL +//-------------------------------------------------------------- + +struct rio_detail { + u8 rio_node_id; + u32 bbar; + u8 rio_type; + u8 owner_id; + u8 port0_node_connect; + u8 port0_port_connect; + u8 port1_node_connect; + u8 port1_port_connect; + u8 first_slot_num; + u8 status; + u8 wpindex; + u8 chassis_num; + struct list_head rio_detail_list; +}; + +struct opt_rio { + u8 rio_type; + u8 chassis_num; + u8 first_slot_num; + u8 middle_num; + struct list_head opt_rio_list; +}; + +struct opt_rio_lo { + u8 rio_type; + u8 chassis_num; + u8 first_slot_num; + u8 middle_num; + u8 pack_count; + struct list_head opt_rio_lo_list; +}; + +/**************************************************************** +* HPC DESCRIPTOR NODE * +****************************************************************/ + +struct ebda_hpc_list { + u8 format; + u16 num_ctlrs; + short phys_addr; +// struct list_head ebda_hpc_list; +}; +/***************************************************************** +* IN HPC DATA STRUCTURE, THE ASSOCIATED SLOT AND BUS * +* STRUCTURE * +*****************************************************************/ + +struct ebda_hpc_slot { + u8 slot_num; + u32 slot_bus_num; + u8 ctl_index; + u8 slot_cap; +}; + +struct ebda_hpc_bus { + u32 bus_num; + u8 slots_at_33_conv; + u8 slots_at_66_conv; + u8 slots_at_66_pcix; + u8 slots_at_100_pcix; + u8 slots_at_133_pcix; +}; + + +/******************************************************************** +* THREE TYPE OF HOT PLUG CONTROLLER * +********************************************************************/ + +struct isa_ctlr_access { + u16 io_start; + u16 io_end; +}; + +struct pci_ctlr_access { + u8 bus; + u8 dev_fun; +}; + +struct wpeg_i2c_ctlr_access { + ulong wpegbbar; + u8 i2c_addr; +}; + +#define HPC_DEVICE_ID 0x0246 +#define HPC_SUBSYSTEM_ID 0x0247 +#define HPC_PCI_OFFSET 0x40 +/************************************************************************* +* RSTC DESCRIPTOR NODE * +*************************************************************************/ + +struct ebda_rsrc_list { + u8 format; + u16 num_entries; + u16 phys_addr; + struct ebda_rsrc_list *next; +}; + + +/*************************************************************************** +* PCI RSRC NODE * +***************************************************************************/ + +struct ebda_pci_rsrc { + u8 rsrc_type; + u8 bus_num; + u8 dev_fun; + u32 start_addr; + u32 end_addr; + u8 marked; /* for NVRAM */ + struct list_head ebda_pci_rsrc_list; +}; + + +/*********************************************************** +* BUS_INFO DATE STRUCTURE * +***********************************************************/ + +struct bus_info { + u8 slot_min; + u8 slot_max; + u8 slot_count; + u8 busno; + u8 controller_id; + u8 current_speed; + u8 current_bus_mode; + u8 index; + u8 slots_at_33_conv; + u8 slots_at_66_conv; + u8 slots_at_66_pcix; + u8 slots_at_100_pcix; + u8 slots_at_133_pcix; + struct list_head bus_info_list; +}; + + +/*********************************************************** +* GLOBAL VARIABLES * +***********************************************************/ +extern struct list_head ibmphp_ebda_pci_rsrc_head; +extern struct list_head ibmphp_slot_head; +/*********************************************************** +* FUNCTION PROTOTYPES * +***********************************************************/ + +void ibmphp_free_ebda_hpc_queue(void); +int ibmphp_access_ebda(void); +struct slot *ibmphp_get_slot_from_physical_num(u8); +int ibmphp_get_total_hp_slots(void); +void ibmphp_free_ibm_slot(struct slot *); +void ibmphp_free_bus_info_queue(void); +void ibmphp_free_ebda_pci_rsrc_queue(void); +struct bus_info *ibmphp_find_same_bus_num(u32); +int ibmphp_get_bus_index(u8); +u16 ibmphp_get_total_controllers(void); +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 + * 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 */ + +int ibmphp_rsrc_init(void); +int ibmphp_add_resource(struct resource_node *); +int ibmphp_remove_resource(struct resource_node *); +int ibmphp_find_resource(struct bus_node *, u32, struct resource_node **, int); +int ibmphp_check_resource(struct resource_node *, u8); +int ibmphp_remove_bus(struct bus_node *, u8); +void ibmphp_free_resources(void); +int ibmphp_add_pfmem_from_mem(struct resource_node *); +struct bus_node *ibmphp_find_res_bus(u8); +void ibmphp_print_test(void); /* for debugging purposes */ + +int ibmphp_hpc_readslot(struct slot *, u8, u8 *); +int ibmphp_hpc_writeslot(struct slot *, u8); +void ibmphp_lock_operations(void); +void ibmphp_unlock_operations(void); +int ibmphp_hpc_start_poll_thread(void); +void ibmphp_hpc_stop_poll_thread(void); + +//---------------------------------------------------------------------------- + + +//---------------------------------------------------------------------------- +// HPC return codes +//---------------------------------------------------------------------------- +#define HPC_ERROR 0xFF + +//----------------------------------------------------------------------------- +// BUS INFO +//----------------------------------------------------------------------------- +#define BUS_SPEED 0x30 +#define BUS_MODE 0x40 +#define BUS_MODE_PCIX 0x01 +#define BUS_MODE_PCI 0x00 +#define BUS_SPEED_2 0x20 +#define BUS_SPEED_1 0x10 +#define BUS_SPEED_33 0x00 +#define BUS_SPEED_66 0x01 +#define BUS_SPEED_100 0x02 +#define BUS_SPEED_133 0x03 +#define BUS_SPEED_66PCIX 0x04 +#define BUS_SPEED_66UNKNOWN 0x05 +#define BUS_STATUS_AVAILABLE 0x01 +#define BUS_CONTROL_AVAILABLE 0x02 +#define SLOT_LATCH_REGS_SUPPORTED 0x10 + +#define PRGM_MODEL_REV_LEVEL 0xF0 +#define MAX_ADAPTER_NONE 0x09 + +//---------------------------------------------------------------------------- +// HPC 'write' operations/commands +//---------------------------------------------------------------------------- +// Command Code State Write to reg +// Machine at index +//------------------------- ---- ------- ------------ +#define HPC_CTLR_ENABLEIRQ 0x00 // N 15 +#define HPC_CTLR_DISABLEIRQ 0x01 // N 15 +#define HPC_SLOT_OFF 0x02 // Y 0-14 +#define HPC_SLOT_ON 0x03 // Y 0-14 +#define HPC_SLOT_ATTNOFF 0x04 // N 0-14 +#define HPC_SLOT_ATTNON 0x05 // N 0-14 +#define HPC_CTLR_CLEARIRQ 0x06 // N 15 +#define HPC_CTLR_RESET 0x07 // Y 15 +#define HPC_CTLR_IRQSTEER 0x08 // N 15 +#define HPC_BUS_33CONVMODE 0x09 // Y 31-34 +#define HPC_BUS_66CONVMODE 0x0A // Y 31-34 +#define HPC_BUS_66PCIXMODE 0x0B // Y 31-34 +#define HPC_BUS_100PCIXMODE 0x0C // Y 31-34 +#define HPC_BUS_133PCIXMODE 0x0D // Y 31-34 +#define HPC_ALLSLOT_OFF 0x11 // Y 15 +#define HPC_ALLSLOT_ON 0x12 // Y 15 +#define HPC_SLOT_BLINKLED 0x13 // N 0-14 + +//---------------------------------------------------------------------------- +// read commands +//---------------------------------------------------------------------------- +#define READ_SLOTSTATUS 0x01 +#define READ_EXTSLOTSTATUS 0x02 +#define READ_BUSSTATUS 0x03 +#define READ_CTLRSTATUS 0x04 +#define READ_ALLSTAT 0x05 +#define READ_ALLSLOT 0x06 +#define READ_SLOTLATCHLOWREG 0x07 +#define READ_REVLEVEL 0x08 +#define READ_HPCOPTIONS 0x09 +//---------------------------------------------------------------------------- +// slot status +//---------------------------------------------------------------------------- +#define HPC_SLOT_POWER 0x01 +#define HPC_SLOT_CONNECT 0x02 +#define HPC_SLOT_ATTN 0x04 +#define HPC_SLOT_PRSNT2 0x08 +#define HPC_SLOT_PRSNT1 0x10 +#define HPC_SLOT_PWRGD 0x20 +#define HPC_SLOT_BUS_SPEED 0x40 +#define HPC_SLOT_LATCH 0x80 + +//---------------------------------------------------------------------------- +// HPC_SLOT_POWER status return codes +//---------------------------------------------------------------------------- +#define HPC_SLOT_POWER_OFF 0x00 +#define HPC_SLOT_POWER_ON 0x01 + +//---------------------------------------------------------------------------- +// HPC_SLOT_CONNECT status return codes +//---------------------------------------------------------------------------- +#define HPC_SLOT_CONNECTED 0x00 +#define HPC_SLOT_DISCONNECTED 0x01 + +//---------------------------------------------------------------------------- +// HPC_SLOT_ATTN status return codes +//---------------------------------------------------------------------------- +#define HPC_SLOT_ATTN_OFF 0x00 +#define HPC_SLOT_ATTN_ON 0x01 +#define HPC_SLOT_ATTN_BLINK 0x02 + +//---------------------------------------------------------------------------- +// HPC_SLOT_PRSNT status return codes +//---------------------------------------------------------------------------- +#define HPC_SLOT_EMPTY 0x00 +#define HPC_SLOT_PRSNT_7 0x01 +#define HPC_SLOT_PRSNT_15 0x02 +#define HPC_SLOT_PRSNT_25 0x03 + +//---------------------------------------------------------------------------- +// HPC_SLOT_PWRGD status return codes +//---------------------------------------------------------------------------- +#define HPC_SLOT_PWRGD_FAULT_NONE 0x00 +#define HPC_SLOT_PWRGD_GOOD 0x01 + +//---------------------------------------------------------------------------- +// HPC_SLOT_BUS_SPEED status return codes +//---------------------------------------------------------------------------- +#define HPC_SLOT_BUS_SPEED_OK 0x00 +#define HPC_SLOT_BUS_SPEED_MISM 0x01 + +//---------------------------------------------------------------------------- +// HPC_SLOT_LATCH status return codes +//---------------------------------------------------------------------------- +#define HPC_SLOT_LATCH_OPEN 0x01 // NOTE : in PCI spec bit off = open +#define HPC_SLOT_LATCH_CLOSED 0x00 // NOTE : in PCI spec bit on = closed + + +//---------------------------------------------------------------------------- +// extended slot status +//---------------------------------------------------------------------------- +#define HPC_SLOT_PCIX 0x01 +#define HPC_SLOT_SPEED1 0x02 +#define HPC_SLOT_SPEED2 0x04 +#define HPC_SLOT_BLINK_ATTN 0x08 +#define HPC_SLOT_RSRVD1 0x10 +#define HPC_SLOT_RSRVD2 0x20 +#define HPC_SLOT_BUS_MODE 0x40 +#define HPC_SLOT_RSRVD3 0x80 + +//---------------------------------------------------------------------------- +// HPC_XSLOT_PCIX_CAP status return codes +//---------------------------------------------------------------------------- +#define HPC_SLOT_PCIX_NO 0x00 +#define HPC_SLOT_PCIX_YES 0x01 + +//---------------------------------------------------------------------------- +// HPC_XSLOT_SPEED status return codes +//---------------------------------------------------------------------------- +#define HPC_SLOT_SPEED_33 0x00 +#define HPC_SLOT_SPEED_66 0x01 +#define HPC_SLOT_SPEED_133 0x02 + +//---------------------------------------------------------------------------- +// HPC_XSLOT_ATTN_BLINK status return codes +//---------------------------------------------------------------------------- +#define HPC_SLOT_ATTN_BLINK_OFF 0x00 +#define HPC_SLOT_ATTN_BLINK_ON 0x01 + +//---------------------------------------------------------------------------- +// HPC_XSLOT_BUS_MODE status return codes +//---------------------------------------------------------------------------- +#define HPC_SLOT_BUS_MODE_OK 0x00 +#define HPC_SLOT_BUS_MODE_MISM 0x01 + +//---------------------------------------------------------------------------- +// Controller status +//---------------------------------------------------------------------------- +#define HPC_CTLR_WORKING 0x01 +#define HPC_CTLR_FINISHED 0x02 +#define HPC_CTLR_RESULT0 0x04 +#define HPC_CTLR_RESULT1 0x08 +#define HPC_CTLR_RESULE2 0x10 +#define HPC_CTLR_RESULT3 0x20 +#define HPC_CTLR_IRQ_ROUTG 0x40 +#define HPC_CTLR_IRQ_PENDG 0x80 + +//---------------------------------------------------------------------------- +// HPC_CTLR_WORKING status return codes +//---------------------------------------------------------------------------- +#define HPC_CTLR_WORKING_NO 0x00 +#define HPC_CTLR_WORKING_YES 0x01 + +//---------------------------------------------------------------------------- +// HPC_CTLR_FINISHED status return codes +//---------------------------------------------------------------------------- +#define HPC_CTLR_FINISHED_NO 0x00 +#define HPC_CTLR_FINISHED_YES 0x01 + +//---------------------------------------------------------------------------- +// HPC_CTLR_RESULT status return codes +//---------------------------------------------------------------------------- +#define HPC_CTLR_RESULT_SUCCESS 0x00 +#define HPC_CTLR_RESULT_FAILED 0x01 +#define HPC_CTLR_RESULT_RSVD 0x02 +#define HPC_CTLR_RESULT_NORESP 0x03 + + +//---------------------------------------------------------------------------- +// macro for slot info +//---------------------------------------------------------------------------- +#define SLOT_POWER(s) ((u8) ((s & HPC_SLOT_POWER) \ + ? HPC_SLOT_POWER_ON : HPC_SLOT_POWER_OFF)) + +#define SLOT_CONNECT(s) ((u8) ((s & HPC_SLOT_CONNECT) \ + ? HPC_SLOT_DISCONNECTED : HPC_SLOT_CONNECTED)) + +#define SLOT_ATTN(s, es) ((u8) ((es & HPC_SLOT_BLINK_ATTN) \ + ? HPC_SLOT_ATTN_BLINK \ + : ((s & HPC_SLOT_ATTN) ? HPC_SLOT_ATTN_ON : HPC_SLOT_ATTN_OFF))) + +#define SLOT_PRESENT(s) ((u8) ((s & HPC_SLOT_PRSNT1) \ + ? ((s & HPC_SLOT_PRSNT2) ? HPC_SLOT_EMPTY : HPC_SLOT_PRSNT_15) \ + : ((s & HPC_SLOT_PRSNT2) ? HPC_SLOT_PRSNT_25 : HPC_SLOT_PRSNT_7))) + +#define SLOT_PWRGD(s) ((u8) ((s & HPC_SLOT_PWRGD) \ + ? HPC_SLOT_PWRGD_GOOD : HPC_SLOT_PWRGD_FAULT_NONE)) + +#define SLOT_BUS_SPEED(s) ((u8) ((s & HPC_SLOT_BUS_SPEED) \ + ? HPC_SLOT_BUS_SPEED_MISM : HPC_SLOT_BUS_SPEED_OK)) + +#define SLOT_LATCH(s) ((u8) ((s & HPC_SLOT_LATCH) \ + ? HPC_SLOT_LATCH_CLOSED : HPC_SLOT_LATCH_OPEN)) + +#define SLOT_PCIX(es) ((u8) ((es & HPC_SLOT_PCIX) \ + ? HPC_SLOT_PCIX_YES : HPC_SLOT_PCIX_NO)) + +#define SLOT_SPEED(es) ((u8) ((es & HPC_SLOT_SPEED2) \ + ? ((es & HPC_SLOT_SPEED1) ? HPC_SLOT_SPEED_133 \ + : HPC_SLOT_SPEED_66) \ + : HPC_SLOT_SPEED_33)) + +#define SLOT_BUS_MODE(es) ((u8) ((es & HPC_SLOT_BUS_MODE) \ + ? HPC_SLOT_BUS_MODE_MISM : HPC_SLOT_BUS_MODE_OK)) + +//-------------------------------------------------------------------------- +// macro for bus info +//--------------------------------------------------------------------------- +#define CURRENT_BUS_SPEED(s) ((u8) (s & BUS_SPEED_2) \ + ? ((s & BUS_SPEED_1) ? BUS_SPEED_133 : BUS_SPEED_100) \ + : ((s & BUS_SPEED_1) ? BUS_SPEED_66 : BUS_SPEED_33)) + +#define CURRENT_BUS_MODE(s) ((u8) (s & BUS_MODE) ? BUS_MODE_PCIX : BUS_MODE_PCI) + +#define READ_BUS_STATUS(s) ((u8) (s->options & BUS_STATUS_AVAILABLE)) + +#define READ_BUS_MODE(s) ((s->revision & PRGM_MODEL_REV_LEVEL) >= 0x20) + +#define SET_BUS_STATUS(s) ((u8) (s->options & BUS_CONTROL_AVAILABLE)) + +#define READ_SLOT_LATCH(s) ((u8) (s->options & SLOT_LATCH_REGS_SUPPORTED)) + +//---------------------------------------------------------------------------- +// macro for controller info +//---------------------------------------------------------------------------- +#define CTLR_WORKING(c) ((u8) ((c & HPC_CTLR_WORKING) \ + ? HPC_CTLR_WORKING_YES : HPC_CTLR_WORKING_NO)) +#define CTLR_FINISHED(c) ((u8) ((c & HPC_CTLR_FINISHED) \ + ? HPC_CTLR_FINISHED_YES : HPC_CTLR_FINISHED_NO)) +#define CTLR_RESULT(c) ((u8) ((c & HPC_CTLR_RESULT1) \ + ? ((c & HPC_CTLR_RESULT0) ? HPC_CTLR_RESULT_NORESP \ + : HPC_CTLR_RESULT_RSVD) \ + : ((c & HPC_CTLR_RESULT0) ? HPC_CTLR_RESULT_FAILED \ + : HPC_CTLR_RESULT_SUCCESS))) + +// command that affect the state machine of HPC +#define NEEDTOCHECK_CMDSTATUS(c) ((c == HPC_SLOT_OFF) || \ + (c == HPC_SLOT_ON) || \ + (c == HPC_CTLR_RESET) || \ + (c == HPC_BUS_33CONVMODE) || \ + (c == HPC_BUS_66CONVMODE) || \ + (c == HPC_BUS_66PCIXMODE) || \ + (c == HPC_BUS_100PCIXMODE) || \ + (c == HPC_BUS_133PCIXMODE) || \ + (c == HPC_ALLSLOT_OFF) || \ + (c == HPC_ALLSLOT_ON)) + + +/* Core part of the driver */ + +#define ENABLE 1 +#define DISABLE 0 + +#define CARD_INFO 0x07 +#define PCIX133 0x07 +#define PCIX66 0x05 +#define PCI66 0x04 + +extern struct pci_bus *ibmphp_pci_bus; + +/* Variables */ + +struct pci_func { + struct pci_dev *dev; /* from the OS */ + u8 busno; + u8 device; + u8 function; + struct resource_node *io[6]; + struct resource_node *mem[6]; + struct resource_node *pfmem[6]; + struct pci_func *next; + int devices[32]; /* for bridge config */ + u8 irq[4]; /* for interrupt config */ + u8 bus; /* flag for unconfiguring, to say if PPB */ +}; + +struct slot { + u8 bus; + u8 device; + u8 number; + u8 real_physical_slot_num; + u32 capabilities; + u8 supported_speed; + u8 supported_bus_mode; + u8 flag; /* this is for disable slot and polling */ + u8 ctlr_index; + struct hotplug_slot hotplug_slot; + struct controller *ctrl; + struct pci_func *func; + u8 irq[4]; + int bit_mode; /* 0 = 32, 1 = 64 */ + struct bus_info *bus_on; + struct list_head ibm_slot_list; + u8 status; + u8 ext_status; + u8 busstatus; +}; + +struct controller { + struct ebda_hpc_slot *slots; + struct ebda_hpc_bus *buses; + struct pci_dev *ctrl_dev; /* in case where controller is PCI */ + u8 starting_slot_num; /* starting and ending slot #'s this ctrl controls*/ + u8 ending_slot_num; + u8 revision; + u8 options; /* which options HPC supports */ + u8 status; + u8 ctlr_id; + u8 slot_count; + u8 bus_count; + u8 ctlr_relative_id; + u32 irq; + union { + struct isa_ctlr_access isa_ctlr; + struct pci_ctlr_access pci_ctlr; + struct wpeg_i2c_ctlr_access wpeg_ctlr; + } u; + u8 ctlr_type; + struct list_head ebda_hpc_list; +}; + +/* Functions */ + +int ibmphp_init_devno(struct slot **); /* This function is called from EBDA, so we need it not be static */ +int ibmphp_do_disable_slot(struct slot *slot_cur); +int ibmphp_update_slot_info(struct slot *); /* This function is called from HPC, so we need it to not be static */ +int ibmphp_configure_card(struct pci_func *, u8); +int ibmphp_unconfigure_card(struct slot **, int); +extern const struct hotplug_slot_ops ibmphp_hotplug_slot_ops; + +static inline struct slot *to_slot(struct hotplug_slot *hotplug_slot) +{ + return container_of(hotplug_slot, struct slot, hotplug_slot); +} + +#endif //__IBMPHP_H + diff --git a/drivers/pci/hotplug/ibmphp_core.c b/drivers/pci/hotplug/ibmphp_core.c new file mode 100644 index 000000000..197997e26 --- /dev/null +++ b/drivers/pci/hotplug/ibmphp_core.c @@ -0,0 +1,1252 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * 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. + * + * Send feedback to <gregkh@us.ibm.com> + * + */ + +#include <linux/init.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/pci.h> +#include <linux/interrupt.h> +#include <linux/delay.h> +#include <linux/wait.h> +#include "../pci.h" +#include <asm/pci_x86.h> /* for struct irq_routing_table */ +#include <asm/io_apic.h> +#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 bool 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 IRQs we're using so far (in case MPS + * tables don't provide default info for empty slots */ + +static int init_flag; + +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; + u8 slot_count = 0; + + list_for_each_entry(slot_cur, &ibmphp_slot_head, ibm_slot_list) { + /* sometimes the hot-pluggable slots start with 4 (not always from 1) */ + slot_count = max(slot_count, slot_cur->number); + } + return slot_count; +} + +/* This routine will put the correct slot->device information per slot. It's + * called from initialization of the slot structures. It will also assign + * interrupt numbers per each slot. + * Parameters: struct slot + * Returns 0 or errors + */ +int ibmphp_init_devno(struct slot **cur_slot) +{ + struct irq_routing_table *rtable; + int len; + int loop; + int i; + + rtable = pcibios_get_irq_routing_table(); + if (!rtable) { + err("no BIOS routing table...\n"); + return -ENOMEM; + } + + len = (rtable->size - sizeof(struct irq_routing_table)) / + sizeof(struct irq_info); + + if (!len) { + kfree(rtable); + return -1; + } + for (loop = 0; loop < len; loop++) { + if ((*cur_slot)->number == rtable->slots[loop].slot && + (*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->exclusive_irqs = %x\n", + rtable->exclusive_irqs); + debug("rtable->slots[loop].irq[0].bitmap = %x\n", + rtable->slots[loop].irq[0].bitmap); + debug("rtable->slots[loop].irq[1].bitmap = %x\n", + rtable->slots[loop].irq[1].bitmap); + debug("rtable->slots[loop].irq[2].bitmap = %x\n", + rtable->slots[loop].irq[2].bitmap); + debug("rtable->slots[loop].irq[3].bitmap = %x\n", + rtable->slots[loop].irq[3].bitmap); + + debug("rtable->slots[loop].irq[0].link = %x\n", + rtable->slots[loop].irq[0].link); + debug("rtable->slots[loop].irq[1].link = %x\n", + rtable->slots[loop].irq[1].link); + debug("rtable->slots[loop].irq[2].link = %x\n", + rtable->slots[loop].irq[2].link); + debug("rtable->slots[loop].irq[3].link = %x\n", + rtable->slots[loop].irq[3].link); + debug("end of init_devno\n"); + kfree(rtable); + return 0; + } + } + + kfree(rtable); + return -1; +} + +static inline int power_on(struct slot *slot_cur) +{ + u8 cmd = HPC_SLOT_ON; + int retval; + + retval = ibmphp_hpc_writeslot(slot_cur, cmd); + if (retval) { + err("power on failed\n"); + return retval; + } + if (CTLR_RESULT(slot_cur->ctrl->status)) { + err("command not completed successfully in power_on\n"); + return -EIO; + } + msleep(3000); /* For ServeRAID cards, and some 66 PCI */ + return 0; +} + +static inline int power_off(struct slot *slot_cur) +{ + u8 cmd = HPC_SLOT_OFF; + int retval; + + retval = ibmphp_hpc_writeslot(slot_cur, cmd); + if (retval) { + err("power off failed\n"); + return retval; + } + if (CTLR_RESULT(slot_cur->ctrl->status)) { + err("command not completed successfully in power_off\n"); + retval = -EIO; + } + return retval; +} + +static int set_attention_status(struct hotplug_slot *hotplug_slot, u8 value) +{ + int rc = 0; + struct slot *pslot; + u8 cmd = 0x00; /* avoid compiler warning */ + + debug("set_attention_status - Entry hotplug_slot[%lx] value[%x]\n", + (ulong) hotplug_slot, value); + ibmphp_lock_operations(); + + + if (hotplug_slot) { + switch (value) { + case HPC_SLOT_ATTN_OFF: + cmd = HPC_SLOT_ATTNOFF; + break; + case HPC_SLOT_ATTN_ON: + cmd = HPC_SLOT_ATTNON; + break; + case HPC_SLOT_ATTN_BLINK: + cmd = HPC_SLOT_BLINKLED; + break; + default: + rc = -ENODEV; + err("set_attention_status - Error : invalid input [%x]\n", + value); + break; + } + if (rc == 0) { + pslot = to_slot(hotplug_slot); + rc = ibmphp_hpc_writeslot(pslot, cmd); + } + } else + rc = -ENODEV; + + ibmphp_unlock_operations(); + + debug("set_attention_status - Exit rc[%d]\n", rc); + return rc; +} + +static int get_attention_status(struct hotplug_slot *hotplug_slot, u8 *value) +{ + int rc = -ENODEV; + struct slot *pslot; + struct slot myslot; + + debug("get_attention_status - Entry hotplug_slot[%lx] pvalue[%lx]\n", + (ulong) hotplug_slot, (ulong) value); + + ibmphp_lock_operations(); + if (hotplug_slot) { + pslot = to_slot(hotplug_slot); + memcpy(&myslot, pslot, sizeof(struct slot)); + rc = ibmphp_hpc_readslot(pslot, READ_SLOTSTATUS, + &myslot.status); + if (!rc) + rc = ibmphp_hpc_readslot(pslot, READ_EXTSLOTSTATUS, + &myslot.ext_status); + if (!rc) + *value = SLOT_ATTN(myslot.status, myslot.ext_status); + } + + ibmphp_unlock_operations(); + debug("get_attention_status - Exit rc[%d] value[%x]\n", rc, *value); + return rc; +} + +static int get_latch_status(struct hotplug_slot *hotplug_slot, u8 *value) +{ + int rc = -ENODEV; + struct slot *pslot; + struct slot myslot; + + debug("get_latch_status - Entry hotplug_slot[%lx] pvalue[%lx]\n", + (ulong) hotplug_slot, (ulong) value); + ibmphp_lock_operations(); + if (hotplug_slot) { + pslot = to_slot(hotplug_slot); + memcpy(&myslot, pslot, sizeof(struct slot)); + rc = ibmphp_hpc_readslot(pslot, READ_SLOTSTATUS, + &myslot.status); + if (!rc) + *value = SLOT_LATCH(myslot.status); + } + + ibmphp_unlock_operations(); + debug("get_latch_status - Exit rc[%d] rc[%x] value[%x]\n", + rc, rc, *value); + return rc; +} + + +static int get_power_status(struct hotplug_slot *hotplug_slot, u8 *value) +{ + int rc = -ENODEV; + struct slot *pslot; + struct slot myslot; + + debug("get_power_status - Entry hotplug_slot[%lx] pvalue[%lx]\n", + (ulong) hotplug_slot, (ulong) value); + ibmphp_lock_operations(); + if (hotplug_slot) { + pslot = to_slot(hotplug_slot); + memcpy(&myslot, pslot, sizeof(struct slot)); + rc = ibmphp_hpc_readslot(pslot, READ_SLOTSTATUS, + &myslot.status); + if (!rc) + *value = SLOT_PWRGD(myslot.status); + } + + ibmphp_unlock_operations(); + debug("get_power_status - Exit rc[%d] rc[%x] value[%x]\n", + rc, rc, *value); + return rc; +} + +static int get_adapter_present(struct hotplug_slot *hotplug_slot, u8 *value) +{ + int rc = -ENODEV; + struct slot *pslot; + u8 present; + struct slot myslot; + + debug("get_adapter_status - Entry hotplug_slot[%lx] pvalue[%lx]\n", + (ulong) hotplug_slot, (ulong) value); + ibmphp_lock_operations(); + if (hotplug_slot) { + pslot = to_slot(hotplug_slot); + 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 slot *slot) +{ + int rc = 0; + u8 mode = 0; + enum pci_bus_speed speed; + struct pci_bus *bus = slot->hotplug_slot.pci_slot->bus; + + debug("%s - Entry slot[%p]\n", __func__, slot); + + ibmphp_lock_operations(); + mode = slot->supported_bus_mode; + speed = slot->supported_speed; + ibmphp_unlock_operations(); + + switch (speed) { + case BUS_SPEED_33: + break; + case BUS_SPEED_66: + if (mode == BUS_MODE_PCIX) + speed += 0x01; + break; + case BUS_SPEED_100: + case BUS_SPEED_133: + speed += 0x01; + break; + default: + /* Note (will need to change): there would be soon 256, 512 also */ + rc = -ENODEV; + } + + if (!rc) + bus->max_bus_speed = speed; + + debug("%s - Exit rc[%d] speed[%x]\n", __func__, rc, speed); + 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; + int retval; + int rc; + + list_for_each_entry(slot_cur, &ibmphp_slot_head, ibm_slot_list) { + 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; + get_max_bus_speed(slot_cur); + + 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 pci_bus *bus = slot_cur->hotplug_slot.pci_slot->bus; + u8 bus_speed; + u8 mode; + + 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; + } + + bus->cur_bus_speed = bus_speed; + // To do: bus_names + + return 0; +} + + +/****************************************************************************** + * 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; + list_for_each_entry(slot_cur, &ibmphp_slot_head, 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, *next; + + debug("%s -- enter\n", __func__); + + list_for_each_entry_safe(slot_cur, next, &ibmphp_slot_head, + ibm_slot_list) { + pci_hp_del(&slot_cur->hotplug_slot); + slot_cur->ctrl = NULL; + slot_cur->bus_on = NULL; + + /* + * We don't want to actually remove the resources, + * since ibmphp_free_resources() will do just that. + */ + ibmphp_unconfigure_card(&slot_cur, -1); + + pci_hp_destroy(&slot_cur->hotplug_slot); + kfree(slot_cur); + } + debug("%s -- exit\n", __func__); +} + +static void ibm_unconfigure_device(struct pci_func *func) +{ + struct pci_dev *temp; + u8 j; + + debug("inside %s\n", __func__); + debug("func->device = %x, func->function = %x\n", + func->device, func->function); + debug("func->device << 3 | 0x0 = %x\n", func->device << 3 | 0x0); + + pci_lock_rescan_remove(); + + for (j = 0; j < 0x08; j++) { + temp = pci_get_domain_bus_and_slot(0, func->busno, + (func->device << 3) | j); + if (temp) { + pci_stop_and_remove_bus_device(temp); + pci_dev_put(temp); + } + } + + pci_dev_put(func->dev); + + pci_unlock_rescan_remove(); +} + +/* + * 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, *b; + 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) + return 1; + + dev = kmalloc(sizeof(*dev), GFP_KERNEL); + if (!dev) { + kfree(bus); + 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_structure_fixup()\n", + __func__); + b = pci_scan_bus(busno, ibmphp_pci_bus->ops, NULL); + if (!b) + continue; + + pci_bus_add_devices(b); + break; + } + } + + kfree(dev); + kfree(bus); + + return 0; +} + +static int ibm_configure_device(struct pci_func *func) +{ + 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 */ + + pci_lock_rescan_remove(); + + if (!(bus_structure_fixup(func->busno))) + flag = 1; + if (func->dev == NULL) + func->dev = pci_get_domain_bus_and_slot(0, func->busno, + PCI_DEVFN(func->device, func->function)); + + if (func->dev == NULL) { + struct pci_bus *bus = pci_find_bus(0, func->busno); + if (!bus) + goto out; + + num = pci_scan_slot(bus, + PCI_DEVFN(func->device, func->function)); + if (num) + pci_bus_add_devices(bus); + + func->dev = pci_get_domain_bus_and_slot(0, func->busno, + PCI_DEVFN(func->device, func->function)); + if (func->dev == NULL) { + err("ERROR... : pci_dev still NULL\n"); + goto out; + } + } + if (!(flag) && (func->dev->hdr_type == PCI_HEADER_TYPE_BRIDGE)) { + pci_hp_add_bridge(func->dev); + child = func->dev->subordinate; + if (child) + pci_bus_add_devices(child); + } + + out: + pci_unlock_rescan_remove(); + 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 const struct pci_device_id ciobx[] = { + { PCI_DEVICE(PCI_VENDOR_ID_SERVERWORKS, 0x0101) }, + { }, + }; + + debug("%s - entry slot # %d\n", __func__, slot_cur->number); + if (SET_BUS_STATUS(slot_cur->ctrl) && is_bus_empty(slot_cur)) { + rc = slot_update(&slot_cur); + if (rc) + return rc; + speed = SLOT_SPEED(slot_cur->ext_status); + debug("ext_status = %x, speed = %x\n", slot_cur->ext_status, speed); + switch (speed) { + case HPC_SLOT_SPEED_33: + cmd = HPC_BUS_33CONVMODE; + break; + case HPC_SLOT_SPEED_66: + if (SLOT_PCIX(slot_cur->ext_status)) { + if ((slot_cur->supported_speed >= BUS_SPEED_66) && + (slot_cur->supported_bus_mode == BUS_MODE_PCIX)) + cmd = HPC_BUS_66PCIXMODE; + else if (!SLOT_BUS_MODE(slot_cur->ext_status)) + /* if max slot/bus capability is 66 pci + and there's no bus mode mismatch, then + the adapter supports 66 pci */ + cmd = HPC_BUS_66CONVMODE; + else + cmd = HPC_BUS_33CONVMODE; + } else { + if (slot_cur->supported_speed >= BUS_SPEED_66) + cmd = HPC_BUS_66CONVMODE; + else + cmd = HPC_BUS_33CONVMODE; + } + break; + case HPC_SLOT_SPEED_133: + switch (slot_cur->supported_speed) { + case BUS_SPEED_33: + cmd = HPC_BUS_33CONVMODE; + break; + case BUS_SPEED_66: + if (slot_cur->supported_bus_mode == BUS_MODE_PCIX) + cmd = HPC_BUS_66PCIXMODE; + else + cmd = HPC_BUS_66CONVMODE; + break; + case BUS_SPEED_100: + cmd = HPC_BUS_100PCIXMODE; + break; + case BUS_SPEED_133: + /* This is to take care of the bug in CIOBX chip */ + if (pci_dev_present(ciobx)) + ibmphp_hpc_writeslot(slot_cur, + HPC_BUS_100PCIXMODE); + cmd = HPC_BUS_133PCIXMODE; + break; + default: + err("Wrong bus speed\n"); + return -ENODEV; + } + break; + default: + err("wrong slot speed\n"); + return -ENODEV; + } + debug("setting bus speed for slot %d, cmd %x\n", + slot_cur->number, cmd); + retval = ibmphp_hpc_writeslot(slot_cur, cmd); + if (retval) { + err("setting bus speed failed\n"); + return retval; + } + if (CTLR_RESULT(slot_cur->ctrl->status)) { + err("command not completed successfully in set_bus\n"); + return -EIO; + } + } + /* This is for x440, once Brandon fixes the firmware, + will not need this delay */ + msleep(1000); + debug("%s -Exit\n", __func__); + return 0; +} + +/* This routine checks the bus limitations that the slot is on from the BIOS. + * This is used in deciding whether or not to power up the slot. + * (electrical/spec limitations. For example, >1 133 MHz or >2 66 PCI cards on + * same bus) + * Parameters: slot + * Returns: 0 = no limitations, -EINVAL = exceeded limitations on the bus + */ +static int check_limitations(struct slot *slot_cur) +{ + u8 i; + struct slot *tmp_slot; + u8 count = 0; + u8 limitation = 0; + + for (i = slot_cur->bus_on->slot_min; i <= slot_cur->bus_on->slot_max; i++) { + tmp_slot = ibmphp_get_slot_from_physical_num(i); + if (!tmp_slot) + return -ENODEV; + if ((SLOT_PWRGD(tmp_slot->status)) && + !(SLOT_CONNECT(tmp_slot->status))) + count++; + } + get_cur_bus_info(&slot_cur); + switch (slot_cur->bus_on->current_speed) { + case BUS_SPEED_33: + limitation = slot_cur->bus_on->slots_at_33_conv; + break; + case BUS_SPEED_66: + if (slot_cur->bus_on->current_bus_mode == BUS_MODE_PCIX) + limitation = slot_cur->bus_on->slots_at_66_pcix; + else + limitation = slot_cur->bus_on->slots_at_66_conv; + break; + case BUS_SPEED_100: + limitation = slot_cur->bus_on->slots_at_100_pcix; + break; + case BUS_SPEED_133: + limitation = slot_cur->bus_on->slots_at_133_pcix; + break; + } + + if ((count + 1) > limitation) + return -EINVAL; + return 0; +} + +static inline void print_card_capability(struct slot *slot_cur) +{ + info("capability of the card is "); + if ((slot_cur->ext_status & CARD_INFO) == PCIX133) + info(" 133 MHz PCI-X\n"); + else if ((slot_cur->ext_status & CARD_INFO) == PCIX66) + info(" 66 MHz PCI-X\n"); + else if ((slot_cur->ext_status & CARD_INFO) == PCI66) + info(" 66 MHz PCI\n"); + else + info(" 33 MHz PCI\n"); + +} + +/* This routine will power on the slot, configure the device(s) and find the + * drivers for them. + * Parameters: hotplug_slot + * Returns: 0 or failure codes + */ +static int enable_slot(struct hotplug_slot *hs) +{ + int rc, i, rcpr; + struct slot *slot_cur; + u8 function; + struct pci_func *tmp_func; + + ibmphp_lock_operations(); + + debug("ENABLING SLOT........\n"); + slot_cur = to_slot(hs); + + rc = validate(slot_cur, ENABLE); + if (rc) { + err("validate function failed\n"); + goto error_nopower; + } + + attn_LED_blink(slot_cur); + + rc = set_bus(slot_cur); + if (rc) { + err("was not able to set the bus\n"); + goto error_nopower; + } + + /*-----------------debugging------------------------------*/ + get_cur_bus_info(&slot_cur); + debug("the current bus speed right after set_bus = %x\n", + slot_cur->bus_on->current_speed); + /*----------------------------------------------------------*/ + + rc = check_limitations(slot_cur); + if (rc) { + err("Adding this card exceeds the limitations of this bus.\n"); + err("(i.e., >1 133MHz cards running on same bus, or >2 66 PCI cards running on same bus.\n"); + err("Try hot-adding into another bus\n"); + rc = -EINVAL; + goto error_nopower; + } + + rc = power_on(slot_cur); + + if (rc) { + err("something wrong when powering up... please see below for details\n"); + /* need to turn off before on, otherwise, blinking overwrites */ + attn_off(slot_cur); + attn_on(slot_cur); + if (slot_update(&slot_cur)) { + attn_off(slot_cur); + attn_on(slot_cur); + rc = -ENODEV; + goto exit; + } + /* Check to see the error of why it failed */ + if ((SLOT_POWER(slot_cur->status)) && + !(SLOT_PWRGD(slot_cur->status))) + err("power fault occurred trying to power up\n"); + else if (SLOT_BUS_SPEED(slot_cur->status)) { + err("bus speed mismatch occurred. please check current bus speed and card capability\n"); + print_card_capability(slot_cur); + } else if (SLOT_BUS_MODE(slot_cur->ext_status)) { + err("bus mode mismatch occurred. please check current bus mode and card capability\n"); + print_card_capability(slot_cur); + } + ibmphp_update_slot_info(slot_cur); + goto exit; + } + debug("after power_on\n"); + /*-----------------------debugging---------------------------*/ + get_cur_bus_info(&slot_cur); + debug("the current bus speed right after power_on = %x\n", + slot_cur->bus_on->current_speed); + /*----------------------------------------------------------*/ + + rc = slot_update(&slot_cur); + if (rc) + goto error_power; + + rc = -EINVAL; + if (SLOT_POWER(slot_cur->status) && !(SLOT_PWRGD(slot_cur->status))) { + err("power fault occurred trying to power up...\n"); + goto error_power; + } + if (SLOT_POWER(slot_cur->status) && (SLOT_BUS_SPEED(slot_cur->status))) { + err("bus speed mismatch occurred. please check current bus speed and card capability\n"); + print_card_capability(slot_cur); + goto error_power; + } + /* Don't think this case will happen after above checks... + * but just in case, for paranoia sake */ + if (!(SLOT_POWER(slot_cur->status))) { + err("power on failed...\n"); + goto error_power; + } + + slot_cur->func = kzalloc(sizeof(struct pci_func), GFP_KERNEL); + if (!slot_cur->func) { + /* do update_slot_info here? */ + rc = -ENOMEM; + goto error_power; + } + slot_cur->func->busno = slot_cur->bus; + slot_cur->func->device = slot_cur->device; + for (i = 0; i < 4; i++) + slot_cur->func->irq[i] = slot_cur->irq[i]; + + debug("b4 configure_card, slot_cur->bus = %x, slot_cur->device = %x\n", + slot_cur->bus, slot_cur->device); + + if (ibmphp_configure_card(slot_cur->func, slot_cur->number)) { + err("configure_card was unsuccessful...\n"); + /* true because don't need to actually deallocate resources, + * just remove references */ + ibmphp_unconfigure_card(&slot_cur, 1); + debug("after unconfigure_card\n"); + slot_cur->func = NULL; + rc = -ENOMEM; + goto error_power; + } + + function = 0x00; + do { + tmp_func = ibm_slot_find(slot_cur->bus, slot_cur->func->device, + function++); + if (tmp_func && !(tmp_func->dev)) + ibm_configure_device(tmp_func); + } while (tmp_func); + + attn_off(slot_cur); + if (slot_update(&slot_cur)) { + rc = -EFAULT; + goto exit; + } + ibmphp_print_test(); + rc = ibmphp_update_slot_info(slot_cur); +exit: + ibmphp_unlock_operations(); + return rc; + +error_nopower: + attn_off(slot_cur); /* need to turn off if was blinking b4 */ + attn_on(slot_cur); +error_cont: + rcpr = slot_update(&slot_cur); + if (rcpr) { + rc = rcpr; + goto exit; + } + ibmphp_update_slot_info(slot_cur); + goto exit; + +error_power: + attn_off(slot_cur); /* need to turn off if was blinking b4 */ + attn_on(slot_cur); + rcpr = power_off(slot_cur); + if (rcpr) { + rc = rcpr; + goto exit; + } + goto error_cont; +} + +/************************************************************** +* HOT REMOVING ADAPTER CARD * +* INPUT: POINTER TO THE HOTPLUG SLOT STRUCTURE * +* OUTPUT: SUCCESS 0 ; FAILURE: UNCONFIGURE , VALIDATE * +* DISABLE POWER , * +**************************************************************/ +static int ibmphp_disable_slot(struct hotplug_slot *hotplug_slot) +{ + struct slot *slot = to_slot(hotplug_slot); + int rc; + + ibmphp_lock_operations(); + rc = ibmphp_do_disable_slot(slot); + ibmphp_unlock_operations(); + return rc; +} + +int ibmphp_do_disable_slot(struct slot *slot_cur) +{ + int rc; + u8 flag; + + debug("DISABLING SLOT...\n"); + + if ((slot_cur == NULL) || (slot_cur->ctrl == NULL)) + return -ENODEV; + + flag = slot_cur->flag; + slot_cur->flag = 1; + + if (flag == 1) { + rc = validate(slot_cur, DISABLE); + /* checking if powered off already & valid slot # */ + if (rc) + goto error; + } + attn_LED_blink(slot_cur); + + if (slot_cur->func == NULL) { + /* We need this for functions that were there on bootup */ + slot_cur->func = kzalloc(sizeof(struct pci_func), GFP_KERNEL); + if (!slot_cur->func) { + rc = -ENOMEM; + goto error; + } + slot_cur->func->busno = slot_cur->bus; + slot_cur->func->device = slot_cur->device; + } + + ibm_unconfigure_device(slot_cur->func); + + /* + * If we got here from latch suddenly opening on operating card or + * a power fault, there's no power to the card, so cannot + * read from it to determine what resources it occupied. This operation + * is forbidden anyhow. The best we can do is remove it from kernel + * lists at least */ + + if (!flag) { + attn_off(slot_cur); + return 0; + } + + rc = ibmphp_unconfigure_card(&slot_cur, 0); + slot_cur->func = NULL; + debug("in disable_slot. after unconfigure_card\n"); + if (rc) { + err("could not unconfigure card.\n"); + goto error; + } + + rc = ibmphp_hpc_writeslot(slot_cur, HPC_SLOT_OFF); + if (rc) + goto error; + + attn_off(slot_cur); + rc = slot_update(&slot_cur); + if (rc) + goto exit; + + rc = ibmphp_update_slot_info(slot_cur); + ibmphp_print_test(); +exit: + return rc; + +error: + /* Need to turn off if was blinking b4 */ + attn_off(slot_cur); + attn_on(slot_cur); + if (slot_update(&slot_cur)) { + rc = -EFAULT; + goto exit; + } + if (flag) + ibmphp_update_slot_info(slot_cur); + goto exit; +} + +const struct hotplug_slot_ops ibmphp_hotplug_slot_ops = { + .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, +}; + +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) { + 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; + + for (i = 0; i < 16; i++) + irqs[i] = 0; + + rc = ibmphp_access_ebda(); + if (rc) + goto error; + debug("after ibmphp_access_ebda()\n"); + + rc = ibmphp_rsrc_init(); + if (rc) + goto error; + debug("AFTER Resource & EBDA INITIALIZATIONS\n"); + + max_slots = get_max_slots(); + + rc = ibmphp_register_pci(); + if (rc) + goto error; + + if (init_ops()) { + rc = -ENODEV; + goto error; + } + + ibmphp_print_test(); + rc = ibmphp_hpc_start_poll_thread(); + if (rc) + goto error; + +exit: + return rc; + +error: + ibmphp_unload(); + goto exit; +} + +static void __exit ibmphp_exit(void) +{ + ibmphp_hpc_stop_poll_thread(); + debug("after polling\n"); + ibmphp_unload(); + debug("done\n"); +} + +module_init(ibmphp_init); +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 000000000..7fb75401a --- /dev/null +++ b/drivers/pci/hotplug/ibmphp_ebda.c @@ -0,0 +1,1118 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * 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. + * + * Send feedback to <gregkh@us.ibm.com> + * + */ + +#include <linux/module.h> +#include <linux/errno.h> +#include <linux/mm.h> +#include <linux/slab.h> +#include <linux/pci.h> +#include <linux/list.h> +#include <linux/init.h> +#include "ibmphp.h" + +/* + * POST builds data blocks(in this data block definition, a char-1 + * byte, short(or word)-2 byte, long(dword)-4 byte) in the Extended + * BIOS Data Area which describe the configuration of the hot-plug + * controllers and resources used by the PCI Hot-Plug devices. + * + * This file walks EBDA, maps data block from physical addr, + * reconstruct linked lists about all system resource(MEM, PFM, IO) + * already assigned by POST, as well as linked lists about hot plug + * controllers (ctlr#, slot#, bus&slot features...) + */ + +/* Global lists */ +LIST_HEAD(ibmphp_ebda_pci_rsrc_head); +LIST_HEAD(ibmphp_slot_head); + +/* Local variables */ +static struct ebda_hpc_list *hpc_list_ptr; +static struct ebda_rsrc_list *rsrc_list_ptr; +static struct rio_table_hdr *rio_table_ptr = NULL; +static LIST_HEAD(ebda_hpc_head); +static LIST_HEAD(bus_info_head); +static LIST_HEAD(rio_vg_head); +static LIST_HEAD(rio_lo_head); +static LIST_HEAD(opt_vg_head); +static LIST_HEAD(opt_lo_head); +static void __iomem *io_mem; + +/* Local functions */ +static int ebda_rsrc_controller(void); +static int ebda_rsrc_rsrc(void); +static int ebda_rio_table(void); + +static struct ebda_hpc_list * __init alloc_ebda_hpc_list(void) +{ + return kzalloc(sizeof(struct ebda_hpc_list), GFP_KERNEL); +} + +static struct controller *alloc_ebda_hpc(u32 slot_count, u32 bus_count) +{ + struct controller *controller; + struct ebda_hpc_slot *slots; + struct ebda_hpc_bus *buses; + + controller = kzalloc(sizeof(struct controller), GFP_KERNEL); + if (!controller) + goto error; + + slots = kcalloc(slot_count, sizeof(struct ebda_hpc_slot), GFP_KERNEL); + if (!slots) + goto error_contr; + controller->slots = slots; + + buses = kcalloc(bus_count, sizeof(struct ebda_hpc_bus), GFP_KERNEL); + if (!buses) + goto error_slots; + controller->buses = buses; + + return controller; +error_slots: + kfree(controller->slots); +error_contr: + kfree(controller); +error: + return NULL; +} + +static void free_ebda_hpc(struct controller *controller) +{ + kfree(controller->slots); + kfree(controller->buses); + kfree(controller); +} + +static struct ebda_rsrc_list * __init alloc_ebda_rsrc_list(void) +{ + return kzalloc(sizeof(struct ebda_rsrc_list), GFP_KERNEL); +} + +static struct ebda_pci_rsrc *alloc_ebda_pci_rsrc(void) +{ + return kzalloc(sizeof(struct ebda_pci_rsrc), GFP_KERNEL); +} + +static void __init print_bus_info(void) +{ + struct bus_info *ptr; + + list_for_each_entry(ptr, &bus_info_head, bus_info_list) { + debug("%s - slot_min = %x\n", __func__, ptr->slot_min); + debug("%s - slot_max = %x\n", __func__, ptr->slot_max); + debug("%s - slot_count = %x\n", __func__, ptr->slot_count); + debug("%s - bus# = %x\n", __func__, ptr->busno); + debug("%s - current_speed = %x\n", __func__, ptr->current_speed); + debug("%s - controller_id = %x\n", __func__, ptr->controller_id); + + debug("%s - slots_at_33_conv = %x\n", __func__, ptr->slots_at_33_conv); + debug("%s - slots_at_66_conv = %x\n", __func__, ptr->slots_at_66_conv); + debug("%s - slots_at_66_pcix = %x\n", __func__, ptr->slots_at_66_pcix); + debug("%s - slots_at_100_pcix = %x\n", __func__, ptr->slots_at_100_pcix); + debug("%s - slots_at_133_pcix = %x\n", __func__, ptr->slots_at_133_pcix); + + } +} + +static void print_lo_info(void) +{ + struct rio_detail *ptr; + debug("print_lo_info ----\n"); + list_for_each_entry(ptr, &rio_lo_head, rio_detail_list) { + debug("%s - rio_node_id = %x\n", __func__, ptr->rio_node_id); + debug("%s - rio_type = %x\n", __func__, ptr->rio_type); + debug("%s - owner_id = %x\n", __func__, ptr->owner_id); + debug("%s - first_slot_num = %x\n", __func__, ptr->first_slot_num); + debug("%s - wpindex = %x\n", __func__, ptr->wpindex); + debug("%s - chassis_num = %x\n", __func__, ptr->chassis_num); + + } +} + +static void print_vg_info(void) +{ + struct rio_detail *ptr; + debug("%s ---\n", __func__); + list_for_each_entry(ptr, &rio_vg_head, rio_detail_list) { + debug("%s - rio_node_id = %x\n", __func__, ptr->rio_node_id); + debug("%s - rio_type = %x\n", __func__, ptr->rio_type); + debug("%s - owner_id = %x\n", __func__, ptr->owner_id); + debug("%s - first_slot_num = %x\n", __func__, ptr->first_slot_num); + debug("%s - wpindex = %x\n", __func__, ptr->wpindex); + debug("%s - chassis_num = %x\n", __func__, ptr->chassis_num); + + } +} + +static void __init print_ebda_pci_rsrc(void) +{ + struct ebda_pci_rsrc *ptr; + + list_for_each_entry(ptr, &ibmphp_ebda_pci_rsrc_head, ebda_pci_rsrc_list) { + debug("%s - rsrc type: %x bus#: %x dev_func: %x start addr: %x end addr: %x\n", + __func__, ptr->rsrc_type, ptr->bus_num, ptr->dev_fun, ptr->start_addr, ptr->end_addr); + } +} + +static void __init print_ibm_slot(void) +{ + struct slot *ptr; + + list_for_each_entry(ptr, &ibmphp_slot_head, ibm_slot_list) { + debug("%s - slot_number: %x\n", __func__, ptr->number); + } +} + +static void __init print_opt_vg(void) +{ + struct opt_rio *ptr; + debug("%s ---\n", __func__); + list_for_each_entry(ptr, &opt_vg_head, opt_rio_list) { + debug("%s - rio_type %x\n", __func__, ptr->rio_type); + debug("%s - chassis_num: %x\n", __func__, ptr->chassis_num); + debug("%s - first_slot_num: %x\n", __func__, ptr->first_slot_num); + debug("%s - middle_num: %x\n", __func__, ptr->middle_num); + } +} + +static void __init print_ebda_hpc(void) +{ + struct controller *hpc_ptr; + u16 index; + + list_for_each_entry(hpc_ptr, &ebda_hpc_head, ebda_hpc_list) { + for (index = 0; index < hpc_ptr->slot_count; index++) { + debug("%s - physical slot#: %x\n", __func__, hpc_ptr->slots[index].slot_num); + debug("%s - pci bus# of the slot: %x\n", __func__, hpc_ptr->slots[index].slot_bus_num); + debug("%s - index into ctlr addr: %x\n", __func__, hpc_ptr->slots[index].ctl_index); + debug("%s - cap of the slot: %x\n", __func__, hpc_ptr->slots[index].slot_cap); + } + + for (index = 0; index < hpc_ptr->bus_count; index++) + debug("%s - bus# of each bus controlled by this ctlr: %x\n", __func__, hpc_ptr->buses[index].bus_num); + + debug("%s - type of hpc: %x\n", __func__, hpc_ptr->ctlr_type); + switch (hpc_ptr->ctlr_type) { + case 1: + debug("%s - bus: %x\n", __func__, hpc_ptr->u.pci_ctlr.bus); + debug("%s - dev_fun: %x\n", __func__, hpc_ptr->u.pci_ctlr.dev_fun); + debug("%s - irq: %x\n", __func__, hpc_ptr->irq); + break; + + case 0: + debug("%s - io_start: %x\n", __func__, hpc_ptr->u.isa_ctlr.io_start); + debug("%s - io_end: %x\n", __func__, hpc_ptr->u.isa_ctlr.io_end); + debug("%s - irq: %x\n", __func__, hpc_ptr->irq); + break; + + case 2: + case 4: + debug("%s - wpegbbar: %lx\n", __func__, hpc_ptr->u.wpeg_ctlr.wpegbbar); + debug("%s - i2c_addr: %x\n", __func__, hpc_ptr->u.wpeg_ctlr.i2c_addr); + debug("%s - irq: %x\n", __func__, hpc_ptr->irq); + break; + } + } +} + +int __init ibmphp_access_ebda(void) +{ + u8 format, num_ctlrs, rio_complete, hs_complete, ebda_sz; + 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, 1); + if (!io_mem) + return -ENOMEM; + ebda_sz = readb(io_mem); + iounmap(io_mem); + debug("ebda size: %d(KiB)\n", ebda_sz); + if (ebda_sz == 0) + return -ENOMEM; + + io_mem = ioremap(ebda_seg<<4, (ebda_sz * 1024)); + if (!io_mem) + return -ENOMEM; + next_offset = 0x180; + + for (;;) { + offset = next_offset; + + /* Make sure what we read is still in the mapped section */ + if (WARN(offset > (ebda_sz * 1024 - 4), + "ibmphp_ebda: next read is beyond ebda_sz\n")) + break; + + 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 entries: %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 entries: %x\n ", sub_addr); + + hs_complete = 1; + } else { + /* found rio table, blk_id == 0x4752 */ + debug("now enter io table ---\n"); + debug("rio blk id: %x\n", blk_id); + + rio_table_ptr = kzalloc(sizeof(struct rio_table_hdr), GFP_KERNEL); + if (!rio_table_ptr) { + rc = -ENOMEM; + goto out; + } + rio_table_ptr->ver_num = readb(io_mem + offset); + rio_table_ptr->scal_count = readb(io_mem + offset + 1); + rio_table_ptr->riodev_count = readb(io_mem + offset + 2); + rio_table_ptr->offset = offset + 3 ; + + debug("info about rio table hdr ---\n"); + debug("ver_num: %x\nscal_count: %x\nriodev_count: %x\noffset of rio table: %x\n ", + rio_table_ptr->ver_num, rio_table_ptr->scal_count, + rio_table_ptr->riodev_count, rio_table_ptr->offset); + + rio_complete = 1; + } + } + + if (!hs_complete && !rio_complete) + goto error_nodev; + + if (rio_table_ptr) { + if (rio_complete && rio_table_ptr->ver_num == 3) { + rc = ebda_rio_table(); + if (rc) + goto out; + } + } + rc = ebda_rsrc_controller(); + if (rc) + goto out; + + rc = ebda_rsrc_rsrc(); + goto out; +error_nodev: + rc = -ENODEV; +out: + iounmap(io_mem); + return rc; +} + +/* + * map info of scalability details and rio details from physical address + */ +static int __init ebda_rio_table(void) +{ + u16 offset; + u8 i; + struct rio_detail *rio_detail_ptr; + + offset = rio_table_ptr->offset; + offset += 12 * rio_table_ptr->scal_count; + + // we do concern about rio details + for (i = 0; i < rio_table_ptr->riodev_count; i++) { + rio_detail_ptr = kzalloc(sizeof(struct rio_detail), GFP_KERNEL); + if (!rio_detail_ptr) + return -ENOMEM; + rio_detail_ptr->rio_node_id = readb(io_mem + offset); + rio_detail_ptr->bbar = readl(io_mem + offset + 1); + rio_detail_ptr->rio_type = readb(io_mem + offset + 5); + rio_detail_ptr->owner_id = readb(io_mem + offset + 6); + rio_detail_ptr->port0_node_connect = readb(io_mem + offset + 7); + rio_detail_ptr->port0_port_connect = readb(io_mem + offset + 8); + rio_detail_ptr->port1_node_connect = readb(io_mem + offset + 9); + rio_detail_ptr->port1_port_connect = readb(io_mem + offset + 10); + rio_detail_ptr->first_slot_num = readb(io_mem + offset + 11); + rio_detail_ptr->status = readb(io_mem + offset + 12); + rio_detail_ptr->wpindex = readb(io_mem + offset + 13); + rio_detail_ptr->chassis_num = readb(io_mem + offset + 14); +// debug("rio_node_id: %x\nbbar: %x\nrio_type: %x\nowner_id: %x\nport0_node: %x\nport0_port: %x\nport1_node: %x\nport1_port: %x\nfirst_slot_num: %x\nstatus: %x\n", rio_detail_ptr->rio_node_id, rio_detail_ptr->bbar, rio_detail_ptr->rio_type, rio_detail_ptr->owner_id, rio_detail_ptr->port0_node_connect, rio_detail_ptr->port0_port_connect, rio_detail_ptr->port1_node_connect, rio_detail_ptr->port1_port_connect, rio_detail_ptr->first_slot_num, rio_detail_ptr->status); + //create linked list of chassis + if (rio_detail_ptr->rio_type == 4 || rio_detail_ptr->rio_type == 5) + list_add(&rio_detail_ptr->rio_detail_list, &rio_vg_head); + //create linked list of expansion box + else if (rio_detail_ptr->rio_type == 6 || rio_detail_ptr->rio_type == 7) + list_add(&rio_detail_ptr->rio_detail_list, &rio_lo_head); + else + // not in my concern + kfree(rio_detail_ptr); + offset += 15; + } + print_lo_info(); + print_vg_info(); + return 0; +} + +/* + * reorganizing linked list of chassis + */ +static struct opt_rio *search_opt_vg(u8 chassis_num) +{ + struct opt_rio *ptr; + list_for_each_entry(ptr, &opt_vg_head, opt_rio_list) { + if (ptr->chassis_num == chassis_num) + return ptr; + } + return NULL; +} + +static int __init combine_wpg_for_chassis(void) +{ + struct opt_rio *opt_rio_ptr = NULL; + struct rio_detail *rio_detail_ptr = NULL; + + list_for_each_entry(rio_detail_ptr, &rio_vg_head, rio_detail_list) { + opt_rio_ptr = search_opt_vg(rio_detail_ptr->chassis_num); + if (!opt_rio_ptr) { + opt_rio_ptr = kzalloc(sizeof(struct opt_rio), GFP_KERNEL); + if (!opt_rio_ptr) + return -ENOMEM; + opt_rio_ptr->rio_type = rio_detail_ptr->rio_type; + opt_rio_ptr->chassis_num = rio_detail_ptr->chassis_num; + opt_rio_ptr->first_slot_num = rio_detail_ptr->first_slot_num; + opt_rio_ptr->middle_num = rio_detail_ptr->first_slot_num; + list_add(&opt_rio_ptr->opt_rio_list, &opt_vg_head); + } else { + opt_rio_ptr->first_slot_num = min(opt_rio_ptr->first_slot_num, rio_detail_ptr->first_slot_num); + opt_rio_ptr->middle_num = max(opt_rio_ptr->middle_num, rio_detail_ptr->first_slot_num); + } + } + print_opt_vg(); + return 0; +} + +/* + * reorganizing linked list of expansion box + */ +static struct opt_rio_lo *search_opt_lo(u8 chassis_num) +{ + struct opt_rio_lo *ptr; + list_for_each_entry(ptr, &opt_lo_head, opt_rio_lo_list) { + if (ptr->chassis_num == chassis_num) + return ptr; + } + return NULL; +} + +static int combine_wpg_for_expansion(void) +{ + struct opt_rio_lo *opt_rio_lo_ptr = NULL; + struct rio_detail *rio_detail_ptr = NULL; + + list_for_each_entry(rio_detail_ptr, &rio_lo_head, rio_detail_list) { + opt_rio_lo_ptr = search_opt_lo(rio_detail_ptr->chassis_num); + if (!opt_rio_lo_ptr) { + opt_rio_lo_ptr = kzalloc(sizeof(struct opt_rio_lo), GFP_KERNEL); + if (!opt_rio_lo_ptr) + return -ENOMEM; + opt_rio_lo_ptr->rio_type = rio_detail_ptr->rio_type; + opt_rio_lo_ptr->chassis_num = rio_detail_ptr->chassis_num; + opt_rio_lo_ptr->first_slot_num = rio_detail_ptr->first_slot_num; + opt_rio_lo_ptr->middle_num = rio_detail_ptr->first_slot_num; + opt_rio_lo_ptr->pack_count = 1; + + list_add(&opt_rio_lo_ptr->opt_rio_lo_list, &opt_lo_head); + } else { + opt_rio_lo_ptr->first_slot_num = min(opt_rio_lo_ptr->first_slot_num, rio_detail_ptr->first_slot_num); + opt_rio_lo_ptr->middle_num = max(opt_rio_lo_ptr->middle_num, rio_detail_ptr->first_slot_num); + opt_rio_lo_ptr->pack_count = 2; + } + } + return 0; +} + + +/* Since we don't know the max slot number per each chassis, hence go + * through the list of all chassis to find out the range + * Arguments: slot_num, 1st slot number of the chassis we think we are on, + * var (0 = chassis, 1 = expansion box) + */ +static int first_slot_num(u8 slot_num, u8 first_slot, u8 var) +{ + struct opt_rio *opt_vg_ptr = NULL; + struct opt_rio_lo *opt_lo_ptr = NULL; + int rc = 0; + + if (!var) { + list_for_each_entry(opt_vg_ptr, &opt_vg_head, opt_rio_list) { + if ((first_slot < opt_vg_ptr->first_slot_num) && (slot_num >= opt_vg_ptr->first_slot_num)) { + rc = -ENODEV; + break; + } + } + } else { + list_for_each_entry(opt_lo_ptr, &opt_lo_head, opt_rio_lo_list) { + if ((first_slot < opt_lo_ptr->first_slot_num) && (slot_num >= opt_lo_ptr->first_slot_num)) { + rc = -ENODEV; + break; + } + } + } + return rc; +} + +static struct opt_rio_lo *find_rxe_num(u8 slot_num) +{ + struct opt_rio_lo *opt_lo_ptr; + + list_for_each_entry(opt_lo_ptr, &opt_lo_head, opt_rio_lo_list) { + //check to see if this slot_num belongs to expansion box + if ((slot_num >= opt_lo_ptr->first_slot_num) && (!first_slot_num(slot_num, opt_lo_ptr->first_slot_num, 1))) + return opt_lo_ptr; + } + return NULL; +} + +static struct opt_rio *find_chassis_num(u8 slot_num) +{ + struct opt_rio *opt_vg_ptr; + + list_for_each_entry(opt_vg_ptr, &opt_vg_head, opt_rio_list) { + //check to see if this slot_num belongs to chassis + if ((slot_num >= opt_vg_ptr->first_slot_num) && (!first_slot_num(slot_num, opt_vg_ptr->first_slot_num, 0))) + return opt_vg_ptr; + } + return NULL; +} + +/* This routine will find out how many slots are in the chassis, so that + * the slot numbers for rxe100 would start from 1, and not from 7, or 6 etc + */ +static u8 calculate_first_slot(u8 slot_num) +{ + u8 first_slot = 1; + struct slot *slot_cur; + + list_for_each_entry(slot_cur, &ibmphp_slot_head, ibm_slot_list) { + if (slot_cur->ctrl) { + if ((slot_cur->ctrl->ctlr_type != 4) && (slot_cur->ctrl->ending_slot_num > first_slot) && (slot_num > slot_cur->ctrl->ending_slot_num)) + first_slot = slot_cur->ctrl->ending_slot_num; + } + } + return first_slot + 1; + +} + +#define SLOT_NAME_SIZE 30 + +static char *create_file_name(struct slot *slot_cur) +{ + struct opt_rio *opt_vg_ptr = NULL; + struct opt_rio_lo *opt_lo_ptr = NULL; + static char str[SLOT_NAME_SIZE]; + int which = 0; /* rxe = 1, chassis = 0 */ + u8 number = 1; /* either chassis or rxe # */ + u8 first_slot = 1; + u8 slot_num; + u8 flag = 0; + + if (!slot_cur) { + err("Structure passed is empty\n"); + return NULL; + } + + slot_num = slot_cur->number; + + memset(str, 0, sizeof(str)); + + if (rio_table_ptr) { + if (rio_table_ptr->ver_num == 3) { + opt_vg_ptr = find_chassis_num(slot_num); + opt_lo_ptr = find_rxe_num(slot_num); + } + } + if (opt_vg_ptr) { + if (opt_lo_ptr) { + if ((slot_num - opt_vg_ptr->first_slot_num) > (slot_num - opt_lo_ptr->first_slot_num)) { + number = opt_lo_ptr->chassis_num; + first_slot = opt_lo_ptr->first_slot_num; + which = 1; /* it is RXE */ + } else { + first_slot = opt_vg_ptr->first_slot_num; + number = opt_vg_ptr->chassis_num; + which = 0; + } + } else { + first_slot = opt_vg_ptr->first_slot_num; + number = opt_vg_ptr->chassis_num; + which = 0; + } + ++flag; + } else if (opt_lo_ptr) { + number = opt_lo_ptr->chassis_num; + first_slot = opt_lo_ptr->first_slot_num; + which = 1; + ++flag; + } else if (rio_table_ptr) { + if (rio_table_ptr->ver_num == 3) { + /* if both NULL and we DO have correct RIO table in BIOS */ + return NULL; + } + } + if (!flag) { + if (slot_cur->ctrl->ctlr_type == 4) { + first_slot = calculate_first_slot(slot_num); + which = 1; + } else { + which = 0; + } + } + + sprintf(str, "%s%dslot%d", + which == 0 ? "chassis" : "rxe", + number, slot_num - first_slot + 1); + return str; +} + +static int fillslotinfo(struct hotplug_slot *hotplug_slot) +{ + struct slot *slot; + int rc = 0; + + slot = to_slot(hotplug_slot); + rc = ibmphp_hpc_readslot(slot, READ_ALLSTAT, NULL); + return rc; +} + +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 controller *hpc_ptr; + struct ebda_hpc_bus *bus_ptr; + struct ebda_hpc_slot *slot_ptr; + struct bus_info *bus_info_ptr1, *bus_info_ptr2; + int rc; + struct slot *tmp_slot; + char name[SLOT_NAME_SIZE]; + + addr = hpc_list_ptr->phys_addr; + for (ctlr = 0; ctlr < hpc_list_ptr->num_ctlrs; ctlr++) { + bus_index = 1; + ctlr_id = readb(io_mem + addr); + addr += 1; + slot_num = readb(io_mem + addr); + + addr += 1; + addr_slot = addr; /* offset of slot structure */ + addr += (slot_num * 4); + + bus_num = readb(io_mem + addr); + + addr += 1; + addr_bus = addr; /* offset of bus */ + addr += (bus_num * 9); /* offset of ctlr_type */ + temp = readb(io_mem + addr); + + addr += 1; + /* init hpc structure */ + hpc_ptr = alloc_ebda_hpc(slot_num, bus_num); + if (!hpc_ptr) { + return -ENOMEM; + } + 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 structure ---\n"); + debug("ctlr id: %x\n", ctlr_id); + debug("ctlr_relative_id: %x\n", hpc_ptr->ctlr_relative_id); + debug("count of slots controlled by this ctlr: %x\n", slot_num); + debug("count of buses controlled by this ctlr: %x\n", bus_num); + + /* init slot structure, fetch slot, bus, cap... */ + slot_ptr = hpc_ptr->slots; + for (slot = 0; slot < slot_num; slot++) { + slot_ptr->slot_num = readb(io_mem + addr_slot); + slot_ptr->slot_bus_num = readb(io_mem + addr_slot + slot_num); + slot_ptr->ctl_index = readb(io_mem + addr_slot + 2*slot_num); + slot_ptr->slot_cap = readb(io_mem + addr_slot + 3*slot_num); + + // create bus_info lined list --- if only one slot per bus: slot_min = slot_max + + bus_info_ptr2 = ibmphp_find_same_bus_num(slot_ptr->slot_bus_num); + if (!bus_info_ptr2) { + bus_info_ptr1 = kzalloc(sizeof(struct bus_info), GFP_KERNEL); + if (!bus_info_ptr1) { + rc = -ENOMEM; + goto error_no_slot; + } + bus_info_ptr1->slot_min = slot_ptr->slot_num; + bus_info_ptr1->slot_max = slot_ptr->slot_num; + bus_info_ptr1->slot_count += 1; + bus_info_ptr1->busno = slot_ptr->slot_bus_num; + bus_info_ptr1->index = bus_index++; + bus_info_ptr1->current_speed = 0xff; + bus_info_ptr1->current_bus_mode = 0xff; + + bus_info_ptr1->controller_id = hpc_ptr->ctlr_id; + + list_add_tail(&bus_info_ptr1->bus_info_list, &bus_info_head); + + } else { + bus_info_ptr2->slot_min = min(bus_info_ptr2->slot_min, slot_ptr->slot_num); + bus_info_ptr2->slot_max = max(bus_info_ptr2->slot_max, slot_ptr->slot_num); + bus_info_ptr2->slot_count += 1; + + } + + // end of creating the bus_info linked list + + slot_ptr++; + addr_slot += 1; + } + + /* init bus structure */ + bus_ptr = hpc_ptr->buses; + for (bus = 0; bus < bus_num; bus++) { + bus_ptr->bus_num = readb(io_mem + addr_bus + bus); + bus_ptr->slots_at_33_conv = readb(io_mem + addr_bus + bus_num + 8 * bus); + bus_ptr->slots_at_66_conv = readb(io_mem + addr_bus + bus_num + 8 * bus + 1); + + bus_ptr->slots_at_66_pcix = readb(io_mem + addr_bus + bus_num + 8 * bus + 2); + + bus_ptr->slots_at_100_pcix = readb(io_mem + addr_bus + bus_num + 8 * bus + 3); + + bus_ptr->slots_at_133_pcix = readb(io_mem + addr_bus + bus_num + 8 * bus + 4); + + bus_info_ptr2 = ibmphp_find_same_bus_num(bus_ptr->bus_num); + if (bus_info_ptr2) { + bus_info_ptr2->slots_at_33_conv = bus_ptr->slots_at_33_conv; + bus_info_ptr2->slots_at_66_conv = bus_ptr->slots_at_66_conv; + bus_info_ptr2->slots_at_66_pcix = bus_ptr->slots_at_66_pcix; + bus_info_ptr2->slots_at_100_pcix = bus_ptr->slots_at_100_pcix; + bus_info_ptr2->slots_at_133_pcix = bus_ptr->slots_at_133_pcix; + } + bus_ptr++; + } + + hpc_ptr->ctlr_type = temp; + + switch (hpc_ptr->ctlr_type) { + case 1: + hpc_ptr->u.pci_ctlr.bus = readb(io_mem + addr); + hpc_ptr->u.pci_ctlr.dev_fun = readb(io_mem + addr + 1); + hpc_ptr->irq = readb(io_mem + addr + 2); + addr += 3; + debug("ctrl bus = %x, ctlr devfun = %x, irq = %x\n", + hpc_ptr->u.pci_ctlr.bus, + hpc_ptr->u.pci_ctlr.dev_fun, hpc_ptr->irq); + break; + + case 0: + hpc_ptr->u.isa_ctlr.io_start = readw(io_mem + addr); + hpc_ptr->u.isa_ctlr.io_end = readw(io_mem + addr + 2); + if (!request_region(hpc_ptr->u.isa_ctlr.io_start, + (hpc_ptr->u.isa_ctlr.io_end - hpc_ptr->u.isa_ctlr.io_start + 1), + "ibmphp")) { + rc = -ENODEV; + goto error_no_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_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++) { + tmp_slot = kzalloc(sizeof(*tmp_slot), GFP_KERNEL); + if (!tmp_slot) { + rc = -ENOMEM; + goto error_no_slot; + } + + tmp_slot->flag = 1; + + tmp_slot->capabilities = hpc_ptr->slots[index].slot_cap; + if ((hpc_ptr->slots[index].slot_cap & EBDA_SLOT_133_MAX) == EBDA_SLOT_133_MAX) + tmp_slot->supported_speed = 3; + else if ((hpc_ptr->slots[index].slot_cap & EBDA_SLOT_100_MAX) == EBDA_SLOT_100_MAX) + tmp_slot->supported_speed = 2; + else if ((hpc_ptr->slots[index].slot_cap & EBDA_SLOT_66_MAX) == EBDA_SLOT_66_MAX) + tmp_slot->supported_speed = 1; + + if ((hpc_ptr->slots[index].slot_cap & EBDA_SLOT_PCIX_CAP) == EBDA_SLOT_PCIX_CAP) + tmp_slot->supported_bus_mode = 1; + else + tmp_slot->supported_bus_mode = 0; + + + tmp_slot->bus = hpc_ptr->slots[index].slot_bus_num; + + bus_info_ptr1 = ibmphp_find_same_bus_num(hpc_ptr->slots[index].slot_bus_num); + if (!bus_info_ptr1) { + 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; + + rc = fillslotinfo(&tmp_slot->hotplug_slot); + if (rc) + goto error; + + rc = ibmphp_init_devno(&tmp_slot); + if (rc) + goto error; + tmp_slot->hotplug_slot.ops = &ibmphp_hotplug_slot_ops; + + // end of registering ibm slot with hotplug core + + list_add(&tmp_slot->ibm_slot_list, &ibmphp_slot_head); + } + + print_bus_info(); + list_add(&hpc_ptr->ebda_hpc_list, &ebda_hpc_head); + + } /* each hpc */ + + list_for_each_entry(tmp_slot, &ibmphp_slot_head, ibm_slot_list) { + snprintf(name, SLOT_NAME_SIZE, "%s", create_file_name(tmp_slot)); + pci_hp_register(&tmp_slot->hotplug_slot, + pci_find_bus(0, tmp_slot->bus), tmp_slot->device, name); + } + + print_ebda_hpc(); + print_ibm_slot(); + return 0; + +error: + kfree(tmp_slot); +error_no_slot: + free_ebda_hpc(hpc_ptr); + return rc; +} + +/* + * map info (bus, devfun, start addr, end addr..) of i/o, memory, + * pfm from the physical addr to a list of resource. + */ +static int __init ebda_rsrc_rsrc(void) +{ + u16 addr; + short rsrc; + u8 type, rsrc_type; + struct ebda_pci_rsrc *rsrc_ptr; + + addr = rsrc_list_ptr->phys_addr; + debug("now entering rsrc land\n"); + debug("offset of rsrc: %x\n", rsrc_list_ptr->phys_addr); + + for (rsrc = 0; rsrc < rsrc_list_ptr->num_entries; rsrc++) { + type = readb(io_mem + addr); + + addr += 1; + rsrc_type = type & EBDA_RSRC_TYPE_MASK; + + if (rsrc_type == EBDA_IO_RSRC_TYPE) { + rsrc_ptr = alloc_ebda_pci_rsrc(); + if (!rsrc_ptr) { + iounmap(io_mem); + return -ENOMEM; + } + rsrc_ptr->rsrc_type = type; + + rsrc_ptr->bus_num = readb(io_mem + addr); + rsrc_ptr->dev_fun = readb(io_mem + addr + 1); + rsrc_ptr->start_addr = readw(io_mem + addr + 2); + rsrc_ptr->end_addr = readw(io_mem + addr + 4); + addr += 6; + + debug("rsrc from io type ----\n"); + debug("rsrc type: %x bus#: %x dev_func: %x start addr: %x end addr: %x\n", + rsrc_ptr->rsrc_type, rsrc_ptr->bus_num, rsrc_ptr->dev_fun, rsrc_ptr->start_addr, rsrc_ptr->end_addr); + + list_add(&rsrc_ptr->ebda_pci_rsrc_list, &ibmphp_ebda_pci_rsrc_head); + } + + if (rsrc_type == EBDA_MEM_RSRC_TYPE || rsrc_type == EBDA_PFM_RSRC_TYPE) { + rsrc_ptr = alloc_ebda_pci_rsrc(); + if (!rsrc_ptr) { + iounmap(io_mem); + return -ENOMEM; + } + rsrc_ptr->rsrc_type = type; + + rsrc_ptr->bus_num = readb(io_mem + addr); + rsrc_ptr->dev_fun = readb(io_mem + addr + 1); + rsrc_ptr->start_addr = readl(io_mem + addr + 2); + rsrc_ptr->end_addr = readl(io_mem + addr + 6); + addr += 10; + + debug("rsrc from mem or pfm ---\n"); + debug("rsrc type: %x bus#: %x dev_func: %x start addr: %x end addr: %x\n", + rsrc_ptr->rsrc_type, rsrc_ptr->bus_num, rsrc_ptr->dev_fun, rsrc_ptr->start_addr, rsrc_ptr->end_addr); + + list_add(&rsrc_ptr->ebda_pci_rsrc_list, &ibmphp_ebda_pci_rsrc_head); + } + } + kfree(rsrc_list_ptr); + rsrc_list_ptr = NULL; + print_ebda_pci_rsrc(); + return 0; +} + +u16 ibmphp_get_total_controllers(void) +{ + return hpc_list_ptr->num_ctlrs; +} + +struct slot *ibmphp_get_slot_from_physical_num(u8 physical_num) +{ + struct slot *slot; + + list_for_each_entry(slot, &ibmphp_slot_head, ibm_slot_list) { + if (slot->number == physical_num) + return slot; + } + return NULL; +} + +/* To find: + * - the smallest slot number + * - the largest slot number + * - the total number of the slots based on each bus + * (if only one slot per bus slot_min = slot_max ) + */ +struct bus_info *ibmphp_find_same_bus_num(u32 num) +{ + struct bus_info *ptr; + + list_for_each_entry(ptr, &bus_info_head, bus_info_list) { + if (ptr->busno == num) + return ptr; + } + return NULL; +} + +/* Finding relative bus number, in order to map corresponding + * bus register + */ +int ibmphp_get_bus_index(u8 num) +{ + struct bus_info *ptr; + + list_for_each_entry(ptr, &bus_info_head, bus_info_list) { + if (ptr->busno == num) + return ptr->index; + } + return -ENODEV; +} + +void ibmphp_free_bus_info_queue(void) +{ + struct bus_info *bus_info, *next; + + list_for_each_entry_safe(bus_info, next, &bus_info_head, + bus_info_list) { + kfree (bus_info); + } +} + +void ibmphp_free_ebda_hpc_queue(void) +{ + struct controller *controller = NULL, *next; + int pci_flag = 0; + + list_for_each_entry_safe(controller, next, &ebda_hpc_head, + 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, *next; + + list_for_each_entry_safe(resource, next, &ibmphp_ebda_pci_rsrc_head, + ebda_pci_rsrc_list) { + kfree (resource); + resource = NULL; + } +} + +static const struct pci_device_id id_table[] = { + { + .vendor = PCI_VENDOR_ID_IBM, + .device = HPC_DEVICE_ID, + .subvendor = PCI_VENDOR_ID_IBM, + .subdevice = HPC_SUBSYSTEM_ID, + .class = ((PCI_CLASS_SYSTEM_PCI_HOTPLUG << 8) | 0x00), + }, {} +}; + +MODULE_DEVICE_TABLE(pci, id_table); + +static int ibmphp_probe(struct pci_dev *, const struct pci_device_id *); +static struct pci_driver ibmphp_driver = { + .name = "ibmphp", + .id_table = id_table, + .probe = ibmphp_probe, +}; + +int ibmphp_register_pci(void) +{ + struct controller *ctrl; + int rc = 0; + + list_for_each_entry(ctrl, &ebda_hpc_head, ebda_hpc_list) { + if (ctrl->ctlr_type == 1) { + rc = pci_register_driver(&ibmphp_driver); + break; + } + } + return rc; +} +static int ibmphp_probe(struct pci_dev *dev, const struct pci_device_id *ids) +{ + struct controller *ctrl; + + debug("inside ibmphp_probe\n"); + + list_for_each_entry(ctrl, &ebda_hpc_head, ebda_hpc_list) { + if (ctrl->ctlr_type == 1) { + if ((dev->devfn == ctrl->u.pci_ctlr.dev_fun) && (dev->bus->number == ctrl->u.pci_ctlr.bus)) { + ctrl->ctrl_dev = dev; + debug("found device!!!\n"); + debug("dev->device = %x, dev->subsystem_device = %x\n", dev->device, dev->subsystem_device); + return 0; + } + } + } + return -ENODEV; +} diff --git a/drivers/pci/hotplug/ibmphp_hpc.c b/drivers/pci/hotplug/ibmphp_hpc.c new file mode 100644 index 000000000..a5720d12e --- /dev/null +++ b/drivers/pci/hotplug/ibmphp_hpc.c @@ -0,0 +1,1094 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * IBM Hot Plug Controller Driver + * + * Written By: Jyoti Shah, IBM Corporation + * + * Copyright (C) 2001-2003 IBM Corp. + * + * All rights reserved. + * + * Send feedback to <gregkh@us.ibm.com> + * <jshah@us.ibm.com> + * + */ + +#include <linux/wait.h> +#include <linux/time.h> +#include <linux/completion.h> +#include <linux/delay.h> +#include <linux/module.h> +#include <linux/pci.h> +#include <linux/init.h> +#include <linux/mutex.h> +#include <linux/sched.h> +#include <linux/kthread.h> +#include "ibmphp.h" + +static int to_debug = 0; +#define debug_polling(fmt, arg...) do { if (to_debug) debug(fmt, arg); } while (0) + +//---------------------------------------------------------------------------- +// timeout values +//---------------------------------------------------------------------------- +#define CMD_COMPLETE_TOUT_SEC 60 // give HPC 60 sec to finish cmd +#define HPC_CTLR_WORKING_TOUT 60 // give HPC 60 sec to finish cmd +#define HPC_GETACCESS_TIMEOUT 60 // seconds +#define POLL_INTERVAL_SEC 2 // poll HPC every 2 seconds +#define POLL_LATCH_CNT 5 // poll latch 5 times, then poll slots + +//---------------------------------------------------------------------------- +// Winnipeg Architected Register Offsets +//---------------------------------------------------------------------------- +#define WPG_I2CMBUFL_OFFSET 0x08 // I2C Message Buffer Low +#define WPG_I2CMOSUP_OFFSET 0x10 // I2C Master Operation Setup Reg +#define WPG_I2CMCNTL_OFFSET 0x20 // I2C Master Control Register +#define WPG_I2CPARM_OFFSET 0x40 // I2C Parameter Register +#define WPG_I2CSTAT_OFFSET 0x70 // I2C Status Register + +//---------------------------------------------------------------------------- +// Winnipeg Store Type commands (Add this commands to the register offset) +//---------------------------------------------------------------------------- +#define WPG_I2C_AND 0x1000 // I2C AND operation +#define WPG_I2C_OR 0x2000 // I2C OR operation + +//---------------------------------------------------------------------------- +// Command set for I2C Master Operation Setup Register +//---------------------------------------------------------------------------- +#define WPG_READATADDR_MASK 0x00010000 // read,bytes,I2C shifted,index +#define WPG_WRITEATADDR_MASK 0x40010000 // write,bytes,I2C shifted,index +#define WPG_READDIRECT_MASK 0x10010000 +#define WPG_WRITEDIRECT_MASK 0x60010000 + + +//---------------------------------------------------------------------------- +// bit masks for I2C Master Control Register +//---------------------------------------------------------------------------- +#define WPG_I2CMCNTL_STARTOP_MASK 0x00000002 // Start the Operation + +//---------------------------------------------------------------------------- +// +//---------------------------------------------------------------------------- +#define WPG_I2C_IOREMAP_SIZE 0x2044 // size of linear address interval + +//---------------------------------------------------------------------------- +// command index +//---------------------------------------------------------------------------- +#define WPG_1ST_SLOT_INDEX 0x01 // index - 1st slot for ctlr +#define WPG_CTLR_INDEX 0x0F // index - ctlr +#define WPG_1ST_EXTSLOT_INDEX 0x10 // index - 1st ext slot for ctlr +#define WPG_1ST_BUS_INDEX 0x1F // index - 1st bus for ctlr + +//---------------------------------------------------------------------------- +// macro utilities +//---------------------------------------------------------------------------- +// if bits 20,22,25,26,27,29,30 are OFF return 1 +#define HPC_I2CSTATUS_CHECK(s) ((u8)((s & 0x00000A76) ? 0 : 1)) + +//---------------------------------------------------------------------------- +// global variables +//---------------------------------------------------------------------------- +static DEFINE_MUTEX(sem_hpcaccess); // lock access to HPC +static DEFINE_MUTEX(operations_mutex); // lock all operations and + // access to data structures +static DECLARE_COMPLETION(exit_complete); // make sure polling thread goes away +static struct task_struct *ibmphp_poll_thread; +//---------------------------------------------------------------------------- +// local function prototypes +//---------------------------------------------------------------------------- +static u8 i2c_ctrl_read(struct controller *, void __iomem *, u8); +static u8 i2c_ctrl_write(struct controller *, void __iomem *, u8, u8); +static u8 hpc_writecmdtoindex(u8, u8); +static u8 hpc_readcmdtoindex(u8, u8); +static void get_hpc_access(void); +static void free_hpc_access(void); +static int poll_hpc(void *data); +static int process_changeinstatus(struct slot *, struct slot *); +static int process_changeinlatch(u8, u8, struct controller *); +static int hpc_wait_ctlr_notworking(int, struct controller *, void __iomem *, u8 *); +//---------------------------------------------------------------------------- + + +/*---------------------------------------------------------------------- +* Name: i2c_ctrl_read +* +* Action: read from HPC over I2C +* +*---------------------------------------------------------------------*/ +static u8 i2c_ctrl_read(struct controller *ctlr_ptr, void __iomem *WPGBbar, u8 index) +{ + u8 status; + int i; + void __iomem *wpg_addr; // base addr + offset + unsigned long wpg_data; // data to/from WPG LOHI format + unsigned long ultemp; + unsigned long data; // actual data HILO format + + debug_polling("%s - Entry WPGBbar[%p] index[%x] \n", __func__, WPGBbar, index); + + //-------------------------------------------------------------------- + // READ - step 1 + // read at address, byte length, I2C address (shifted), index + // or read direct, byte length, index + if (ctlr_ptr->ctlr_type == 0x02) { + data = WPG_READATADDR_MASK; + // fill in I2C address + ultemp = (unsigned long)ctlr_ptr->u.wpeg_ctlr.i2c_addr; + ultemp = ultemp >> 1; + data |= (ultemp << 8); + + // fill in index + data |= (unsigned long)index; + } else if (ctlr_ptr->ctlr_type == 0x04) { + data = WPG_READDIRECT_MASK; + + // fill in index + ultemp = (unsigned long)index; + ultemp = ultemp << 8; + data |= ultemp; + } else { + err("this controller type is not supported \n"); + return HPC_ERROR; + } + + wpg_data = swab32(data); // swap data before writing + wpg_addr = WPGBbar + WPG_I2CMOSUP_OFFSET; + writel(wpg_data, wpg_addr); + + //-------------------------------------------------------------------- + // READ - step 2 : clear the message buffer + data = 0x00000000; + wpg_data = swab32(data); + wpg_addr = WPGBbar + WPG_I2CMBUFL_OFFSET; + writel(wpg_data, wpg_addr); + + //-------------------------------------------------------------------- + // READ - step 3 : issue start operation, I2C master control bit 30:ON + // 2020 : [20] OR operation at [20] offset 0x20 + data = WPG_I2CMCNTL_STARTOP_MASK; + wpg_data = swab32(data); + wpg_addr = WPGBbar + WPG_I2CMCNTL_OFFSET + WPG_I2C_OR; + writel(wpg_data, wpg_addr); + + //-------------------------------------------------------------------- + // READ - step 4 : wait until start operation bit clears + i = CMD_COMPLETE_TOUT_SEC; + while (i) { + msleep(10); + wpg_addr = WPGBbar + WPG_I2CMCNTL_OFFSET; + wpg_data = readl(wpg_addr); + data = swab32(wpg_data); + if (!(data & WPG_I2CMCNTL_STARTOP_MASK)) + break; + i--; + } + if (i == 0) { + debug("%s - Error : WPG timeout\n", __func__); + return HPC_ERROR; + } + //-------------------------------------------------------------------- + // READ - step 5 : read I2C status register + i = CMD_COMPLETE_TOUT_SEC; + while (i) { + msleep(10); + wpg_addr = WPGBbar + WPG_I2CSTAT_OFFSET; + wpg_data = readl(wpg_addr); + data = swab32(wpg_data); + if (HPC_I2CSTATUS_CHECK(data)) + break; + i--; + } + if (i == 0) { + debug("ctrl_read - Exit Error:I2C timeout\n"); + return HPC_ERROR; + } + + //-------------------------------------------------------------------- + // READ - step 6 : get DATA + wpg_addr = WPGBbar + WPG_I2CMBUFL_OFFSET; + wpg_data = readl(wpg_addr); + data = swab32(wpg_data); + + status = (u8) data; + + debug_polling("%s - Exit index[%x] status[%x]\n", __func__, index, status); + + return (status); +} + +/*---------------------------------------------------------------------- +* Name: i2c_ctrl_write +* +* Action: write to HPC over I2C +* +* Return 0 or error codes +*---------------------------------------------------------------------*/ +static u8 i2c_ctrl_write(struct controller *ctlr_ptr, void __iomem *WPGBbar, u8 index, u8 cmd) +{ + u8 rc; + void __iomem *wpg_addr; // base addr + offset + unsigned long wpg_data; // data to/from WPG LOHI format + unsigned long ultemp; + unsigned long data; // actual data HILO format + int i; + + debug_polling("%s - Entry WPGBbar[%p] index[%x] cmd[%x]\n", __func__, WPGBbar, index, cmd); + + rc = 0; + //-------------------------------------------------------------------- + // WRITE - step 1 + // write at address, byte length, I2C address (shifted), index + // or write direct, byte length, index + data = 0x00000000; + + if (ctlr_ptr->ctlr_type == 0x02) { + data = WPG_WRITEATADDR_MASK; + // fill in I2C address + ultemp = (unsigned long)ctlr_ptr->u.wpeg_ctlr.i2c_addr; + ultemp = ultemp >> 1; + data |= (ultemp << 8); + + // fill in index + data |= (unsigned long)index; + } else if (ctlr_ptr->ctlr_type == 0x04) { + data = WPG_WRITEDIRECT_MASK; + + // fill in index + ultemp = (unsigned long)index; + ultemp = ultemp << 8; + data |= ultemp; + } else { + err("this controller type is not supported \n"); + return HPC_ERROR; + } + + wpg_data = swab32(data); // swap data before writing + wpg_addr = WPGBbar + WPG_I2CMOSUP_OFFSET; + writel(wpg_data, wpg_addr); + + //-------------------------------------------------------------------- + // WRITE - step 2 : clear the message buffer + data = 0x00000000 | (unsigned long)cmd; + wpg_data = swab32(data); + wpg_addr = WPGBbar + WPG_I2CMBUFL_OFFSET; + writel(wpg_data, wpg_addr); + + //-------------------------------------------------------------------- + // WRITE - step 3 : issue start operation,I2C master control bit 30:ON + // 2020 : [20] OR operation at [20] offset 0x20 + data = WPG_I2CMCNTL_STARTOP_MASK; + wpg_data = swab32(data); + wpg_addr = WPGBbar + WPG_I2CMCNTL_OFFSET + WPG_I2C_OR; + writel(wpg_data, wpg_addr); + + //-------------------------------------------------------------------- + // WRITE - step 4 : wait until start operation bit clears + i = CMD_COMPLETE_TOUT_SEC; + while (i) { + msleep(10); + wpg_addr = WPGBbar + WPG_I2CMCNTL_OFFSET; + wpg_data = readl(wpg_addr); + data = swab32(wpg_data); + if (!(data & WPG_I2CMCNTL_STARTOP_MASK)) + break; + i--; + } + if (i == 0) { + debug("%s - Exit Error:WPG timeout\n", __func__); + rc = HPC_ERROR; + } + + //-------------------------------------------------------------------- + // WRITE - step 5 : read I2C status register + i = CMD_COMPLETE_TOUT_SEC; + while (i) { + msleep(10); + wpg_addr = WPGBbar + WPG_I2CSTAT_OFFSET; + wpg_data = readl(wpg_addr); + data = swab32(wpg_data); + if (HPC_I2CSTATUS_CHECK(data)) + break; + i--; + } + if (i == 0) { + debug("ctrl_read - Error : I2C timeout\n"); + rc = HPC_ERROR; + } + + debug_polling("%s Exit rc[%x]\n", __func__, rc); + return (rc); +} + +//------------------------------------------------------------ +// Read from ISA type HPC +//------------------------------------------------------------ +static u8 isa_ctrl_read(struct controller *ctlr_ptr, u8 offset) +{ + u16 start_address; + u8 data; + + start_address = ctlr_ptr->u.isa_ctlr.io_start; + data = inb(start_address + offset); + return data; +} + +//-------------------------------------------------------------- +// Write to ISA type HPC +//-------------------------------------------------------------- +static void isa_ctrl_write(struct controller *ctlr_ptr, u8 offset, u8 data) +{ + u16 start_address; + u16 port_address; + + start_address = ctlr_ptr->u.isa_ctlr.io_start; + port_address = start_address + (u16) offset; + outb(data, port_address); +} + +static u8 pci_ctrl_read(struct controller *ctrl, u8 offset) +{ + u8 data = 0x00; + debug("inside pci_ctrl_read\n"); + if (ctrl->ctrl_dev) + pci_read_config_byte(ctrl->ctrl_dev, HPC_PCI_OFFSET + offset, &data); + return data; +} + +static u8 pci_ctrl_write(struct controller *ctrl, u8 offset, u8 data) +{ + u8 rc = -ENODEV; + debug("inside pci_ctrl_write\n"); + if (ctrl->ctrl_dev) { + pci_write_config_byte(ctrl->ctrl_dev, HPC_PCI_OFFSET + offset, data); + rc = 0; + } + return rc; +} + +static u8 ctrl_read(struct controller *ctlr, void __iomem *base, u8 offset) +{ + u8 rc; + switch (ctlr->ctlr_type) { + case 0: + rc = isa_ctrl_read(ctlr, offset); + break; + case 1: + rc = pci_ctrl_read(ctlr, offset); + break; + case 2: + case 4: + rc = i2c_ctrl_read(ctlr, base, offset); + break; + default: + return -ENODEV; + } + return rc; +} + +static u8 ctrl_write(struct controller *ctlr, void __iomem *base, u8 offset, u8 data) +{ + u8 rc = 0; + switch (ctlr->ctlr_type) { + case 0: + isa_ctrl_write(ctlr, offset, data); + break; + case 1: + rc = pci_ctrl_write(ctlr, offset, data); + break; + case 2: + case 4: + rc = i2c_ctrl_write(ctlr, base, offset, data); + break; + default: + return -ENODEV; + } + return rc; +} +/*---------------------------------------------------------------------- +* Name: hpc_writecmdtoindex() +* +* Action: convert a write command to proper index within a controller +* +* Return index, HPC_ERROR +*---------------------------------------------------------------------*/ +static u8 hpc_writecmdtoindex(u8 cmd, u8 index) +{ + u8 rc; + + switch (cmd) { + case HPC_CTLR_ENABLEIRQ: // 0x00.N.15 + case HPC_CTLR_CLEARIRQ: // 0x06.N.15 + case HPC_CTLR_RESET: // 0x07.N.15 + case HPC_CTLR_IRQSTEER: // 0x08.N.15 + case HPC_CTLR_DISABLEIRQ: // 0x01.N.15 + case HPC_ALLSLOT_ON: // 0x11.N.15 + case HPC_ALLSLOT_OFF: // 0x12.N.15 + rc = 0x0F; + break; + + case HPC_SLOT_OFF: // 0x02.Y.0-14 + case HPC_SLOT_ON: // 0x03.Y.0-14 + case HPC_SLOT_ATTNOFF: // 0x04.N.0-14 + case HPC_SLOT_ATTNON: // 0x05.N.0-14 + case HPC_SLOT_BLINKLED: // 0x13.N.0-14 + rc = index; + break; + + case HPC_BUS_33CONVMODE: + case HPC_BUS_66CONVMODE: + case HPC_BUS_66PCIXMODE: + case HPC_BUS_100PCIXMODE: + case HPC_BUS_133PCIXMODE: + rc = index + WPG_1ST_BUS_INDEX - 1; + break; + + default: + err("hpc_writecmdtoindex - Error invalid cmd[%x]\n", cmd); + rc = HPC_ERROR; + } + + return rc; +} + +/*---------------------------------------------------------------------- +* Name: hpc_readcmdtoindex() +* +* Action: convert a read command to proper index within a controller +* +* Return index, HPC_ERROR +*---------------------------------------------------------------------*/ +static u8 hpc_readcmdtoindex(u8 cmd, u8 index) +{ + u8 rc; + + switch (cmd) { + case READ_CTLRSTATUS: + rc = 0x0F; + break; + case READ_SLOTSTATUS: + case READ_ALLSTAT: + rc = index; + break; + case READ_EXTSLOTSTATUS: + rc = index + WPG_1ST_EXTSLOT_INDEX; + break; + case READ_BUSSTATUS: + rc = index + WPG_1ST_BUS_INDEX - 1; + break; + case READ_SLOTLATCHLOWREG: + rc = 0x28; + break; + case READ_REVLEVEL: + rc = 0x25; + break; + case READ_HPCOPTIONS: + rc = 0x27; + break; + default: + rc = HPC_ERROR; + } + return rc; +} + +/*---------------------------------------------------------------------- +* Name: HPCreadslot() +* +* Action: issue a READ command to HPC +* +* Input: pslot - cannot be NULL for READ_ALLSTAT +* pstatus - can be NULL for READ_ALLSTAT +* +* Return 0 or error codes +*---------------------------------------------------------------------*/ +int ibmphp_hpc_readslot(struct slot *pslot, u8 cmd, u8 *pstatus) +{ + void __iomem *wpg_bbar = NULL; + struct controller *ctlr_ptr; + u8 index, status; + int rc = 0; + int busindex; + + debug_polling("%s - Entry pslot[%p] cmd[%x] pstatus[%p]\n", __func__, pslot, cmd, pstatus); + + if ((pslot == NULL) + || ((pstatus == NULL) && (cmd != READ_ALLSTAT) && (cmd != READ_BUSSTATUS))) { + rc = -EINVAL; + err("%s - Error invalid pointer, rc[%d]\n", __func__, rc); + return rc; + } + + if (cmd == READ_BUSSTATUS) { + busindex = ibmphp_get_bus_index(pslot->bus); + if (busindex < 0) { + rc = -EINVAL; + err("%s - Exit Error:invalid bus, rc[%d]\n", __func__, rc); + return rc; + } else + index = (u8) busindex; + } else + index = pslot->ctlr_index; + + index = hpc_readcmdtoindex(cmd, index); + + if (index == HPC_ERROR) { + rc = -EINVAL; + err("%s - Exit Error:invalid index, rc[%d]\n", __func__, rc); + return rc; + } + + ctlr_ptr = pslot->ctrl; + + get_hpc_access(); + + //-------------------------------------------------------------------- + // map physical address to logical address + //-------------------------------------------------------------------- + if ((ctlr_ptr->ctlr_type == 2) || (ctlr_ptr->ctlr_type == 4)) + wpg_bbar = ioremap(ctlr_ptr->u.wpeg_ctlr.wpegbbar, WPG_I2C_IOREMAP_SIZE); + + //-------------------------------------------------------------------- + // check controller status before reading + //-------------------------------------------------------------------- + rc = hpc_wait_ctlr_notworking(HPC_CTLR_WORKING_TOUT, ctlr_ptr, wpg_bbar, &status); + if (!rc) { + switch (cmd) { + case READ_ALLSTAT: + // update the slot structure + pslot->ctrl->status = status; + pslot->status = ctrl_read(ctlr_ptr, wpg_bbar, index); + rc = hpc_wait_ctlr_notworking(HPC_CTLR_WORKING_TOUT, ctlr_ptr, wpg_bbar, + &status); + if (!rc) + pslot->ext_status = ctrl_read(ctlr_ptr, wpg_bbar, index + WPG_1ST_EXTSLOT_INDEX); + + break; + + case READ_SLOTSTATUS: + // DO NOT update the slot structure + *pstatus = ctrl_read(ctlr_ptr, wpg_bbar, index); + break; + + case READ_EXTSLOTSTATUS: + // DO NOT update the slot structure + *pstatus = ctrl_read(ctlr_ptr, wpg_bbar, index); + break; + + case READ_CTLRSTATUS: + // DO NOT update the slot structure + *pstatus = status; + break; + + case READ_BUSSTATUS: + pslot->busstatus = ctrl_read(ctlr_ptr, wpg_bbar, index); + break; + case READ_REVLEVEL: + *pstatus = ctrl_read(ctlr_ptr, wpg_bbar, index); + break; + case READ_HPCOPTIONS: + *pstatus = ctrl_read(ctlr_ptr, wpg_bbar, index); + break; + case READ_SLOTLATCHLOWREG: + // DO NOT update the slot structure + *pstatus = ctrl_read(ctlr_ptr, wpg_bbar, index); + break; + + // Not used + case READ_ALLSLOT: + list_for_each_entry(pslot, &ibmphp_slot_head, + ibm_slot_list) { + index = pslot->ctlr_index; + rc = hpc_wait_ctlr_notworking(HPC_CTLR_WORKING_TOUT, ctlr_ptr, + wpg_bbar, &status); + if (!rc) { + pslot->status = ctrl_read(ctlr_ptr, wpg_bbar, index); + rc = hpc_wait_ctlr_notworking(HPC_CTLR_WORKING_TOUT, + ctlr_ptr, wpg_bbar, &status); + if (!rc) + pslot->ext_status = + ctrl_read(ctlr_ptr, wpg_bbar, + index + WPG_1ST_EXTSLOT_INDEX); + } else { + err("%s - Error ctrl_read failed\n", __func__); + rc = -EINVAL; + break; + } + } + break; + default: + rc = -EINVAL; + break; + } + } + //-------------------------------------------------------------------- + // cleanup + //-------------------------------------------------------------------- + + // remove physical to logical address mapping + if ((ctlr_ptr->ctlr_type == 2) || (ctlr_ptr->ctlr_type == 4)) + iounmap(wpg_bbar); + + free_hpc_access(); + + debug_polling("%s - Exit rc[%d]\n", __func__, rc); + return rc; +} + +/*---------------------------------------------------------------------- +* Name: ibmphp_hpc_writeslot() +* +* Action: issue a WRITE command to HPC +*---------------------------------------------------------------------*/ +int ibmphp_hpc_writeslot(struct slot *pslot, u8 cmd) +{ + void __iomem *wpg_bbar = NULL; + struct controller *ctlr_ptr; + u8 index, status; + int busindex; + u8 done; + int rc = 0; + int timeout; + + debug_polling("%s - Entry pslot[%p] cmd[%x]\n", __func__, pslot, cmd); + if (pslot == NULL) { + rc = -EINVAL; + err("%s - Error Exit rc[%d]\n", __func__, rc); + return rc; + } + + if ((cmd == HPC_BUS_33CONVMODE) || (cmd == HPC_BUS_66CONVMODE) || + (cmd == HPC_BUS_66PCIXMODE) || (cmd == HPC_BUS_100PCIXMODE) || + (cmd == HPC_BUS_133PCIXMODE)) { + busindex = ibmphp_get_bus_index(pslot->bus); + if (busindex < 0) { + rc = -EINVAL; + err("%s - Exit Error:invalid bus, rc[%d]\n", __func__, rc); + return rc; + } else + index = (u8) busindex; + } else + index = pslot->ctlr_index; + + index = hpc_writecmdtoindex(cmd, index); + + if (index == HPC_ERROR) { + rc = -EINVAL; + err("%s - Error Exit rc[%d]\n", __func__, rc); + return rc; + } + + ctlr_ptr = pslot->ctrl; + + get_hpc_access(); + + //-------------------------------------------------------------------- + // map physical address to logical address + //-------------------------------------------------------------------- + if ((ctlr_ptr->ctlr_type == 2) || (ctlr_ptr->ctlr_type == 4)) { + wpg_bbar = ioremap(ctlr_ptr->u.wpeg_ctlr.wpegbbar, WPG_I2C_IOREMAP_SIZE); + + debug("%s - ctlr id[%x] physical[%lx] logical[%lx] i2c[%x]\n", __func__, + ctlr_ptr->ctlr_id, (ulong) (ctlr_ptr->u.wpeg_ctlr.wpegbbar), (ulong) wpg_bbar, + ctlr_ptr->u.wpeg_ctlr.i2c_addr); + } + //-------------------------------------------------------------------- + // check controller status before writing + //-------------------------------------------------------------------- + rc = hpc_wait_ctlr_notworking(HPC_CTLR_WORKING_TOUT, ctlr_ptr, wpg_bbar, &status); + if (!rc) { + + ctrl_write(ctlr_ptr, wpg_bbar, index, cmd); + + //-------------------------------------------------------------------- + // check controller is still not working on the command + //-------------------------------------------------------------------- + timeout = CMD_COMPLETE_TOUT_SEC; + done = 0; + while (!done) { + rc = hpc_wait_ctlr_notworking(HPC_CTLR_WORKING_TOUT, ctlr_ptr, wpg_bbar, + &status); + if (!rc) { + if (NEEDTOCHECK_CMDSTATUS(cmd)) { + if (CTLR_FINISHED(status) == HPC_CTLR_FINISHED_YES) + done = 1; + } else + done = 1; + } + if (!done) { + msleep(1000); + if (timeout < 1) { + done = 1; + err("%s - Error command complete timeout\n", __func__); + rc = -EFAULT; + } else + timeout--; + } + } + ctlr_ptr->status = status; + } + // cleanup + + // remove physical to logical address mapping + if ((ctlr_ptr->ctlr_type == 2) || (ctlr_ptr->ctlr_type == 4)) + iounmap(wpg_bbar); + free_hpc_access(); + + debug_polling("%s - Exit rc[%d]\n", __func__, rc); + return rc; +} + +/*---------------------------------------------------------------------- +* Name: get_hpc_access() +* +* Action: make sure only one process can access HPC at one time +*---------------------------------------------------------------------*/ +static void get_hpc_access(void) +{ + mutex_lock(&sem_hpcaccess); +} + +/*---------------------------------------------------------------------- +* Name: free_hpc_access() +*---------------------------------------------------------------------*/ +void free_hpc_access(void) +{ + mutex_unlock(&sem_hpcaccess); +} + +/*---------------------------------------------------------------------- +* Name: ibmphp_lock_operations() +* +* Action: make sure only one process can change the data structure +*---------------------------------------------------------------------*/ +void ibmphp_lock_operations(void) +{ + mutex_lock(&operations_mutex); + to_debug = 1; +} + +/*---------------------------------------------------------------------- +* Name: ibmphp_unlock_operations() +*---------------------------------------------------------------------*/ +void ibmphp_unlock_operations(void) +{ + debug("%s - Entry\n", __func__); + mutex_unlock(&operations_mutex); + to_debug = 0; + debug("%s - Exit\n", __func__); +} + +/*---------------------------------------------------------------------- +* Name: poll_hpc() +*---------------------------------------------------------------------*/ +#define POLL_LATCH_REGISTER 0 +#define POLL_SLOTS 1 +#define POLL_SLEEP 2 +static int poll_hpc(void *data) +{ + struct slot myslot; + struct slot *pslot = NULL; + int rc; + int poll_state = POLL_LATCH_REGISTER; + u8 oldlatchlow = 0x00; + u8 curlatchlow = 0x00; + int poll_count = 0; + u8 ctrl_count = 0x00; + + debug("%s - Entry\n", __func__); + + while (!kthread_should_stop()) { + /* try to get the lock to do some kind of hardware access */ + mutex_lock(&operations_mutex); + + switch (poll_state) { + case POLL_LATCH_REGISTER: + oldlatchlow = curlatchlow; + ctrl_count = 0x00; + list_for_each_entry(pslot, &ibmphp_slot_head, + ibm_slot_list) { + if (ctrl_count >= ibmphp_get_total_controllers()) + break; + 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_entry(pslot, &ibmphp_slot_head, + 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_entry(pslot, &ibmphp_slot_head, + ibm_slot_list) { + if (ctrl_count >= ibmphp_get_total_controllers()) + break; + 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 */ + mutex_unlock(&operations_mutex); + msleep(POLL_INTERVAL_SEC * 1000); + + if (kthread_should_stop()) + goto out_sleep; + + mutex_lock(&operations_mutex); + + if (poll_count >= POLL_LATCH_CNT) { + poll_count = 0; + poll_state = POLL_SLOTS; + } else + poll_state = POLL_LATCH_REGISTER; + break; + } + /* give up the hardware semaphore */ + mutex_unlock(&operations_mutex); + /* sleep for a short time just for good measure */ +out_sleep: + msleep(100); + } + complete(&exit_complete); + debug("%s - Exit\n", __func__); + return 0; +} + + +/*---------------------------------------------------------------------- +* Name: process_changeinstatus +* +* Action: compare old and new slot status, process the change in status +* +* Input: pointer to slot struct, old slot struct +* +* Return 0 or error codes +* Value: +* +* Side +* Effects: None. +* +* Notes: +*---------------------------------------------------------------------*/ +static int process_changeinstatus(struct slot *pslot, struct slot *poldslot) +{ + u8 status; + int rc = 0; + u8 disable = 0; + u8 update = 0; + + debug("process_changeinstatus - Entry pslot[%p], poldslot[%p]\n", pslot, poldslot); + + // bit 0 - HPC_SLOT_POWER + if ((pslot->status & 0x01) != (poldslot->status & 0x01)) + update = 1; + + // bit 1 - HPC_SLOT_CONNECT + // ignore + + // bit 2 - HPC_SLOT_ATTN + if ((pslot->status & 0x04) != (poldslot->status & 0x04)) + update = 1; + + // bit 3 - HPC_SLOT_PRSNT2 + // bit 4 - HPC_SLOT_PRSNT1 + if (((pslot->status & 0x08) != (poldslot->status & 0x08)) + || ((pslot->status & 0x10) != (poldslot->status & 0x10))) + update = 1; + + // bit 5 - HPC_SLOT_PWRGD + if ((pslot->status & 0x20) != (poldslot->status & 0x20)) + // OFF -> ON: ignore, ON -> OFF: disable slot + if ((poldslot->status & 0x20) && (SLOT_CONNECT(poldslot->status) == HPC_SLOT_CONNECTED) && (SLOT_PRESENT(poldslot->status))) + disable = 1; + + // bit 6 - HPC_SLOT_BUS_SPEED + // ignore + + // bit 7 - HPC_SLOT_LATCH + if ((pslot->status & 0x80) != (poldslot->status & 0x80)) { + update = 1; + // OPEN -> CLOSE + if (pslot->status & 0x80) { + if (SLOT_PWRGD(pslot->status)) { + // power goes on and off after closing latch + // check again to make sure power is still ON + msleep(1000); + rc = ibmphp_hpc_readslot(pslot, READ_SLOTSTATUS, &status); + if (SLOT_PWRGD(status)) + update = 1; + else // overwrite power in pslot to OFF + pslot->status &= ~HPC_SLOT_POWER; + } + } + // CLOSE -> OPEN + else if ((SLOT_PWRGD(poldslot->status) == HPC_SLOT_PWRGD_GOOD) + && (SLOT_CONNECT(poldslot->status) == HPC_SLOT_CONNECTED) && (SLOT_PRESENT(poldslot->status))) { + disable = 1; + } + // else - ignore + } + // bit 4 - HPC_SLOT_BLINK_ATTN + if ((pslot->ext_status & 0x08) != (poldslot->ext_status & 0x08)) + update = 1; + + if (disable) { + debug("process_changeinstatus - disable slot\n"); + pslot->flag = 0; + rc = ibmphp_do_disable_slot(pslot); + } + + if (update || disable) + ibmphp_update_slot_info(pslot); + + debug("%s - Exit rc[%d] disable[%x] update[%x]\n", __func__, rc, disable, update); + + return rc; +} + +/*---------------------------------------------------------------------- +* Name: process_changeinlatch +* +* Action: compare old and new latch reg status, process the change +* +* Input: old and current latch register status +* +* Return 0 or error codes +* Value: +*---------------------------------------------------------------------*/ +static int process_changeinlatch(u8 old, u8 new, struct controller *ctrl) +{ + struct slot myslot, *pslot; + u8 i; + u8 mask; + int rc = 0; + + debug("%s - Entry old[%x], new[%x]\n", __func__, old, new); + // bit 0 reserved, 0 is LSB, check bit 1-6 for 6 slots + + for (i = ctrl->starting_slot_num; i <= ctrl->ending_slot_num; i++) { + mask = 0x01 << i; + if ((mask & old) != (mask & new)) { + pslot = ibmphp_get_slot_from_physical_num(i); + if (pslot) { + memcpy((void *) &myslot, (void *) pslot, sizeof(struct slot)); + rc = ibmphp_hpc_readslot(pslot, READ_ALLSTAT, NULL); + debug("%s - call process_changeinstatus for slot[%d]\n", __func__, i); + process_changeinstatus(pslot, &myslot); + } else { + rc = -EINVAL; + err("%s - Error bad pointer for slot[%d]\n", __func__, i); + } + } + } + debug("%s - Exit rc[%d]\n", __func__, rc); + return rc; +} + +/*---------------------------------------------------------------------- +* Name: ibmphp_hpc_start_poll_thread +* +* Action: start polling thread +*---------------------------------------------------------------------*/ +int __init ibmphp_hpc_start_poll_thread(void) +{ + debug("%s - Entry\n", __func__); + + ibmphp_poll_thread = kthread_run(poll_hpc, NULL, "hpc_poll"); + if (IS_ERR(ibmphp_poll_thread)) { + err("%s - Error, thread not started\n", __func__); + return PTR_ERR(ibmphp_poll_thread); + } + return 0; +} + +/*---------------------------------------------------------------------- +* Name: ibmphp_hpc_stop_poll_thread +* +* Action: stop polling thread and cleanup +*---------------------------------------------------------------------*/ +void __exit ibmphp_hpc_stop_poll_thread(void) +{ + debug("%s - Entry\n", __func__); + + kthread_stop(ibmphp_poll_thread); + debug("before locking operations\n"); + ibmphp_lock_operations(); + debug("after locking operations\n"); + + // wait for poll thread to exit + debug("before exit_complete down\n"); + wait_for_completion(&exit_complete); + debug("after exit_completion 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"); + + debug("%s - Exit\n", __func__); +} + +/*---------------------------------------------------------------------- +* Name: hpc_wait_ctlr_notworking +* +* Action: wait until the controller is in a not working state +* +* Return 0, HPC_ERROR +* Value: +*---------------------------------------------------------------------*/ +static int hpc_wait_ctlr_notworking(int timeout, struct controller *ctlr_ptr, void __iomem *wpg_bbar, + u8 *pstatus) +{ + int rc = 0; + u8 done = 0; + + debug_polling("hpc_wait_ctlr_notworking - Entry timeout[%d]\n", timeout); + + while (!done) { + *pstatus = ctrl_read(ctlr_ptr, wpg_bbar, WPG_CTLR_INDEX); + if (*pstatus == HPC_ERROR) { + rc = HPC_ERROR; + done = 1; + } + if (CTLR_WORKING(*pstatus) == HPC_CTLR_WORKING_NO) + done = 1; + if (!done) { + msleep(1000); + if (timeout < 1) { + done = 1; + err("HPCreadslot - Error ctlr timeout\n"); + rc = HPC_ERROR; + } else + timeout--; + } + } + debug_polling("hpc_wait_ctlr_notworking - Exit rc[%x] status[%x]\n", rc, *pstatus); + return rc; +} diff --git a/drivers/pci/hotplug/ibmphp_pci.c b/drivers/pci/hotplug/ibmphp_pci.c new file mode 100644 index 000000000..754c3f232 --- /dev/null +++ b/drivers/pci/hotplug/ibmphp_pci.c @@ -0,0 +1,1689 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * 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. + * + * 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->function = %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); + rc = configure_device(cur_func); + if (rc < 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); + rc = configure_device(cur_func); + if (rc < 0) { + /* We need to do this in case some other BARs were properly inserted */ + err("was not able to configure devfunc %x on bus %x...bailing out\n", + cur_func->device, cur_func->busno); + cleanup_count = 6; + goto error; + } + newfunc = kzalloc(sizeof(*newfunc), GFP_KERNEL); + if (!newfunc) + return -ENOMEM; + + newfunc->busno = cur_func->busno; + newfunc->device = device; + cur_func->next = newfunc; + cur_func = newfunc; + for (j = 0; j < 4; j++) + newfunc->irq[j] = cur_func->irq[j]; + break; + case PCI_HEADER_TYPE_MULTIBRIDGE: + class >>= 8; + if (class != PCI_CLASS_BRIDGE_PCI) { + err("This %x is not PCI-to-PCI bridge, and as is not supported for hot-plugging. Please insert another card.\n", + cur_func->device); + return -ENODEV; + } + assign_alt_irq(cur_func, class_code); + rc = configure_bridge(&cur_func, slotno); + if (rc == -ENODEV) { + err("You chose to insert Single Bridge, or nested bridges, this is not supported...\n"); + err("Bus %x, devfunc %x\n", cur_func->busno, cur_func->device); + return rc; + } + if (rc) { + /* We need to do this in case some other BARs were properly inserted */ + err("was not able to hot-add PPB properly.\n"); + func->bus = 1; /* To indicate to the unconfigure function that this is a PPB */ + cleanup_count = 2; + goto error; + } + + pci_bus_read_config_byte(ibmphp_pci_bus, devfn, PCI_SECONDARY_BUS, &sec_number); + flag = 0; + for (i = 0; i < 32; i++) { + if (func->devices[i]) { + newfunc = kzalloc(sizeof(*newfunc), GFP_KERNEL); + if (!newfunc) + return -ENOMEM; + + newfunc->busno = sec_number; + newfunc->device = (u8) i; + for (j = 0; j < 4; j++) + newfunc->irq[j] = cur_func->irq[j]; + + if (flag) { + for (prev_func = cur_func; prev_func->next; prev_func = prev_func->next) ; + prev_func->next = newfunc; + } else + cur_func->next = newfunc; + + rc = ibmphp_configure_card(newfunc, slotno); + /* This could only happen if kmalloc failed */ + if (rc) { + /* We need to do this in case bridge itself got configured properly, but devices behind it failed */ + func->bus = 1; /* To indicate to the unconfigure function that this is a PPB */ + cleanup_count = 2; + goto error; + } + flag = 1; + } + } + + newfunc = kzalloc(sizeof(*newfunc), GFP_KERNEL); + if (!newfunc) + return -ENOMEM; + + newfunc->busno = cur_func->busno; + newfunc->device = device; + for (j = 0; j < 4; j++) + newfunc->irq[j] = cur_func->irq[j]; + for (prev_func = cur_func; prev_func->next; prev_func = prev_func->next); + prev_func->next = newfunc; + cur_func = newfunc; + break; + case PCI_HEADER_TYPE_BRIDGE: + class >>= 8; + debug("class now is %x\n", class); + if (class != PCI_CLASS_BRIDGE_PCI) { + err("This %x is not PCI-to-PCI bridge, and as is not supported for hot-plugging. Please insert another card.\n", + cur_func->device); + return -ENODEV; + } + + assign_alt_irq(cur_func, class_code); + + debug("cur_func->busno b4 configure_bridge is %x\n", cur_func->busno); + rc = configure_bridge(&cur_func, slotno); + if (rc == -ENODEV) { + err("You chose to insert Single Bridge, or nested bridges, this is not supported...\n"); + err("Bus %x, devfunc %x\n", cur_func->busno, cur_func->device); + return rc; + } + if (rc) { + /* We need to do this in case some other BARs were properly inserted */ + func->bus = 1; /* To indicate to the unconfigure function that this is a PPB */ + err("was not able to hot-add PPB properly.\n"); + cleanup_count = 2; + goto error; + } + debug("cur_func->busno = %x, device = %x, function = %x\n", + cur_func->busno, device, function); + pci_bus_read_config_byte(ibmphp_pci_bus, devfn, PCI_SECONDARY_BUS, &sec_number); + debug("after configuring bridge..., sec_number = %x\n", sec_number); + flag = 0; + for (i = 0; i < 32; i++) { + if (func->devices[i]) { + debug("inside for loop, device is %x\n", i); + newfunc = kzalloc(sizeof(*newfunc), GFP_KERNEL); + if (!newfunc) + return -ENOMEM; + + newfunc->busno = sec_number; + newfunc->device = (u8) i; + for (j = 0; j < 4; j++) + newfunc->irq[j] = cur_func->irq[j]; + + if (flag) { + for (prev_func = cur_func; prev_func->next; prev_func = prev_func->next); + prev_func->next = newfunc; + } else + cur_func->next = newfunc; + + rc = ibmphp_configure_card(newfunc, slotno); + + /* Again, this case should not happen... For complete paranoia, will need to call remove_bus */ + if (rc) { + /* We need to do this in case some other BARs were properly inserted */ + func->bus = 1; /* To indicate to the unconfigure function that this is a PPB */ + cleanup_count = 2; + goto error; + } + flag = 1; + } + } + + function = 0x8; + break; + default: + err("MAJOR PROBLEM!!!!, header type not supported? %x\n", hdr_type); + return -ENXIO; + } /* end of switch */ + } /* end of valid device */ + } /* end of for */ + + if (!valid_device) { + err("Cannot find any valid devices on the card. Or unable to read from card.\n"); + return -ENODEV; + } + + return 0; + +error: + for (i = 0; i < cleanup_count; i++) { + if (cur_func->io[i]) { + ibmphp_remove_resource(cur_func->io[i]); + cur_func->io[i] = NULL; + } else if (cur_func->pfmem[i]) { + ibmphp_remove_resource(cur_func->pfmem[i]); + cur_func->pfmem[i] = NULL; + } else if (cur_func->mem[i]) { + ibmphp_remove_resource(cur_func->mem[i]); + cur_func->mem[i] = NULL; + } + } + return rc; +} + +/* + * This function configures the pci BARs of a single device. + * Input: pointer to the pci_func + * Output: configured PCI, 0, or error + */ +static int configure_device(struct pci_func *func) +{ + u32 bar[6]; + u32 address[] = { + PCI_BASE_ADDRESS_0, + PCI_BASE_ADDRESS_1, + PCI_BASE_ADDRESS_2, + PCI_BASE_ADDRESS_3, + PCI_BASE_ADDRESS_4, + PCI_BASE_ADDRESS_5, + 0 + }; + u8 irq; + int count; + int len[6]; + struct resource_node *io[6]; + struct resource_node *mem[6]; + struct resource_node *mem_tmp; + struct resource_node *pfmem[6]; + unsigned int devfn; + + debug("%s - inside\n", __func__); + + devfn = PCI_DEVFN(func->device, func->function); + ibmphp_pci_bus->number = func->busno; + + for (count = 0; address[count]; count++) { /* for 6 BARs */ + + /* not sure if i need this. per scott, said maybe need * something like this + if devices don't adhere 100% to the spec, so don't want to write + to the reserved bits + + pcibios_read_config_byte(cur_func->busno, cur_func->device, + PCI_BASE_ADDRESS_0 + 4 * count, &tmp); + if (tmp & 0x01) // IO + pcibios_write_config_dword(cur_func->busno, cur_func->device, + PCI_BASE_ADDRESS_0 + 4 * count, 0xFFFFFFFD); + else // Memory + pcibios_write_config_dword(cur_func->busno, cur_func->device, + PCI_BASE_ADDRESS_0 + 4 * count, 0xFFFFFFFF); + */ + pci_bus_write_config_dword(ibmphp_pci_bus, devfn, address[count], 0xFFFFFFFF); + pci_bus_read_config_dword(ibmphp_pci_bus, devfn, address[count], &bar[count]); + + if (!bar[count]) /* This BAR is not implemented */ + continue; + + debug("Device %x BAR %d wants %x\n", func->device, count, bar[count]); + + if (bar[count] & PCI_BASE_ADDRESS_SPACE_IO) { + /* This is IO */ + debug("inside IO SPACE\n"); + + len[count] = bar[count] & 0xFFFFFFFC; + len[count] = ~len[count] + 1; + + debug("len[count] in IO %x, count %d\n", len[count], count); + + io[count] = kzalloc(sizeof(struct resource_node), GFP_KERNEL); + + if (!io[count]) + return -ENOMEM; + + io[count]->type = IO; + io[count]->busno = func->busno; + io[count]->devfunc = PCI_DEVFN(func->device, func->function); + io[count]->len = len[count]; + if (ibmphp_check_resource(io[count], 0) == 0) { + ibmphp_add_resource(io[count]); + func->io[count] = io[count]; + } else { + err("cannot allocate requested io for bus %x device %x function %x len %x\n", + func->busno, func->device, func->function, len[count]); + kfree(io[count]); + return -EIO; + } + pci_bus_write_config_dword(ibmphp_pci_bus, devfn, address[count], func->io[count]->start); + + /* _______________This is for debugging purposes only_____________________ */ + debug("b4 writing, the IO address is %x\n", func->io[count]->start); + pci_bus_read_config_dword(ibmphp_pci_bus, devfn, address[count], &bar[count]); + debug("after writing.... the start address is %x\n", bar[count]); + /* _________________________________________________________________________*/ + + } else { + /* This is Memory */ + if (bar[count] & PCI_BASE_ADDRESS_MEM_PREFETCH) { + /* pfmem */ + debug("PFMEM SPACE\n"); + + len[count] = bar[count] & 0xFFFFFFF0; + len[count] = ~len[count] + 1; + + debug("len[count] in PFMEM %x, count %d\n", len[count], count); + + pfmem[count] = kzalloc(sizeof(struct resource_node), GFP_KERNEL); + if (!pfmem[count]) + return -ENOMEM; + + pfmem[count]->type = PFMEM; + pfmem[count]->busno = func->busno; + pfmem[count]->devfunc = PCI_DEVFN(func->device, + func->function); + pfmem[count]->len = len[count]; + pfmem[count]->fromMem = 0; + if (ibmphp_check_resource(pfmem[count], 0) == 0) { + ibmphp_add_resource(pfmem[count]); + func->pfmem[count] = pfmem[count]; + } else { + mem_tmp = kzalloc(sizeof(*mem_tmp), GFP_KERNEL); + if (!mem_tmp) { + kfree(pfmem[count]); + return -ENOMEM; + } + mem_tmp->type = MEM; + mem_tmp->busno = pfmem[count]->busno; + mem_tmp->devfunc = pfmem[count]->devfunc; + mem_tmp->len = pfmem[count]->len; + debug("there's no pfmem... going into mem.\n"); + if (ibmphp_check_resource(mem_tmp, 0) == 0) { + ibmphp_add_resource(mem_tmp); + pfmem[count]->fromMem = 1; + pfmem[count]->rangeno = mem_tmp->rangeno; + pfmem[count]->start = mem_tmp->start; + pfmem[count]->end = mem_tmp->end; + ibmphp_add_pfmem_from_mem(pfmem[count]); + func->pfmem[count] = pfmem[count]; + } else { + err("cannot allocate requested pfmem for bus %x, device %x, len %x\n", + func->busno, func->device, len[count]); + kfree(mem_tmp); + kfree(pfmem[count]); + return -EIO; + } + } + + pci_bus_write_config_dword(ibmphp_pci_bus, devfn, address[count], func->pfmem[count]->start); + + /*_______________This is for debugging purposes only______________________________*/ + debug("b4 writing, start address is %x\n", func->pfmem[count]->start); + pci_bus_read_config_dword(ibmphp_pci_bus, devfn, address[count], &bar[count]); + debug("after writing, start address is %x\n", bar[count]); + /*_________________________________________________________________________________*/ + + if (bar[count] & PCI_BASE_ADDRESS_MEM_TYPE_64) { /* takes up another dword */ + debug("inside the mem 64 case, count %d\n", count); + count += 1; + /* on the 2nd dword, write all 0s, since we can't handle them n.e.ways */ + pci_bus_write_config_dword(ibmphp_pci_bus, devfn, address[count], 0x00000000); + } + } else { + /* regular memory */ + debug("REGULAR MEM SPACE\n"); + + len[count] = bar[count] & 0xFFFFFFF0; + len[count] = ~len[count] + 1; + + debug("len[count] in Mem %x, count %d\n", len[count], count); + + mem[count] = kzalloc(sizeof(struct resource_node), GFP_KERNEL); + if (!mem[count]) + return -ENOMEM; + + mem[count]->type = MEM; + mem[count]->busno = func->busno; + mem[count]->devfunc = PCI_DEVFN(func->device, + func->function); + mem[count]->len = len[count]; + if (ibmphp_check_resource(mem[count], 0) == 0) { + ibmphp_add_resource(mem[count]); + func->mem[count] = mem[count]; + } else { + err("cannot allocate requested mem for bus %x, device %x, len %x\n", + func->busno, func->device, len[count]); + kfree(mem[count]); + return -EIO; + } + pci_bus_write_config_dword(ibmphp_pci_bus, devfn, address[count], func->mem[count]->start); + /* _______________________This is for debugging purposes only _______________________*/ + debug("b4 writing, start address is %x\n", func->mem[count]->start); + pci_bus_read_config_dword(ibmphp_pci_bus, devfn, address[count], &bar[count]); + debug("after writing, the address is %x\n", bar[count]); + /* __________________________________________________________________________________*/ + + if (bar[count] & PCI_BASE_ADDRESS_MEM_TYPE_64) { + /* takes up another dword */ + debug("inside mem 64 case, reg. mem, count %d\n", count); + count += 1; + /* on the 2nd dword, write all 0s, since we can't handle them n.e.ways */ + pci_bus_write_config_dword(ibmphp_pci_bus, devfn, address[count], 0x00000000); + } + } + } /* end of mem */ + } /* end of for */ + + func->bus = 0; /* To indicate that this is not a PPB */ + pci_bus_read_config_byte(ibmphp_pci_bus, devfn, PCI_INTERRUPT_PIN, &irq); + if ((irq > 0x00) && (irq < 0x05)) + pci_bus_write_config_byte(ibmphp_pci_bus, devfn, PCI_INTERRUPT_LINE, func->irq[irq - 1]); + + pci_bus_write_config_byte(ibmphp_pci_bus, devfn, PCI_CACHE_LINE_SIZE, CACHE); + pci_bus_write_config_byte(ibmphp_pci_bus, devfn, PCI_LATENCY_TIMER, LATENCY); + + pci_bus_write_config_dword(ibmphp_pci_bus, devfn, PCI_ROM_ADDRESS, 0x00L); + pci_bus_write_config_word(ibmphp_pci_bus, devfn, PCI_COMMAND, DEVICEENABLE); + + return 0; +} + +/****************************************************************************** + * This routine configures a PCI-2-PCI bridge and the functions behind it + * Parameters: pci_func + * Returns: + ******************************************************************************/ +static int configure_bridge(struct pci_func **func_passed, u8 slotno) +{ + int count; + int i; + int rc; + u8 sec_number; + u8 io_base; + u16 pfmem_base; + u32 bar[2]; + u32 len[2]; + u8 flag_io = 0; + u8 flag_mem = 0; + u8 flag_pfmem = 0; + u8 need_io_upper = 0; + u8 need_pfmem_upper = 0; + struct res_needed *amount_needed = NULL; + struct resource_node *io = NULL; + struct resource_node *bus_io[2] = {NULL, NULL}; + struct resource_node *mem = NULL; + struct resource_node *bus_mem[2] = {NULL, NULL}; + struct resource_node *mem_tmp = NULL; + struct resource_node *pfmem = NULL; + struct resource_node *bus_pfmem[2] = {NULL, NULL}; + struct bus_node *bus; + u32 address[] = { + PCI_BASE_ADDRESS_0, + PCI_BASE_ADDRESS_1, + 0 + }; + struct pci_func *func = *func_passed; + unsigned int devfn; + u8 irq; + int retval; + + debug("%s - enter\n", __func__); + + devfn = PCI_DEVFN(func->function, func->device); + ibmphp_pci_bus->number = func->busno; + + /* Configuring necessary info for the bridge so that we could see the devices + * behind it + */ + + pci_bus_write_config_byte(ibmphp_pci_bus, devfn, PCI_PRIMARY_BUS, func->busno); + + /* _____________________For debugging purposes only __________________________ + pci_bus_config_byte(ibmphp_pci_bus, devfn, PCI_PRIMARY_BUS, &pri_number); + debug("primary # written into the bridge is %x\n", pri_number); + ___________________________________________________________________________*/ + + /* in EBDA, only get allocated 1 additional bus # per slot */ + sec_number = find_sec_number(func->busno, slotno); + if (sec_number == 0xff) { + err("cannot allocate secondary bus number for the bridged device\n"); + return -EINVAL; + } + + debug("after find_sec_number, the number we got is %x\n", sec_number); + debug("AFTER FIND_SEC_NUMBER, func->busno IS %x\n", func->busno); + + pci_bus_write_config_byte(ibmphp_pci_bus, devfn, PCI_SECONDARY_BUS, sec_number); + + /* __________________For debugging purposes only __________________________________ + pci_bus_read_config_byte(ibmphp_pci_bus, devfn, PCI_SECONDARY_BUS, &sec_number); + debug("sec_number after write/read is %x\n", sec_number); + ________________________________________________________________________________*/ + + pci_bus_write_config_byte(ibmphp_pci_bus, devfn, PCI_SUBORDINATE_BUS, sec_number); + + /* __________________For debugging purposes only ____________________________________ + pci_bus_read_config_byte(ibmphp_pci_bus, devfn, PCI_SUBORDINATE_BUS, &sec_number); + debug("subordinate number after write/read is %x\n", sec_number); + __________________________________________________________________________________*/ + + pci_bus_write_config_byte(ibmphp_pci_bus, devfn, PCI_CACHE_LINE_SIZE, CACHE); + pci_bus_write_config_byte(ibmphp_pci_bus, devfn, PCI_LATENCY_TIMER, LATENCY); + pci_bus_write_config_byte(ibmphp_pci_bus, devfn, PCI_SEC_LATENCY_TIMER, LATENCY); + + debug("func->busno is %x\n", func->busno); + debug("sec_number after writing is %x\n", sec_number); + + + /* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !!!!!!!!!!!!!!!NEED TO ADD!!! FAST BACK-TO-BACK ENABLE!!!!!!!!!!!!!!!!!!!! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!*/ + + + /* First we need to allocate mem/io for the bridge itself in case it needs it */ + for (count = 0; address[count]; count++) { /* for 2 BARs */ + pci_bus_write_config_dword(ibmphp_pci_bus, devfn, address[count], 0xFFFFFFFF); + pci_bus_read_config_dword(ibmphp_pci_bus, devfn, address[count], &bar[count]); + + if (!bar[count]) { + /* This BAR is not implemented */ + debug("so we come here then, eh?, count = %d\n", count); + continue; + } + // tmp_bar = bar[count]; + + debug("Bar %d wants %x\n", count, bar[count]); + + if (bar[count] & PCI_BASE_ADDRESS_SPACE_IO) { + /* This is IO */ + len[count] = bar[count] & 0xFFFFFFFC; + len[count] = ~len[count] + 1; + + debug("len[count] in IO = %x\n", len[count]); + + bus_io[count] = kzalloc(sizeof(struct resource_node), GFP_KERNEL); + + if (!bus_io[count]) { + retval = -ENOMEM; + goto error; + } + bus_io[count]->type = IO; + bus_io[count]->busno = func->busno; + bus_io[count]->devfunc = PCI_DEVFN(func->device, + func->function); + bus_io[count]->len = len[count]; + if (ibmphp_check_resource(bus_io[count], 0) == 0) { + ibmphp_add_resource(bus_io[count]); + func->io[count] = bus_io[count]; + } else { + err("cannot allocate requested io for bus %x, device %x, len %x\n", + func->busno, func->device, len[count]); + kfree(bus_io[count]); + return -EIO; + } + + pci_bus_write_config_dword(ibmphp_pci_bus, devfn, address[count], func->io[count]->start); + + } else { + /* This is Memory */ + if (bar[count] & PCI_BASE_ADDRESS_MEM_PREFETCH) { + /* pfmem */ + len[count] = bar[count] & 0xFFFFFFF0; + len[count] = ~len[count] + 1; + + debug("len[count] in PFMEM = %x\n", len[count]); + + bus_pfmem[count] = kzalloc(sizeof(struct resource_node), GFP_KERNEL); + if (!bus_pfmem[count]) { + retval = -ENOMEM; + goto error; + } + bus_pfmem[count]->type = PFMEM; + bus_pfmem[count]->busno = func->busno; + bus_pfmem[count]->devfunc = PCI_DEVFN(func->device, + func->function); + bus_pfmem[count]->len = len[count]; + bus_pfmem[count]->fromMem = 0; + if (ibmphp_check_resource(bus_pfmem[count], 0) == 0) { + ibmphp_add_resource(bus_pfmem[count]); + func->pfmem[count] = bus_pfmem[count]; + } else { + mem_tmp = kzalloc(sizeof(*mem_tmp), GFP_KERNEL); + if (!mem_tmp) { + retval = -ENOMEM; + goto error; + } + mem_tmp->type = MEM; + mem_tmp->busno = bus_pfmem[count]->busno; + mem_tmp->devfunc = bus_pfmem[count]->devfunc; + mem_tmp->len = bus_pfmem[count]->len; + if (ibmphp_check_resource(mem_tmp, 0) == 0) { + ibmphp_add_resource(mem_tmp); + bus_pfmem[count]->fromMem = 1; + bus_pfmem[count]->rangeno = mem_tmp->rangeno; + ibmphp_add_pfmem_from_mem(bus_pfmem[count]); + func->pfmem[count] = bus_pfmem[count]; + } else { + err("cannot allocate requested pfmem for bus %x, device %x, len %x\n", + func->busno, func->device, len[count]); + kfree(mem_tmp); + kfree(bus_pfmem[count]); + return -EIO; + } + } + + pci_bus_write_config_dword(ibmphp_pci_bus, devfn, address[count], func->pfmem[count]->start); + + if (bar[count] & PCI_BASE_ADDRESS_MEM_TYPE_64) { + /* takes up another dword */ + count += 1; + /* on the 2nd dword, write all 0s, since we can't handle them n.e.ways */ + pci_bus_write_config_dword(ibmphp_pci_bus, devfn, address[count], 0x00000000); + + } + } else { + /* regular memory */ + len[count] = bar[count] & 0xFFFFFFF0; + len[count] = ~len[count] + 1; + + debug("len[count] in Memory is %x\n", len[count]); + + bus_mem[count] = kzalloc(sizeof(struct resource_node), GFP_KERNEL); + if (!bus_mem[count]) { + retval = -ENOMEM; + goto error; + } + bus_mem[count]->type = MEM; + bus_mem[count]->busno = func->busno; + bus_mem[count]->devfunc = PCI_DEVFN(func->device, + func->function); + bus_mem[count]->len = len[count]; + if (ibmphp_check_resource(bus_mem[count], 0) == 0) { + ibmphp_add_resource(bus_mem[count]); + func->mem[count] = bus_mem[count]; + } else { + err("cannot allocate requested mem for bus %x, device %x, len %x\n", + func->busno, func->device, len[count]); + kfree(bus_mem[count]); + return -EIO; + } + + pci_bus_write_config_dword(ibmphp_pci_bus, devfn, address[count], func->mem[count]->start); + + if (bar[count] & PCI_BASE_ADDRESS_MEM_TYPE_64) { + /* takes up another dword */ + count += 1; + /* on the 2nd dword, write all 0s, since we can't handle them n.e.ways */ + pci_bus_write_config_dword(ibmphp_pci_bus, devfn, address[count], 0x00000000); + + } + } + } /* end of mem */ + } /* end of for */ + + /* Now need to see how much space the devices behind the bridge needed */ + amount_needed = scan_behind_bridge(func, sec_number); + if (amount_needed == NULL) + return -ENOMEM; + + ibmphp_pci_bus->number = func->busno; + debug("after coming back from scan_behind_bridge\n"); + debug("amount_needed->not_correct = %x\n", amount_needed->not_correct); + debug("amount_needed->io = %x\n", amount_needed->io); + debug("amount_needed->mem = %x\n", amount_needed->mem); + debug("amount_needed->pfmem = %x\n", amount_needed->pfmem); + + if (amount_needed->not_correct) { + debug("amount_needed is not correct\n"); + for (count = 0; address[count]; count++) { + /* for 2 BARs */ + if (bus_io[count]) { + ibmphp_remove_resource(bus_io[count]); + func->io[count] = NULL; + } else if (bus_pfmem[count]) { + ibmphp_remove_resource(bus_pfmem[count]); + func->pfmem[count] = NULL; + } else if (bus_mem[count]) { + ibmphp_remove_resource(bus_mem[count]); + func->mem[count] = NULL; + } + } + kfree(amount_needed); + return -ENODEV; + } + + if (!amount_needed->io) { + debug("it doesn't want IO?\n"); + flag_io = 1; + } else { + debug("it wants %x IO behind the bridge\n", amount_needed->io); + io = kzalloc(sizeof(*io), GFP_KERNEL); + + if (!io) { + retval = -ENOMEM; + goto error; + } + io->type = IO; + io->busno = func->busno; + io->devfunc = PCI_DEVFN(func->device, func->function); + io->len = amount_needed->io; + if (ibmphp_check_resource(io, 1) == 0) { + debug("were we able to add io\n"); + ibmphp_add_resource(io); + flag_io = 1; + } + } + + if (!amount_needed->mem) { + debug("it doesn't want n.e.memory?\n"); + flag_mem = 1; + } else { + debug("it wants %x memory behind the bridge\n", amount_needed->mem); + mem = kzalloc(sizeof(*mem), GFP_KERNEL); + if (!mem) { + retval = -ENOMEM; + goto error; + } + mem->type = MEM; + mem->busno = func->busno; + mem->devfunc = PCI_DEVFN(func->device, func->function); + mem->len = amount_needed->mem; + if (ibmphp_check_resource(mem, 1) == 0) { + ibmphp_add_resource(mem); + flag_mem = 1; + debug("were we able to add mem\n"); + } + } + + if (!amount_needed->pfmem) { + debug("it doesn't want n.e.pfmem mem?\n"); + flag_pfmem = 1; + } else { + debug("it wants %x pfmemory behind the bridge\n", amount_needed->pfmem); + pfmem = kzalloc(sizeof(*pfmem), GFP_KERNEL); + if (!pfmem) { + retval = -ENOMEM; + goto error; + } + pfmem->type = PFMEM; + pfmem->busno = func->busno; + pfmem->devfunc = PCI_DEVFN(func->device, func->function); + pfmem->len = amount_needed->pfmem; + pfmem->fromMem = 0; + if (ibmphp_check_resource(pfmem, 1) == 0) { + ibmphp_add_resource(pfmem); + flag_pfmem = 1; + } else { + mem_tmp = kzalloc(sizeof(*mem_tmp), GFP_KERNEL); + if (!mem_tmp) { + retval = -ENOMEM; + goto error; + } + mem_tmp->type = MEM; + mem_tmp->busno = pfmem->busno; + mem_tmp->devfunc = pfmem->devfunc; + mem_tmp->len = pfmem->len; + if (ibmphp_check_resource(mem_tmp, 1) == 0) { + ibmphp_add_resource(mem_tmp); + pfmem->fromMem = 1; + pfmem->rangeno = mem_tmp->rangeno; + ibmphp_add_pfmem_from_mem(pfmem); + flag_pfmem = 1; + } + } + } + + debug("b4 if (flag_io && flag_mem && flag_pfmem)\n"); + debug("flag_io = %x, flag_mem = %x, flag_pfmem = %x\n", flag_io, flag_mem, flag_pfmem); + + if (flag_io && flag_mem && flag_pfmem) { + /* If on bootup, there was a bridged card in this slot, + * then card was removed and ibmphp got unloaded and loaded + * back again, there's no way for us to remove the bus + * struct, so no need to kmalloc, can use existing node + */ + bus = ibmphp_find_res_bus(sec_number); + if (!bus) { + bus = kzalloc(sizeof(*bus), GFP_KERNEL); + if (!bus) { + retval = -ENOMEM; + goto error; + } + bus->busno = sec_number; + debug("b4 adding new bus\n"); + rc = add_new_bus(bus, io, mem, pfmem, func->busno); + } else if (!(bus->rangeIO) && !(bus->rangeMem) && !(bus->rangePFMem)) + rc = add_new_bus(bus, io, mem, pfmem, 0xFF); + else { + err("expected bus structure not empty?\n"); + retval = -EIO; + goto error; + } + if (rc) { + if (rc == -ENOMEM) { + ibmphp_remove_bus(bus, func->busno); + kfree(amount_needed); + return rc; + } + retval = rc; + goto error; + } + pci_bus_read_config_byte(ibmphp_pci_bus, devfn, PCI_IO_BASE, &io_base); + pci_bus_read_config_word(ibmphp_pci_bus, devfn, PCI_PREF_MEMORY_BASE, &pfmem_base); + + if ((io_base & PCI_IO_RANGE_TYPE_MASK) == PCI_IO_RANGE_TYPE_32) { + debug("io 32\n"); + need_io_upper = 1; + } + if ((pfmem_base & PCI_PREF_RANGE_TYPE_MASK) == PCI_PREF_RANGE_TYPE_64) { + debug("pfmem 64\n"); + need_pfmem_upper = 1; + } + + if (bus->noIORanges) { + pci_bus_write_config_byte(ibmphp_pci_bus, devfn, PCI_IO_BASE, 0x00 | bus->rangeIO->start >> 8); + pci_bus_write_config_byte(ibmphp_pci_bus, devfn, PCI_IO_LIMIT, 0x00 | bus->rangeIO->end >> 8); + + /* _______________This is for debugging purposes only ____________________ + pci_bus_read_config_byte(ibmphp_pci_bus, devfn, PCI_IO_BASE, &temp); + debug("io_base = %x\n", (temp & PCI_IO_RANGE_TYPE_MASK) << 8); + pci_bus_read_config_byte(ibmphp_pci_bus, devfn, PCI_IO_LIMIT, &temp); + debug("io_limit = %x\n", (temp & PCI_IO_RANGE_TYPE_MASK) << 8); + ________________________________________________________________________*/ + + if (need_io_upper) { /* since can't support n.e.ways */ + pci_bus_write_config_word(ibmphp_pci_bus, devfn, PCI_IO_BASE_UPPER16, 0x0000); + pci_bus_write_config_word(ibmphp_pci_bus, devfn, PCI_IO_LIMIT_UPPER16, 0x0000); + } + } else { + pci_bus_write_config_byte(ibmphp_pci_bus, devfn, PCI_IO_BASE, 0x00); + pci_bus_write_config_byte(ibmphp_pci_bus, devfn, PCI_IO_LIMIT, 0x00); + } + + if (bus->noMemRanges) { + pci_bus_write_config_word(ibmphp_pci_bus, devfn, PCI_MEMORY_BASE, 0x0000 | bus->rangeMem->start >> 16); + pci_bus_write_config_word(ibmphp_pci_bus, devfn, PCI_MEMORY_LIMIT, 0x0000 | bus->rangeMem->end >> 16); + + /* ____________________This is for debugging purposes only ________________________ + pci_bus_read_config_word(ibmphp_pci_bus, devfn, PCI_MEMORY_BASE, &temp); + debug("mem_base = %x\n", (temp & PCI_MEMORY_RANGE_TYPE_MASK) << 16); + pci_bus_read_config_word(ibmphp_pci_bus, devfn, PCI_MEMORY_LIMIT, &temp); + debug("mem_limit = %x\n", (temp & PCI_MEMORY_RANGE_TYPE_MASK) << 16); + __________________________________________________________________________________*/ + + } else { + pci_bus_write_config_word(ibmphp_pci_bus, devfn, PCI_MEMORY_BASE, 0xffff); + pci_bus_write_config_word(ibmphp_pci_bus, devfn, PCI_MEMORY_LIMIT, 0x0000); + } + if (bus->noPFMemRanges) { + pci_bus_write_config_word(ibmphp_pci_bus, devfn, PCI_PREF_MEMORY_BASE, 0x0000 | bus->rangePFMem->start >> 16); + pci_bus_write_config_word(ibmphp_pci_bus, devfn, PCI_PREF_MEMORY_LIMIT, 0x0000 | bus->rangePFMem->end >> 16); + + /* __________________________This is for debugging purposes only _______________________ + pci_bus_read_config_word(ibmphp_pci_bus, devfn, PCI_PREF_MEMORY_BASE, &temp); + debug("pfmem_base = %x", (temp & PCI_MEMORY_RANGE_TYPE_MASK) << 16); + pci_bus_read_config_word(ibmphp_pci_bus, devfn, PCI_PREF_MEMORY_LIMIT, &temp); + debug("pfmem_limit = %x\n", (temp & PCI_MEMORY_RANGE_TYPE_MASK) << 16); + ______________________________________________________________________________________*/ + + if (need_pfmem_upper) { /* since can't support n.e.ways */ + pci_bus_write_config_dword(ibmphp_pci_bus, devfn, PCI_PREF_BASE_UPPER32, 0x00000000); + pci_bus_write_config_dword(ibmphp_pci_bus, devfn, PCI_PREF_LIMIT_UPPER32, 0x00000000); + } + } else { + pci_bus_write_config_word(ibmphp_pci_bus, devfn, PCI_PREF_MEMORY_BASE, 0xffff); + pci_bus_write_config_word(ibmphp_pci_bus, devfn, PCI_PREF_MEMORY_LIMIT, 0x0000); + } + + debug("b4 writing control information\n"); + + pci_bus_read_config_byte(ibmphp_pci_bus, devfn, PCI_INTERRUPT_PIN, &irq); + if ((irq > 0x00) && (irq < 0x05)) + pci_bus_write_config_byte(ibmphp_pci_bus, devfn, PCI_INTERRUPT_LINE, func->irq[irq - 1]); + /* + pci_bus_write_config_byte(ibmphp_pci_bus, devfn, PCI_BRIDGE_CONTROL, ctrl); + pci_bus_write_config_byte(ibmphp_pci_bus, devfn, PCI_BRIDGE_CONTROL, PCI_BRIDGE_CTL_PARITY); + pci_bus_write_config_byte(ibmphp_pci_bus, devfn, PCI_BRIDGE_CONTROL, PCI_BRIDGE_CTL_SERR); + */ + + pci_bus_write_config_word(ibmphp_pci_bus, devfn, PCI_COMMAND, DEVICEENABLE); + pci_bus_write_config_word(ibmphp_pci_bus, devfn, PCI_BRIDGE_CONTROL, 0x07); + for (i = 0; i < 32; i++) { + if (amount_needed->devices[i]) { + debug("device where devices[i] is 1 = %x\n", i); + func->devices[i] = 1; + } + } + func->bus = 1; /* For unconfiguring, to indicate it's PPB */ + func_passed = &func; + debug("func->busno b4 returning is %x\n", func->busno); + debug("func->busno b4 returning in the other structure is %x\n", (*func_passed)->busno); + kfree(amount_needed); + return 0; + } else { + err("Configuring bridge was unsuccessful...\n"); + mem_tmp = NULL; + retval = -EIO; + goto error; + } + +error: + kfree(amount_needed); + if (pfmem) + ibmphp_remove_resource(pfmem); + if (io) + ibmphp_remove_resource(io); + if (mem) + ibmphp_remove_resource(mem); + for (i = 0; i < 2; i++) { /* for 2 BARs */ + if (bus_io[i]) { + ibmphp_remove_resource(bus_io[i]); + func->io[i] = NULL; + } else if (bus_pfmem[i]) { + ibmphp_remove_resource(bus_pfmem[i]); + func->pfmem[i] = NULL; + } else if (bus_mem[i]) { + ibmphp_remove_resource(bus_mem[i]); + func->mem[i] = NULL; + } + } + return retval; +} + +/***************************************************************************** + * This function adds up the amount of resources needed behind the PPB bridge + * and passes it to the configure_bridge function + * Input: bridge function + * Output: amount of resources needed + *****************************************************************************/ +static struct res_needed *scan_behind_bridge(struct pci_func *func, u8 busno) +{ + int count, len[6]; + u16 vendor_id; + u8 hdr_type; + u8 device, function; + unsigned int devfn; + int howmany = 0; /*this is to see if there are any devices behind the bridge */ + + u32 bar[6], class; + u32 address[] = { + PCI_BASE_ADDRESS_0, + PCI_BASE_ADDRESS_1, + PCI_BASE_ADDRESS_2, + PCI_BASE_ADDRESS_3, + PCI_BASE_ADDRESS_4, + PCI_BASE_ADDRESS_5, + 0 + }; + struct res_needed *amount; + + amount = kzalloc(sizeof(*amount), GFP_KERNEL); + if (amount == NULL) + return NULL; + + ibmphp_pci_bus->number = busno; + + debug("the bus_no behind the bridge is %x\n", busno); + debug("scanning devices behind the bridge...\n"); + for (device = 0; device < 32; device++) { + amount->devices[device] = 0; + for (function = 0; function < 8; function++) { + devfn = PCI_DEVFN(device, function); + + pci_bus_read_config_word(ibmphp_pci_bus, devfn, PCI_VENDOR_ID, &vendor_id); + + if (vendor_id != PCI_VENDOR_ID_NOTVALID) { + /* found correct device!!! */ + howmany++; + + pci_bus_read_config_byte(ibmphp_pci_bus, devfn, PCI_HEADER_TYPE, &hdr_type); + pci_bus_read_config_dword(ibmphp_pci_bus, devfn, PCI_CLASS_REVISION, &class); + + debug("hdr_type behind the bridge is %x\n", hdr_type); + if ((hdr_type & 0x7f) == PCI_HEADER_TYPE_BRIDGE) { + err("embedded bridges not supported for hot-plugging.\n"); + amount->not_correct = 1; + return amount; + } + + class >>= 8; /* to take revision out, class = class.subclass.prog i/f */ + if (class == PCI_CLASS_NOT_DEFINED_VGA) { + err("The device %x is VGA compatible and as is not supported for hot plugging. Please choose another device.\n", device); + amount->not_correct = 1; + return amount; + } else if (class == PCI_CLASS_DISPLAY_VGA) { + err("The device %x is not supported for hot plugging. Please choose another device.\n", device); + amount->not_correct = 1; + return amount; + } + + amount->devices[device] = 1; + + for (count = 0; address[count]; count++) { + /* for 6 BARs */ + /* + pci_bus_read_config_byte(ibmphp_pci_bus, devfn, address[count], &tmp); + if (tmp & 0x01) // IO + pci_bus_write_config_dword(ibmphp_pci_bus, devfn, address[count], 0xFFFFFFFD); + else // MEMORY + pci_bus_write_config_dword(ibmphp_pci_bus, devfn, address[count], 0xFFFFFFFF); + */ + pci_bus_write_config_dword(ibmphp_pci_bus, devfn, address[count], 0xFFFFFFFF); + pci_bus_read_config_dword(ibmphp_pci_bus, devfn, address[count], &bar[count]); + + debug("what is bar[count]? %x, count = %d\n", bar[count], count); + + if (!bar[count]) /* This BAR is not implemented */ + continue; + + //tmp_bar = bar[count]; + + debug("count %d device %x function %x wants %x resources\n", count, device, function, bar[count]); + + if (bar[count] & PCI_BASE_ADDRESS_SPACE_IO) { + /* This is IO */ + len[count] = bar[count] & 0xFFFFFFFC; + len[count] = ~len[count] + 1; + amount->io += len[count]; + } else { + /* This is Memory */ + if (bar[count] & PCI_BASE_ADDRESS_MEM_PREFETCH) { + /* pfmem */ + len[count] = bar[count] & 0xFFFFFFF0; + len[count] = ~len[count] + 1; + amount->pfmem += len[count]; + if (bar[count] & PCI_BASE_ADDRESS_MEM_TYPE_64) + /* takes up another dword */ + count += 1; + + } else { + /* regular memory */ + len[count] = bar[count] & 0xFFFFFFF0; + len[count] = ~len[count] + 1; + amount->mem += len[count]; + if (bar[count] & PCI_BASE_ADDRESS_MEM_TYPE_64) { + /* takes up another dword */ + count += 1; + } + } + } + } /* end for */ + } /* end if (valid) */ + } /* end for */ + } /* end for */ + + if (!howmany) + amount->not_correct = 1; + else + amount->not_correct = 0; + if ((amount->io) && (amount->io < IOBRIDGE)) + amount->io = IOBRIDGE; + if ((amount->mem) && (amount->mem < MEMBRIDGE)) + amount->mem = MEMBRIDGE; + if ((amount->pfmem) && (amount->pfmem < MEMBRIDGE)) + amount->pfmem = MEMBRIDGE; + return amount; +} + +/* The following 3 unconfigure_boot_ routines deal with the case when we had the card + * upon bootup in the system, since we don't allocate func to such case, we need to read + * the start addresses from pci config space and then find the corresponding entries in + * our resource lists. The functions return either 0, -ENODEV, or -1 (general failure) + * Change: we also call these functions even if we configured the card ourselves (i.e., not + * the bootup case), since it should work same way + */ +static int unconfigure_boot_device(u8 busno, u8 device, u8 function) +{ + u32 start_address; + u32 address[] = { + PCI_BASE_ADDRESS_0, + PCI_BASE_ADDRESS_1, + PCI_BASE_ADDRESS_2, + PCI_BASE_ADDRESS_3, + PCI_BASE_ADDRESS_4, + PCI_BASE_ADDRESS_5, + 0 + }; + int count; + struct resource_node *io; + struct resource_node *mem; + struct resource_node *pfmem; + struct bus_node *bus; + u32 end_address; + u32 temp_end; + u32 size; + u32 tmp_address; + unsigned int devfn; + + debug("%s - enter\n", __func__); + + bus = ibmphp_find_res_bus(busno); + if (!bus) { + debug("cannot find corresponding bus.\n"); + return -EINVAL; + } + + devfn = PCI_DEVFN(device, function); + ibmphp_pci_bus->number = busno; + for (count = 0; address[count]; count++) { /* for 6 BARs */ + pci_bus_read_config_dword(ibmphp_pci_bus, devfn, address[count], &start_address); + + /* We can do this here, b/c by that time the device driver of the card has been stopped */ + + pci_bus_write_config_dword(ibmphp_pci_bus, devfn, address[count], 0xFFFFFFFF); + pci_bus_read_config_dword(ibmphp_pci_bus, devfn, address[count], &size); + pci_bus_write_config_dword(ibmphp_pci_bus, devfn, address[count], start_address); + + debug("start_address is %x\n", start_address); + debug("busno, device, function %x %x %x\n", busno, device, function); + if (!size) { + /* This BAR is not implemented */ + debug("is this bar no implemented?, count = %d\n", count); + continue; + } + tmp_address = start_address; + if (start_address & PCI_BASE_ADDRESS_SPACE_IO) { + /* This is IO */ + start_address &= PCI_BASE_ADDRESS_IO_MASK; + size = size & 0xFFFFFFFC; + size = ~size + 1; + end_address = start_address + size - 1; + if (ibmphp_find_resource(bus, start_address, &io, IO)) + goto report_search_failure; + + 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)) + goto report_search_failure; + + debug("io->start = %x\n", io->start); + temp_end = io->end; + start_address = io->end + 1; + ibmphp_remove_resource(io); + } + + /* ????????? DO WE NEED TO WRITE ANYTHING INTO THE PCI CONFIG SPACE BACK ?????????? */ + } else { + /* This is Memory */ + if (start_address & PCI_BASE_ADDRESS_MEM_PREFETCH) { + /* pfmem */ + debug("start address of pfmem is %x\n", start_address); + start_address &= PCI_BASE_ADDRESS_MEM_MASK; + + if (ibmphp_find_resource(bus, start_address, &pfmem, PFMEM) < 0) { + err("cannot find corresponding PFMEM resource to remove\n"); + return -EIO; + } + if (pfmem) { + debug("pfmem->start = %x\n", pfmem->start); + + ibmphp_remove_resource(pfmem); + } + } else { + /* regular memory */ + debug("start address of mem is %x\n", start_address); + start_address &= PCI_BASE_ADDRESS_MEM_MASK; + + if (ibmphp_find_resource(bus, start_address, &mem, MEM) < 0) { + err("cannot find corresponding MEM resource to remove\n"); + return -EIO; + } + if (mem) { + debug("mem->start = %x\n", mem->start); + + ibmphp_remove_resource(mem); + } + } + if (tmp_address & PCI_BASE_ADDRESS_MEM_TYPE_64) { + /* takes up another dword */ + count += 1; + } + } /* end of mem */ + } /* end of for */ + + return 0; + +report_search_failure: + err("cannot find corresponding IO resource to remove\n"); + return -EIO; +} + +static int unconfigure_boot_bridge(u8 busno, u8 device, u8 function) +{ + int count; + int bus_no, pri_no, sub_no, sec_no = 0; + u32 start_address, tmp_address; + u8 sec_number, sub_number, pri_number; + struct resource_node *io = NULL; + struct resource_node *mem = NULL; + struct resource_node *pfmem = NULL; + struct bus_node *bus; + u32 address[] = { + PCI_BASE_ADDRESS_0, + PCI_BASE_ADDRESS_1, + 0 + }; + unsigned int devfn; + + devfn = PCI_DEVFN(device, function); + ibmphp_pci_bus->number = busno; + bus_no = (int) busno; + debug("busno is %x\n", busno); + pci_bus_read_config_byte(ibmphp_pci_bus, devfn, PCI_PRIMARY_BUS, &pri_number); + debug("%s - busno = %x, primary_number = %x\n", __func__, busno, pri_number); + + pci_bus_read_config_byte(ibmphp_pci_bus, devfn, PCI_SECONDARY_BUS, &sec_number); + debug("sec_number is %x\n", sec_number); + sec_no = (int) sec_number; + pri_no = (int) pri_number; + if (pri_no != bus_no) { + err("primary numbers in our structures and pci config space don't match.\n"); + return -EINVAL; + } + + pci_bus_read_config_byte(ibmphp_pci_bus, devfn, PCI_SUBORDINATE_BUS, &sub_number); + sub_no = (int) sub_number; + debug("sub_no is %d, sec_no is %d\n", sub_no, sec_no); + if (sec_no != sub_number) { + err("there're more buses behind this bridge. Hot removal is not supported. Please choose another card\n"); + return -ENODEV; + } + + bus = ibmphp_find_res_bus(sec_number); + if (!bus) { + err("cannot find Bus structure for the bridged device\n"); + return -EINVAL; + } + debug("bus->busno is %x\n", bus->busno); + debug("sec_number is %x\n", sec_number); + + ibmphp_remove_bus(bus, busno); + + for (count = 0; address[count]; count++) { + /* for 2 BARs */ + pci_bus_read_config_dword(ibmphp_pci_bus, devfn, address[count], &start_address); + + if (!start_address) { + /* This BAR is not implemented */ + continue; + } + + tmp_address = start_address; + + if (start_address & PCI_BASE_ADDRESS_SPACE_IO) { + /* This is IO */ + start_address &= PCI_BASE_ADDRESS_IO_MASK; + if (ibmphp_find_resource(bus, start_address, &io, IO) < 0) { + err("cannot find corresponding IO resource to remove\n"); + return -EIO; + } + if (io) + debug("io->start = %x\n", io->start); + + ibmphp_remove_resource(io); + + /* ????????? DO WE NEED TO WRITE ANYTHING INTO THE PCI CONFIG SPACE BACK ?????????? */ + } else { + /* This is Memory */ + if (start_address & PCI_BASE_ADDRESS_MEM_PREFETCH) { + /* pfmem */ + start_address &= PCI_BASE_ADDRESS_MEM_MASK; + if (ibmphp_find_resource(bus, start_address, &pfmem, PFMEM) < 0) { + err("cannot find corresponding PFMEM resource to remove\n"); + return -EINVAL; + } + if (pfmem) { + debug("pfmem->start = %x\n", pfmem->start); + + ibmphp_remove_resource(pfmem); + } + } else { + /* regular memory */ + start_address &= PCI_BASE_ADDRESS_MEM_MASK; + if (ibmphp_find_resource(bus, start_address, &mem, MEM) < 0) { + err("cannot find corresponding MEM resource to remove\n"); + return -EINVAL; + } + if (mem) { + debug("mem->start = %x\n", mem->start); + + ibmphp_remove_resource(mem); + } + } + if (tmp_address & PCI_BASE_ADDRESS_MEM_TYPE_64) { + /* takes up another dword */ + count += 1; + } + } /* end of mem */ + } /* end of for */ + debug("%s - exiting, returning success\n", __func__); + return 0; +} + +static int unconfigure_boot_card(struct slot *slot_cur) +{ + u16 vendor_id; + u32 class; + u8 hdr_type; + u8 device; + u8 busno; + u8 function; + int rc; + unsigned int devfn; + u8 valid_device = 0x00; /* To see if we are ever able to find valid device and read it */ + + debug("%s - enter\n", __func__); + + device = slot_cur->device; + busno = slot_cur->bus; + + debug("b4 for loop, device is %x\n", device); + /* For every function on the card */ + for (function = 0x0; function < 0x08; function++) { + devfn = PCI_DEVFN(device, function); + ibmphp_pci_bus->number = busno; + + pci_bus_read_config_word(ibmphp_pci_bus, devfn, PCI_VENDOR_ID, &vendor_id); + + if (vendor_id != PCI_VENDOR_ID_NOTVALID) { + /* found correct device!!! */ + ++valid_device; + + debug("%s - found correct device\n", __func__); + + /* header: x x x x x x x x + * | |___________|=> 1=PPB bridge, 0=normal device, 2=CardBus Bridge + * |_=> 0 = single function device, 1 = multi-function device + */ + + pci_bus_read_config_byte(ibmphp_pci_bus, devfn, PCI_HEADER_TYPE, &hdr_type); + pci_bus_read_config_dword(ibmphp_pci_bus, devfn, PCI_CLASS_REVISION, &class); + + debug("hdr_type %x, class %x\n", hdr_type, class); + class >>= 8; /* to take revision out, class = class.subclass.prog i/f */ + if (class == PCI_CLASS_NOT_DEFINED_VGA) { + err("The device %x function %x is VGA compatible and is not supported for hot removing. Please choose another device.\n", device, function); + return -ENODEV; + } else if (class == PCI_CLASS_DISPLAY_VGA) { + err("The device %x function %x is not supported for hot removing. Please choose another device.\n", device, function); + return -ENODEV; + } + + switch (hdr_type) { + case PCI_HEADER_TYPE_NORMAL: + rc = unconfigure_boot_device(busno, device, function); + if (rc) { + err("was not able to unconfigure device %x func %x on bus %x. bailing out...\n", + device, function, busno); + return rc; + } + function = 0x8; + break; + case PCI_HEADER_TYPE_MULTIDEVICE: + rc = unconfigure_boot_device(busno, device, function); + if (rc) { + err("was not able to unconfigure device %x func %x on bus %x. bailing out...\n", + device, function, busno); + return rc; + } + break; + case PCI_HEADER_TYPE_BRIDGE: + class >>= 8; + if (class != PCI_CLASS_BRIDGE_PCI) { + err("This device %x function %x is not PCI-to-PCI bridge, and is not supported for hot-removing. Please try another card.\n", device, function); + return -ENODEV; + } + rc = unconfigure_boot_bridge(busno, device, function); + if (rc != 0) { + err("was not able to hot-remove PPB properly.\n"); + return rc; + } + + function = 0x8; + break; + case PCI_HEADER_TYPE_MULTIBRIDGE: + class >>= 8; + if (class != PCI_CLASS_BRIDGE_PCI) { + err("This device %x function %x is not PCI-to-PCI bridge, and is not supported for hot-removing. Please try another card.\n", device, function); + return -ENODEV; + } + rc = unconfigure_boot_bridge(busno, device, function); + if (rc != 0) { + err("was not able to hot-remove PPB properly.\n"); + return rc; + } + break; + default: + err("MAJOR PROBLEM!!!! Cannot read device's header\n"); + return -1; + } /* end of switch */ + } /* end of valid device */ + } /* end of for */ + + if (!valid_device) { + err("Could not find device to unconfigure. Or could not read the card.\n"); + return -1; + } + return 0; +} + +/* + * free the resources of the card (multi, single, or bridged) + * Parameters: slot, flag to say if this is for removing entire module or just + * unconfiguring the device + * TO DO: will probably need to add some code in case there was some resource, + * to remove it... this is from when we have errors in the configure_card... + * !!!!!!!!!!!!!!!!!!!!!!!!!FOR BUSES!!!!!!!!!!!! + * Returns: 0, -1, -ENODEV + */ +int ibmphp_unconfigure_card(struct slot **slot_cur, int the_end) +{ + int i; + int count; + int rc; + struct slot *sl = *slot_cur; + struct pci_func *cur_func = NULL; + struct pci_func *temp_func; + + debug("%s - enter\n", __func__); + + if (!the_end) { + /* Need to unconfigure the card */ + rc = unconfigure_boot_card(sl); + if ((rc == -ENODEV) || (rc == -EIO) || (rc == -EINVAL)) { + /* In all other cases, will still need to get rid of func structure if it exists */ + return rc; + } + } + + if (sl->func) { + cur_func = sl->func; + while (cur_func) { + /* TO DO: WILL MOST LIKELY NEED TO GET RID OF THE BUS STRUCTURE FROM RESOURCES AS WELL */ + if (cur_func->bus) { + /* in other words, it's a PPB */ + count = 2; + } else { + count = 6; + } + + for (i = 0; i < count; i++) { + if (cur_func->io[i]) { + debug("io[%d] exists\n", i); + if (the_end > 0) + ibmphp_remove_resource(cur_func->io[i]); + cur_func->io[i] = NULL; + } + if (cur_func->mem[i]) { + debug("mem[%d] exists\n", i); + if (the_end > 0) + ibmphp_remove_resource(cur_func->mem[i]); + cur_func->mem[i] = NULL; + } + if (cur_func->pfmem[i]) { + debug("pfmem[%d] exists\n", i); + if (the_end > 0) + ibmphp_remove_resource(cur_func->pfmem[i]); + cur_func->pfmem[i] = NULL; + } + } + + temp_func = cur_func->next; + kfree(cur_func); + cur_func = temp_func; + } + } + + sl->func = NULL; + *slot_cur = sl; + debug("%s - exit\n", __func__); + return 0; +} + +/* + * add a new bus resulting from hot-plugging a PPB bridge with devices + * + * Input: bus and the amount of resources needed (we know we can assign those, + * since they've been checked already + * Output: bus added to the correct spot + * 0, -1, error + */ +static int add_new_bus(struct bus_node *bus, struct resource_node *io, struct resource_node *mem, struct resource_node *pfmem, u8 parent_busno) +{ + struct range_node *io_range = NULL; + struct range_node *mem_range = NULL; + struct range_node *pfmem_range = NULL; + struct bus_node *cur_bus = NULL; + + /* Trying to find the parent bus number */ + if (parent_busno != 0xFF) { + cur_bus = ibmphp_find_res_bus(parent_busno); + if (!cur_bus) { + err("strange, cannot find bus which is supposed to be at the system... something is terribly wrong...\n"); + return -ENODEV; + } + + list_add(&bus->bus_list, &cur_bus->bus_list); + } + if (io) { + io_range = kzalloc(sizeof(*io_range), GFP_KERNEL); + if (!io_range) + return -ENOMEM; + + io_range->start = io->start; + io_range->end = io->end; + io_range->rangeno = 1; + bus->noIORanges = 1; + bus->rangeIO = io_range; + } + if (mem) { + mem_range = kzalloc(sizeof(*mem_range), GFP_KERNEL); + if (!mem_range) + return -ENOMEM; + + mem_range->start = mem->start; + mem_range->end = mem->end; + mem_range->rangeno = 1; + bus->noMemRanges = 1; + bus->rangeMem = mem_range; + } + if (pfmem) { + pfmem_range = kzalloc(sizeof(*pfmem_range), GFP_KERNEL); + if (!pfmem_range) + return -ENOMEM; + + pfmem_range->start = pfmem->start; + pfmem_range->end = pfmem->end; + pfmem_range->rangeno = 1; + bus->noPFMemRanges = 1; + bus->rangePFMem = pfmem_range; + } + return 0; +} + +/* + * find the 1st available bus number for PPB to set as its secondary bus + * Parameters: bus_number of the primary bus + * Returns: bus_number of the secondary bus or 0xff in case of failure + */ +static u8 find_sec_number(u8 primary_busno, u8 slotno) +{ + int min, max; + u8 busno; + struct bus_info *bus; + struct bus_node *bus_cur; + + bus = ibmphp_find_same_bus_num(primary_busno); + if (!bus) { + err("cannot get slot range of the bus from the BIOS\n"); + return 0xff; + } + max = bus->slot_max; + min = bus->slot_min; + if ((slotno > max) || (slotno < min)) { + err("got the wrong range\n"); + return 0xff; + } + busno = (u8) (slotno - (u8) min); + busno += primary_busno + 0x01; + bus_cur = ibmphp_find_res_bus(busno); + /* either there is no such bus number, or there are no ranges, which + * can only happen if we removed the bridged device in previous load + * of the driver, and now only have the skeleton bus struct + */ + if ((!bus_cur) || (!(bus_cur->rangeIO) && !(bus_cur->rangeMem) && !(bus_cur->rangePFMem))) + return busno; + return 0xff; +} diff --git a/drivers/pci/hotplug/ibmphp_res.c b/drivers/pci/hotplug/ibmphp_res.c new file mode 100644 index 000000000..4a72ade2c --- /dev/null +++ b/drivers/pci/hotplug/ibmphp_res.c @@ -0,0 +1,2118 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * 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. + * + * 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_bus_range(int type, struct range_node *, struct bus_node *); +static void fix_resources(struct bus_node *); +static struct bus_node *find_bus_wprev(u8, struct bus_node **, u8); + +static LIST_HEAD(gbuses); + +static struct bus_node * __init alloc_error_bus(struct ebda_pci_rsrc *curr, u8 busno, int flag) +{ + struct bus_node *newbus; + + if (!(curr) && !(flag)) { + err("NULL pointer passed\n"); + return NULL; + } + + newbus = kzalloc(sizeof(struct bus_node), GFP_KERNEL); + if (!newbus) + return NULL; + + if (flag) + newbus->busno = busno; + else + newbus->busno = curr->bus_num; + list_add_tail(&newbus->bus_list, &gbuses); + return newbus; +} + +static struct resource_node * __init alloc_resources(struct ebda_pci_rsrc *curr) +{ + struct resource_node *rs; + + if (!curr) { + err("NULL passed to allocate\n"); + return NULL; + } + + rs = kzalloc(sizeof(struct resource_node), GFP_KERNEL); + if (!rs) + return NULL; + + rs->busno = curr->bus_num; + rs->devfunc = curr->dev_fun; + rs->start = curr->start_addr; + rs->end = curr->end_addr; + rs->len = curr->end_addr - curr->start_addr + 1; + return rs; +} + +static int __init alloc_bus_range(struct bus_node **new_bus, struct range_node **new_range, struct ebda_pci_rsrc *curr, int flag, u8 first_bus) +{ + struct bus_node *newbus; + struct range_node *newrange; + u8 num_ranges = 0; + + if (first_bus) { + newbus = kzalloc(sizeof(struct bus_node), GFP_KERNEL); + if (!newbus) + return -ENOMEM; + + newbus->busno = curr->bus_num; + } else { + newbus = *new_bus; + switch (flag) { + case MEM: + num_ranges = newbus->noMemRanges; + break; + case PFMEM: + num_ranges = newbus->noPFMemRanges; + break; + case IO: + num_ranges = newbus->noIORanges; + break; + } + } + + newrange = kzalloc(sizeof(struct range_node), GFP_KERNEL); + if (!newrange) { + if (first_bus) + kfree(newbus); + return -ENOMEM; + } + newrange->start = curr->start_addr; + newrange->end = curr->end_addr; + + if (first_bus || (!num_ranges)) + newrange->rangeno = 1; + else { + /* need to insert our range */ + add_bus_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 resource_node *new_io = NULL; + struct resource_node *new_mem = NULL; + struct resource_node *new_pfmem = NULL; + int rc; + + list_for_each_entry(curr, &ibmphp_ebda_pci_rsrc_head, + 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)) { + rc = alloc_bus_range(&newbus, &newrange, curr, MEM, 1); + if (rc) + 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 */ + rc = alloc_bus_range(&newbus, &newrange, curr, MEM, 1); + if (rc) + 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 */ + rc = alloc_bus_range(&newbus, &newrange, curr, PFMEM, 1); + if (rc) + 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 */ + rc = alloc_bus_range(&newbus, &newrange, curr, PFMEM, 1); + if (rc) + 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 */ + rc = alloc_bus_range(&newbus, &newrange, curr, IO, 1); + if (rc) + 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 */ + rc = alloc_bus_range(&newbus, &newrange, curr, IO, 1); + if (rc) + return rc; + list_add_tail(&newbus->bus_list, &gbuses); + debug("1st Bus, IO Primary Bus %x [%x - %x]\n", newbus->busno, newrange->start, newrange->end); + } + } + + } else { + ; /* type is reserved WHAT TO DO IN THIS CASE??? + NOTHING TO DO??? */ + } + } else { + /* regular pci device resource */ + if ((curr->rsrc_type & RESTYPE) == MMASK) { + /* Memory resource */ + new_mem = alloc_resources(curr); + if (!new_mem) + return -ENOMEM; + new_mem->type = MEM; + /* + * if it didn't find the bus, means PCI dev + * came b4 the Primary Bus info, so need to + * create a bus rangeno becomes a problem... + * assign a -1 and then update once the range + * actually appears... + */ + if (ibmphp_add_resource(new_mem) < 0) { + newbus = alloc_error_bus(curr, 0, 0); + if (!newbus) + return -ENOMEM; + newbus->firstMem = new_mem; + ++newbus->needMemUpdate; + new_mem->rangeno = -1; + } + debug("Memory resource for device %x, bus %x, [%x - %x]\n", new_mem->devfunc, new_mem->busno, new_mem->start, new_mem->end); + + } else if ((curr->rsrc_type & RESTYPE) == PFMASK) { + /* PFMemory resource */ + new_pfmem = alloc_resources(curr); + if (!new_pfmem) + return -ENOMEM; + new_pfmem->type = PFMEM; + new_pfmem->fromMem = 0; + if (ibmphp_add_resource(new_pfmem) < 0) { + newbus = alloc_error_bus(curr, 0, 0); + if (!newbus) + return -ENOMEM; + newbus->firstPFMem = new_pfmem; + ++newbus->needPFMemUpdate; + new_pfmem->rangeno = -1; + } + + debug("PFMemory resource for device %x, bus %x, [%x - %x]\n", new_pfmem->devfunc, new_pfmem->busno, new_pfmem->start, new_pfmem->end); + } else if ((curr->rsrc_type & RESTYPE) == IOMASK) { + /* IO resource */ + new_io = alloc_resources(curr); + if (!new_io) + return -ENOMEM; + new_io->type = IO; + + /* + * if it didn't find the bus, means PCI dev + * came b4 the Primary Bus info, so need to + * create a bus rangeno becomes a problem... + * Can assign a -1 and then update once the + * range actually appears... + */ + if (ibmphp_add_resource(new_io) < 0) { + newbus = alloc_error_bus(curr, 0, 0); + if (!newbus) + return -ENOMEM; + newbus->firstIO = new_io; + ++newbus->needIOUpdate; + new_io->rangeno = -1; + } + debug("IO resource for device %x, bus %x, [%x - %x]\n", new_io->devfunc, new_io->busno, new_io->start, new_io->end); + } + } + } + + list_for_each_entry(bus_cur, &gbuses, 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; + } + return once_over(); /* This is to align ranges (so no -1) */ +} + +/******************************************************************************** + * 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_bus_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_bus_range fnc + * + * Input: bus, type of the resource, the rangeno starting from which to update + ******************************************************************************/ +static void update_resources(struct bus_node *bus_cur, int type, int rangeno) +{ + struct resource_node *res = NULL; + u8 eol = 0; /* end of list indicator */ + + switch (type) { + case MEM: + if (bus_cur->firstMem) + res = bus_cur->firstMem; + break; + case PFMEM: + if (bus_cur->firstPFMem) + res = bus_cur->firstPFMem; + break; + case IO: + if (bus_cur->firstIO) + res = bus_cur->firstIO; + break; + } + + if (res) { + while (res) { + if (res->rangeno == rangeno) + break; + if (res->next) + res = res->next; + else if (res->nextRange) + res = res->nextRange; + else { + eol = 1; + break; + } + } + + if (!eol) { + /* found the range */ + while (res) { + ++res->rangeno; + res = res->next; + } + } + } +} + +static void fix_me(struct resource_node *res, struct bus_node *bus_cur, struct range_node *range) +{ + char *str = ""; + switch (res->type) { + case IO: + str = "io"; + break; + case MEM: + str = "mem"; + break; + case PFMEM: + str = "pfmem"; + break; + } + + while (res) { + if (res->rangeno == -1) { + while (range) { + if ((res->start >= range->start) && (res->end <= range->end)) { + res->rangeno = range->rangeno; + debug("%s->rangeno in fix_resources is %d\n", str, res->rangeno); + switch (res->type) { + case IO: + --bus_cur->needIOUpdate; + break; + case MEM: + --bus_cur->needMemUpdate; + break; + case PFMEM: + --bus_cur->needPFMemUpdate; + break; + } + break; + } + range = range->next; + } + } + if (res->next) + res = res->next; + else + res = res->nextRange; + } + +} + +/***************************************************************************** + * This routine reassigns the range numbers to the resources that had a -1 + * This case can happen only if upon initialization, resources taken by pci dev + * appear in EBDA before the resources allocated for that bus, since we don't + * know the range, we assign -1, and this routine is called after a new range + * is assigned to see the resources with unknown range belong to the added range + * + * Input: current bus + * Output: none, list of resources for that bus are fixed if can be + *******************************************************************************/ +static void fix_resources(struct bus_node *bus_cur) +{ + struct range_node *range; + struct resource_node *res; + + debug("%s - bus_cur->busno = %d\n", __func__, bus_cur->busno); + + if (bus_cur->needIOUpdate) { + res = bus_cur->firstIO; + range = bus_cur->rangeIO; + fix_me(res, bus_cur, range); + } + if (bus_cur->needMemUpdate) { + res = bus_cur->firstMem; + range = bus_cur->rangeMem; + fix_me(res, bus_cur, range); + } + if (bus_cur->needPFMemUpdate) { + res = bus_cur->firstPFMem; + range = bus_cur->rangePFMem; + fix_me(res, bus_cur, range); + } +} + +/******************************************************************************* + * This routine adds a resource to the list of resources to the appropriate bus + * based on their resource type and sorted by their starting addresses. It assigns + * the ptrs to next and nextRange if needed. + * + * Input: resource ptr + * Output: ptrs assigned (to the node) + * 0 or -1 + *******************************************************************************/ +int ibmphp_add_resource(struct resource_node *res) +{ + struct resource_node *res_cur; + struct resource_node *res_prev; + struct bus_node *bus_cur; + struct range_node *range_cur = NULL; + struct resource_node *res_start = NULL; + + debug("%s - enter\n", __func__); + + if (!res) { + err("NULL passed to add\n"); + return -ENODEV; + } + + bus_cur = find_bus_wprev(res->busno, NULL, 0); + + if (!bus_cur) { + /* didn't find a bus, something's wrong!!! */ + debug("no bus in the system, either pci_dev's wrong or allocation failed\n"); + return -ENODEV; + } + + /* Normal case */ + switch (res->type) { + case IO: + range_cur = bus_cur->rangeIO; + res_start = bus_cur->firstIO; + break; + case MEM: + range_cur = bus_cur->rangeMem; + res_start = bus_cur->firstMem; + break; + case PFMEM: + range_cur = bus_cur->rangePFMem; + res_start = bus_cur->firstPFMem; + break; + default: + err("cannot read the type of the resource to add... problem\n"); + return -EINVAL; + } + while (range_cur) { + if ((res->start >= range_cur->start) && (res->end <= range_cur->end)) { + res->rangeno = range_cur->rangeno; + break; + } + range_cur = range_cur->next; + } + + /* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + * this is again the case of rangeno = -1 + * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + */ + + if (!range_cur) { + switch (res->type) { + case IO: + ++bus_cur->needIOUpdate; + break; + case MEM: + ++bus_cur->needMemUpdate; + break; + case PFMEM: + ++bus_cur->needPFMemUpdate; + break; + } + res->rangeno = -1; + } + + debug("The range is %d\n", res->rangeno); + if (!res_start) { + /* no first{IO,Mem,Pfmem} on the bus, 1st IO/Mem/Pfmem resource ever */ + switch (res->type) { + case IO: + bus_cur->firstIO = res; + break; + case MEM: + bus_cur->firstMem = res; + break; + case PFMEM: + bus_cur->firstPFMem = res; + break; + } + res->next = NULL; + res->nextRange = NULL; + } else { + res_cur = res_start; + res_prev = NULL; + + debug("res_cur->rangeno is %d\n", res_cur->rangeno); + + while (res_cur) { + if (res_cur->rangeno >= res->rangeno) + break; + res_prev = res_cur; + if (res_cur->next) + res_cur = res_cur->next; + else + res_cur = res_cur->nextRange; + } + + if (!res_cur) { + /* at the end of the resource list */ + debug("i should be here, [%x - %x]\n", res->start, res->end); + res_prev->nextRange = res; + res->next = NULL; + res->nextRange = NULL; + } else if (res_cur->rangeno == res->rangeno) { + /* in the same range */ + while (res_cur) { + if (res->start < res_cur->start) + break; + res_prev = res_cur; + res_cur = res_cur->next; + } + if (!res_cur) { + /* the last resource in this range */ + res_prev->next = res; + res->next = NULL; + res->nextRange = res_prev->nextRange; + res_prev->nextRange = NULL; + } else if (res->start < res_cur->start) { + /* at the beginning or middle of the range */ + if (!res_prev) { + switch (res->type) { + case IO: + bus_cur->firstIO = res; + break; + case MEM: + bus_cur->firstMem = res; + break; + case PFMEM: + bus_cur->firstPFMem = res; + break; + } + } else if (res_prev->rangeno == res_cur->rangeno) + res_prev->next = res; + else + res_prev->nextRange = res; + + res->next = res_cur; + res->nextRange = NULL; + } + } else { + /* this is the case where it is 1st occurrence of the range */ + if (!res_prev) { + /* at the beginning of the resource list */ + res->next = NULL; + switch (res->type) { + case IO: + res->nextRange = bus_cur->firstIO; + bus_cur->firstIO = res; + break; + case MEM: + res->nextRange = bus_cur->firstMem; + bus_cur->firstMem = res; + break; + case PFMEM: + res->nextRange = bus_cur->firstPFMem; + bus_cur->firstPFMem = res; + break; + } + } else if (res_cur->rangeno > res->rangeno) { + /* in the middle of the resource list */ + res_prev->nextRange = res; + res->next = NULL; + res->nextRange = res_cur; + } + } + } + + debug("%s - exit\n", __func__); + return 0; +} + +/**************************************************************************** + * This routine will remove the resource from the list of resources + * + * Input: io, mem, and/or pfmem resource to be deleted + * Output: 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 + * Output: the correct start and end address are inputted into the resource node, + * 0 or -EINVAL + *****************************************************************************/ +int ibmphp_check_resource(struct resource_node *res, u8 bridge) +{ + struct bus_node *bus_cur; + struct range_node *range = NULL; + struct resource_node *res_prev; + struct resource_node *res_cur = NULL; + u32 len_cur = 0, start_cur = 0, len_tmp = 0; + int noranges = 0; + u32 tmp_start; /* this is to make sure start address is divisible by the length needed */ + u32 tmp_divide; + u8 flag = 0; + + if (!res) + return -EINVAL; + + if (bridge) { + /* The rules for bridges are different, 4K divisible for IO, 1M for (pf)mem*/ + if (res->type == IO) + tmp_divide = IOBRIDGE; + else + tmp_divide = MEMBRIDGE; + } else + tmp_divide = res->len; + + bus_cur = find_bus_wprev(res->busno, NULL, 0); + + if (!bus_cur) { + /* didn't find a bus, something's wrong!!! */ + debug("no bus in the system, either pci_dev's wrong or allocation failed\n"); + return -EINVAL; + } + + debug("%s - enter\n", __func__); + debug("bus_cur->busno is %d\n", bus_cur->busno); + + /* This is a quick fix to not mess up with the code very much. i.e., + * 2000-2fff, len = 1000, but when we compare, we need it to be fff */ + res->len -= 1; + + switch (res->type) { + case IO: + res_cur = bus_cur->firstIO; + noranges = bus_cur->noIORanges; + break; + case MEM: + res_cur = bus_cur->firstMem; + noranges = bus_cur->noMemRanges; + break; + case PFMEM: + res_cur = bus_cur->firstPFMem; + noranges = bus_cur->noPFMemRanges; + break; + default: + err("wrong type of resource to check\n"); + return -EINVAL; + } + res_prev = NULL; + + while (res_cur) { + range = find_range(bus_cur, res_cur); + debug("%s - rangeno = %d\n", __func__, res_cur->rangeno); + + if (!range) { + err("no range for the device exists... bailing out...\n"); + return -EINVAL; + } + + /* found our range */ + if (!res_prev) { + /* first time in the loop */ + len_tmp = res_cur->start - 1 - range->start; + + if ((res_cur->start != range->start) && (len_tmp >= res->len)) { + debug("len_tmp = %x\n", len_tmp); + + if ((len_tmp < len_cur) || (len_cur == 0)) { + + if ((range->start % tmp_divide) == 0) { + /* just perfect, starting address is divisible by length */ + flag = 1; + len_cur = len_tmp; + start_cur = range->start; + } else { + /* Needs adjusting */ + tmp_start = range->start; + flag = 0; + + while ((len_tmp = res_cur->start - 1 - tmp_start) >= res->len) { + if ((tmp_start % tmp_divide) == 0) { + flag = 1; + len_cur = len_tmp; + start_cur = tmp_start; + break; + } + tmp_start += tmp_divide - tmp_start % tmp_divide; + if (tmp_start >= res_cur->start - 1) + break; + } + } + + if (flag && len_cur == res->len) { + debug("but we are not here, right?\n"); + res->start = start_cur; + res->len += 1; /* To restore the balance */ + res->end = res->start + res->len - 1; + return 0; + } + } + } + } + if (!res_cur->next) { + /* last device on the range */ + len_tmp = range->end - (res_cur->end + 1); + + if ((range->end != res_cur->end) && (len_tmp >= res->len)) { + debug("len_tmp = %x\n", len_tmp); + if ((len_tmp < len_cur) || (len_cur == 0)) { + + if (((res_cur->end + 1) % tmp_divide) == 0) { + /* just perfect, starting address is divisible by length */ + flag = 1; + len_cur = len_tmp; + start_cur = res_cur->end + 1; + } else { + /* Needs adjusting */ + tmp_start = res_cur->end + 1; + flag = 0; + + while ((len_tmp = range->end - tmp_start) >= res->len) { + if ((tmp_start % tmp_divide) == 0) { + flag = 1; + len_cur = len_tmp; + start_cur = tmp_start; + break; + } + tmp_start += tmp_divide - tmp_start % tmp_divide; + if (tmp_start >= range->end) + break; + } + } + if (flag && len_cur == res->len) { + res->start = start_cur; + res->len += 1; /* To restore the balance */ + res->end = res->start + res->len - 1; + return 0; + } + } + } + } + + if (res_prev) { + if (res_prev->rangeno != res_cur->rangeno) { + /* 1st device on this range */ + len_tmp = res_cur->start - 1 - range->start; + + if ((res_cur->start != range->start) && (len_tmp >= res->len)) { + if ((len_tmp < len_cur) || (len_cur == 0)) { + if ((range->start % tmp_divide) == 0) { + /* just perfect, starting address is divisible by length */ + flag = 1; + len_cur = len_tmp; + start_cur = range->start; + } else { + /* Needs adjusting */ + tmp_start = range->start; + flag = 0; + + while ((len_tmp = res_cur->start - 1 - tmp_start) >= res->len) { + if ((tmp_start % tmp_divide) == 0) { + flag = 1; + len_cur = len_tmp; + start_cur = tmp_start; + break; + } + tmp_start += tmp_divide - tmp_start % tmp_divide; + if (tmp_start >= res_cur->start - 1) + break; + } + } + + if (flag && len_cur == res->len) { + res->start = start_cur; + res->len += 1; /* To restore the balance */ + res->end = res->start + res->len - 1; + return 0; + } + } + } + } else { + /* in the same range */ + len_tmp = res_cur->start - 1 - res_prev->end - 1; + + if (len_tmp >= res->len) { + if ((len_tmp < len_cur) || (len_cur == 0)) { + if (((res_prev->end + 1) % tmp_divide) == 0) { + /* just perfect, starting address's divisible by length */ + flag = 1; + len_cur = len_tmp; + start_cur = res_prev->end + 1; + } else { + /* Needs adjusting */ + tmp_start = res_prev->end + 1; + flag = 0; + + while ((len_tmp = res_cur->start - 1 - tmp_start) >= res->len) { + if ((tmp_start % tmp_divide) == 0) { + flag = 1; + len_cur = len_tmp; + start_cur = tmp_start; + break; + } + tmp_start += tmp_divide - tmp_start % tmp_divide; + if (tmp_start >= res_cur->start - 1) + break; + } + } + + if (flag && len_cur == res->len) { + res->start = start_cur; + res->len += 1; /* To restore the balance */ + res->end = res->start + res->len - 1; + return 0; + } + } + } + } + } + /* end if (res_prev) */ + res_prev = res_cur; + if (res_cur->next) + res_cur = res_cur->next; + else + res_cur = res_cur->nextRange; + } /* end of while */ + + + if (!res_prev) { + /* 1st device ever */ + /* need to find appropriate range */ + switch (res->type) { + case IO: + range = bus_cur->rangeIO; + break; + case MEM: + range = bus_cur->rangeMem; + break; + case PFMEM: + range = bus_cur->rangePFMem; + break; + } + while (range) { + len_tmp = range->end - range->start; + + if (len_tmp >= res->len) { + if ((len_tmp < len_cur) || (len_cur == 0)) { + if ((range->start % tmp_divide) == 0) { + /* just perfect, starting address's divisible by length */ + flag = 1; + len_cur = len_tmp; + start_cur = range->start; + } else { + /* Needs adjusting */ + tmp_start = range->start; + flag = 0; + + while ((len_tmp = range->end - tmp_start) >= res->len) { + if ((tmp_start % tmp_divide) == 0) { + flag = 1; + len_cur = len_tmp; + start_cur = tmp_start; + break; + } + tmp_start += tmp_divide - tmp_start % tmp_divide; + if (tmp_start >= range->end) + break; + } + } + + if (flag && len_cur == res->len) { + res->start = start_cur; + res->len += 1; /* To restore the balance */ + res->end = res->start + res->len - 1; + return 0; + } + } + } + range = range->next; + } /* end of while */ + + if ((!range) && (len_cur == 0)) { + /* have gone through the list of devices and ranges and haven't found n.e.thing */ + err("no appropriate range.. bailing out...\n"); + return -EINVAL; + } else if (len_cur) { + res->start = start_cur; + res->len += 1; /* To restore the balance */ + res->end = res->start + res->len - 1; + return 0; + } + } + + if (!res_cur) { + debug("prev->rangeno = %d, noranges = %d\n", res_prev->rangeno, noranges); + if (res_prev->rangeno < noranges) { + /* if there're more ranges out there to check */ + switch (res->type) { + case IO: + range = bus_cur->rangeIO; + break; + case MEM: + range = bus_cur->rangeMem; + break; + case PFMEM: + range = bus_cur->rangePFMem; + break; + } + while (range) { + len_tmp = range->end - range->start; + + if (len_tmp >= res->len) { + if ((len_tmp < len_cur) || (len_cur == 0)) { + if ((range->start % tmp_divide) == 0) { + /* just perfect, starting address's divisible by length */ + flag = 1; + len_cur = len_tmp; + start_cur = range->start; + } else { + /* Needs adjusting */ + tmp_start = range->start; + flag = 0; + + while ((len_tmp = range->end - tmp_start) >= res->len) { + if ((tmp_start % tmp_divide) == 0) { + flag = 1; + len_cur = len_tmp; + start_cur = tmp_start; + break; + } + tmp_start += tmp_divide - tmp_start % tmp_divide; + if (tmp_start >= range->end) + break; + } + } + + if (flag && len_cur == res->len) { + res->start = start_cur; + res->len += 1; /* To restore the balance */ + res->end = res->start + res->len - 1; + return 0; + } + } + } + range = range->next; + } /* end of while */ + + if ((!range) && (len_cur == 0)) { + /* have gone through the list of devices and ranges and haven't found n.e.thing */ + err("no appropriate range.. bailing out...\n"); + return -EINVAL; + } else if (len_cur) { + res->start = start_cur; + res->len += 1; /* To restore the balance */ + res->end = res->start + res->len - 1; + return 0; + } + } else { + /* no more ranges to check on */ + if (len_cur) { + res->start = start_cur; + res->len += 1; /* To restore the balance */ + res->end = res->start + res->len - 1; + return 0; + } else { + /* have gone through the list of devices and haven't found n.e.thing */ + err("no appropriate range.. bailing out...\n"); + return -EINVAL; + } + } + } /* end if (!res_cur) */ + return -EINVAL; +} + +/******************************************************************************** + * This routine is called from remove_card if the card contained PPB. + * It will remove all the resources on the bus as well as the bus itself + * Input: Bus + * Output: 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, *next; + 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; + int i = 0; + flags = 1; + + list_for_each_entry_safe(bus_cur, next, &gbuses, 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; + + list_for_each_entry(bus_cur, &gbuses, bus_list) { + if ((!bus_cur->rangePFMem) && (bus_cur->firstPFMem)) { + for (pfmem_cur = bus_cur->firstPFMem, pfmem_prev = NULL; pfmem_cur; pfmem_prev = pfmem_cur, pfmem_cur = pfmem_cur->next) { + pfmem_cur->fromMem = 1; + if (pfmem_prev) + pfmem_prev->next = pfmem_cur->next; + else + bus_cur->firstPFMem = pfmem_cur->next; + + if (!bus_cur->firstPFMemFromMem) + pfmem_cur->next = NULL; + else + /* we don't need to sort PFMemFromMem since we're using mem node for + all the real work anyways, so just insert at the beginning of the + list + */ + pfmem_cur->next = bus_cur->firstPFMemFromMem; + + bus_cur->firstPFMemFromMem = pfmem_cur; + + mem = kzalloc(sizeof(struct resource_node), GFP_KERNEL); + if (!mem) + return -ENOMEM; + + mem->type = MEM; + mem->busno = pfmem_cur->busno; + mem->devfunc = pfmem_cur->devfunc; + mem->start = pfmem_cur->start; + mem->end = pfmem_cur->end; + mem->len = pfmem_cur->len; + if (ibmphp_add_resource(mem) < 0) + err("Trouble...trouble... EBDA allocated pfmem from mem, but system doesn't display it has this space... unless not PCI device...\n"); + pfmem_cur->rangeno = mem->rangeno; + } /* end for pfmem */ + } /* end if */ + } /* end list_for_each bus */ + return 0; +} + +int ibmphp_add_pfmem_from_mem(struct resource_node *pfmem) +{ + struct bus_node *bus_cur = find_bus_wprev(pfmem->busno, NULL, 0); + + if (!bus_cur) { + err("cannot find bus of pfmem to add...\n"); + return -ENODEV; + } + + if (bus_cur->firstPFMemFromMem) + pfmem->next = bus_cur->firstPFMemFromMem; + else + pfmem->next = NULL; + + bus_cur->firstPFMemFromMem = pfmem; + + return 0; +} + +/* This routine just goes through the buses to see if the bus already exists. + * It is called from ibmphp_find_sec_number, to find out a secondary bus number for + * bridged cards + * Parameters: bus_number + * Returns: Bus pointer or NULL + */ +struct bus_node *ibmphp_find_res_bus(u8 bus_number) +{ + return find_bus_wprev(bus_number, NULL, 0); +} + +static struct bus_node *find_bus_wprev(u8 bus_number, struct bus_node **prev, u8 flag) +{ + struct bus_node *bus_cur; + + list_for_each_entry(bus_cur, &gbuses, bus_list) { + if (flag) + *prev = list_prev_entry(bus_cur, 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; + + debug_pci("*****************START**********************\n"); + + if ((!list_empty(&gbuses)) && flags) { + err("The GBUSES is not NULL?!?!?!?!?\n"); + return; + } + + list_for_each_entry(bus_cur, &gbuses, 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 PPBs that can have more than 1 *bus + * behind them All these are TO DO. + * Also need to add more error checkings... (from fnc returns etc) + */ +static int __init update_bridge_ranges(struct bus_node **bus) +{ + u8 sec_busno, device, function, hdr_type, start_io_address, end_io_address; + u16 vendor_id, upper_io_start, upper_io_end, start_mem_address, end_mem_address; + u32 start_address, end_address, upper_start, upper_end; + struct bus_node *bus_sec; + struct bus_node *bus_cur; + struct resource_node *io; + struct resource_node *mem; + struct resource_node *pfmem; + struct range_node *range; + unsigned int devfn; + + bus_cur = *bus; + if (!bus_cur) + return -ENODEV; + ibmphp_pci_bus->number = bus_cur->busno; + + debug("inside %s\n", __func__); + debug("bus_cur->busno = %x\n", bus_cur->busno); + + for (device = 0; device < 32; device++) { + for (function = 0x00; function < 0x08; function++) { + devfn = PCI_DEVFN(device, function); + pci_bus_read_config_word(ibmphp_pci_bus, devfn, PCI_VENDOR_ID, &vendor_id); + + if (vendor_id != PCI_VENDOR_ID_NOTVALID) { + /* found correct device!!! */ + pci_bus_read_config_byte(ibmphp_pci_bus, devfn, PCI_HEADER_TYPE, &hdr_type); + + switch (hdr_type) { + case PCI_HEADER_TYPE_NORMAL: + function = 0x8; + break; + case PCI_HEADER_TYPE_MULTIDEVICE: + break; + case PCI_HEADER_TYPE_BRIDGE: + function = 0x8; + fallthrough; + 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) { + alloc_error_bus(NULL, sec_busno, 1); + /* the rest will be populated during NVRAM call */ + return 0; + } + pci_bus_read_config_byte(ibmphp_pci_bus, devfn, PCI_IO_BASE, &start_io_address); + pci_bus_read_config_byte(ibmphp_pci_bus, devfn, PCI_IO_LIMIT, &end_io_address); + pci_bus_read_config_word(ibmphp_pci_bus, devfn, PCI_IO_BASE_UPPER16, &upper_io_start); + pci_bus_read_config_word(ibmphp_pci_bus, devfn, PCI_IO_LIMIT_UPPER16, &upper_io_end); + start_address = (start_io_address & PCI_IO_RANGE_MASK) << 8; + start_address |= (upper_io_start << 16); + end_address = (end_io_address & PCI_IO_RANGE_MASK) << 8; + end_address |= (upper_io_end << 16); + + if ((start_address) && (start_address <= end_address)) { + range = kzalloc(sizeof(struct range_node), GFP_KERNEL); + if (!range) + return -ENOMEM; + + range->start = start_address; + range->end = end_address + 0xfff; + + if (bus_sec->noIORanges > 0) { + if (!range_exists_already(range, bus_sec, IO)) { + add_bus_range(IO, range, bus_sec); + ++bus_sec->noIORanges; + } else { + kfree(range); + range = NULL; + } + } else { + /* 1st IO Range on the bus */ + range->rangeno = 1; + bus_sec->rangeIO = range; + ++bus_sec->noIORanges; + } + fix_resources(bus_sec); + + if (ibmphp_find_resource(bus_cur, start_address, &io, IO)) { + io = kzalloc(sizeof(struct resource_node), GFP_KERNEL); + if (!io) { + kfree(range); + return -ENOMEM; + } + io->type = IO; + io->busno = bus_cur->busno; + io->devfunc = ((device << 3) | (function & 0x7)); + io->start = start_address; + io->end = end_address + 0xfff; + io->len = io->end - io->start + 1; + ibmphp_add_resource(io); + } + } + + pci_bus_read_config_word(ibmphp_pci_bus, devfn, PCI_MEMORY_BASE, &start_mem_address); + pci_bus_read_config_word(ibmphp_pci_bus, devfn, PCI_MEMORY_LIMIT, &end_mem_address); + + start_address = 0x00000000 | (start_mem_address & PCI_MEMORY_RANGE_MASK) << 16; + end_address = 0x00000000 | (end_mem_address & PCI_MEMORY_RANGE_MASK) << 16; + + if ((start_address) && (start_address <= end_address)) { + + range = kzalloc(sizeof(struct range_node), GFP_KERNEL); + if (!range) + return -ENOMEM; + + range->start = start_address; + range->end = end_address + 0xfffff; + + if (bus_sec->noMemRanges > 0) { + if (!range_exists_already(range, bus_sec, MEM)) { + add_bus_range(MEM, range, bus_sec); + ++bus_sec->noMemRanges; + } else { + kfree(range); + range = NULL; + } + } else { + /* 1st Mem Range on the bus */ + range->rangeno = 1; + bus_sec->rangeMem = range; + ++bus_sec->noMemRanges; + } + + fix_resources(bus_sec); + + if (ibmphp_find_resource(bus_cur, start_address, &mem, MEM)) { + mem = kzalloc(sizeof(struct resource_node), GFP_KERNEL); + if (!mem) { + kfree(range); + return -ENOMEM; + } + mem->type = MEM; + mem->busno = bus_cur->busno; + mem->devfunc = ((device << 3) | (function & 0x7)); + mem->start = start_address; + mem->end = end_address + 0xfffff; + mem->len = mem->end - mem->start + 1; + ibmphp_add_resource(mem); + } + } + pci_bus_read_config_word(ibmphp_pci_bus, devfn, PCI_PREF_MEMORY_BASE, &start_mem_address); + pci_bus_read_config_word(ibmphp_pci_bus, devfn, PCI_PREF_MEMORY_LIMIT, &end_mem_address); + pci_bus_read_config_dword(ibmphp_pci_bus, devfn, PCI_PREF_BASE_UPPER32, &upper_start); + pci_bus_read_config_dword(ibmphp_pci_bus, devfn, PCI_PREF_LIMIT_UPPER32, &upper_end); + start_address = 0x00000000 | (start_mem_address & PCI_MEMORY_RANGE_MASK) << 16; + end_address = 0x00000000 | (end_mem_address & PCI_MEMORY_RANGE_MASK) << 16; +#if BITS_PER_LONG == 64 + start_address |= ((long) upper_start) << 32; + end_address |= ((long) upper_end) << 32; +#endif + + if ((start_address) && (start_address <= end_address)) { + + range = kzalloc(sizeof(struct range_node), GFP_KERNEL); + if (!range) + return -ENOMEM; + + range->start = start_address; + range->end = end_address + 0xfffff; + + if (bus_sec->noPFMemRanges > 0) { + if (!range_exists_already(range, bus_sec, PFMEM)) { + add_bus_range(PFMEM, range, bus_sec); + ++bus_sec->noPFMemRanges; + } else { + kfree(range); + range = NULL; + } + } else { + /* 1st PFMem Range on the bus */ + range->rangeno = 1; + bus_sec->rangePFMem = range; + ++bus_sec->noPFMemRanges; + } + + fix_resources(bus_sec); + if (ibmphp_find_resource(bus_cur, start_address, &pfmem, PFMEM)) { + pfmem = kzalloc(sizeof(struct resource_node), GFP_KERNEL); + if (!pfmem) { + kfree(range); + return -ENOMEM; + } + pfmem->type = PFMEM; + pfmem->busno = bus_cur->busno; + pfmem->devfunc = ((device << 3) | (function & 0x7)); + pfmem->start = start_address; + pfmem->end = end_address + 0xfffff; + pfmem->len = pfmem->end - pfmem->start + 1; + pfmem->fromMem = 0; + + ibmphp_add_resource(pfmem); + } + } + break; + } /* end of switch */ + } /* end if vendor */ + } /* end for function */ + } /* end for device */ + + return 0; +} diff --git a/drivers/pci/hotplug/pci_hotplug_core.c b/drivers/pci/hotplug/pci_hotplug_core.c new file mode 100644 index 000000000..058d5937d --- /dev/null +++ b/drivers/pci/hotplug/pci_hotplug_core.c @@ -0,0 +1,580 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * PCI HotPlug Controller Core + * + * Copyright (C) 2001-2002 Greg Kroah-Hartman (greg@kroah.com) + * Copyright (C) 2001-2002 IBM Corp. + * + * All rights reserved. + * + * Send feedback to <kristen.c.accardi@intel.com> + * + * Authors: + * Greg Kroah-Hartman <greg@kroah.com> + * Scott Murray <scottm@somanetworks.com> + */ + +#include <linux/module.h> /* try_module_get & module_put */ +#include <linux/moduleparam.h> +#include <linux/kernel.h> +#include <linux/types.h> +#include <linux/list.h> +#include <linux/kobject.h> +#include <linux/sysfs.h> +#include <linux/pagemap.h> +#include <linux/init.h> +#include <linux/mount.h> +#include <linux/namei.h> +#include <linux/mutex.h> +#include <linux/pci.h> +#include <linux/pci_hotplug.h> +#include <linux/uaccess.h> +#include "../pci.h" +#include "cpci_hotplug.h" + +#define MY_NAME "pci_hotplug" + +#define dbg(fmt, arg...) do { if (debug) printk(KERN_DEBUG "%s: %s: " fmt, MY_NAME, __func__, ## arg); } while (0) +#define err(format, arg...) printk(KERN_ERR "%s: " format, MY_NAME, ## arg) +#define info(format, arg...) printk(KERN_INFO "%s: " format, MY_NAME, ## arg) +#define warn(format, arg...) printk(KERN_WARNING "%s: " format, MY_NAME, ## arg) + +/* local variables */ +static bool debug; + +static LIST_HEAD(pci_hotplug_slot_list); +static DEFINE_MUTEX(pci_hp_mutex); + +/* Weee, fun with macros... */ +#define GET_STATUS(name, type) \ +static int get_##name(struct hotplug_slot *slot, type *value) \ +{ \ + const struct hotplug_slot_ops *ops = slot->ops; \ + int retval = 0; \ + if (!try_module_get(slot->owner)) \ + return -ENODEV; \ + if (ops->get_##name) \ + retval = ops->get_##name(slot, value); \ + module_put(slot->owner); \ + return retval; \ +} + +GET_STATUS(power_status, u8) +GET_STATUS(attention_status, u8) +GET_STATUS(latch_status, u8) +GET_STATUS(adapter_status, u8) + +static ssize_t power_read_file(struct pci_slot *pci_slot, char *buf) +{ + int retval; + u8 value; + + retval = get_power_status(pci_slot->hotplug, &value); + if (retval) + return retval; + + return sysfs_emit(buf, "%d\n", value); +} + +static ssize_t power_write_file(struct pci_slot *pci_slot, const char *buf, + size_t count) +{ + struct hotplug_slot *slot = pci_slot->hotplug; + unsigned long lpower; + u8 power; + int retval = 0; + + lpower = simple_strtoul(buf, NULL, 10); + power = (u8)(lpower & 0xff); + dbg("power = %d\n", power); + + if (!try_module_get(slot->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->owner); + +exit: + if (retval) + return retval; + return count; +} + +static struct pci_slot_attribute hotplug_slot_attr_power = { + .attr = {.name = "power", .mode = S_IFREG | S_IRUGO | S_IWUSR}, + .show = power_read_file, + .store = power_write_file +}; + +static ssize_t attention_read_file(struct pci_slot *pci_slot, char *buf) +{ + int retval; + u8 value; + + retval = get_attention_status(pci_slot->hotplug, &value); + if (retval) + return retval; + + return sysfs_emit(buf, "%d\n", value); +} + +static ssize_t attention_write_file(struct pci_slot *pci_slot, const char *buf, + size_t count) +{ + struct hotplug_slot *slot = pci_slot->hotplug; + const struct hotplug_slot_ops *ops = slot->ops; + unsigned long lattention; + u8 attention; + int retval = 0; + + lattention = simple_strtoul(buf, NULL, 10); + attention = (u8)(lattention & 0xff); + dbg(" - attention = %d\n", attention); + + if (!try_module_get(slot->owner)) { + retval = -ENODEV; + goto exit; + } + if (ops->set_attention_status) + retval = ops->set_attention_status(slot, attention); + module_put(slot->owner); + +exit: + if (retval) + return retval; + return count; +} + +static struct pci_slot_attribute hotplug_slot_attr_attention = { + .attr = {.name = "attention", .mode = S_IFREG | S_IRUGO | S_IWUSR}, + .show = attention_read_file, + .store = attention_write_file +}; + +static ssize_t latch_read_file(struct pci_slot *pci_slot, char *buf) +{ + int retval; + u8 value; + + retval = get_latch_status(pci_slot->hotplug, &value); + if (retval) + return retval; + + return sysfs_emit(buf, "%d\n", value); +} + +static struct pci_slot_attribute hotplug_slot_attr_latch = { + .attr = {.name = "latch", .mode = S_IFREG | S_IRUGO}, + .show = latch_read_file, +}; + +static ssize_t presence_read_file(struct pci_slot *pci_slot, char *buf) +{ + int retval; + u8 value; + + retval = get_adapter_status(pci_slot->hotplug, &value); + if (retval) + return retval; + + return sysfs_emit(buf, "%d\n", value); +} + +static struct pci_slot_attribute hotplug_slot_attr_presence = { + .attr = {.name = "adapter", .mode = S_IFREG | S_IRUGO}, + .show = presence_read_file, +}; + +static ssize_t test_write_file(struct pci_slot *pci_slot, const char *buf, + size_t count) +{ + struct hotplug_slot *slot = pci_slot->hotplug; + unsigned long ltest; + u32 test; + int retval = 0; + + ltest = simple_strtoul(buf, NULL, 10); + test = (u32)(ltest & 0xffffffff); + dbg("test = %d\n", test); + + if (!try_module_get(slot->owner)) { + retval = -ENODEV; + goto exit; + } + if (slot->ops->hardware_test) + retval = slot->ops->hardware_test(slot, test); + module_put(slot->owner); + +exit: + if (retval) + return retval; + return count; +} + +static struct pci_slot_attribute hotplug_slot_attr_test = { + .attr = {.name = "test", .mode = S_IFREG | S_IRUGO | S_IWUSR}, + .store = test_write_file +}; + +static bool has_power_file(struct pci_slot *pci_slot) +{ + struct hotplug_slot *slot = pci_slot->hotplug; + + if ((!slot) || (!slot->ops)) + return false; + if ((slot->ops->enable_slot) || + (slot->ops->disable_slot) || + (slot->ops->get_power_status)) + return true; + return false; +} + +static bool has_attention_file(struct pci_slot *pci_slot) +{ + struct hotplug_slot *slot = pci_slot->hotplug; + + if ((!slot) || (!slot->ops)) + return false; + if ((slot->ops->set_attention_status) || + (slot->ops->get_attention_status)) + return true; + return false; +} + +static bool has_latch_file(struct pci_slot *pci_slot) +{ + struct hotplug_slot *slot = pci_slot->hotplug; + + if ((!slot) || (!slot->ops)) + return false; + if (slot->ops->get_latch_status) + return true; + return false; +} + +static bool has_adapter_file(struct pci_slot *pci_slot) +{ + struct hotplug_slot *slot = pci_slot->hotplug; + + if ((!slot) || (!slot->ops)) + return false; + if (slot->ops->get_adapter_status) + return true; + return false; +} + +static bool has_test_file(struct pci_slot *pci_slot) +{ + struct hotplug_slot *slot = pci_slot->hotplug; + + if ((!slot) || (!slot->ops)) + return false; + if (slot->ops->hardware_test) + return true; + return false; +} + +static int fs_add_slot(struct pci_slot *pci_slot) +{ + int retval = 0; + + /* Create symbolic link to the hotplug driver module */ + pci_hp_create_module_link(pci_slot); + + if (has_power_file(pci_slot)) { + retval = sysfs_create_file(&pci_slot->kobj, + &hotplug_slot_attr_power.attr); + if (retval) + goto exit_power; + } + + if (has_attention_file(pci_slot)) { + retval = sysfs_create_file(&pci_slot->kobj, + &hotplug_slot_attr_attention.attr); + if (retval) + goto exit_attention; + } + + if (has_latch_file(pci_slot)) { + retval = sysfs_create_file(&pci_slot->kobj, + &hotplug_slot_attr_latch.attr); + if (retval) + goto exit_latch; + } + + if (has_adapter_file(pci_slot)) { + retval = sysfs_create_file(&pci_slot->kobj, + &hotplug_slot_attr_presence.attr); + if (retval) + goto exit_adapter; + } + + if (has_test_file(pci_slot)) { + retval = sysfs_create_file(&pci_slot->kobj, + &hotplug_slot_attr_test.attr); + if (retval) + goto exit_test; + } + + goto exit; + +exit_test: + if (has_adapter_file(pci_slot)) + sysfs_remove_file(&pci_slot->kobj, + &hotplug_slot_attr_presence.attr); +exit_adapter: + if (has_latch_file(pci_slot)) + sysfs_remove_file(&pci_slot->kobj, &hotplug_slot_attr_latch.attr); +exit_latch: + if (has_attention_file(pci_slot)) + sysfs_remove_file(&pci_slot->kobj, + &hotplug_slot_attr_attention.attr); +exit_attention: + if (has_power_file(pci_slot)) + sysfs_remove_file(&pci_slot->kobj, &hotplug_slot_attr_power.attr); +exit_power: + pci_hp_remove_module_link(pci_slot); +exit: + return retval; +} + +static void fs_remove_slot(struct pci_slot *pci_slot) +{ + if (has_power_file(pci_slot)) + sysfs_remove_file(&pci_slot->kobj, &hotplug_slot_attr_power.attr); + + if (has_attention_file(pci_slot)) + sysfs_remove_file(&pci_slot->kobj, + &hotplug_slot_attr_attention.attr); + + if (has_latch_file(pci_slot)) + sysfs_remove_file(&pci_slot->kobj, &hotplug_slot_attr_latch.attr); + + if (has_adapter_file(pci_slot)) + sysfs_remove_file(&pci_slot->kobj, + &hotplug_slot_attr_presence.attr); + + if (has_test_file(pci_slot)) + sysfs_remove_file(&pci_slot->kobj, &hotplug_slot_attr_test.attr); + + pci_hp_remove_module_link(pci_slot); +} + +static struct hotplug_slot *get_slot_from_name(const char *name) +{ + struct hotplug_slot *slot; + + list_for_each_entry(slot, &pci_hotplug_slot_list, slot_list) { + if (strcmp(hotplug_slot_name(slot), name) == 0) + return slot; + } + return NULL; +} + +/** + * __pci_hp_register - register a hotplug_slot with the PCI hotplug subsystem + * @bus: bus this slot is on + * @slot: pointer to the &struct hotplug_slot to register + * @devnr: device number + * @name: name registered with kobject core + * @owner: caller module owner + * @mod_name: caller module name + * + * Prepares a hotplug slot for in-kernel use and immediately publishes it to + * user space in one go. Drivers may alternatively carry out the two steps + * separately by invoking pci_hp_initialize() and pci_hp_add(). + * + * Returns 0 if successful, anything else for an error. + */ +int __pci_hp_register(struct hotplug_slot *slot, struct pci_bus *bus, + int devnr, const char *name, + struct module *owner, const char *mod_name) +{ + int result; + + result = __pci_hp_initialize(slot, bus, devnr, name, owner, mod_name); + if (result) + return result; + + result = pci_hp_add(slot); + if (result) + pci_hp_destroy(slot); + + return result; +} +EXPORT_SYMBOL_GPL(__pci_hp_register); + +/** + * __pci_hp_initialize - prepare hotplug slot for in-kernel use + * @slot: pointer to the &struct hotplug_slot to initialize + * @bus: bus this slot is on + * @devnr: slot number + * @name: name registered with kobject core + * @owner: caller module owner + * @mod_name: caller module name + * + * Allocate and fill in a PCI slot for use by a hotplug driver. Once this has + * been called, the driver may invoke hotplug_slot_name() to get the slot's + * unique name. The driver must be prepared to handle a ->reset_slot callback + * from this point on. + * + * Returns 0 on success or a negative int on error. + */ +int __pci_hp_initialize(struct hotplug_slot *slot, struct pci_bus *bus, + int devnr, const char *name, struct module *owner, + const char *mod_name) +{ + struct pci_slot *pci_slot; + + if (slot == NULL) + return -ENODEV; + if (slot->ops == NULL) + return -EINVAL; + + slot->owner = owner; + slot->mod_name = mod_name; + + /* + * No problems if we call this interface from both ACPI_PCI_SLOT + * driver and call it here again. If we've already created the + * pci_slot, the interface will simply bump the refcount. + */ + pci_slot = pci_create_slot(bus, devnr, name, slot); + if (IS_ERR(pci_slot)) + return PTR_ERR(pci_slot); + + slot->pci_slot = pci_slot; + pci_slot->hotplug = slot; + return 0; +} +EXPORT_SYMBOL_GPL(__pci_hp_initialize); + +/** + * pci_hp_add - publish hotplug slot to user space + * @slot: pointer to the &struct hotplug_slot to publish + * + * Make a hotplug slot's sysfs interface available and inform user space of its + * addition by sending a uevent. The hotplug driver must be prepared to handle + * all &struct hotplug_slot_ops callbacks from this point on. + * + * Returns 0 on success or a negative int on error. + */ +int pci_hp_add(struct hotplug_slot *slot) +{ + struct pci_slot *pci_slot = slot->pci_slot; + int result; + + result = fs_add_slot(pci_slot); + if (result) + return result; + + kobject_uevent(&pci_slot->kobj, KOBJ_ADD); + mutex_lock(&pci_hp_mutex); + list_add(&slot->slot_list, &pci_hotplug_slot_list); + mutex_unlock(&pci_hp_mutex); + dbg("Added slot %s to the list\n", hotplug_slot_name(slot)); + return 0; +} +EXPORT_SYMBOL_GPL(pci_hp_add); + +/** + * 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. + */ +void pci_hp_deregister(struct hotplug_slot *slot) +{ + pci_hp_del(slot); + pci_hp_destroy(slot); +} +EXPORT_SYMBOL_GPL(pci_hp_deregister); + +/** + * pci_hp_del - unpublish hotplug slot from user space + * @slot: pointer to the &struct hotplug_slot to unpublish + * + * Remove a hotplug slot's sysfs interface. + * + * Returns 0 on success or a negative int on error. + */ +void pci_hp_del(struct hotplug_slot *slot) +{ + struct hotplug_slot *temp; + + if (WARN_ON(!slot)) + return; + + mutex_lock(&pci_hp_mutex); + temp = get_slot_from_name(hotplug_slot_name(slot)); + if (WARN_ON(temp != slot)) { + mutex_unlock(&pci_hp_mutex); + return; + } + + list_del(&slot->slot_list); + mutex_unlock(&pci_hp_mutex); + dbg("Removed slot %s from the list\n", hotplug_slot_name(slot)); + fs_remove_slot(slot->pci_slot); +} +EXPORT_SYMBOL_GPL(pci_hp_del); + +/** + * pci_hp_destroy - remove hotplug slot from in-kernel use + * @slot: pointer to the &struct hotplug_slot to destroy + * + * Destroy a PCI slot used by a hotplug driver. Once this has been called, + * the driver may no longer invoke hotplug_slot_name() to get the slot's + * unique name. The driver no longer needs to handle a ->reset_slot callback + * from this point on. + * + * Returns 0 on success or a negative int on error. + */ +void pci_hp_destroy(struct hotplug_slot *slot) +{ + struct pci_slot *pci_slot = slot->pci_slot; + + slot->pci_slot = NULL; + pci_slot->hotplug = NULL; + pci_destroy_slot(pci_slot); +} +EXPORT_SYMBOL_GPL(pci_hp_destroy); + +static int __init pci_hotplug_init(void) +{ + int result; + + result = cpci_hotplug_init(debug); + if (result) { + err("cpci_hotplug_init with error %d\n", result); + return result; + } + + return result; +} +device_initcall(pci_hotplug_init); + +/* + * not really modular, but the easiest way to keep compat with existing + * bootargs behaviour is to continue using module_param here. + */ +module_param(debug, bool, 0644); +MODULE_PARM_DESC(debug, "Debugging mode enabled or not"); diff --git a/drivers/pci/hotplug/pciehp.h b/drivers/pci/hotplug/pciehp.h new file mode 100644 index 000000000..e0a614ace --- /dev/null +++ b/drivers/pci/hotplug/pciehp.h @@ -0,0 +1,207 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * 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. + * + * Send feedback to <greg@kroah.com>, <kristen.c.accardi@intel.com> + * + */ +#ifndef _PCIEHP_H +#define _PCIEHP_H + +#include <linux/types.h> +#include <linux/pci.h> +#include <linux/pci_hotplug.h> +#include <linux/delay.h> +#include <linux/mutex.h> +#include <linux/rwsem.h> +#include <linux/workqueue.h> + +#include "../pcie/portdrv.h" + +extern bool pciehp_poll_mode; +extern int pciehp_poll_time; + +/* + * Set CONFIG_DYNAMIC_DEBUG=y and boot with 'dyndbg="file pciehp* +p"' to + * enable debug messages. + */ +#define ctrl_dbg(ctrl, format, arg...) \ + pci_dbg(ctrl->pcie->port, format, ## arg) +#define ctrl_err(ctrl, format, arg...) \ + pci_err(ctrl->pcie->port, format, ## arg) +#define ctrl_info(ctrl, format, arg...) \ + pci_info(ctrl->pcie->port, format, ## arg) +#define ctrl_warn(ctrl, format, arg...) \ + pci_warn(ctrl->pcie->port, format, ## arg) + +#define SLOT_NAME_SIZE 10 + +/** + * struct controller - PCIe hotplug controller + * @pcie: pointer to the controller's PCIe port service device + * @slot_cap: cached copy of the Slot Capabilities register + * @inband_presence_disabled: In-Band Presence Detect Disable supported by + * controller and disabled per spec recommendation (PCIe r5.0, appendix I + * implementation note) + * @slot_ctrl: cached copy of the Slot Control register + * @ctrl_lock: serializes writes to the Slot Control register + * @cmd_started: jiffies when the Slot Control register was last written; + * the next write is allowed 1 second later, absent a Command Completed + * interrupt (PCIe r4.0, sec 6.7.3.2) + * @cmd_busy: flag set on Slot Control register write, cleared by IRQ handler + * on reception of a Command Completed event + * @queue: wait queue to wake up on reception of a Command Completed event, + * used for synchronous writes to the Slot Control register + * @pending_events: used by the IRQ handler to save events retrieved from the + * Slot Status register for later consumption by the IRQ thread + * @notification_enabled: whether the IRQ was requested successfully + * @power_fault_detected: whether a power fault was detected by the hardware + * that has not yet been cleared by the user + * @poll_thread: thread to poll for slot events if no IRQ is available, + * enabled with pciehp_poll_mode module parameter + * @state: current state machine position + * @state_lock: protects reads and writes of @state; + * protects scheduling, execution and cancellation of @button_work + * @button_work: work item to turn the slot on or off after 5 seconds + * in response to an Attention Button press + * @hotplug_slot: structure registered with the PCI hotplug core + * @reset_lock: prevents access to the Data Link Layer Link Active bit in the + * Link Status register and to the Presence Detect State bit in the Slot + * Status register during a slot reset which may cause them to flap + * @depth: Number of additional hotplug ports in the path to the root bus, + * used as lock subclass for @reset_lock + * @ist_running: flag to keep user request waiting while IRQ thread is running + * @request_result: result of last user request submitted to the IRQ thread + * @requester: wait queue to wake up on completion of user request, + * used for synchronous slot enable/disable request via sysfs + * + * PCIe hotplug has a 1:1 relationship between controller and slot, hence + * unlike other drivers, the two aren't represented by separate structures. + */ +struct controller { + struct pcie_device *pcie; + + u32 slot_cap; /* capabilities and quirks */ + unsigned int inband_presence_disabled:1; + + u16 slot_ctrl; /* control register access */ + struct mutex ctrl_lock; + unsigned long cmd_started; + unsigned int cmd_busy:1; + wait_queue_head_t queue; + + atomic_t pending_events; /* event handling */ + unsigned int notification_enabled:1; + unsigned int power_fault_detected; + struct task_struct *poll_thread; + + u8 state; /* state machine */ + struct mutex state_lock; + struct delayed_work button_work; + + struct hotplug_slot hotplug_slot; /* hotplug core interface */ + struct rw_semaphore reset_lock; + unsigned int depth; + unsigned int ist_running; + int request_result; + wait_queue_head_t requester; +}; + +/** + * DOC: Slot state + * + * @OFF_STATE: slot is powered off, no subordinate devices are enumerated + * @BLINKINGON_STATE: slot will be powered on after the 5 second delay, + * Power Indicator is blinking + * @BLINKINGOFF_STATE: slot will be powered off after the 5 second delay, + * Power Indicator is blinking + * @POWERON_STATE: slot is currently powering on + * @POWEROFF_STATE: slot is currently powering off + * @ON_STATE: slot is powered on, subordinate devices have been enumerated + */ +#define OFF_STATE 0 +#define BLINKINGON_STATE 1 +#define BLINKINGOFF_STATE 2 +#define POWERON_STATE 3 +#define POWEROFF_STATE 4 +#define ON_STATE 5 + +/** + * DOC: Flags to request an action from the IRQ thread + * + * These are stored together with events read from the Slot Status register, + * hence must be greater than its 16-bit width. + * + * %DISABLE_SLOT: Disable the slot in response to a user request via sysfs or + * an Attention Button press after the 5 second delay + * %RERUN_ISR: Used by the IRQ handler to inform the IRQ thread that the + * hotplug port was inaccessible when the interrupt occurred, requiring + * that the IRQ handler is rerun by the IRQ thread after it has made the + * hotplug port accessible by runtime resuming its parents to D0 + */ +#define DISABLE_SLOT (1 << 16) +#define RERUN_ISR (1 << 17) + +#define ATTN_BUTTN(ctrl) ((ctrl)->slot_cap & PCI_EXP_SLTCAP_ABP) +#define POWER_CTRL(ctrl) ((ctrl)->slot_cap & PCI_EXP_SLTCAP_PCP) +#define MRL_SENS(ctrl) ((ctrl)->slot_cap & PCI_EXP_SLTCAP_MRLSP) +#define ATTN_LED(ctrl) ((ctrl)->slot_cap & PCI_EXP_SLTCAP_AIP) +#define PWR_LED(ctrl) ((ctrl)->slot_cap & PCI_EXP_SLTCAP_PIP) +#define NO_CMD_CMPL(ctrl) ((ctrl)->slot_cap & PCI_EXP_SLTCAP_NCCS) +#define PSN(ctrl) (((ctrl)->slot_cap & PCI_EXP_SLTCAP_PSN) >> 19) + +void pciehp_request(struct controller *ctrl, int action); +void pciehp_handle_button_press(struct controller *ctrl); +void pciehp_handle_disable_request(struct controller *ctrl); +void pciehp_handle_presence_or_link_change(struct controller *ctrl, u32 events); +int pciehp_configure_device(struct controller *ctrl); +void pciehp_unconfigure_device(struct controller *ctrl, bool presence); +void pciehp_queue_pushbutton_work(struct work_struct *work); +struct controller *pcie_init(struct pcie_device *dev); +int pcie_init_notification(struct controller *ctrl); +void pcie_shutdown_notification(struct controller *ctrl); +void pcie_clear_hotplug_events(struct controller *ctrl); +void pcie_enable_interrupt(struct controller *ctrl); +void pcie_disable_interrupt(struct controller *ctrl); +int pciehp_power_on_slot(struct controller *ctrl); +void pciehp_power_off_slot(struct controller *ctrl); +void pciehp_get_power_status(struct controller *ctrl, u8 *status); + +#define INDICATOR_NOOP -1 /* Leave indicator unchanged */ +void pciehp_set_indicators(struct controller *ctrl, int pwr, int attn); + +void pciehp_get_latch_status(struct controller *ctrl, u8 *status); +int pciehp_query_power_fault(struct controller *ctrl); +int pciehp_card_present(struct controller *ctrl); +int pciehp_card_present_or_link_active(struct controller *ctrl); +int pciehp_check_link_status(struct controller *ctrl); +int pciehp_check_link_active(struct controller *ctrl); +void pciehp_release_ctrl(struct controller *ctrl); + +int pciehp_sysfs_enable_slot(struct hotplug_slot *hotplug_slot); +int pciehp_sysfs_disable_slot(struct hotplug_slot *hotplug_slot); +int pciehp_reset_slot(struct hotplug_slot *hotplug_slot, bool probe); +int pciehp_get_attention_status(struct hotplug_slot *hotplug_slot, u8 *status); +int pciehp_set_raw_indicator_status(struct hotplug_slot *h_slot, u8 status); +int pciehp_get_raw_indicator_status(struct hotplug_slot *h_slot, u8 *status); + +int pciehp_slot_reset(struct pcie_device *dev); + +static inline const char *slot_name(struct controller *ctrl) +{ + return hotplug_slot_name(&ctrl->hotplug_slot); +} + +static inline struct controller *to_ctrl(struct hotplug_slot *hotplug_slot) +{ + return container_of(hotplug_slot, struct controller, hotplug_slot); +} + +#endif /* _PCIEHP_H */ diff --git a/drivers/pci/hotplug/pciehp_core.c b/drivers/pci/hotplug/pciehp_core.c new file mode 100644 index 000000000..4042d87d5 --- /dev/null +++ b/drivers/pci/hotplug/pciehp_core.c @@ -0,0 +1,368 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * 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. + * + * Send feedback to <greg@kroah.com>, <kristen.c.accardi@intel.com> + * + * Authors: + * Dan Zink <dan.zink@compaq.com> + * Greg Kroah-Hartman <greg@kroah.com> + * Dely Sy <dely.l.sy@intel.com>" + */ + +#define pr_fmt(fmt) "pciehp: " fmt +#define dev_fmt pr_fmt + +#include <linux/moduleparam.h> +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/types.h> +#include <linux/pci.h> +#include "pciehp.h" + +#include "../pci.h" + +/* Global variables */ +bool pciehp_poll_mode; +int pciehp_poll_time; + +/* + * not really modular, but the easiest way to keep compat with existing + * bootargs behaviour is to continue using module_param here. + */ +module_param(pciehp_poll_mode, bool, 0644); +module_param(pciehp_poll_time, int, 0644); +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"); + +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_latch_status(struct hotplug_slot *slot, u8 *value); +static int get_adapter_status(struct hotplug_slot *slot, u8 *value); + +static int init_slot(struct controller *ctrl) +{ + struct hotplug_slot_ops *ops; + char name[SLOT_NAME_SIZE]; + int retval; + + /* Setup hotplug slot ops */ + ops = kzalloc(sizeof(*ops), GFP_KERNEL); + if (!ops) + return -ENOMEM; + + ops->enable_slot = pciehp_sysfs_enable_slot; + ops->disable_slot = pciehp_sysfs_disable_slot; + ops->get_power_status = get_power_status; + ops->get_adapter_status = get_adapter_status; + ops->reset_slot = pciehp_reset_slot; + if (MRL_SENS(ctrl)) + ops->get_latch_status = get_latch_status; + if (ATTN_LED(ctrl)) { + ops->get_attention_status = pciehp_get_attention_status; + ops->set_attention_status = set_attention_status; + } else if (ctrl->pcie->port->hotplug_user_indicators) { + ops->get_attention_status = pciehp_get_raw_indicator_status; + ops->set_attention_status = pciehp_set_raw_indicator_status; + } + + /* register this slot with the hotplug pci core */ + ctrl->hotplug_slot.ops = ops; + snprintf(name, SLOT_NAME_SIZE, "%u", PSN(ctrl)); + + retval = pci_hp_initialize(&ctrl->hotplug_slot, + ctrl->pcie->port->subordinate, 0, name); + if (retval) { + ctrl_err(ctrl, "pci_hp_initialize failed: error %d\n", retval); + kfree(ops); + } + return retval; +} + +static void cleanup_slot(struct controller *ctrl) +{ + struct hotplug_slot *hotplug_slot = &ctrl->hotplug_slot; + + pci_hp_destroy(hotplug_slot); + kfree(hotplug_slot->ops); +} + +/* + * set_attention_status - Turns the Attention Indicator on, off or blinking + */ +static int set_attention_status(struct hotplug_slot *hotplug_slot, u8 status) +{ + struct controller *ctrl = to_ctrl(hotplug_slot); + struct pci_dev *pdev = ctrl->pcie->port; + + if (status) + status <<= PCI_EXP_SLTCTL_ATTN_IND_SHIFT; + else + status = PCI_EXP_SLTCTL_ATTN_IND_OFF; + + pci_config_pm_runtime_get(pdev); + pciehp_set_indicators(ctrl, INDICATOR_NOOP, status); + pci_config_pm_runtime_put(pdev); + return 0; +} + +static int get_power_status(struct hotplug_slot *hotplug_slot, u8 *value) +{ + struct controller *ctrl = to_ctrl(hotplug_slot); + struct pci_dev *pdev = ctrl->pcie->port; + + pci_config_pm_runtime_get(pdev); + pciehp_get_power_status(ctrl, value); + pci_config_pm_runtime_put(pdev); + return 0; +} + +static int get_latch_status(struct hotplug_slot *hotplug_slot, u8 *value) +{ + struct controller *ctrl = to_ctrl(hotplug_slot); + struct pci_dev *pdev = ctrl->pcie->port; + + pci_config_pm_runtime_get(pdev); + pciehp_get_latch_status(ctrl, value); + pci_config_pm_runtime_put(pdev); + return 0; +} + +static int get_adapter_status(struct hotplug_slot *hotplug_slot, u8 *value) +{ + struct controller *ctrl = to_ctrl(hotplug_slot); + struct pci_dev *pdev = ctrl->pcie->port; + int ret; + + pci_config_pm_runtime_get(pdev); + ret = pciehp_card_present_or_link_active(ctrl); + pci_config_pm_runtime_put(pdev); + if (ret < 0) + return ret; + + *value = ret; + return 0; +} + +/** + * pciehp_check_presence() - synthesize event if presence has changed + * @ctrl: controller to check + * + * On probe and resume, an explicit presence check is necessary to bring up an + * occupied slot or bring down an unoccupied slot. This can't be triggered by + * events in the Slot Status register, they may be stale and are therefore + * cleared. Secondly, sending an interrupt for "events that occur while + * interrupt generation is disabled [when] interrupt generation is subsequently + * enabled" is optional per PCIe r4.0, sec 6.7.3.4. + */ +static void pciehp_check_presence(struct controller *ctrl) +{ + int occupied; + + down_read_nested(&ctrl->reset_lock, ctrl->depth); + mutex_lock(&ctrl->state_lock); + + occupied = pciehp_card_present_or_link_active(ctrl); + if ((occupied > 0 && (ctrl->state == OFF_STATE || + ctrl->state == BLINKINGON_STATE)) || + (!occupied && (ctrl->state == ON_STATE || + ctrl->state == BLINKINGOFF_STATE))) + pciehp_request(ctrl, PCI_EXP_SLTSTA_PDC); + + mutex_unlock(&ctrl->state_lock); + up_read(&ctrl->reset_lock); +} + +static int pciehp_probe(struct pcie_device *dev) +{ + int rc; + struct controller *ctrl; + + /* If this is not a "hotplug" service, we have no business here. */ + if (dev->service != PCIE_PORT_SERVICE_HP) + return -ENODEV; + + if (!dev->port->subordinate) { + /* Can happen if we run out of bus numbers during probe */ + pci_err(dev->port, + "Hotplug bridge without secondary bus, ignoring\n"); + return -ENODEV; + } + + ctrl = pcie_init(dev); + if (!ctrl) { + pci_err(dev->port, "Controller initialization failed\n"); + return -ENODEV; + } + set_service_data(dev, ctrl); + + /* Setup the slot information structures */ + rc = init_slot(ctrl); + if (rc) { + if (rc == -EBUSY) + ctrl_warn(ctrl, "Slot already registered by another hotplug driver\n"); + else + ctrl_err(ctrl, "Slot initialization failed (%d)\n", rc); + goto err_out_release_ctlr; + } + + /* Enable events after we have setup the data structures */ + rc = pcie_init_notification(ctrl); + if (rc) { + ctrl_err(ctrl, "Notification initialization failed (%d)\n", rc); + goto err_out_free_ctrl_slot; + } + + /* Publish to user space */ + rc = pci_hp_add(&ctrl->hotplug_slot); + if (rc) { + ctrl_err(ctrl, "Publication to user space failed (%d)\n", rc); + goto err_out_shutdown_notification; + } + + pciehp_check_presence(ctrl); + + return 0; + +err_out_shutdown_notification: + pcie_shutdown_notification(ctrl); +err_out_free_ctrl_slot: + cleanup_slot(ctrl); +err_out_release_ctlr: + pciehp_release_ctrl(ctrl); + return -ENODEV; +} + +static void pciehp_remove(struct pcie_device *dev) +{ + struct controller *ctrl = get_service_data(dev); + + pci_hp_del(&ctrl->hotplug_slot); + pcie_shutdown_notification(ctrl); + cleanup_slot(ctrl); + pciehp_release_ctrl(ctrl); +} + +#ifdef CONFIG_PM +static bool pme_is_native(struct pcie_device *dev) +{ + const struct pci_host_bridge *host; + + host = pci_find_host_bridge(dev->port->bus); + return pcie_ports_native || host->native_pme; +} + +static void pciehp_disable_interrupt(struct pcie_device *dev) +{ + /* + * Disable hotplug interrupt so that it does not trigger + * immediately when the downstream link goes down. + */ + if (pme_is_native(dev)) + pcie_disable_interrupt(get_service_data(dev)); +} + +#ifdef CONFIG_PM_SLEEP +static int pciehp_suspend(struct pcie_device *dev) +{ + /* + * If the port is already runtime suspended we can keep it that + * way. + */ + if (dev_pm_skip_suspend(&dev->port->dev)) + return 0; + + pciehp_disable_interrupt(dev); + return 0; +} + +static int pciehp_resume_noirq(struct pcie_device *dev) +{ + struct controller *ctrl = get_service_data(dev); + + /* pci_restore_state() just wrote to the Slot Control register */ + ctrl->cmd_started = jiffies; + ctrl->cmd_busy = true; + + /* clear spurious events from rediscovery of inserted card */ + if (ctrl->state == ON_STATE || ctrl->state == BLINKINGOFF_STATE) + pcie_clear_hotplug_events(ctrl); + + return 0; +} +#endif + +static int pciehp_resume(struct pcie_device *dev) +{ + struct controller *ctrl = get_service_data(dev); + + if (pme_is_native(dev)) + pcie_enable_interrupt(ctrl); + + pciehp_check_presence(ctrl); + + return 0; +} + +static int pciehp_runtime_suspend(struct pcie_device *dev) +{ + pciehp_disable_interrupt(dev); + return 0; +} + +static int pciehp_runtime_resume(struct pcie_device *dev) +{ + struct controller *ctrl = get_service_data(dev); + + /* pci_restore_state() just wrote to the Slot Control register */ + ctrl->cmd_started = jiffies; + ctrl->cmd_busy = true; + + /* clear spurious events from rediscovery of inserted card */ + if ((ctrl->state == ON_STATE || ctrl->state == BLINKINGOFF_STATE) && + pme_is_native(dev)) + pcie_clear_hotplug_events(ctrl); + + return pciehp_resume(dev); +} +#endif /* PM */ + +static struct pcie_port_service_driver hpdriver_portdrv = { + .name = "pciehp", + .port_type = PCIE_ANY_PORT, + .service = PCIE_PORT_SERVICE_HP, + + .probe = pciehp_probe, + .remove = pciehp_remove, + +#ifdef CONFIG_PM +#ifdef CONFIG_PM_SLEEP + .suspend = pciehp_suspend, + .resume_noirq = pciehp_resume_noirq, + .resume = pciehp_resume, +#endif + .runtime_suspend = pciehp_runtime_suspend, + .runtime_resume = pciehp_runtime_resume, +#endif /* PM */ + + .slot_reset = pciehp_slot_reset, +}; + +int __init pcie_hp_init(void) +{ + int retval = 0; + + retval = pcie_port_service_register(&hpdriver_portdrv); + pr_debug("pcie_port_service_register = %d\n", retval); + if (retval) + pr_debug("Failure to register service\n"); + + return retval; +} diff --git a/drivers/pci/hotplug/pciehp_ctrl.c b/drivers/pci/hotplug/pciehp_ctrl.c new file mode 100644 index 000000000..32baba1b7 --- /dev/null +++ b/drivers/pci/hotplug/pciehp_ctrl.c @@ -0,0 +1,439 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * 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. + * + * Send feedback to <greg@kroah.com>, <kristen.c.accardi@intel.com> + * + */ + +#define dev_fmt(fmt) "pciehp: " fmt + +#include <linux/kernel.h> +#include <linux/types.h> +#include <linux/pm_runtime.h> +#include <linux/pci.h> +#include "pciehp.h" + +/* The following routines constitute the bulk of the + hotplug controller logic + */ + +#define SAFE_REMOVAL true +#define SURPRISE_REMOVAL false + +static void set_slot_off(struct controller *ctrl) +{ + /* + * Turn off slot, turn on attention indicator, turn off power + * indicator + */ + if (POWER_CTRL(ctrl)) { + pciehp_power_off_slot(ctrl); + + /* + * After turning power off, we must wait for at least 1 second + * before taking any action that relies on power having been + * removed from the slot/adapter. + */ + msleep(1000); + } + + pciehp_set_indicators(ctrl, PCI_EXP_SLTCTL_PWR_IND_OFF, + PCI_EXP_SLTCTL_ATTN_IND_ON); +} + +/** + * board_added - Called after a board has been added to the system. + * @ctrl: PCIe hotplug controller where board is added + * + * Turns power on for the board. + * Configures board. + */ +static int board_added(struct controller *ctrl) +{ + int retval = 0; + struct pci_bus *parent = ctrl->pcie->port->subordinate; + + if (POWER_CTRL(ctrl)) { + /* Power on slot */ + retval = pciehp_power_on_slot(ctrl); + if (retval) + return retval; + } + + pciehp_set_indicators(ctrl, PCI_EXP_SLTCTL_PWR_IND_BLINK, + INDICATOR_NOOP); + + /* Check link training status */ + retval = pciehp_check_link_status(ctrl); + if (retval) + goto err_exit; + + /* Check for a power fault */ + if (ctrl->power_fault_detected || pciehp_query_power_fault(ctrl)) { + ctrl_err(ctrl, "Slot(%s): Power fault\n", slot_name(ctrl)); + retval = -EIO; + goto err_exit; + } + + retval = pciehp_configure_device(ctrl); + if (retval) { + if (retval != -EEXIST) { + ctrl_err(ctrl, "Cannot add device at %04x:%02x:00\n", + pci_domain_nr(parent), parent->number); + goto err_exit; + } + } + + pciehp_set_indicators(ctrl, PCI_EXP_SLTCTL_PWR_IND_ON, + PCI_EXP_SLTCTL_ATTN_IND_OFF); + return 0; + +err_exit: + set_slot_off(ctrl); + return retval; +} + +/** + * remove_board - Turn off slot and Power Indicator + * @ctrl: PCIe hotplug controller where board is being removed + * @safe_removal: whether the board is safely removed (versus surprise removed) + */ +static void remove_board(struct controller *ctrl, bool safe_removal) +{ + pciehp_unconfigure_device(ctrl, safe_removal); + + if (POWER_CTRL(ctrl)) { + pciehp_power_off_slot(ctrl); + + /* + * After turning power off, we must wait for at least 1 second + * before taking any action that relies on power having been + * removed from the slot/adapter. + */ + msleep(1000); + + /* Ignore link or presence changes caused by power off */ + atomic_and(~(PCI_EXP_SLTSTA_DLLSC | PCI_EXP_SLTSTA_PDC), + &ctrl->pending_events); + } + + pciehp_set_indicators(ctrl, PCI_EXP_SLTCTL_PWR_IND_OFF, + INDICATOR_NOOP); +} + +static int pciehp_enable_slot(struct controller *ctrl); +static int pciehp_disable_slot(struct controller *ctrl, bool safe_removal); + +void pciehp_request(struct controller *ctrl, int action) +{ + atomic_or(action, &ctrl->pending_events); + if (!pciehp_poll_mode) + irq_wake_thread(ctrl->pcie->irq, ctrl); +} + +void pciehp_queue_pushbutton_work(struct work_struct *work) +{ + struct controller *ctrl = container_of(work, struct controller, + button_work.work); + + mutex_lock(&ctrl->state_lock); + switch (ctrl->state) { + case BLINKINGOFF_STATE: + pciehp_request(ctrl, DISABLE_SLOT); + break; + case BLINKINGON_STATE: + pciehp_request(ctrl, PCI_EXP_SLTSTA_PDC); + break; + default: + break; + } + mutex_unlock(&ctrl->state_lock); +} + +void pciehp_handle_button_press(struct controller *ctrl) +{ + mutex_lock(&ctrl->state_lock); + switch (ctrl->state) { + case OFF_STATE: + case ON_STATE: + if (ctrl->state == ON_STATE) { + ctrl->state = BLINKINGOFF_STATE; + ctrl_info(ctrl, "Slot(%s): Powering off due to button press\n", + slot_name(ctrl)); + } else { + ctrl->state = BLINKINGON_STATE; + ctrl_info(ctrl, "Slot(%s) Powering on due to button press\n", + slot_name(ctrl)); + } + /* blink power indicator and turn off attention */ + pciehp_set_indicators(ctrl, PCI_EXP_SLTCTL_PWR_IND_BLINK, + PCI_EXP_SLTCTL_ATTN_IND_OFF); + schedule_delayed_work(&ctrl->button_work, 5 * HZ); + break; + case BLINKINGOFF_STATE: + case BLINKINGON_STATE: + /* + * Cancel if we are still blinking; this means that we + * press the attention again before the 5 sec. limit + * expires to cancel hot-add or hot-remove + */ + ctrl_info(ctrl, "Slot(%s): Button cancel\n", slot_name(ctrl)); + cancel_delayed_work(&ctrl->button_work); + if (ctrl->state == BLINKINGOFF_STATE) { + ctrl->state = ON_STATE; + pciehp_set_indicators(ctrl, PCI_EXP_SLTCTL_PWR_IND_ON, + PCI_EXP_SLTCTL_ATTN_IND_OFF); + } else { + ctrl->state = OFF_STATE; + pciehp_set_indicators(ctrl, PCI_EXP_SLTCTL_PWR_IND_OFF, + PCI_EXP_SLTCTL_ATTN_IND_OFF); + } + ctrl_info(ctrl, "Slot(%s): Action canceled due to button press\n", + slot_name(ctrl)); + break; + default: + ctrl_err(ctrl, "Slot(%s): Ignoring invalid state %#x\n", + slot_name(ctrl), ctrl->state); + break; + } + mutex_unlock(&ctrl->state_lock); +} + +void pciehp_handle_disable_request(struct controller *ctrl) +{ + mutex_lock(&ctrl->state_lock); + switch (ctrl->state) { + case BLINKINGON_STATE: + case BLINKINGOFF_STATE: + cancel_delayed_work(&ctrl->button_work); + break; + } + ctrl->state = POWEROFF_STATE; + mutex_unlock(&ctrl->state_lock); + + ctrl->request_result = pciehp_disable_slot(ctrl, SAFE_REMOVAL); +} + +void pciehp_handle_presence_or_link_change(struct controller *ctrl, u32 events) +{ + int present, link_active; + + /* + * If the slot is on and presence or link has changed, turn it off. + * Even if it's occupied again, we cannot assume the card is the same. + */ + mutex_lock(&ctrl->state_lock); + switch (ctrl->state) { + case BLINKINGOFF_STATE: + cancel_delayed_work(&ctrl->button_work); + fallthrough; + case ON_STATE: + ctrl->state = POWEROFF_STATE; + mutex_unlock(&ctrl->state_lock); + if (events & PCI_EXP_SLTSTA_DLLSC) + ctrl_info(ctrl, "Slot(%s): Link Down\n", + slot_name(ctrl)); + if (events & PCI_EXP_SLTSTA_PDC) + ctrl_info(ctrl, "Slot(%s): Card not present\n", + slot_name(ctrl)); + pciehp_disable_slot(ctrl, SURPRISE_REMOVAL); + break; + default: + mutex_unlock(&ctrl->state_lock); + break; + } + + /* Turn the slot on if it's occupied or link is up */ + mutex_lock(&ctrl->state_lock); + present = pciehp_card_present(ctrl); + link_active = pciehp_check_link_active(ctrl); + if (present <= 0 && link_active <= 0) { + if (ctrl->state == BLINKINGON_STATE) { + ctrl->state = OFF_STATE; + cancel_delayed_work(&ctrl->button_work); + pciehp_set_indicators(ctrl, PCI_EXP_SLTCTL_PWR_IND_OFF, + INDICATOR_NOOP); + ctrl_info(ctrl, "Slot(%s): Card not present\n", + slot_name(ctrl)); + } + mutex_unlock(&ctrl->state_lock); + return; + } + + switch (ctrl->state) { + case BLINKINGON_STATE: + cancel_delayed_work(&ctrl->button_work); + fallthrough; + case OFF_STATE: + ctrl->state = POWERON_STATE; + mutex_unlock(&ctrl->state_lock); + if (present) + ctrl_info(ctrl, "Slot(%s): Card present\n", + slot_name(ctrl)); + if (link_active) + ctrl_info(ctrl, "Slot(%s): Link Up\n", + slot_name(ctrl)); + ctrl->request_result = pciehp_enable_slot(ctrl); + break; + default: + mutex_unlock(&ctrl->state_lock); + break; + } +} + +static int __pciehp_enable_slot(struct controller *ctrl) +{ + u8 getstatus = 0; + + if (MRL_SENS(ctrl)) { + pciehp_get_latch_status(ctrl, &getstatus); + if (getstatus) { + ctrl_info(ctrl, "Slot(%s): Latch open\n", + slot_name(ctrl)); + return -ENODEV; + } + } + + if (POWER_CTRL(ctrl)) { + pciehp_get_power_status(ctrl, &getstatus); + if (getstatus) { + ctrl_info(ctrl, "Slot(%s): Already enabled\n", + slot_name(ctrl)); + return 0; + } + } + + return board_added(ctrl); +} + +static int pciehp_enable_slot(struct controller *ctrl) +{ + int ret; + + pm_runtime_get_sync(&ctrl->pcie->port->dev); + ret = __pciehp_enable_slot(ctrl); + if (ret && ATTN_BUTTN(ctrl)) + /* may be blinking */ + pciehp_set_indicators(ctrl, PCI_EXP_SLTCTL_PWR_IND_OFF, + INDICATOR_NOOP); + pm_runtime_put(&ctrl->pcie->port->dev); + + mutex_lock(&ctrl->state_lock); + ctrl->state = ret ? OFF_STATE : ON_STATE; + mutex_unlock(&ctrl->state_lock); + + return ret; +} + +static int __pciehp_disable_slot(struct controller *ctrl, bool safe_removal) +{ + u8 getstatus = 0; + + if (POWER_CTRL(ctrl)) { + pciehp_get_power_status(ctrl, &getstatus); + if (!getstatus) { + ctrl_info(ctrl, "Slot(%s): Already disabled\n", + slot_name(ctrl)); + return -EINVAL; + } + } + + remove_board(ctrl, safe_removal); + return 0; +} + +static int pciehp_disable_slot(struct controller *ctrl, bool safe_removal) +{ + int ret; + + pm_runtime_get_sync(&ctrl->pcie->port->dev); + ret = __pciehp_disable_slot(ctrl, safe_removal); + pm_runtime_put(&ctrl->pcie->port->dev); + + mutex_lock(&ctrl->state_lock); + ctrl->state = OFF_STATE; + mutex_unlock(&ctrl->state_lock); + + return ret; +} + +int pciehp_sysfs_enable_slot(struct hotplug_slot *hotplug_slot) +{ + struct controller *ctrl = to_ctrl(hotplug_slot); + + mutex_lock(&ctrl->state_lock); + switch (ctrl->state) { + case BLINKINGON_STATE: + case OFF_STATE: + mutex_unlock(&ctrl->state_lock); + /* + * The IRQ thread becomes a no-op if the user pulls out the + * card before the thread wakes up, so initialize to -ENODEV. + */ + ctrl->request_result = -ENODEV; + pciehp_request(ctrl, PCI_EXP_SLTSTA_PDC); + wait_event(ctrl->requester, + !atomic_read(&ctrl->pending_events) && + !ctrl->ist_running); + return ctrl->request_result; + case POWERON_STATE: + ctrl_info(ctrl, "Slot(%s): Already in powering on state\n", + slot_name(ctrl)); + break; + case BLINKINGOFF_STATE: + case ON_STATE: + case POWEROFF_STATE: + ctrl_info(ctrl, "Slot(%s): Already enabled\n", + slot_name(ctrl)); + break; + default: + ctrl_err(ctrl, "Slot(%s): Invalid state %#x\n", + slot_name(ctrl), ctrl->state); + break; + } + mutex_unlock(&ctrl->state_lock); + + return -ENODEV; +} + +int pciehp_sysfs_disable_slot(struct hotplug_slot *hotplug_slot) +{ + struct controller *ctrl = to_ctrl(hotplug_slot); + + mutex_lock(&ctrl->state_lock); + switch (ctrl->state) { + case BLINKINGOFF_STATE: + case ON_STATE: + mutex_unlock(&ctrl->state_lock); + pciehp_request(ctrl, DISABLE_SLOT); + wait_event(ctrl->requester, + !atomic_read(&ctrl->pending_events) && + !ctrl->ist_running); + return ctrl->request_result; + case POWEROFF_STATE: + ctrl_info(ctrl, "Slot(%s): Already in powering off state\n", + slot_name(ctrl)); + break; + case BLINKINGON_STATE: + case OFF_STATE: + case POWERON_STATE: + ctrl_info(ctrl, "Slot(%s): Already disabled\n", + slot_name(ctrl)); + break; + default: + ctrl_err(ctrl, "Slot(%s): Invalid state %#x\n", + slot_name(ctrl), ctrl->state); + break; + } + mutex_unlock(&ctrl->state_lock); + + return -ENODEV; +} diff --git a/drivers/pci/hotplug/pciehp_hpc.c b/drivers/pci/hotplug/pciehp_hpc.c new file mode 100644 index 000000000..358f07728 --- /dev/null +++ b/drivers/pci/hotplug/pciehp_hpc.c @@ -0,0 +1,1092 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * 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. + * + * Send feedback to <greg@kroah.com>,<kristen.c.accardi@intel.com> + */ + +#define dev_fmt(fmt) "pciehp: " fmt + +#include <linux/dmi.h> +#include <linux/kernel.h> +#include <linux/types.h> +#include <linux/jiffies.h> +#include <linux/kthread.h> +#include <linux/pci.h> +#include <linux/pm_runtime.h> +#include <linux/interrupt.h> +#include <linux/slab.h> + +#include "../pci.h" +#include "pciehp.h" + +static const struct dmi_system_id inband_presence_disabled_dmi_table[] = { + /* + * Match all Dell systems, as some Dell systems have inband + * presence disabled on NVMe slots (but don't support the bit to + * report it). Setting inband presence disabled should have no + * negative effect, except on broken hotplug slots that never + * assert presence detect--and those will still work, they will + * just have a bit of extra delay before being probed. + */ + { + .ident = "Dell System", + .matches = { + DMI_MATCH(DMI_OEM_STRING, "Dell System"), + }, + }, + {} +}; + +static inline struct pci_dev *ctrl_dev(struct controller *ctrl) +{ + return ctrl->pcie->port; +} + +static irqreturn_t pciehp_isr(int irq, void *dev_id); +static irqreturn_t pciehp_ist(int irq, void *dev_id); +static int pciehp_poll(void *data); + +static inline int pciehp_request_irq(struct controller *ctrl) +{ + int retval, irq = ctrl->pcie->irq; + + if (pciehp_poll_mode) { + ctrl->poll_thread = kthread_run(&pciehp_poll, ctrl, + "pciehp_poll-%s", + slot_name(ctrl)); + return PTR_ERR_OR_ZERO(ctrl->poll_thread); + } + + /* Installs the interrupt handler */ + retval = request_threaded_irq(irq, pciehp_isr, pciehp_ist, + IRQF_SHARED, "pciehp", ctrl); + if (retval) + ctrl_err(ctrl, "Cannot get irq %d for the hotplug controller\n", + irq); + return retval; +} + +static inline void pciehp_free_irq(struct controller *ctrl) +{ + if (pciehp_poll_mode) + kthread_stop(ctrl->poll_thread); + else + free_irq(ctrl->pcie->irq, ctrl); +} + +static int pcie_poll_cmd(struct controller *ctrl, int timeout) +{ + struct pci_dev *pdev = ctrl_dev(ctrl); + u16 slot_status; + + do { + pcie_capability_read_word(pdev, PCI_EXP_SLTSTA, &slot_status); + if (PCI_POSSIBLE_ERROR(slot_status)) { + ctrl_info(ctrl, "%s: no response from device\n", + __func__); + return 0; + } + + if (slot_status & PCI_EXP_SLTSTA_CC) { + pcie_capability_write_word(pdev, PCI_EXP_SLTSTA, + PCI_EXP_SLTSTA_CC); + ctrl->cmd_busy = 0; + smp_mb(); + return 1; + } + msleep(10); + timeout -= 10; + } while (timeout >= 0); + return 0; /* timeout */ +} + +static void pcie_wait_cmd(struct controller *ctrl) +{ + unsigned int msecs = pciehp_poll_mode ? 2500 : 1000; + unsigned long duration = msecs_to_jiffies(msecs); + unsigned long cmd_timeout = ctrl->cmd_started + duration; + unsigned long now, timeout; + int rc; + + /* + * If the controller does not generate notifications for command + * completions, we never need to wait between writes. + */ + if (NO_CMD_CMPL(ctrl)) + return; + + if (!ctrl->cmd_busy) + return; + + /* + * Even if the command has already timed out, we want to call + * pcie_poll_cmd() so it can clear PCI_EXP_SLTSTA_CC. + */ + now = jiffies; + if (time_before_eq(cmd_timeout, now)) + timeout = 1; + else + timeout = cmd_timeout - now; + + if (ctrl->slot_ctrl & PCI_EXP_SLTCTL_HPIE && + ctrl->slot_ctrl & PCI_EXP_SLTCTL_CCIE) + rc = wait_event_timeout(ctrl->queue, !ctrl->cmd_busy, timeout); + else + rc = pcie_poll_cmd(ctrl, jiffies_to_msecs(timeout)); + + if (!rc) + ctrl_info(ctrl, "Timeout on hotplug command %#06x (issued %u msec ago)\n", + ctrl->slot_ctrl, + jiffies_to_msecs(jiffies - ctrl->cmd_started)); +} + +#define CC_ERRATUM_MASK (PCI_EXP_SLTCTL_PCC | \ + PCI_EXP_SLTCTL_PIC | \ + PCI_EXP_SLTCTL_AIC | \ + PCI_EXP_SLTCTL_EIC) + +static void pcie_do_write_cmd(struct controller *ctrl, u16 cmd, + u16 mask, bool wait) +{ + struct pci_dev *pdev = ctrl_dev(ctrl); + u16 slot_ctrl_orig, slot_ctrl; + + mutex_lock(&ctrl->ctrl_lock); + + /* + * Always wait for any previous command that might still be in progress + */ + pcie_wait_cmd(ctrl); + + pcie_capability_read_word(pdev, PCI_EXP_SLTCTL, &slot_ctrl); + if (PCI_POSSIBLE_ERROR(slot_ctrl)) { + ctrl_info(ctrl, "%s: no response from device\n", __func__); + goto out; + } + + slot_ctrl_orig = slot_ctrl; + slot_ctrl &= ~mask; + slot_ctrl |= (cmd & mask); + ctrl->cmd_busy = 1; + smp_mb(); + ctrl->slot_ctrl = slot_ctrl; + pcie_capability_write_word(pdev, PCI_EXP_SLTCTL, slot_ctrl); + ctrl->cmd_started = jiffies; + + /* + * Controllers with the Intel CF118 and similar errata advertise + * Command Completed support, but they only set Command Completed + * if we change the "Control" bits for power, power indicator, + * attention indicator, or interlock. If we only change the + * "Enable" bits, they never set the Command Completed bit. + */ + if (pdev->broken_cmd_compl && + (slot_ctrl_orig & CC_ERRATUM_MASK) == (slot_ctrl & CC_ERRATUM_MASK)) + ctrl->cmd_busy = 0; + + /* + * Optionally wait for the hardware to be ready for a new command, + * indicating completion of the above issued command. + */ + if (wait) + pcie_wait_cmd(ctrl); + +out: + mutex_unlock(&ctrl->ctrl_lock); +} + +/** + * pcie_write_cmd - Issue controller command + * @ctrl: controller to which the command is issued + * @cmd: command value written to slot control register + * @mask: bitmask of slot control register to be modified + */ +static void pcie_write_cmd(struct controller *ctrl, u16 cmd, u16 mask) +{ + pcie_do_write_cmd(ctrl, cmd, mask, true); +} + +/* Same as above without waiting for the hardware to latch */ +static void pcie_write_cmd_nowait(struct controller *ctrl, u16 cmd, u16 mask) +{ + pcie_do_write_cmd(ctrl, cmd, mask, false); +} + +/** + * pciehp_check_link_active() - Is the link active + * @ctrl: PCIe hotplug controller + * + * Check whether the downstream link is currently active. Note it is + * possible that the card is removed immediately after this so the + * caller may need to take it into account. + * + * If the hotplug controller itself is not available anymore returns + * %-ENODEV. + */ +int pciehp_check_link_active(struct controller *ctrl) +{ + struct pci_dev *pdev = ctrl_dev(ctrl); + u16 lnk_status; + int ret; + + ret = pcie_capability_read_word(pdev, PCI_EXP_LNKSTA, &lnk_status); + if (ret == PCIBIOS_DEVICE_NOT_FOUND || PCI_POSSIBLE_ERROR(lnk_status)) + return -ENODEV; + + ret = !!(lnk_status & PCI_EXP_LNKSTA_DLLLA); + ctrl_dbg(ctrl, "%s: lnk_status = %x\n", __func__, lnk_status); + + return ret; +} + +static bool pci_bus_check_dev(struct pci_bus *bus, int devfn) +{ + u32 l; + int count = 0; + int delay = 1000, step = 20; + bool found = false; + + do { + found = pci_bus_read_dev_vendor_id(bus, devfn, &l, 0); + count++; + + if (found) + break; + + msleep(step); + delay -= step; + } while (delay > 0); + + if (count > 1) + pr_debug("pci %04x:%02x:%02x.%d id reading try %d times with interval %d ms to get %08x\n", + pci_domain_nr(bus), bus->number, PCI_SLOT(devfn), + PCI_FUNC(devfn), count, step, l); + + return found; +} + +static void pcie_wait_for_presence(struct pci_dev *pdev) +{ + int timeout = 1250; + u16 slot_status; + + do { + pcie_capability_read_word(pdev, PCI_EXP_SLTSTA, &slot_status); + if (slot_status & PCI_EXP_SLTSTA_PDS) + return; + msleep(10); + timeout -= 10; + } while (timeout > 0); +} + +int pciehp_check_link_status(struct controller *ctrl) +{ + struct pci_dev *pdev = ctrl_dev(ctrl); + bool found; + u16 lnk_status; + + if (!pcie_wait_for_link(pdev, true)) { + ctrl_info(ctrl, "Slot(%s): No link\n", slot_name(ctrl)); + return -1; + } + + if (ctrl->inband_presence_disabled) + pcie_wait_for_presence(pdev); + + found = pci_bus_check_dev(ctrl->pcie->port->subordinate, + PCI_DEVFN(0, 0)); + + /* ignore link or presence changes up to this point */ + if (found) + atomic_and(~(PCI_EXP_SLTSTA_DLLSC | PCI_EXP_SLTSTA_PDC), + &ctrl->pending_events); + + pcie_capability_read_word(pdev, PCI_EXP_LNKSTA, &lnk_status); + ctrl_dbg(ctrl, "%s: lnk_status = %x\n", __func__, lnk_status); + if ((lnk_status & PCI_EXP_LNKSTA_LT) || + !(lnk_status & PCI_EXP_LNKSTA_NLW)) { + ctrl_info(ctrl, "Slot(%s): Cannot train link: status %#06x\n", + slot_name(ctrl), lnk_status); + return -1; + } + + pcie_update_link_speed(ctrl->pcie->port->subordinate, lnk_status); + + if (!found) { + ctrl_info(ctrl, "Slot(%s): No device found\n", + slot_name(ctrl)); + return -1; + } + + return 0; +} + +static int __pciehp_link_set(struct controller *ctrl, bool enable) +{ + struct pci_dev *pdev = ctrl_dev(ctrl); + + pcie_capability_clear_and_set_word(pdev, PCI_EXP_LNKCTL, + PCI_EXP_LNKCTL_LD, + enable ? 0 : PCI_EXP_LNKCTL_LD); + + return 0; +} + +static int pciehp_link_enable(struct controller *ctrl) +{ + return __pciehp_link_set(ctrl, true); +} + +int pciehp_get_raw_indicator_status(struct hotplug_slot *hotplug_slot, + u8 *status) +{ + struct controller *ctrl = to_ctrl(hotplug_slot); + struct pci_dev *pdev = ctrl_dev(ctrl); + u16 slot_ctrl; + + pci_config_pm_runtime_get(pdev); + pcie_capability_read_word(pdev, PCI_EXP_SLTCTL, &slot_ctrl); + pci_config_pm_runtime_put(pdev); + *status = (slot_ctrl & (PCI_EXP_SLTCTL_AIC | PCI_EXP_SLTCTL_PIC)) >> 6; + return 0; +} + +int pciehp_get_attention_status(struct hotplug_slot *hotplug_slot, u8 *status) +{ + struct controller *ctrl = to_ctrl(hotplug_slot); + struct pci_dev *pdev = ctrl_dev(ctrl); + u16 slot_ctrl; + + pci_config_pm_runtime_get(pdev); + pcie_capability_read_word(pdev, PCI_EXP_SLTCTL, &slot_ctrl); + pci_config_pm_runtime_put(pdev); + ctrl_dbg(ctrl, "%s: SLOTCTRL %x, value read %x\n", __func__, + pci_pcie_cap(ctrl->pcie->port) + PCI_EXP_SLTCTL, slot_ctrl); + + switch (slot_ctrl & PCI_EXP_SLTCTL_AIC) { + case PCI_EXP_SLTCTL_ATTN_IND_ON: + *status = 1; /* On */ + break; + case PCI_EXP_SLTCTL_ATTN_IND_BLINK: + *status = 2; /* Blink */ + break; + case PCI_EXP_SLTCTL_ATTN_IND_OFF: + *status = 0; /* Off */ + break; + default: + *status = 0xFF; + break; + } + + return 0; +} + +void pciehp_get_power_status(struct controller *ctrl, u8 *status) +{ + struct pci_dev *pdev = ctrl_dev(ctrl); + u16 slot_ctrl; + + pcie_capability_read_word(pdev, PCI_EXP_SLTCTL, &slot_ctrl); + ctrl_dbg(ctrl, "%s: SLOTCTRL %x value read %x\n", __func__, + pci_pcie_cap(ctrl->pcie->port) + PCI_EXP_SLTCTL, slot_ctrl); + + switch (slot_ctrl & PCI_EXP_SLTCTL_PCC) { + case PCI_EXP_SLTCTL_PWR_ON: + *status = 1; /* On */ + break; + case PCI_EXP_SLTCTL_PWR_OFF: + *status = 0; /* Off */ + break; + default: + *status = 0xFF; + break; + } +} + +void pciehp_get_latch_status(struct controller *ctrl, u8 *status) +{ + struct pci_dev *pdev = ctrl_dev(ctrl); + u16 slot_status; + + pcie_capability_read_word(pdev, PCI_EXP_SLTSTA, &slot_status); + *status = !!(slot_status & PCI_EXP_SLTSTA_MRLSS); +} + +/** + * pciehp_card_present() - Is the card present + * @ctrl: PCIe hotplug controller + * + * Function checks whether the card is currently present in the slot and + * in that case returns true. Note it is possible that the card is + * removed immediately after the check so the caller may need to take + * this into account. + * + * It the hotplug controller itself is not available anymore returns + * %-ENODEV. + */ +int pciehp_card_present(struct controller *ctrl) +{ + struct pci_dev *pdev = ctrl_dev(ctrl); + u16 slot_status; + int ret; + + ret = pcie_capability_read_word(pdev, PCI_EXP_SLTSTA, &slot_status); + if (ret == PCIBIOS_DEVICE_NOT_FOUND || PCI_POSSIBLE_ERROR(slot_status)) + return -ENODEV; + + return !!(slot_status & PCI_EXP_SLTSTA_PDS); +} + +/** + * pciehp_card_present_or_link_active() - whether given slot is occupied + * @ctrl: PCIe hotplug controller + * + * Unlike pciehp_card_present(), which determines presence solely from the + * Presence Detect State bit, this helper also returns true if the Link Active + * bit is set. This is a concession to broken hotplug ports which hardwire + * Presence Detect State to zero, such as Wilocity's [1ae9:0200]. + * + * Returns: %1 if the slot is occupied and %0 if it is not. If the hotplug + * port is not present anymore returns %-ENODEV. + */ +int pciehp_card_present_or_link_active(struct controller *ctrl) +{ + int ret; + + ret = pciehp_card_present(ctrl); + if (ret) + return ret; + + return pciehp_check_link_active(ctrl); +} + +int pciehp_query_power_fault(struct controller *ctrl) +{ + struct pci_dev *pdev = ctrl_dev(ctrl); + u16 slot_status; + + pcie_capability_read_word(pdev, PCI_EXP_SLTSTA, &slot_status); + return !!(slot_status & PCI_EXP_SLTSTA_PFD); +} + +int pciehp_set_raw_indicator_status(struct hotplug_slot *hotplug_slot, + u8 status) +{ + struct controller *ctrl = to_ctrl(hotplug_slot); + struct pci_dev *pdev = ctrl_dev(ctrl); + + pci_config_pm_runtime_get(pdev); + pcie_write_cmd_nowait(ctrl, status << 6, + PCI_EXP_SLTCTL_AIC | PCI_EXP_SLTCTL_PIC); + pci_config_pm_runtime_put(pdev); + return 0; +} + +/** + * pciehp_set_indicators() - set attention indicator, power indicator, or both + * @ctrl: PCIe hotplug controller + * @pwr: one of: + * PCI_EXP_SLTCTL_PWR_IND_ON + * PCI_EXP_SLTCTL_PWR_IND_BLINK + * PCI_EXP_SLTCTL_PWR_IND_OFF + * @attn: one of: + * PCI_EXP_SLTCTL_ATTN_IND_ON + * PCI_EXP_SLTCTL_ATTN_IND_BLINK + * PCI_EXP_SLTCTL_ATTN_IND_OFF + * + * Either @pwr or @attn can also be INDICATOR_NOOP to leave that indicator + * unchanged. + */ +void pciehp_set_indicators(struct controller *ctrl, int pwr, int attn) +{ + u16 cmd = 0, mask = 0; + + if (PWR_LED(ctrl) && pwr != INDICATOR_NOOP) { + cmd |= (pwr & PCI_EXP_SLTCTL_PIC); + mask |= PCI_EXP_SLTCTL_PIC; + } + + if (ATTN_LED(ctrl) && attn != INDICATOR_NOOP) { + cmd |= (attn & PCI_EXP_SLTCTL_AIC); + mask |= PCI_EXP_SLTCTL_AIC; + } + + if (cmd) { + pcie_write_cmd_nowait(ctrl, cmd, mask); + ctrl_dbg(ctrl, "%s: SLOTCTRL %x write cmd %x\n", __func__, + pci_pcie_cap(ctrl->pcie->port) + PCI_EXP_SLTCTL, cmd); + } +} + +int pciehp_power_on_slot(struct controller *ctrl) +{ + struct pci_dev *pdev = ctrl_dev(ctrl); + u16 slot_status; + int retval; + + /* Clear power-fault bit from previous power failures */ + pcie_capability_read_word(pdev, PCI_EXP_SLTSTA, &slot_status); + if (slot_status & PCI_EXP_SLTSTA_PFD) + pcie_capability_write_word(pdev, PCI_EXP_SLTSTA, + PCI_EXP_SLTSTA_PFD); + ctrl->power_fault_detected = 0; + + pcie_write_cmd(ctrl, PCI_EXP_SLTCTL_PWR_ON, PCI_EXP_SLTCTL_PCC); + ctrl_dbg(ctrl, "%s: SLOTCTRL %x write cmd %x\n", __func__, + pci_pcie_cap(ctrl->pcie->port) + PCI_EXP_SLTCTL, + PCI_EXP_SLTCTL_PWR_ON); + + retval = pciehp_link_enable(ctrl); + if (retval) + ctrl_err(ctrl, "%s: Can not enable the link!\n", __func__); + + return retval; +} + +void pciehp_power_off_slot(struct controller *ctrl) +{ + pcie_write_cmd(ctrl, PCI_EXP_SLTCTL_PWR_OFF, PCI_EXP_SLTCTL_PCC); + ctrl_dbg(ctrl, "%s: SLOTCTRL %x write cmd %x\n", __func__, + pci_pcie_cap(ctrl->pcie->port) + PCI_EXP_SLTCTL, + PCI_EXP_SLTCTL_PWR_OFF); +} + +static void pciehp_ignore_dpc_link_change(struct controller *ctrl, + struct pci_dev *pdev, int irq) +{ + /* + * Ignore link changes which occurred while waiting for DPC recovery. + * Could be several if DPC triggered multiple times consecutively. + */ + synchronize_hardirq(irq); + atomic_and(~PCI_EXP_SLTSTA_DLLSC, &ctrl->pending_events); + if (pciehp_poll_mode) + pcie_capability_write_word(pdev, PCI_EXP_SLTSTA, + PCI_EXP_SLTSTA_DLLSC); + ctrl_info(ctrl, "Slot(%s): Link Down/Up ignored (recovered by DPC)\n", + slot_name(ctrl)); + + /* + * If the link is unexpectedly down after successful recovery, + * the corresponding link change may have been ignored above. + * Synthesize it to ensure that it is acted on. + */ + down_read_nested(&ctrl->reset_lock, ctrl->depth); + if (!pciehp_check_link_active(ctrl)) + pciehp_request(ctrl, PCI_EXP_SLTSTA_DLLSC); + up_read(&ctrl->reset_lock); +} + +static irqreturn_t pciehp_isr(int irq, void *dev_id) +{ + struct controller *ctrl = (struct controller *)dev_id; + struct pci_dev *pdev = ctrl_dev(ctrl); + struct device *parent = pdev->dev.parent; + u16 status, events = 0; + + /* + * Interrupts only occur in D3hot or shallower and only if enabled + * in the Slot Control register (PCIe r4.0, sec 6.7.3.4). + */ + if (pdev->current_state == PCI_D3cold || + (!(ctrl->slot_ctrl & PCI_EXP_SLTCTL_HPIE) && !pciehp_poll_mode)) + return IRQ_NONE; + + /* + * Keep the port accessible by holding a runtime PM ref on its parent. + * Defer resume of the parent to the IRQ thread if it's suspended. + * Mask the interrupt until then. + */ + if (parent) { + pm_runtime_get_noresume(parent); + if (!pm_runtime_active(parent)) { + pm_runtime_put(parent); + disable_irq_nosync(irq); + atomic_or(RERUN_ISR, &ctrl->pending_events); + return IRQ_WAKE_THREAD; + } + } + +read_status: + pcie_capability_read_word(pdev, PCI_EXP_SLTSTA, &status); + if (PCI_POSSIBLE_ERROR(status)) { + ctrl_info(ctrl, "%s: no response from device\n", __func__); + if (parent) + pm_runtime_put(parent); + return IRQ_NONE; + } + + /* + * Slot Status contains plain status bits as well as event + * notification bits; right now we only want the event bits. + */ + status &= PCI_EXP_SLTSTA_ABP | PCI_EXP_SLTSTA_PFD | + PCI_EXP_SLTSTA_PDC | PCI_EXP_SLTSTA_CC | + PCI_EXP_SLTSTA_DLLSC; + + /* + * If we've already reported a power fault, don't report it again + * until we've done something to handle it. + */ + if (ctrl->power_fault_detected) + status &= ~PCI_EXP_SLTSTA_PFD; + else if (status & PCI_EXP_SLTSTA_PFD) + ctrl->power_fault_detected = true; + + events |= status; + if (!events) { + if (parent) + pm_runtime_put(parent); + return IRQ_NONE; + } + + if (status) { + pcie_capability_write_word(pdev, PCI_EXP_SLTSTA, status); + + /* + * In MSI mode, all event bits must be zero before the port + * will send a new interrupt (PCIe Base Spec r5.0 sec 6.7.3.4). + * So re-read the Slot Status register in case a bit was set + * between read and write. + */ + if (pci_dev_msi_enabled(pdev) && !pciehp_poll_mode) + goto read_status; + } + + ctrl_dbg(ctrl, "pending interrupts %#06x from Slot Status\n", events); + if (parent) + pm_runtime_put(parent); + + /* + * Command Completed notifications are not deferred to the + * IRQ thread because it may be waiting for their arrival. + */ + if (events & PCI_EXP_SLTSTA_CC) { + ctrl->cmd_busy = 0; + smp_mb(); + wake_up(&ctrl->queue); + + if (events == PCI_EXP_SLTSTA_CC) + return IRQ_HANDLED; + + events &= ~PCI_EXP_SLTSTA_CC; + } + + if (pdev->ignore_hotplug) { + ctrl_dbg(ctrl, "ignoring hotplug event %#06x\n", events); + return IRQ_HANDLED; + } + + /* Save pending events for consumption by IRQ thread. */ + atomic_or(events, &ctrl->pending_events); + return IRQ_WAKE_THREAD; +} + +static irqreturn_t pciehp_ist(int irq, void *dev_id) +{ + struct controller *ctrl = (struct controller *)dev_id; + struct pci_dev *pdev = ctrl_dev(ctrl); + irqreturn_t ret; + u32 events; + + ctrl->ist_running = true; + pci_config_pm_runtime_get(pdev); + + /* rerun pciehp_isr() if the port was inaccessible on interrupt */ + if (atomic_fetch_and(~RERUN_ISR, &ctrl->pending_events) & RERUN_ISR) { + ret = pciehp_isr(irq, dev_id); + enable_irq(irq); + if (ret != IRQ_WAKE_THREAD) + goto out; + } + + synchronize_hardirq(irq); + events = atomic_xchg(&ctrl->pending_events, 0); + if (!events) { + ret = IRQ_NONE; + goto out; + } + + /* Check Attention Button Pressed */ + if (events & PCI_EXP_SLTSTA_ABP) { + ctrl_info(ctrl, "Slot(%s): Attention button pressed\n", + slot_name(ctrl)); + pciehp_handle_button_press(ctrl); + } + + /* Check Power Fault Detected */ + if (events & PCI_EXP_SLTSTA_PFD) { + ctrl_err(ctrl, "Slot(%s): Power fault\n", slot_name(ctrl)); + pciehp_set_indicators(ctrl, PCI_EXP_SLTCTL_PWR_IND_OFF, + PCI_EXP_SLTCTL_ATTN_IND_ON); + } + + /* + * Ignore Link Down/Up events caused by Downstream Port Containment + * if recovery from the error succeeded. + */ + if ((events & PCI_EXP_SLTSTA_DLLSC) && pci_dpc_recovered(pdev) && + ctrl->state == ON_STATE) { + events &= ~PCI_EXP_SLTSTA_DLLSC; + pciehp_ignore_dpc_link_change(ctrl, pdev, irq); + } + + /* + * Disable requests have higher priority than Presence Detect Changed + * or Data Link Layer State Changed events. + */ + down_read_nested(&ctrl->reset_lock, ctrl->depth); + if (events & DISABLE_SLOT) + pciehp_handle_disable_request(ctrl); + else if (events & (PCI_EXP_SLTSTA_PDC | PCI_EXP_SLTSTA_DLLSC)) + pciehp_handle_presence_or_link_change(ctrl, events); + up_read(&ctrl->reset_lock); + + ret = IRQ_HANDLED; +out: + pci_config_pm_runtime_put(pdev); + ctrl->ist_running = false; + wake_up(&ctrl->requester); + return ret; +} + +static int pciehp_poll(void *data) +{ + struct controller *ctrl = data; + + schedule_timeout_idle(10 * HZ); /* start with 10 sec delay */ + + while (!kthread_should_stop()) { + /* poll for interrupt events or user requests */ + while (pciehp_isr(IRQ_NOTCONNECTED, ctrl) == IRQ_WAKE_THREAD || + atomic_read(&ctrl->pending_events)) + pciehp_ist(IRQ_NOTCONNECTED, ctrl); + + if (pciehp_poll_time <= 0 || pciehp_poll_time > 60) + pciehp_poll_time = 2; /* clamp to sane value */ + + schedule_timeout_idle(pciehp_poll_time * HZ); + } + + return 0; +} + +static void pcie_enable_notification(struct controller *ctrl) +{ + u16 cmd, mask; + + /* + * TBD: Power fault detected software notification support. + * + * Power fault detected software notification is not enabled + * now, because it caused power fault detected interrupt storm + * on some machines. On those machines, power fault detected + * bit in the slot status register was set again immediately + * when it is cleared in the interrupt service routine, and + * next power fault detected interrupt was notified again. + */ + + /* + * Always enable link events: thus link-up and link-down shall + * always be treated as hotplug and unplug respectively. Enable + * presence detect only if Attention Button is not present. + */ + cmd = PCI_EXP_SLTCTL_DLLSCE; + if (ATTN_BUTTN(ctrl)) + cmd |= PCI_EXP_SLTCTL_ABPE; + else + cmd |= PCI_EXP_SLTCTL_PDCE; + if (!pciehp_poll_mode) + cmd |= PCI_EXP_SLTCTL_HPIE | PCI_EXP_SLTCTL_CCIE; + + mask = (PCI_EXP_SLTCTL_PDCE | PCI_EXP_SLTCTL_ABPE | + PCI_EXP_SLTCTL_PFDE | + PCI_EXP_SLTCTL_HPIE | PCI_EXP_SLTCTL_CCIE | + PCI_EXP_SLTCTL_DLLSCE); + + pcie_write_cmd_nowait(ctrl, cmd, mask); + ctrl_dbg(ctrl, "%s: SLOTCTRL %x write cmd %x\n", __func__, + pci_pcie_cap(ctrl->pcie->port) + PCI_EXP_SLTCTL, cmd); +} + +static void pcie_disable_notification(struct controller *ctrl) +{ + u16 mask; + + mask = (PCI_EXP_SLTCTL_PDCE | PCI_EXP_SLTCTL_ABPE | + PCI_EXP_SLTCTL_MRLSCE | PCI_EXP_SLTCTL_PFDE | + PCI_EXP_SLTCTL_HPIE | PCI_EXP_SLTCTL_CCIE | + PCI_EXP_SLTCTL_DLLSCE); + pcie_write_cmd(ctrl, 0, mask); + ctrl_dbg(ctrl, "%s: SLOTCTRL %x write cmd %x\n", __func__, + pci_pcie_cap(ctrl->pcie->port) + PCI_EXP_SLTCTL, 0); +} + +void pcie_clear_hotplug_events(struct controller *ctrl) +{ + pcie_capability_write_word(ctrl_dev(ctrl), PCI_EXP_SLTSTA, + PCI_EXP_SLTSTA_PDC | PCI_EXP_SLTSTA_DLLSC); +} + +void pcie_enable_interrupt(struct controller *ctrl) +{ + u16 mask; + + mask = PCI_EXP_SLTCTL_HPIE | PCI_EXP_SLTCTL_DLLSCE; + pcie_write_cmd(ctrl, mask, mask); +} + +void pcie_disable_interrupt(struct controller *ctrl) +{ + u16 mask; + + /* + * Mask hot-plug interrupt to prevent it triggering immediately + * when the link goes inactive (we still get PME when any of the + * enabled events is detected). Same goes with Link Layer State + * changed event which generates PME immediately when the link goes + * inactive so mask it as well. + */ + mask = PCI_EXP_SLTCTL_HPIE | PCI_EXP_SLTCTL_DLLSCE; + pcie_write_cmd(ctrl, 0, mask); +} + +/** + * pciehp_slot_reset() - ignore link event caused by error-induced hot reset + * @dev: PCI Express port service device + * + * Called from pcie_portdrv_slot_reset() after AER or DPC initiated a reset + * further up in the hierarchy to recover from an error. The reset was + * propagated down to this hotplug port. Ignore the resulting link flap. + * If the link failed to retrain successfully, synthesize the ignored event. + * Surprise removal during reset is detected through Presence Detect Changed. + */ +int pciehp_slot_reset(struct pcie_device *dev) +{ + struct controller *ctrl = get_service_data(dev); + + if (ctrl->state != ON_STATE) + return 0; + + pcie_capability_write_word(dev->port, PCI_EXP_SLTSTA, + PCI_EXP_SLTSTA_DLLSC); + + if (!pciehp_check_link_active(ctrl)) + pciehp_request(ctrl, PCI_EXP_SLTSTA_DLLSC); + + return 0; +} + +/* + * pciehp has a 1:1 bus:slot relationship so we ultimately want a secondary + * bus reset of the bridge, but at the same time we want to ensure that it is + * not seen as a hot-unplug, followed by the hot-plug of the device. Thus, + * disable link state notification and presence detection change notification + * momentarily, if we see that they could interfere. Also, clear any spurious + * events after. + */ +int pciehp_reset_slot(struct hotplug_slot *hotplug_slot, bool probe) +{ + struct controller *ctrl = to_ctrl(hotplug_slot); + struct pci_dev *pdev = ctrl_dev(ctrl); + u16 stat_mask = 0, ctrl_mask = 0; + int rc; + + if (probe) + return 0; + + down_write_nested(&ctrl->reset_lock, ctrl->depth); + + if (!ATTN_BUTTN(ctrl)) { + ctrl_mask |= PCI_EXP_SLTCTL_PDCE; + stat_mask |= PCI_EXP_SLTSTA_PDC; + } + ctrl_mask |= PCI_EXP_SLTCTL_DLLSCE; + stat_mask |= PCI_EXP_SLTSTA_DLLSC; + + pcie_write_cmd(ctrl, 0, ctrl_mask); + ctrl_dbg(ctrl, "%s: SLOTCTRL %x write cmd %x\n", __func__, + pci_pcie_cap(ctrl->pcie->port) + PCI_EXP_SLTCTL, 0); + + rc = pci_bridge_secondary_bus_reset(ctrl->pcie->port); + + pcie_capability_write_word(pdev, PCI_EXP_SLTSTA, stat_mask); + pcie_write_cmd_nowait(ctrl, ctrl_mask, ctrl_mask); + ctrl_dbg(ctrl, "%s: SLOTCTRL %x write cmd %x\n", __func__, + pci_pcie_cap(ctrl->pcie->port) + PCI_EXP_SLTCTL, ctrl_mask); + + up_write(&ctrl->reset_lock); + return rc; +} + +int pcie_init_notification(struct controller *ctrl) +{ + if (pciehp_request_irq(ctrl)) + return -1; + pcie_enable_notification(ctrl); + ctrl->notification_enabled = 1; + return 0; +} + +void pcie_shutdown_notification(struct controller *ctrl) +{ + if (ctrl->notification_enabled) { + pcie_disable_notification(ctrl); + pciehp_free_irq(ctrl); + ctrl->notification_enabled = 0; + } +} + +static inline void dbg_ctrl(struct controller *ctrl) +{ + struct pci_dev *pdev = ctrl->pcie->port; + u16 reg16; + + ctrl_dbg(ctrl, "Slot Capabilities : 0x%08x\n", ctrl->slot_cap); + pcie_capability_read_word(pdev, PCI_EXP_SLTSTA, ®16); + ctrl_dbg(ctrl, "Slot Status : 0x%04x\n", reg16); + pcie_capability_read_word(pdev, PCI_EXP_SLTCTL, ®16); + ctrl_dbg(ctrl, "Slot Control : 0x%04x\n", reg16); +} + +#define FLAG(x, y) (((x) & (y)) ? '+' : '-') + +static inline int pcie_hotplug_depth(struct pci_dev *dev) +{ + struct pci_bus *bus = dev->bus; + int depth = 0; + + while (bus->parent) { + bus = bus->parent; + if (bus->self && bus->self->is_hotplug_bridge) + depth++; + } + + return depth; +} + +struct controller *pcie_init(struct pcie_device *dev) +{ + struct controller *ctrl; + u32 slot_cap, slot_cap2, link_cap; + u8 poweron; + struct pci_dev *pdev = dev->port; + struct pci_bus *subordinate = pdev->subordinate; + + ctrl = kzalloc(sizeof(*ctrl), GFP_KERNEL); + if (!ctrl) + return NULL; + + ctrl->pcie = dev; + ctrl->depth = pcie_hotplug_depth(dev->port); + pcie_capability_read_dword(pdev, PCI_EXP_SLTCAP, &slot_cap); + + if (pdev->hotplug_user_indicators) + slot_cap &= ~(PCI_EXP_SLTCAP_AIP | PCI_EXP_SLTCAP_PIP); + + /* + * We assume no Thunderbolt controllers support Command Complete events, + * but some controllers falsely claim they do. + */ + if (pdev->is_thunderbolt) + slot_cap |= PCI_EXP_SLTCAP_NCCS; + + ctrl->slot_cap = slot_cap; + mutex_init(&ctrl->ctrl_lock); + mutex_init(&ctrl->state_lock); + init_rwsem(&ctrl->reset_lock); + init_waitqueue_head(&ctrl->requester); + init_waitqueue_head(&ctrl->queue); + INIT_DELAYED_WORK(&ctrl->button_work, pciehp_queue_pushbutton_work); + dbg_ctrl(ctrl); + + down_read(&pci_bus_sem); + ctrl->state = list_empty(&subordinate->devices) ? OFF_STATE : ON_STATE; + up_read(&pci_bus_sem); + + pcie_capability_read_dword(pdev, PCI_EXP_SLTCAP2, &slot_cap2); + if (slot_cap2 & PCI_EXP_SLTCAP2_IBPD) { + pcie_write_cmd_nowait(ctrl, PCI_EXP_SLTCTL_IBPD_DISABLE, + PCI_EXP_SLTCTL_IBPD_DISABLE); + ctrl->inband_presence_disabled = 1; + } + + if (dmi_first_match(inband_presence_disabled_dmi_table)) + ctrl->inband_presence_disabled = 1; + + /* Check if Data Link Layer Link Active Reporting is implemented */ + pcie_capability_read_dword(pdev, PCI_EXP_LNKCAP, &link_cap); + + /* Clear all remaining event bits in Slot Status register. */ + pcie_capability_write_word(pdev, PCI_EXP_SLTSTA, + PCI_EXP_SLTSTA_ABP | PCI_EXP_SLTSTA_PFD | + PCI_EXP_SLTSTA_MRLSC | PCI_EXP_SLTSTA_CC | + PCI_EXP_SLTSTA_DLLSC | PCI_EXP_SLTSTA_PDC); + + ctrl_info(ctrl, "Slot #%d AttnBtn%c PwrCtrl%c MRL%c AttnInd%c PwrInd%c HotPlug%c Surprise%c Interlock%c NoCompl%c IbPresDis%c LLActRep%c%s\n", + (slot_cap & PCI_EXP_SLTCAP_PSN) >> 19, + FLAG(slot_cap, PCI_EXP_SLTCAP_ABP), + FLAG(slot_cap, PCI_EXP_SLTCAP_PCP), + FLAG(slot_cap, PCI_EXP_SLTCAP_MRLSP), + FLAG(slot_cap, PCI_EXP_SLTCAP_AIP), + FLAG(slot_cap, PCI_EXP_SLTCAP_PIP), + FLAG(slot_cap, PCI_EXP_SLTCAP_HPC), + FLAG(slot_cap, PCI_EXP_SLTCAP_HPS), + FLAG(slot_cap, PCI_EXP_SLTCAP_EIP), + FLAG(slot_cap, PCI_EXP_SLTCAP_NCCS), + FLAG(slot_cap2, PCI_EXP_SLTCAP2_IBPD), + FLAG(link_cap, PCI_EXP_LNKCAP_DLLLARC), + pdev->broken_cmd_compl ? " (with Cmd Compl erratum)" : ""); + + /* + * If empty slot's power status is on, turn power off. The IRQ isn't + * requested yet, so avoid triggering a notification with this command. + */ + if (POWER_CTRL(ctrl)) { + pciehp_get_power_status(ctrl, &poweron); + if (!pciehp_card_present_or_link_active(ctrl) && poweron) { + pcie_disable_notification(ctrl); + pciehp_power_off_slot(ctrl); + } + } + + return ctrl; +} + +void pciehp_release_ctrl(struct controller *ctrl) +{ + cancel_delayed_work_sync(&ctrl->button_work); + kfree(ctrl); +} + +static void quirk_cmd_compl(struct pci_dev *pdev) +{ + u32 slot_cap; + + if (pci_is_pcie(pdev)) { + pcie_capability_read_dword(pdev, PCI_EXP_SLTCAP, &slot_cap); + if (slot_cap & PCI_EXP_SLTCAP_HPC && + !(slot_cap & PCI_EXP_SLTCAP_NCCS)) + pdev->broken_cmd_compl = 1; + } +} +DECLARE_PCI_FIXUP_CLASS_EARLY(PCI_VENDOR_ID_INTEL, PCI_ANY_ID, + PCI_CLASS_BRIDGE_PCI, 8, quirk_cmd_compl); +DECLARE_PCI_FIXUP_CLASS_EARLY(PCI_VENDOR_ID_QCOM, 0x010e, + PCI_CLASS_BRIDGE_PCI, 8, quirk_cmd_compl); +DECLARE_PCI_FIXUP_CLASS_EARLY(PCI_VENDOR_ID_QCOM, 0x0110, + PCI_CLASS_BRIDGE_PCI, 8, quirk_cmd_compl); +DECLARE_PCI_FIXUP_CLASS_EARLY(PCI_VENDOR_ID_QCOM, 0x0400, + PCI_CLASS_BRIDGE_PCI, 8, quirk_cmd_compl); +DECLARE_PCI_FIXUP_CLASS_EARLY(PCI_VENDOR_ID_QCOM, 0x0401, + PCI_CLASS_BRIDGE_PCI, 8, quirk_cmd_compl); +DECLARE_PCI_FIXUP_CLASS_EARLY(PCI_VENDOR_ID_HXT, 0x0401, + PCI_CLASS_BRIDGE_PCI, 8, quirk_cmd_compl); diff --git a/drivers/pci/hotplug/pciehp_pci.c b/drivers/pci/hotplug/pciehp_pci.c new file mode 100644 index 000000000..ad12515a4 --- /dev/null +++ b/drivers/pci/hotplug/pciehp_pci.c @@ -0,0 +1,137 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * 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. + * + * Send feedback to <greg@kroah.com>, <kristen.c.accardi@intel.com> + * + */ + +#define dev_fmt(fmt) "pciehp: " fmt + +#include <linux/kernel.h> +#include <linux/types.h> +#include <linux/pci.h> +#include "../pci.h" +#include "pciehp.h" + +/** + * pciehp_configure_device() - enumerate PCI devices below a hotplug bridge + * @ctrl: PCIe hotplug controller + * + * Enumerate PCI devices below a hotplug bridge and add them to the system. + * Return 0 on success, %-EEXIST if the devices are already enumerated or + * %-ENODEV if enumeration failed. + */ +int pciehp_configure_device(struct controller *ctrl) +{ + struct pci_dev *dev; + struct pci_dev *bridge = ctrl->pcie->port; + struct pci_bus *parent = bridge->subordinate; + int num, ret = 0; + + pci_lock_rescan_remove(); + + dev = pci_get_slot(parent, PCI_DEVFN(0, 0)); + if (dev) { + /* + * The device is already there. Either configured by the + * boot firmware or a previous hotplug event. + */ + ctrl_dbg(ctrl, "Device %s already exists at %04x:%02x:00, skipping hot-add\n", + pci_name(dev), pci_domain_nr(parent), parent->number); + pci_dev_put(dev); + ret = -EEXIST; + goto out; + } + + num = pci_scan_slot(parent, PCI_DEVFN(0, 0)); + if (num == 0) { + ctrl_err(ctrl, "No new device found\n"); + ret = -ENODEV; + goto out; + } + + for_each_pci_bridge(dev, parent) + pci_hp_add_bridge(dev); + + pci_assign_unassigned_bridge_resources(bridge); + pcie_bus_configure_settings(parent); + + /* + * Release reset_lock during driver binding + * to avoid AB-BA deadlock with device_lock. + */ + up_read(&ctrl->reset_lock); + pci_bus_add_devices(parent); + down_read_nested(&ctrl->reset_lock, ctrl->depth); + + out: + pci_unlock_rescan_remove(); + return ret; +} + +/** + * pciehp_unconfigure_device() - remove PCI devices below a hotplug bridge + * @ctrl: PCIe hotplug controller + * @presence: whether the card is still present in the slot; + * true for safe removal via sysfs or an Attention Button press, + * false for surprise removal + * + * Unbind PCI devices below a hotplug bridge from their drivers and remove + * them from the system. Safely removed devices are quiesced. Surprise + * removed devices are marked as such to prevent further accesses. + */ +void pciehp_unconfigure_device(struct controller *ctrl, bool presence) +{ + struct pci_dev *dev, *temp; + struct pci_bus *parent = ctrl->pcie->port->subordinate; + u16 command; + + ctrl_dbg(ctrl, "%s: domain:bus:dev = %04x:%02x:00\n", + __func__, pci_domain_nr(parent), parent->number); + + if (!presence) + pci_walk_bus(parent, pci_dev_set_disconnected, NULL); + + pci_lock_rescan_remove(); + + /* + * Stopping an SR-IOV PF device removes all the associated VFs, + * which will update the bus->devices list and confuse the + * iterator. Therefore, iterate in reverse so we remove the VFs + * first, then the PF. We do the same in pci_stop_bus_device(). + */ + list_for_each_entry_safe_reverse(dev, temp, &parent->devices, + bus_list) { + pci_dev_get(dev); + + /* + * Release reset_lock during driver unbinding + * to avoid AB-BA deadlock with device_lock. + */ + up_read(&ctrl->reset_lock); + pci_stop_and_remove_bus_device(dev); + down_read_nested(&ctrl->reset_lock, ctrl->depth); + + /* + * Ensure that no new Requests will be generated from + * the device. + */ + if (presence) { + pci_read_config_word(dev, PCI_COMMAND, &command); + command &= ~(PCI_COMMAND_MASTER | PCI_COMMAND_SERR); + command |= PCI_COMMAND_INTX_DISABLE; + pci_write_config_word(dev, PCI_COMMAND, command); + } + pci_dev_put(dev); + } + + pci_unlock_rescan_remove(); +} diff --git a/drivers/pci/hotplug/pnv_php.c b/drivers/pci/hotplug/pnv_php.c new file mode 100644 index 000000000..881d42063 --- /dev/null +++ b/drivers/pci/hotplug/pnv_php.c @@ -0,0 +1,1049 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * PCI Hotplug Driver for PowerPC PowerNV platform. + * + * Copyright Gavin Shan, IBM Corporation 2016. + */ + +#include <linux/libfdt.h> +#include <linux/module.h> +#include <linux/pci.h> +#include <linux/pci_hotplug.h> +#include <linux/of_fdt.h> + +#include <asm/opal.h> +#include <asm/pnv-pci.h> +#include <asm/ppc-pci.h> + +#define DRIVER_VERSION "0.1" +#define DRIVER_AUTHOR "Gavin Shan, IBM Corporation" +#define DRIVER_DESC "PowerPC PowerNV PCI Hotplug Driver" + +#define SLOT_WARN(sl, x...) \ + ((sl)->pdev ? pci_warn((sl)->pdev, x) : dev_warn(&(sl)->bus->dev, x)) + +struct pnv_php_event { + bool added; + struct pnv_php_slot *php_slot; + struct work_struct work; +}; + +static LIST_HEAD(pnv_php_slot_list); +static DEFINE_SPINLOCK(pnv_php_lock); + +static void pnv_php_register(struct device_node *dn); +static void pnv_php_unregister_one(struct device_node *dn); +static void pnv_php_unregister(struct device_node *dn); + +static void pnv_php_disable_irq(struct pnv_php_slot *php_slot, + bool disable_device) +{ + struct pci_dev *pdev = php_slot->pdev; + int irq = php_slot->irq; + u16 ctrl; + + if (php_slot->irq > 0) { + pcie_capability_read_word(pdev, PCI_EXP_SLTCTL, &ctrl); + ctrl &= ~(PCI_EXP_SLTCTL_HPIE | + PCI_EXP_SLTCTL_PDCE | + PCI_EXP_SLTCTL_DLLSCE); + pcie_capability_write_word(pdev, PCI_EXP_SLTCTL, ctrl); + + free_irq(php_slot->irq, php_slot); + php_slot->irq = 0; + } + + if (php_slot->wq) { + destroy_workqueue(php_slot->wq); + php_slot->wq = NULL; + } + + if (disable_device || irq > 0) { + if (pdev->msix_enabled) + pci_disable_msix(pdev); + else if (pdev->msi_enabled) + pci_disable_msi(pdev); + + pci_disable_device(pdev); + } +} + +static void pnv_php_free_slot(struct kref *kref) +{ + struct pnv_php_slot *php_slot = container_of(kref, + struct pnv_php_slot, kref); + + WARN_ON(!list_empty(&php_slot->children)); + pnv_php_disable_irq(php_slot, false); + kfree(php_slot->name); + kfree(php_slot); +} + +static inline void pnv_php_put_slot(struct pnv_php_slot *php_slot) +{ + + if (!php_slot) + return; + + kref_put(&php_slot->kref, pnv_php_free_slot); +} + +static struct pnv_php_slot *pnv_php_match(struct device_node *dn, + struct pnv_php_slot *php_slot) +{ + struct pnv_php_slot *target, *tmp; + + if (php_slot->dn == dn) { + kref_get(&php_slot->kref); + return php_slot; + } + + list_for_each_entry(tmp, &php_slot->children, link) { + target = pnv_php_match(dn, tmp); + if (target) + return target; + } + + return NULL; +} + +struct pnv_php_slot *pnv_php_find_slot(struct device_node *dn) +{ + struct pnv_php_slot *php_slot, *tmp; + unsigned long flags; + + spin_lock_irqsave(&pnv_php_lock, flags); + list_for_each_entry(tmp, &pnv_php_slot_list, link) { + php_slot = pnv_php_match(dn, tmp); + if (php_slot) { + spin_unlock_irqrestore(&pnv_php_lock, flags); + return php_slot; + } + } + spin_unlock_irqrestore(&pnv_php_lock, flags); + + return NULL; +} +EXPORT_SYMBOL_GPL(pnv_php_find_slot); + +/* + * Remove pdn for all children of the indicated device node. + * The function should remove pdn in a depth-first manner. + */ +static void pnv_php_rmv_pdns(struct device_node *dn) +{ + struct device_node *child; + + for_each_child_of_node(dn, child) { + pnv_php_rmv_pdns(child); + + pci_remove_device_node_info(child); + } +} + +/* + * Detach all child nodes of the indicated device nodes. The + * function should handle device nodes in depth-first manner. + * + * We should not invoke of_node_release() as the memory for + * individual device node is part of large memory block. The + * large block is allocated from memblock (system bootup) or + * kmalloc() when unflattening the device tree by OF changeset. + * We can not free the large block allocated from memblock. For + * later case, it should be released at once. + */ +static void pnv_php_detach_device_nodes(struct device_node *parent) +{ + struct device_node *dn; + + for_each_child_of_node(parent, dn) { + pnv_php_detach_device_nodes(dn); + + of_node_put(dn); + of_detach_node(dn); + } +} + +static void pnv_php_rmv_devtree(struct pnv_php_slot *php_slot) +{ + pnv_php_rmv_pdns(php_slot->dn); + + /* + * Decrease the refcount if the device nodes were created + * through OF changeset before detaching them. + */ + if (php_slot->fdt) + of_changeset_destroy(&php_slot->ocs); + pnv_php_detach_device_nodes(php_slot->dn); + + if (php_slot->fdt) { + kfree(php_slot->dt); + kfree(php_slot->fdt); + php_slot->dt = NULL; + php_slot->dn->child = NULL; + php_slot->fdt = NULL; + } +} + +/* + * As the nodes in OF changeset are applied in reverse order, we + * need revert the nodes in advance so that we have correct node + * order after the changeset is applied. + */ +static void pnv_php_reverse_nodes(struct device_node *parent) +{ + struct device_node *child, *next; + + /* In-depth first */ + for_each_child_of_node(parent, child) + pnv_php_reverse_nodes(child); + + /* Reverse the nodes in the child list */ + child = parent->child; + parent->child = NULL; + while (child) { + next = child->sibling; + + child->sibling = parent->child; + parent->child = child; + child = next; + } +} + +static int pnv_php_populate_changeset(struct of_changeset *ocs, + struct device_node *dn) +{ + struct device_node *child; + int ret = 0; + + for_each_child_of_node(dn, child) { + ret = of_changeset_attach_node(ocs, child); + if (ret) { + of_node_put(child); + break; + } + + ret = pnv_php_populate_changeset(ocs, child); + if (ret) { + of_node_put(child); + break; + } + } + + return ret; +} + +static void *pnv_php_add_one_pdn(struct device_node *dn, void *data) +{ + struct pci_controller *hose = (struct pci_controller *)data; + struct pci_dn *pdn; + + pdn = pci_add_device_node_info(hose, dn); + if (!pdn) + return ERR_PTR(-ENOMEM); + + return NULL; +} + +static void pnv_php_add_pdns(struct pnv_php_slot *slot) +{ + struct pci_controller *hose = pci_bus_to_host(slot->bus); + + pci_traverse_device_nodes(slot->dn, pnv_php_add_one_pdn, hose); +} + +static int pnv_php_add_devtree(struct pnv_php_slot *php_slot) +{ + void *fdt, *fdt1, *dt; + int ret; + + /* We don't know the FDT blob size. We try to get it through + * maximal memory chunk and then copy it to another chunk that + * fits the real size. + */ + fdt1 = kzalloc(0x10000, GFP_KERNEL); + if (!fdt1) { + ret = -ENOMEM; + goto out; + } + + ret = pnv_pci_get_device_tree(php_slot->dn->phandle, fdt1, 0x10000); + if (ret) { + SLOT_WARN(php_slot, "Error %d getting FDT blob\n", ret); + goto free_fdt1; + } + + fdt = kmemdup(fdt1, fdt_totalsize(fdt1), GFP_KERNEL); + if (!fdt) { + ret = -ENOMEM; + goto free_fdt1; + } + + /* Unflatten device tree blob */ + dt = of_fdt_unflatten_tree(fdt, php_slot->dn, NULL); + if (!dt) { + ret = -EINVAL; + SLOT_WARN(php_slot, "Cannot unflatten FDT\n"); + goto free_fdt; + } + + /* Initialize and apply the changeset */ + of_changeset_init(&php_slot->ocs); + pnv_php_reverse_nodes(php_slot->dn); + ret = pnv_php_populate_changeset(&php_slot->ocs, php_slot->dn); + if (ret) { + pnv_php_reverse_nodes(php_slot->dn); + SLOT_WARN(php_slot, "Error %d populating changeset\n", + ret); + goto free_dt; + } + + php_slot->dn->child = NULL; + ret = of_changeset_apply(&php_slot->ocs); + if (ret) { + SLOT_WARN(php_slot, "Error %d applying changeset\n", ret); + goto destroy_changeset; + } + + /* Add device node firmware data */ + pnv_php_add_pdns(php_slot); + php_slot->fdt = fdt; + php_slot->dt = dt; + kfree(fdt1); + goto out; + +destroy_changeset: + of_changeset_destroy(&php_slot->ocs); +free_dt: + kfree(dt); + php_slot->dn->child = NULL; +free_fdt: + kfree(fdt); +free_fdt1: + kfree(fdt1); +out: + return ret; +} + +static inline struct pnv_php_slot *to_pnv_php_slot(struct hotplug_slot *slot) +{ + return container_of(slot, struct pnv_php_slot, slot); +} + +int pnv_php_set_slot_power_state(struct hotplug_slot *slot, + uint8_t state) +{ + struct pnv_php_slot *php_slot = to_pnv_php_slot(slot); + struct opal_msg msg; + int ret; + + ret = pnv_pci_set_power_state(php_slot->id, state, &msg); + if (ret > 0) { + if (be64_to_cpu(msg.params[1]) != php_slot->dn->phandle || + be64_to_cpu(msg.params[2]) != state) { + SLOT_WARN(php_slot, "Wrong msg (%lld, %lld, %lld)\n", + be64_to_cpu(msg.params[1]), + be64_to_cpu(msg.params[2]), + be64_to_cpu(msg.params[3])); + return -ENOMSG; + } + if (be64_to_cpu(msg.params[3]) != OPAL_SUCCESS) { + ret = -ENODEV; + goto error; + } + } else if (ret < 0) { + goto error; + } + + if (state == OPAL_PCI_SLOT_POWER_OFF || state == OPAL_PCI_SLOT_OFFLINE) + pnv_php_rmv_devtree(php_slot); + else + ret = pnv_php_add_devtree(php_slot); + + return ret; + +error: + SLOT_WARN(php_slot, "Error %d powering %s\n", + ret, (state == OPAL_PCI_SLOT_POWER_ON) ? "on" : "off"); + return ret; +} +EXPORT_SYMBOL_GPL(pnv_php_set_slot_power_state); + +static int pnv_php_get_power_state(struct hotplug_slot *slot, u8 *state) +{ + struct pnv_php_slot *php_slot = to_pnv_php_slot(slot); + uint8_t power_state = OPAL_PCI_SLOT_POWER_ON; + int ret; + + /* + * Retrieve power status from firmware. If we fail + * getting that, the power status fails back to + * be on. + */ + ret = pnv_pci_get_power_state(php_slot->id, &power_state); + if (ret) { + SLOT_WARN(php_slot, "Error %d getting power status\n", + ret); + } else { + *state = power_state; + } + + return 0; +} + +static int pnv_php_get_adapter_state(struct hotplug_slot *slot, u8 *state) +{ + struct pnv_php_slot *php_slot = to_pnv_php_slot(slot); + uint8_t presence = OPAL_PCI_SLOT_EMPTY; + int ret; + + /* + * Retrieve presence status from firmware. If we can't + * get that, it will fail back to be empty. + */ + ret = pnv_pci_get_presence_state(php_slot->id, &presence); + if (ret >= 0) { + *state = presence; + ret = 0; + } else { + SLOT_WARN(php_slot, "Error %d getting presence\n", ret); + } + + return ret; +} + +static int pnv_php_get_attention_state(struct hotplug_slot *slot, u8 *state) +{ + struct pnv_php_slot *php_slot = to_pnv_php_slot(slot); + + *state = php_slot->attention_state; + return 0; +} + +static int pnv_php_set_attention_state(struct hotplug_slot *slot, u8 state) +{ + struct pnv_php_slot *php_slot = to_pnv_php_slot(slot); + struct pci_dev *bridge = php_slot->pdev; + u16 new, mask; + + php_slot->attention_state = state; + if (!bridge) + return 0; + + mask = PCI_EXP_SLTCTL_AIC; + + if (state) + new = PCI_EXP_SLTCTL_ATTN_IND_ON; + else + new = PCI_EXP_SLTCTL_ATTN_IND_OFF; + + pcie_capability_clear_and_set_word(bridge, PCI_EXP_SLTCTL, mask, new); + + return 0; +} + +static int pnv_php_enable(struct pnv_php_slot *php_slot, bool rescan) +{ + struct hotplug_slot *slot = &php_slot->slot; + uint8_t presence = OPAL_PCI_SLOT_EMPTY; + uint8_t power_status = OPAL_PCI_SLOT_POWER_ON; + int ret; + + /* Check if the slot has been configured */ + if (php_slot->state != PNV_PHP_STATE_REGISTERED) + return 0; + + /* Retrieve slot presence status */ + ret = pnv_php_get_adapter_state(slot, &presence); + if (ret) + return ret; + + /* + * Proceed if there have nothing behind the slot. However, + * we should leave the slot in registered state at the + * beginning. Otherwise, the PCI devices inserted afterwards + * won't be probed and populated. + */ + if (presence == OPAL_PCI_SLOT_EMPTY) { + if (!php_slot->power_state_check) { + php_slot->power_state_check = true; + + return 0; + } + + goto scan; + } + + /* + * If the power supply to the slot is off, we can't detect + * adapter presence state. That means we have to turn the + * slot on before going to probe slot's presence state. + * + * On the first time, we don't change the power status to + * boost system boot with assumption that the firmware + * supplies consistent slot power status: empty slot always + * has its power off and non-empty slot has its power on. + */ + if (!php_slot->power_state_check) { + php_slot->power_state_check = true; + + ret = pnv_php_get_power_state(slot, &power_status); + if (ret) + return ret; + + if (power_status != OPAL_PCI_SLOT_POWER_ON) + return 0; + } + + /* Check the power status. Scan the slot if it is already on */ + ret = pnv_php_get_power_state(slot, &power_status); + if (ret) + return ret; + + if (power_status == OPAL_PCI_SLOT_POWER_ON) + goto scan; + + /* Power is off, turn it on and then scan the slot */ + ret = pnv_php_set_slot_power_state(slot, OPAL_PCI_SLOT_POWER_ON); + if (ret) + return ret; + +scan: + if (presence == OPAL_PCI_SLOT_PRESENT) { + if (rescan) { + pci_lock_rescan_remove(); + pci_hp_add_devices(php_slot->bus); + pci_unlock_rescan_remove(); + } + + /* Rescan for child hotpluggable slots */ + php_slot->state = PNV_PHP_STATE_POPULATED; + if (rescan) + pnv_php_register(php_slot->dn); + } else { + php_slot->state = PNV_PHP_STATE_POPULATED; + } + + return 0; +} + +static int pnv_php_reset_slot(struct hotplug_slot *slot, bool probe) +{ + struct pnv_php_slot *php_slot = to_pnv_php_slot(slot); + struct pci_dev *bridge = php_slot->pdev; + uint16_t sts; + + /* + * The CAPI folks want pnv_php to drive OpenCAPI slots + * which don't have a bridge. Only claim to support + * reset_slot() if we have a bridge device (for now...) + */ + if (probe) + return !bridge; + + /* mask our interrupt while resetting the bridge */ + if (php_slot->irq > 0) + disable_irq(php_slot->irq); + + pci_bridge_secondary_bus_reset(bridge); + + /* clear any state changes that happened due to the reset */ + pcie_capability_read_word(php_slot->pdev, PCI_EXP_SLTSTA, &sts); + sts &= (PCI_EXP_SLTSTA_PDC | PCI_EXP_SLTSTA_DLLSC); + pcie_capability_write_word(php_slot->pdev, PCI_EXP_SLTSTA, sts); + + if (php_slot->irq > 0) + enable_irq(php_slot->irq); + + return 0; +} + +static int pnv_php_enable_slot(struct hotplug_slot *slot) +{ + struct pnv_php_slot *php_slot = to_pnv_php_slot(slot); + + return pnv_php_enable(php_slot, true); +} + +static int pnv_php_disable_slot(struct hotplug_slot *slot) +{ + struct pnv_php_slot *php_slot = to_pnv_php_slot(slot); + int ret; + + /* + * Allow to disable a slot already in the registered state to + * cover cases where the slot couldn't be enabled and never + * reached the populated state + */ + if (php_slot->state != PNV_PHP_STATE_POPULATED && + php_slot->state != PNV_PHP_STATE_REGISTERED) + return 0; + + /* Remove all devices behind the slot */ + pci_lock_rescan_remove(); + pci_hp_remove_devices(php_slot->bus); + pci_unlock_rescan_remove(); + + /* Detach the child hotpluggable slots */ + pnv_php_unregister(php_slot->dn); + + /* Notify firmware and remove device nodes */ + ret = pnv_php_set_slot_power_state(slot, OPAL_PCI_SLOT_POWER_OFF); + + php_slot->state = PNV_PHP_STATE_REGISTERED; + return ret; +} + +static const struct hotplug_slot_ops php_slot_ops = { + .get_power_status = pnv_php_get_power_state, + .get_adapter_status = pnv_php_get_adapter_state, + .get_attention_status = pnv_php_get_attention_state, + .set_attention_status = pnv_php_set_attention_state, + .enable_slot = pnv_php_enable_slot, + .disable_slot = pnv_php_disable_slot, + .reset_slot = pnv_php_reset_slot, +}; + +static void pnv_php_release(struct pnv_php_slot *php_slot) +{ + unsigned long flags; + + /* Remove from global or child list */ + spin_lock_irqsave(&pnv_php_lock, flags); + list_del(&php_slot->link); + spin_unlock_irqrestore(&pnv_php_lock, flags); + + /* Detach from parent */ + pnv_php_put_slot(php_slot); + pnv_php_put_slot(php_slot->parent); +} + +static struct pnv_php_slot *pnv_php_alloc_slot(struct device_node *dn) +{ + struct pnv_php_slot *php_slot; + struct pci_bus *bus; + const char *label; + uint64_t id; + int ret; + + ret = of_property_read_string(dn, "ibm,slot-label", &label); + if (ret) + return NULL; + + if (pnv_pci_get_slot_id(dn, &id)) + return NULL; + + bus = pci_find_bus_by_node(dn); + if (!bus) + return NULL; + + php_slot = kzalloc(sizeof(*php_slot), GFP_KERNEL); + if (!php_slot) + return NULL; + + php_slot->name = kstrdup(label, GFP_KERNEL); + if (!php_slot->name) { + kfree(php_slot); + return NULL; + } + + if (dn->child && PCI_DN(dn->child)) + php_slot->slot_no = PCI_SLOT(PCI_DN(dn->child)->devfn); + else + php_slot->slot_no = -1; /* Placeholder slot */ + + kref_init(&php_slot->kref); + php_slot->state = PNV_PHP_STATE_INITIALIZED; + php_slot->dn = dn; + php_slot->pdev = bus->self; + php_slot->bus = bus; + php_slot->id = id; + php_slot->power_state_check = false; + php_slot->slot.ops = &php_slot_ops; + + INIT_LIST_HEAD(&php_slot->children); + INIT_LIST_HEAD(&php_slot->link); + + return php_slot; +} + +static int pnv_php_register_slot(struct pnv_php_slot *php_slot) +{ + struct pnv_php_slot *parent; + struct device_node *dn = php_slot->dn; + unsigned long flags; + int ret; + + /* Check if the slot is registered or not */ + parent = pnv_php_find_slot(php_slot->dn); + if (parent) { + pnv_php_put_slot(parent); + return -EEXIST; + } + + /* Register PCI slot */ + ret = pci_hp_register(&php_slot->slot, php_slot->bus, + php_slot->slot_no, php_slot->name); + if (ret) { + SLOT_WARN(php_slot, "Error %d registering slot\n", ret); + return ret; + } + + /* Attach to the parent's child list or global list */ + while ((dn = of_get_parent(dn))) { + if (!PCI_DN(dn)) { + of_node_put(dn); + break; + } + + parent = pnv_php_find_slot(dn); + if (parent) { + of_node_put(dn); + break; + } + + of_node_put(dn); + } + + spin_lock_irqsave(&pnv_php_lock, flags); + php_slot->parent = parent; + if (parent) + list_add_tail(&php_slot->link, &parent->children); + else + list_add_tail(&php_slot->link, &pnv_php_slot_list); + spin_unlock_irqrestore(&pnv_php_lock, flags); + + php_slot->state = PNV_PHP_STATE_REGISTERED; + return 0; +} + +static int pnv_php_enable_msix(struct pnv_php_slot *php_slot) +{ + struct pci_dev *pdev = php_slot->pdev; + struct msix_entry entry; + int nr_entries, ret; + u16 pcie_flag; + + /* Get total number of MSIx entries */ + nr_entries = pci_msix_vec_count(pdev); + if (nr_entries < 0) + return nr_entries; + + /* Check hotplug MSIx entry is in range */ + pcie_capability_read_word(pdev, PCI_EXP_FLAGS, &pcie_flag); + entry.entry = (pcie_flag & PCI_EXP_FLAGS_IRQ) >> 9; + if (entry.entry >= nr_entries) + return -ERANGE; + + /* Enable MSIx */ + ret = pci_enable_msix_exact(pdev, &entry, 1); + if (ret) { + SLOT_WARN(php_slot, "Error %d enabling MSIx\n", ret); + return ret; + } + + return entry.vector; +} + +static void pnv_php_event_handler(struct work_struct *work) +{ + struct pnv_php_event *event = + container_of(work, struct pnv_php_event, work); + struct pnv_php_slot *php_slot = event->php_slot; + + if (event->added) + pnv_php_enable_slot(&php_slot->slot); + else + pnv_php_disable_slot(&php_slot->slot); + + kfree(event); +} + +static irqreturn_t pnv_php_interrupt(int irq, void *data) +{ + struct pnv_php_slot *php_slot = data; + struct pci_dev *pchild, *pdev = php_slot->pdev; + struct eeh_dev *edev; + struct eeh_pe *pe; + struct pnv_php_event *event; + u16 sts, lsts; + u8 presence; + bool added; + unsigned long flags; + int ret; + + pcie_capability_read_word(pdev, PCI_EXP_SLTSTA, &sts); + sts &= (PCI_EXP_SLTSTA_PDC | PCI_EXP_SLTSTA_DLLSC); + pcie_capability_write_word(pdev, PCI_EXP_SLTSTA, sts); + + pci_dbg(pdev, "PCI slot [%s]: HP int! DLAct: %d, PresDet: %d\n", + php_slot->name, + !!(sts & PCI_EXP_SLTSTA_DLLSC), + !!(sts & PCI_EXP_SLTSTA_PDC)); + + if (sts & PCI_EXP_SLTSTA_DLLSC) { + pcie_capability_read_word(pdev, PCI_EXP_LNKSTA, &lsts); + added = !!(lsts & PCI_EXP_LNKSTA_DLLLA); + } else if (!(php_slot->flags & PNV_PHP_FLAG_BROKEN_PDC) && + (sts & PCI_EXP_SLTSTA_PDC)) { + ret = pnv_pci_get_presence_state(php_slot->id, &presence); + if (ret) { + SLOT_WARN(php_slot, + "PCI slot [%s] error %d getting presence (0x%04x), to retry the operation.\n", + php_slot->name, ret, sts); + return IRQ_HANDLED; + } + + added = !!(presence == OPAL_PCI_SLOT_PRESENT); + } else { + pci_dbg(pdev, "PCI slot [%s]: Spurious IRQ?\n", php_slot->name); + return IRQ_NONE; + } + + /* Freeze the removed PE to avoid unexpected error reporting */ + if (!added) { + pchild = list_first_entry_or_null(&php_slot->bus->devices, + struct pci_dev, bus_list); + edev = pchild ? pci_dev_to_eeh_dev(pchild) : NULL; + pe = edev ? edev->pe : NULL; + if (pe) { + eeh_serialize_lock(&flags); + eeh_pe_mark_isolated(pe); + eeh_serialize_unlock(flags); + eeh_pe_set_option(pe, EEH_OPT_FREEZE_PE); + } + } + + /* + * The PE is left in frozen state if the event is missed. It's + * fine as the PCI devices (PE) aren't functional any more. + */ + event = kzalloc(sizeof(*event), GFP_ATOMIC); + if (!event) { + SLOT_WARN(php_slot, + "PCI slot [%s] missed hotplug event 0x%04x\n", + php_slot->name, sts); + return IRQ_HANDLED; + } + + pci_info(pdev, "PCI slot [%s] %s (IRQ: %d)\n", + php_slot->name, added ? "added" : "removed", irq); + INIT_WORK(&event->work, pnv_php_event_handler); + event->added = added; + event->php_slot = php_slot; + queue_work(php_slot->wq, &event->work); + + return IRQ_HANDLED; +} + +static void pnv_php_init_irq(struct pnv_php_slot *php_slot, int irq) +{ + struct pci_dev *pdev = php_slot->pdev; + u32 broken_pdc = 0; + u16 sts, ctrl; + int ret; + + /* Allocate workqueue */ + php_slot->wq = alloc_workqueue("pciehp-%s", 0, 0, php_slot->name); + if (!php_slot->wq) { + SLOT_WARN(php_slot, "Cannot alloc workqueue\n"); + pnv_php_disable_irq(php_slot, true); + return; + } + + /* Check PDC (Presence Detection Change) is broken or not */ + ret = of_property_read_u32(php_slot->dn, "ibm,slot-broken-pdc", + &broken_pdc); + if (!ret && broken_pdc) + php_slot->flags |= PNV_PHP_FLAG_BROKEN_PDC; + + /* Clear pending interrupts */ + pcie_capability_read_word(pdev, PCI_EXP_SLTSTA, &sts); + if (php_slot->flags & PNV_PHP_FLAG_BROKEN_PDC) + sts |= PCI_EXP_SLTSTA_DLLSC; + else + sts |= (PCI_EXP_SLTSTA_PDC | PCI_EXP_SLTSTA_DLLSC); + pcie_capability_write_word(pdev, PCI_EXP_SLTSTA, sts); + + /* Request the interrupt */ + ret = request_irq(irq, pnv_php_interrupt, IRQF_SHARED, + php_slot->name, php_slot); + if (ret) { + pnv_php_disable_irq(php_slot, true); + SLOT_WARN(php_slot, "Error %d enabling IRQ %d\n", ret, irq); + return; + } + + /* Enable the interrupts */ + pcie_capability_read_word(pdev, PCI_EXP_SLTCTL, &ctrl); + if (php_slot->flags & PNV_PHP_FLAG_BROKEN_PDC) { + ctrl &= ~PCI_EXP_SLTCTL_PDCE; + ctrl |= (PCI_EXP_SLTCTL_HPIE | + PCI_EXP_SLTCTL_DLLSCE); + } else { + ctrl |= (PCI_EXP_SLTCTL_HPIE | + PCI_EXP_SLTCTL_PDCE | + PCI_EXP_SLTCTL_DLLSCE); + } + pcie_capability_write_word(pdev, PCI_EXP_SLTCTL, ctrl); + + /* The interrupt is initialized successfully when @irq is valid */ + php_slot->irq = irq; +} + +static void pnv_php_enable_irq(struct pnv_php_slot *php_slot) +{ + struct pci_dev *pdev = php_slot->pdev; + int irq, ret; + + /* + * The MSI/MSIx interrupt might have been occupied by other + * drivers. Don't populate the surprise hotplug capability + * in that case. + */ + if (pci_dev_msi_enabled(pdev)) + return; + + ret = pci_enable_device(pdev); + if (ret) { + SLOT_WARN(php_slot, "Error %d enabling device\n", ret); + return; + } + + pci_set_master(pdev); + + /* Enable MSIx interrupt */ + irq = pnv_php_enable_msix(php_slot); + if (irq > 0) { + pnv_php_init_irq(php_slot, irq); + return; + } + + /* + * Use MSI if MSIx doesn't work. Fail back to legacy INTx + * if MSI doesn't work either + */ + ret = pci_enable_msi(pdev); + if (!ret || pdev->irq) { + irq = pdev->irq; + pnv_php_init_irq(php_slot, irq); + } +} + +static int pnv_php_register_one(struct device_node *dn) +{ + struct pnv_php_slot *php_slot; + u32 prop32; + int ret; + + /* Check if it's hotpluggable slot */ + ret = of_property_read_u32(dn, "ibm,slot-pluggable", &prop32); + if (ret || !prop32) + return -ENXIO; + + ret = of_property_read_u32(dn, "ibm,reset-by-firmware", &prop32); + if (ret || !prop32) + return -ENXIO; + + php_slot = pnv_php_alloc_slot(dn); + if (!php_slot) + return -ENODEV; + + ret = pnv_php_register_slot(php_slot); + if (ret) + goto free_slot; + + ret = pnv_php_enable(php_slot, false); + if (ret) + goto unregister_slot; + + /* Enable interrupt if the slot supports surprise hotplug */ + ret = of_property_read_u32(dn, "ibm,slot-surprise-pluggable", &prop32); + if (!ret && prop32) + pnv_php_enable_irq(php_slot); + + return 0; + +unregister_slot: + pnv_php_unregister_one(php_slot->dn); +free_slot: + pnv_php_put_slot(php_slot); + return ret; +} + +static void pnv_php_register(struct device_node *dn) +{ + struct device_node *child; + + /* + * The parent slots should be registered before their + * child slots. + */ + for_each_child_of_node(dn, child) { + pnv_php_register_one(child); + pnv_php_register(child); + } +} + +static void pnv_php_unregister_one(struct device_node *dn) +{ + struct pnv_php_slot *php_slot; + + php_slot = pnv_php_find_slot(dn); + if (!php_slot) + return; + + php_slot->state = PNV_PHP_STATE_OFFLINE; + pci_hp_deregister(&php_slot->slot); + pnv_php_release(php_slot); + pnv_php_put_slot(php_slot); +} + +static void pnv_php_unregister(struct device_node *dn) +{ + struct device_node *child; + + /* The child slots should go before their parent slots */ + for_each_child_of_node(dn, child) { + pnv_php_unregister(child); + pnv_php_unregister_one(child); + } +} + +static int __init pnv_php_init(void) +{ + struct device_node *dn; + + pr_info(DRIVER_DESC " version: " DRIVER_VERSION "\n"); + for_each_compatible_node(dn, NULL, "ibm,ioda2-phb") + pnv_php_register(dn); + + for_each_compatible_node(dn, NULL, "ibm,ioda3-phb") + pnv_php_register(dn); + + for_each_compatible_node(dn, NULL, "ibm,ioda2-npu2-opencapi-phb") + pnv_php_register_one(dn); /* slot directly under the PHB */ + return 0; +} + +static void __exit pnv_php_exit(void) +{ + struct device_node *dn; + + for_each_compatible_node(dn, NULL, "ibm,ioda2-phb") + pnv_php_unregister(dn); + + for_each_compatible_node(dn, NULL, "ibm,ioda3-phb") + pnv_php_unregister(dn); + + for_each_compatible_node(dn, NULL, "ibm,ioda2-npu2-opencapi-phb") + pnv_php_unregister_one(dn); /* slot directly under the PHB */ +} + +module_init(pnv_php_init); +module_exit(pnv_php_exit); + +MODULE_VERSION(DRIVER_VERSION); +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_DESCRIPTION(DRIVER_DESC); diff --git a/drivers/pci/hotplug/rpadlpar.h b/drivers/pci/hotplug/rpadlpar.h new file mode 100644 index 000000000..1eeb55d33 --- /dev/null +++ b/drivers/pci/hotplug/rpadlpar.h @@ -0,0 +1,20 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * 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. + */ +#ifndef _RPADLPAR_IO_H_ +#define _RPADLPAR_IO_H_ + +int dlpar_sysfs_init(void); +void dlpar_sysfs_exit(void); + +int dlpar_add_slot(char *drc_name); +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 000000000..980bb3afd --- /dev/null +++ b/drivers/pci/hotplug/rpadlpar_core.c @@ -0,0 +1,482 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * 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. + */ + +#undef DEBUG + +#include <linux/init.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/pci.h> +#include <linux/string.h> +#include <linux/vmalloc.h> + +#include <asm/pci-bridge.h> +#include <linux/mutex.h> +#include <asm/rtas.h> +#include <asm/vio.h> +#include <linux/firmware.h> + +#include "../pci.h" +#include "rpaphp.h" +#include "rpadlpar.h" + +static DEFINE_MUTEX(rpadlpar_mutex); + +#define DLPAR_MODULE_NAME "rpadlpar_io" + +#define NODE_TYPE_VIO 1 +#define NODE_TYPE_SLOT 2 +#define NODE_TYPE_PHB 3 + +static struct device_node *find_vio_slot_node(char *drc_name) +{ + struct device_node *parent = of_find_node_by_name(NULL, "vdevice"); + struct device_node *dn; + int rc; + + if (!parent) + return NULL; + + for_each_child_of_node(parent, dn) { + rc = rpaphp_check_drc_props(dn, drc_name, NULL); + if (rc == 0) + break; + } + of_node_put(parent); + + return dn; +} + +/* Find dlpar-capable pci node that contains the specified name and type */ +static struct device_node *find_php_slot_pci_node(char *drc_name, + char *drc_type) +{ + struct device_node *np; + int rc; + + for_each_node_by_name(np, "pci") { + rc = rpaphp_check_drc_props(np, drc_name, drc_type); + if (rc == 0) + break; + } + + return np; +} + +/* Returns a device_node with its reference count incremented */ +static struct device_node *find_dlpar_node(char *drc_name, int *node_type) +{ + struct device_node *dn; + + dn = find_php_slot_pci_node(drc_name, "SLOT"); + if (dn) { + *node_type = NODE_TYPE_SLOT; + return dn; + } + + dn = find_php_slot_pci_node(drc_name, "PHB"); + if (dn) { + *node_type = NODE_TYPE_PHB; + return dn; + } + + dn = find_vio_slot_node(drc_name); + if (dn) { + *node_type = NODE_TYPE_VIO; + return dn; + } + + return NULL; +} + +/** + * find_php_slot - return hotplug slot structure for device node + * @dn: target &device_node + * + * This routine will return the hotplug slot structure + * for a given device node. Note that built-in PCI slots + * may be dlpar-able, but not hot-pluggable, so this routine + * will return NULL for built-in PCI slots. + */ +static struct slot *find_php_slot(struct device_node *dn) +{ + struct slot *slot, *next; + + list_for_each_entry_safe(slot, next, &rpaphp_slot_head, + rpaphp_slot_list) { + if (slot->dn == dn) + return slot; + } + + return NULL; +} + +static struct pci_dev *dlpar_find_new_dev(struct pci_bus *parent, + struct device_node *dev_dn) +{ + struct pci_dev *tmp = NULL; + struct device_node *child_dn; + + list_for_each_entry(tmp, &parent->devices, bus_list) { + child_dn = pci_device_to_OF_node(tmp); + if (child_dn == dev_dn) + return tmp; + } + return NULL; +} + +static void dlpar_pci_add_bus(struct device_node *dn) +{ + struct pci_dn *pdn = PCI_DN(dn); + struct pci_controller *phb = pdn->phb; + struct pci_dev *dev = NULL; + + pseries_eeh_init_edev_recursive(pdn); + + /* Add EADS device to PHB bus, adding new entry to bus->devices */ + dev = of_create_pci_dev(dn, phb->bus, pdn->devfn); + if (!dev) { + printk(KERN_ERR "%s: failed to create pci dev for %pOF\n", + __func__, dn); + return; + } + + /* Scan below the new bridge */ + if (pci_is_bridge(dev)) + of_scan_pci_bridge(dev); + + /* Map IO space for child bus, which may or may not succeed */ + pcibios_map_io_space(dev->subordinate); + + /* Finish adding it : resource allocation, adding devices, etc... + * Note that we need to perform the finish pass on the -parent- + * bus of the EADS bridge so the bridge device itself gets + * properly added + */ + pcibios_finish_adding_to_bus(phb->bus); +} + +static int dlpar_add_pci_slot(char *drc_name, struct device_node *dn) +{ + struct pci_dev *dev; + struct pci_controller *phb; + + if (pci_find_bus_by_node(dn)) + return -EINVAL; + + /* Add pci bus */ + dlpar_pci_add_bus(dn); + + /* Confirm new bridge dev was created */ + phb = PCI_DN(dn)->phb; + dev = dlpar_find_new_dev(phb->bus, dn); + + if (!dev) { + printk(KERN_ERR "%s: unable to add bus %s\n", __func__, + drc_name); + return -EIO; + } + + if (dev->hdr_type != PCI_HEADER_TYPE_BRIDGE) { + printk(KERN_ERR "%s: unexpected header type %d, unable to add bus %s\n", + __func__, dev->hdr_type, drc_name); + return -EIO; + } + + /* Add hotplug slot */ + if (rpaphp_add_slot(dn)) { + printk(KERN_ERR "%s: unable to add hotplug slot %s\n", + __func__, drc_name); + return -EIO; + } + return 0; +} + +static int dlpar_remove_phb(char *drc_name, struct device_node *dn) +{ + struct slot *slot; + struct pci_dn *pdn; + int rc = 0; + + if (!pci_find_bus_by_node(dn)) + return -EINVAL; + + /* If pci slot is hotpluggable, use hotplug to remove it */ + slot = find_php_slot(dn); + if (slot && rpaphp_deregister_slot(slot)) { + printk(KERN_ERR "%s: unable to remove hotplug slot %s\n", + __func__, drc_name); + return -EIO; + } + + pdn = dn->data; + BUG_ON(!pdn || !pdn->phb); + rc = remove_phb_dynamic(pdn->phb); + if (rc < 0) + return rc; + + pdn->phb = NULL; + + return 0; +} + +static int dlpar_add_phb(char *drc_name, struct device_node *dn) +{ + struct pci_controller *phb; + + if (PCI_DN(dn) && PCI_DN(dn)->phb) { + /* PHB already exists */ + return -EINVAL; + } + + phb = init_phb_dynamic(dn); + if (!phb) + return -EIO; + + if (rpaphp_add_slot(dn)) { + printk(KERN_ERR "%s: unable to add hotplug slot %s\n", + __func__, drc_name); + return -EIO; + } + return 0; +} + +static int dlpar_add_vio_slot(char *drc_name, struct device_node *dn) +{ + struct vio_dev *vio_dev; + + vio_dev = vio_find_node(dn); + if (vio_dev) { + put_device(&vio_dev->dev); + return -EINVAL; + } + + if (!vio_register_device_node(dn)) { + printk(KERN_ERR + "%s: failed to register vio node %s\n", + __func__, drc_name); + return -EIO; + } + return 0; +} + +/** + * dlpar_add_slot - DLPAR add an I/O Slot + * @drc_name: drc-name of newly added slot + * + * Make the hotplug module and the kernel aware of a newly added I/O Slot. + * Return Codes: + * 0 Success + * -ENODEV Not a valid drc_name + * -EINVAL Slot already added + * -ERESTARTSYS Signalled before obtaining lock + * -EIO Internal PCI Error + */ +int dlpar_add_slot(char *drc_name) +{ + struct device_node *dn = NULL; + int node_type; + int rc = -EIO; + + if (mutex_lock_interruptible(&rpadlpar_mutex)) + return -ERESTARTSYS; + + /* Find newly added node */ + dn = find_dlpar_node(drc_name, &node_type); + if (!dn) { + rc = -ENODEV; + goto exit; + } + + switch (node_type) { + case NODE_TYPE_VIO: + rc = dlpar_add_vio_slot(drc_name, dn); + break; + case NODE_TYPE_SLOT: + rc = dlpar_add_pci_slot(drc_name, dn); + break; + case NODE_TYPE_PHB: + rc = dlpar_add_phb(drc_name, dn); + break; + } + of_node_put(dn); + + printk(KERN_INFO "%s: slot %s added\n", DLPAR_MODULE_NAME, drc_name); +exit: + mutex_unlock(&rpadlpar_mutex); + return rc; +} + +/** + * dlpar_remove_vio_slot - DLPAR remove a virtual I/O Slot + * @drc_name: drc-name of newly added slot + * @dn: &device_node + * + * Remove the kernel and hotplug representations of an I/O Slot. + * Return Codes: + * 0 Success + * -EINVAL Vio dev doesn't exist + */ +static int dlpar_remove_vio_slot(char *drc_name, struct device_node *dn) +{ + struct vio_dev *vio_dev; + + vio_dev = vio_find_node(dn); + if (!vio_dev) + return -EINVAL; + + vio_unregister_device(vio_dev); + + put_device(&vio_dev->dev); + + return 0; +} + +/** + * dlpar_remove_pci_slot - DLPAR remove a PCI I/O Slot + * @drc_name: drc-name of newly added slot + * @dn: &device_node + * + * Remove the kernel and hotplug representations of a PCI I/O Slot. + * Return Codes: + * 0 Success + * -ENODEV Not a valid drc_name + * -EIO Internal PCI Error + */ +static int dlpar_remove_pci_slot(char *drc_name, struct device_node *dn) +{ + struct pci_bus *bus; + struct slot *slot; + int ret = 0; + + pci_lock_rescan_remove(); + + bus = pci_find_bus_by_node(dn); + if (!bus) { + ret = -EINVAL; + goto out; + } + + pr_debug("PCI: Removing PCI slot below EADS bridge %s\n", + bus->self ? pci_name(bus->self) : "<!PHB!>"); + + slot = find_php_slot(dn); + if (slot) { + pr_debug("PCI: Removing hotplug slot for %04x:%02x...\n", + pci_domain_nr(bus), bus->number); + + if (rpaphp_deregister_slot(slot)) { + printk(KERN_ERR + "%s: unable to remove hotplug slot %s\n", + __func__, drc_name); + ret = -EIO; + goto out; + } + } + + /* Remove all devices below slot */ + pci_hp_remove_devices(bus); + + /* Unmap PCI IO space */ + if (pcibios_unmap_io_space(bus)) { + printk(KERN_ERR "%s: failed to unmap bus range\n", + __func__); + ret = -ERANGE; + goto out; + } + + /* Remove the EADS bridge device itself */ + BUG_ON(!bus->self); + pr_debug("PCI: Now removing bridge device %s\n", pci_name(bus->self)); + pci_stop_and_remove_bus_device(bus->self); + + out: + pci_unlock_rescan_remove(); + return ret; +} + +/** + * dlpar_remove_slot - DLPAR remove an I/O Slot + * @drc_name: drc-name of newly added slot + * + * Remove the kernel and hotplug representations of an I/O Slot. + * Return Codes: + * 0 Success + * -ENODEV Not a valid drc_name + * -EINVAL Slot already removed + * -ERESTARTSYS Signalled before obtaining lock + * -EIO Internal Error + */ +int dlpar_remove_slot(char *drc_name) +{ + struct device_node *dn; + int node_type; + int rc = 0; + + if (mutex_lock_interruptible(&rpadlpar_mutex)) + return -ERESTARTSYS; + + dn = find_dlpar_node(drc_name, &node_type); + if (!dn) { + rc = -ENODEV; + goto exit; + } + + switch (node_type) { + case NODE_TYPE_VIO: + rc = dlpar_remove_vio_slot(drc_name, dn); + break; + case NODE_TYPE_PHB: + rc = dlpar_remove_phb(drc_name, dn); + break; + case NODE_TYPE_SLOT: + rc = dlpar_remove_pci_slot(drc_name, dn); + break; + } + of_node_put(dn); + vm_unmap_aliases(); + + printk(KERN_INFO "%s: slot %s removed\n", DLPAR_MODULE_NAME, drc_name); +exit: + mutex_unlock(&rpadlpar_mutex); + return rc; +} + +static inline int is_dlpar_capable(void) +{ + int rc = rtas_token("ibm,configure-connector"); + + return (int) (rc != RTAS_UNKNOWN_SERVICE); +} + +static int __init rpadlpar_io_init(void) +{ + + if (!is_dlpar_capable()) { + printk(KERN_WARNING "%s: partition not DLPAR capable\n", + __func__); + return -EPERM; + } + + return dlpar_sysfs_init(); +} + +static void __exit rpadlpar_io_exit(void) +{ + dlpar_sysfs_exit(); +} + +module_init(rpadlpar_io_init); +module_exit(rpadlpar_io_exit); +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("RPA Dynamic Logical Partitioning driver for I/O slots"); diff --git a/drivers/pci/hotplug/rpadlpar_sysfs.c b/drivers/pci/hotplug/rpadlpar_sysfs.c new file mode 100644 index 000000000..068b7810a --- /dev/null +++ b/drivers/pci/hotplug/rpadlpar_sysfs.c @@ -0,0 +1,123 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * 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. + */ +#include <linux/kobject.h> +#include <linux/string.h> +#include <linux/pci.h> +#include <linux/pci_hotplug.h> +#include "rpaphp.h" +#include "rpadlpar.h" +#include "../pci.h" + +#define DLPAR_KOBJ_NAME "control" + +/* Those two have no quotes because they are passed to __ATTR() which + * stringifies the argument (yuck !) + */ +#define ADD_SLOT_ATTR_NAME add_slot +#define REMOVE_SLOT_ATTR_NAME remove_slot + +static ssize_t add_slot_store(struct kobject *kobj, struct kobj_attribute *attr, + const char *buf, size_t nbytes) +{ + char drc_name[MAX_DRC_NAME_LEN]; + char *end; + int rc; + + if (nbytes >= MAX_DRC_NAME_LEN) + return 0; + + strscpy(drc_name, buf, nbytes + 1); + + end = strchr(drc_name, '\n'); + if (end) + *end = '\0'; + + rc = dlpar_add_slot(drc_name); + if (rc) + return rc; + + return nbytes; +} + +static ssize_t add_slot_show(struct kobject *kobj, + struct kobj_attribute *attr, char *buf) +{ + return sysfs_emit(buf, "0\n"); +} + +static ssize_t remove_slot_store(struct kobject *kobj, + struct kobj_attribute *attr, + const char *buf, size_t nbytes) +{ + char drc_name[MAX_DRC_NAME_LEN]; + int rc; + char *end; + + if (nbytes >= MAX_DRC_NAME_LEN) + return 0; + + strscpy(drc_name, buf, nbytes + 1); + + end = strchr(drc_name, '\n'); + if (end) + *end = '\0'; + + rc = dlpar_remove_slot(drc_name); + if (rc) + return rc; + + return nbytes; +} + +static ssize_t remove_slot_show(struct kobject *kobj, + struct kobj_attribute *attr, char *buf) +{ + return sysfs_emit(buf, "0\n"); +} + +static struct kobj_attribute add_slot_attr = + __ATTR(ADD_SLOT_ATTR_NAME, 0644, add_slot_show, add_slot_store); + +static struct kobj_attribute remove_slot_attr = + __ATTR(REMOVE_SLOT_ATTR_NAME, 0644, remove_slot_show, remove_slot_store); + +static struct attribute *default_attrs[] = { + &add_slot_attr.attr, + &remove_slot_attr.attr, + NULL, +}; + +static const struct attribute_group dlpar_attr_group = { + .attrs = default_attrs, +}; + +static struct kobject *dlpar_kobj; + +int dlpar_sysfs_init(void) +{ + int error; + + dlpar_kobj = kobject_create_and_add(DLPAR_KOBJ_NAME, + &pci_slots_kset->kobj); + if (!dlpar_kobj) + return -EINVAL; + + error = sysfs_create_group(dlpar_kobj, &dlpar_attr_group); + if (error) + kobject_put(dlpar_kobj); + return error; +} + +void dlpar_sysfs_exit(void) +{ + sysfs_remove_group(dlpar_kobj, &dlpar_attr_group); + kobject_put(dlpar_kobj); +} diff --git a/drivers/pci/hotplug/rpaphp.h b/drivers/pci/hotplug/rpaphp.h new file mode 100644 index 000000000..bdc954d70 --- /dev/null +++ b/drivers/pci/hotplug/rpaphp.h @@ -0,0 +1,99 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * PCI Hot Plug Controller Driver for RPA-compliant PPC64 platform. + * + * Copyright (C) 2003 Linda Xie <lxie@us.ibm.com> + * + * All rights reserved. + * + * Send feedback to <lxie@us.ibm.com>, + * + */ + +#ifndef _PPC64PHP_H +#define _PPC64PHP_H + +#include <linux/pci.h> +#include <linux/pci_hotplug.h> + +#define DR_INDICATOR 9002 +#define DR_ENTITY_SENSE 9003 + +#define POWER_ON 100 +#define POWER_OFF 0 + +#define LED_OFF 0 +#define LED_ON 1 /* continuous on */ +#define LED_ID 2 /* slow blinking */ +#define LED_ACTION 3 /* fast blinking */ + +/* Sensor values from rtas_get-sensor */ +#define EMPTY 0 /* No card in slot */ +#define PRESENT 1 /* Card in slot */ + +#define MY_NAME "rpaphp" +extern bool rpaphp_debug; +#define dbg(format, arg...) \ + do { \ + if (rpaphp_debug) \ + printk(KERN_DEBUG "%s: " format, \ + MY_NAME, ## arg); \ + } while (0) +#define err(format, arg...) printk(KERN_ERR "%s: " format, MY_NAME, ## arg) +#define info(format, arg...) printk(KERN_INFO "%s: " format, MY_NAME, ## arg) +#define warn(format, arg...) printk(KERN_WARNING "%s: " format, MY_NAME, ## arg) + +/* slot states */ + +#define NOT_VALID 3 +#define NOT_CONFIGURED 2 +#define CONFIGURED 1 +#define EMPTY 0 + +/* DRC constants */ + +#define MAX_DRC_NAME_LEN 64 + +/* + * 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; + u8 attention_status; + char *name; + struct device_node *dn; + struct pci_bus *bus; + struct list_head *pci_devs; + struct hotplug_slot hotplug_slot; +}; + +extern const struct hotplug_slot_ops rpaphp_hotplug_slot_ops; +extern struct list_head rpaphp_slot_head; + +static inline struct slot *to_slot(struct hotplug_slot *hotplug_slot) +{ + return container_of(hotplug_slot, struct slot, hotplug_slot); +} + +/* function prototypes */ + +/* rpaphp_pci.c */ +int rpaphp_enable_slot(struct slot *slot); +int rpaphp_get_sensor_state(struct slot *slot, int *state); + +/* rpaphp_core.c */ +int rpaphp_add_slot(struct device_node *dn); +int rpaphp_check_drc_props(struct device_node *dn, char *drc_name, + char *drc_type); + +/* rpaphp_slot.c */ +void dealloc_slot_struct(struct slot *slot); +struct slot *alloc_slot_struct(struct device_node *dn, int drc_index, char *drc_name, int power_domain); +int rpaphp_register_slot(struct slot *slot); +int rpaphp_deregister_slot(struct slot *slot); + +#endif /* _PPC64PHP_H */ diff --git a/drivers/pci/hotplug/rpaphp_core.c b/drivers/pci/hotplug/rpaphp_core.c new file mode 100644 index 000000000..491986197 --- /dev/null +++ b/drivers/pci/hotplug/rpaphp_core.c @@ -0,0 +1,541 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * PCI Hot Plug Controller Driver for RPA-compliant PPC64 platform. + * Copyright (C) 2003 Linda Xie <lxie@us.ibm.com> + * + * All rights reserved. + * + * Send feedback to <lxie@us.ibm.com> + * + */ +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/of.h> +#include <linux/pci.h> +#include <linux/pci_hotplug.h> +#include <linux/smp.h> +#include <linux/init.h> +#include <linux/vmalloc.h> +#include <asm/firmware.h> +#include <asm/eeh.h> /* for eeh_add_device() */ +#include <asm/rtas.h> /* rtas_call */ +#include <asm/pci-bridge.h> /* for pci_controller */ +#include <asm/prom.h> +#include "../pci.h" /* for pci_add_new_bus */ + /* and pci_do_scan_bus */ +#include "rpaphp.h" + +bool rpaphp_debug; +LIST_HEAD(rpaphp_slot_head); +EXPORT_SYMBOL_GPL(rpaphp_slot_head); + +#define DRIVER_VERSION "0.1" +#define DRIVER_AUTHOR "Linda Xie <lxie@us.ibm.com>" +#define DRIVER_DESC "RPA HOT Plug PCI Controller Driver" + +#define MAX_LOC_CODE 128 + +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); + +module_param_named(debug, rpaphp_debug, bool, 0644); + +/** + * set_attention_status - set attention LED + * @hotplug_slot: target &hotplug_slot + * @value: LED control value + * + * echo 0 > attention -- set LED OFF + * echo 1 > attention -- set LED ON + * echo 2 > attention -- set LED ID(identify, light is blinking) + */ +static int set_attention_status(struct hotplug_slot *hotplug_slot, u8 value) +{ + int rc; + struct slot *slot = to_slot(hotplug_slot); + + switch (value) { + case 0: + case 1: + case 2: + break; + default: + value = 1; + break; + } + + rc = rtas_set_indicator(DR_INDICATOR, slot->index, value); + if (!rc) + slot->attention_status = value; + + return rc; +} + +/** + * get_power_status - get power status of a slot + * @hotplug_slot: slot to get status + * @value: pointer to store status + */ +static int get_power_status(struct hotplug_slot *hotplug_slot, u8 *value) +{ + int retval, level; + struct slot *slot = to_slot(hotplug_slot); + + retval = rtas_get_power_level(slot->power_domain, &level); + if (!retval) + *value = level; + return retval; +} + +/** + * get_attention_status - get attention LED status + * @hotplug_slot: slot to get status + * @value: pointer to store status + */ +static int get_attention_status(struct hotplug_slot *hotplug_slot, u8 *value) +{ + struct slot *slot = to_slot(hotplug_slot); + *value = slot->attention_status; + return 0; +} + +static int get_adapter_status(struct hotplug_slot *hotplug_slot, u8 *value) +{ + struct slot *slot = to_slot(hotplug_slot); + int rc, state; + + rc = rpaphp_get_sensor_state(slot, &state); + + *value = NOT_VALID; + if (rc) + return rc; + + if (state == EMPTY) + *value = EMPTY; + else if (state == PRESENT) + *value = slot->state; + + return 0; +} + +static enum pci_bus_speed get_max_bus_speed(struct slot *slot) +{ + enum pci_bus_speed speed; + switch (slot->type) { + case 1: + case 2: + case 3: + case 4: + case 5: + case 6: + speed = PCI_SPEED_33MHz; /* speed for case 1-6 */ + break; + case 7: + case 8: + speed = PCI_SPEED_66MHz; + break; + case 11: + case 14: + speed = PCI_SPEED_66MHz_PCIX; + break; + case 12: + case 15: + speed = PCI_SPEED_100MHz_PCIX; + break; + case 13: + case 16: + speed = PCI_SPEED_133MHz_PCIX; + break; + default: + speed = PCI_SPEED_UNKNOWN; + break; + } + + return speed; +} + +static int get_children_props(struct device_node *dn, const __be32 **drc_indexes, + const __be32 **drc_names, const __be32 **drc_types, + const __be32 **drc_power_domains) +{ + const __be32 *indexes, *names, *types, *domains; + + indexes = of_get_property(dn, "ibm,drc-indexes", NULL); + names = of_get_property(dn, "ibm,drc-names", NULL); + types = of_get_property(dn, "ibm,drc-types", NULL); + domains = of_get_property(dn, "ibm,drc-power-domains", NULL); + + if (!indexes || !names || !types || !domains) { + /* Slot does not have dynamically-removable children */ + return -EINVAL; + } + if (drc_indexes) + *drc_indexes = indexes; + if (drc_names) + /* &drc_names[1] contains NULL terminated slot names */ + *drc_names = names; + if (drc_types) + /* &drc_types[1] contains NULL terminated slot types */ + *drc_types = types; + if (drc_power_domains) + *drc_power_domains = domains; + + return 0; +} + + +/* Verify the existence of 'drc_name' and/or 'drc_type' within the + * current node. First obtain its my-drc-index property. Next, + * obtain the DRC info from its parent. Use the my-drc-index for + * correlation, and obtain/validate the requested properties. + */ + +static int rpaphp_check_drc_props_v1(struct device_node *dn, char *drc_name, + char *drc_type, unsigned int my_index) +{ + char *name_tmp, *type_tmp; + const __be32 *indexes, *names; + const __be32 *types, *domains; + int i, rc; + + 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 < be32_to_cpu(indexes[0]); i++) { + if (be32_to_cpu(indexes[i + 1]) == my_index) + break; + + name_tmp += (strlen(name_tmp) + 1); + type_tmp += (strlen(type_tmp) + 1); + } + + if (((drc_name == NULL) || (drc_name && !strcmp(drc_name, name_tmp))) && + ((drc_type == NULL) || (drc_type && !strcmp(drc_type, type_tmp)))) + return 0; + + return -EINVAL; +} + +static int rpaphp_check_drc_props_v2(struct device_node *dn, char *drc_name, + char *drc_type, unsigned int my_index) +{ + struct property *info; + unsigned int entries; + struct of_drc_info drc; + const __be32 *value; + char cell_drc_name[MAX_DRC_NAME_LEN]; + int j; + + info = of_find_property(dn->parent, "ibm,drc-info", NULL); + if (info == NULL) + return -EINVAL; + + value = of_prop_next_u32(info, NULL, &entries); + if (!value) + return -EINVAL; + else + value++; + + for (j = 0; j < entries; j++) { + of_read_drc_info_cell(&info, &value, &drc); + + /* Should now know end of current entry */ + + /* Found it */ + if (my_index >= drc.drc_index_start && my_index <= drc.last_drc_index) { + int index = my_index - drc.drc_index_start; + sprintf(cell_drc_name, "%s%d", drc.drc_name_prefix, + drc.drc_name_suffix_start + index); + break; + } + } + + if (((drc_name == NULL) || + (drc_name && !strcmp(drc_name, cell_drc_name))) && + ((drc_type == NULL) || + (drc_type && !strcmp(drc_type, drc.drc_type)))) + return 0; + + return -EINVAL; +} + +int rpaphp_check_drc_props(struct device_node *dn, char *drc_name, + char *drc_type) +{ + const __be32 *my_index; + + my_index = of_get_property(dn, "ibm,my-drc-index", NULL); + if (!my_index) { + /* Node isn't DLPAR/hotplug capable */ + return -EINVAL; + } + + if (of_find_property(dn->parent, "ibm,drc-info", NULL)) + return rpaphp_check_drc_props_v2(dn, drc_name, drc_type, + be32_to_cpu(*my_index)); + else + return rpaphp_check_drc_props_v1(dn, drc_name, drc_type, + be32_to_cpu(*my_index)); +} +EXPORT_SYMBOL_GPL(rpaphp_check_drc_props); + + +static int is_php_type(char *drc_type) +{ + char *endptr; + + /* PCI Hotplug nodes have an integer for drc_type */ + simple_strtoul(drc_type, &endptr, 10); + if (endptr == drc_type) + return 0; + + return 1; +} + +/** + * is_php_dn() - return 1 if this is a hotpluggable pci slot, else 0 + * @dn: target &device_node + * @indexes: passed to get_children_props() + * @names: passed to get_children_props() + * @types: returned from get_children_props() + * @power_domains: + * + * This routine will return true only if the device node is + * a hotpluggable slot. This routine will return false + * for built-in pci slots (even when the built-in slots are + * dlparable.) + */ +static int is_php_dn(struct device_node *dn, const __be32 **indexes, + const __be32 **names, const __be32 **types, + const __be32 **power_domains) +{ + const __be32 *drc_types; + int rc; + + rc = get_children_props(dn, indexes, names, &drc_types, power_domains); + if (rc < 0) + return 0; + + if (!is_php_type((char *) &drc_types[1])) + return 0; + + *types = drc_types; + return 1; +} + +static int rpaphp_drc_info_add_slot(struct device_node *dn) +{ + struct slot *slot; + struct property *info; + struct of_drc_info drc; + char drc_name[MAX_DRC_NAME_LEN]; + const __be32 *cur; + u32 count; + int retval = 0; + + info = of_find_property(dn, "ibm,drc-info", NULL); + if (!info) + return 0; + + cur = of_prop_next_u32(info, NULL, &count); + if (cur) + cur++; + else + return 0; + + of_read_drc_info_cell(&info, &cur, &drc); + if (!is_php_type(drc.drc_type)) + return 0; + + sprintf(drc_name, "%s%d", drc.drc_name_prefix, drc.drc_name_suffix_start); + + slot = alloc_slot_struct(dn, drc.drc_index_start, drc_name, drc.drc_power_domain); + if (!slot) + return -ENOMEM; + + slot->type = simple_strtoul(drc.drc_type, NULL, 10); + retval = rpaphp_enable_slot(slot); + if (!retval) + retval = rpaphp_register_slot(slot); + + if (retval) + dealloc_slot_struct(slot); + + return retval; +} + +static int rpaphp_drc_add_slot(struct device_node *dn) +{ + struct slot *slot; + int retval = 0; + int i; + const __be32 *indexes, *names, *types, *power_domains; + char *name, *type; + + /* If this is not a hotplug slot, return without doing anything. */ + if (!is_php_dn(dn, &indexes, &names, &types, &power_domains)) + return 0; + + dbg("Entry %s: dn=%pOF\n", __func__, dn); + + /* register PCI devices */ + name = (char *) &names[1]; + type = (char *) &types[1]; + for (i = 0; i < be32_to_cpu(indexes[0]); i++) { + int index; + + index = be32_to_cpu(indexes[i + 1]); + slot = alloc_slot_struct(dn, index, name, + be32_to_cpu(power_domains[i + 1])); + if (!slot) + return -ENOMEM; + + slot->type = simple_strtoul(type, NULL, 10); + + dbg("Found drc-index:0x%x drc-name:%s drc-type:%s\n", + index, name, type); + + retval = rpaphp_enable_slot(slot); + if (!retval) + retval = rpaphp_register_slot(slot); + + if (retval) + dealloc_slot_struct(slot); + + name += strlen(name) + 1; + type += strlen(type) + 1; + } + dbg("%s - Exit: rc[%d]\n", __func__, retval); + + /* XXX FIXME: reports a failure only if last entry in loop failed */ + return retval; +} + +/** + * rpaphp_add_slot -- declare a hotplug slot to the hotplug subsystem. + * @dn: device node of slot + * + * This subroutine will register a hotpluggable slot with the + * PCI hotplug infrastructure. This routine is typically called + * during boot time, if the hotplug slots are present at boot time, + * or is called later, by the dlpar add code, if the slot is + * being dynamically added during runtime. + * + * If the device node points at an embedded (built-in) slot, this + * routine will just return without doing anything, since embedded + * slots cannot be hotplugged. + * + * To remove a slot, it suffices to call rpaphp_deregister_slot(). + */ +int rpaphp_add_slot(struct device_node *dn) +{ + if (!of_node_name_eq(dn, "pci")) + return 0; + + if (of_find_property(dn, "ibm,drc-info", NULL)) + return rpaphp_drc_info_add_slot(dn); + else + return rpaphp_drc_add_slot(dn); +} +EXPORT_SYMBOL_GPL(rpaphp_add_slot); + +static void __exit cleanup_slots(void) +{ + struct slot *slot, *next; + + /* + * Unregister all of our slots with the pci_hotplug subsystem, + * and free up all memory that we had allocated. + */ + + list_for_each_entry_safe(slot, next, &rpaphp_slot_head, + rpaphp_slot_list) { + list_del(&slot->rpaphp_slot_list); + pci_hp_deregister(&slot->hotplug_slot); + dealloc_slot_struct(slot); + } +} + +static int __init rpaphp_init(void) +{ + struct device_node *dn; + + info(DRIVER_DESC " version: " DRIVER_VERSION "\n"); + + for_each_node_by_name(dn, "pci") + rpaphp_add_slot(dn); + + return 0; +} + +static void __exit rpaphp_exit(void) +{ + cleanup_slots(); +} + +static int enable_slot(struct hotplug_slot *hotplug_slot) +{ + struct slot *slot = to_slot(hotplug_slot); + int state; + int retval; + + if (slot->state == CONFIGURED) + return 0; + + retval = rpaphp_get_sensor_state(slot, &state); + if (retval) + return retval; + + if (state == PRESENT) { + pseries_eeh_init_edev_recursive(PCI_DN(slot->dn)); + + pci_lock_rescan_remove(); + pci_hp_add_devices(slot->bus); + pci_unlock_rescan_remove(); + slot->state = CONFIGURED; + } else if (state == EMPTY) { + slot->state = EMPTY; + } else { + err("%s: slot[%s] is in invalid state\n", __func__, slot->name); + slot->state = NOT_VALID; + return -EINVAL; + } + + slot->bus->max_bus_speed = get_max_bus_speed(slot); + return 0; +} + +static int disable_slot(struct hotplug_slot *hotplug_slot) +{ + struct slot *slot = to_slot(hotplug_slot); + if (slot->state == NOT_CONFIGURED) + return -EINVAL; + + pci_lock_rescan_remove(); + pci_hp_remove_devices(slot->bus); + pci_unlock_rescan_remove(); + vm_unmap_aliases(); + + slot->state = NOT_CONFIGURED; + return 0; +} + +const struct hotplug_slot_ops rpaphp_hotplug_slot_ops = { + .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, +}; + +module_init(rpaphp_init); +module_exit(rpaphp_exit); diff --git a/drivers/pci/hotplug/rpaphp_pci.c b/drivers/pci/hotplug/rpaphp_pci.c new file mode 100644 index 000000000..630f77057 --- /dev/null +++ b/drivers/pci/hotplug/rpaphp_pci.c @@ -0,0 +1,117 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * PCI Hot Plug Controller Driver for RPA-compliant PPC64 platform. + * Copyright (C) 2003 Linda Xie <lxie@us.ibm.com> + * + * All rights reserved. + * + * Send feedback to <lxie@us.ibm.com> + * + */ +#include <linux/of.h> +#include <linux/pci.h> +#include <linux/string.h> + +#include <asm/pci-bridge.h> +#include <asm/rtas.h> +#include <asm/machdep.h> + +#include "../pci.h" /* for pci_add_new_bus */ +#include "rpaphp.h" + +int rpaphp_get_sensor_state(struct slot *slot, int *state) +{ + int rc; + int setlevel; + + rc = rtas_get_sensor(DR_ENTITY_SENSE, slot->index, state); + + if (rc < 0) { + if (rc == -EFAULT || rc == -EEXIST) { + dbg("%s: slot must be power up to get sensor-state\n", + __func__); + + /* some slots have to be powered up + * before get-sensor will succeed. + */ + rc = rtas_set_power_level(slot->power_domain, POWER_ON, + &setlevel); + if (rc < 0) { + dbg("%s: power on slot[%s] failed rc=%d.\n", + __func__, slot->name, rc); + } else { + rc = rtas_get_sensor(DR_ENTITY_SENSE, + slot->index, state); + } + } else if (rc == -ENODEV) + info("%s: slot is unusable\n", __func__); + else + err("%s failed to get sensor state\n", __func__); + } + return rc; +} + +/** + * rpaphp_enable_slot - record slot state, config pci device + * @slot: target &slot + * + * Initialize values in the slot structure to indicate if there is a pci card + * plugged into the slot. If the slot is not empty, run the pcibios routine + * to get pcibios stuff correctly set up. + */ +int rpaphp_enable_slot(struct slot *slot) +{ + int rc, level, state; + struct pci_bus *bus; + + slot->state = EMPTY; + + /* Find out if the power is turned on for the slot */ + rc = rtas_get_power_level(slot->power_domain, &level); + if (rc) + return rc; + + /* Figure out if there is an adapter in the slot */ + rc = rpaphp_get_sensor_state(slot, &state); + if (rc) + return rc; + + bus = pci_find_bus_by_node(slot->dn); + if (!bus) { + err("%s: no pci_bus for dn %pOF\n", __func__, slot->dn); + return -EINVAL; + } + + slot->bus = bus; + slot->pci_devs = &bus->devices; + + /* if there's an adapter in the slot, go add the pci devices */ + if (state == PRESENT) { + slot->state = NOT_CONFIGURED; + + /* non-empty slot has to have child */ + if (!slot->dn->child) { + err("%s: slot[%s]'s device_node doesn't have child for adapter\n", + __func__, slot->name); + return -EINVAL; + } + + if (list_empty(&bus->devices)) { + pseries_eeh_init_edev_recursive(PCI_DN(slot->dn)); + pci_hp_add_devices(bus); + } + + if (!list_empty(&bus->devices)) { + slot->state = CONFIGURED; + } + + if (rpaphp_debug) { + struct pci_dev *dev; + dbg("%s: pci_devs of slot[%pOF]\n", __func__, slot->dn); + list_for_each_entry(dev, &bus->devices, bus_list) + dbg("\t%s\n", pci_name(dev)); + } + } + + return 0; +} diff --git a/drivers/pci/hotplug/rpaphp_slot.c b/drivers/pci/hotplug/rpaphp_slot.c new file mode 100644 index 000000000..779eab12e --- /dev/null +++ b/drivers/pci/hotplug/rpaphp_slot.c @@ -0,0 +1,119 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * RPA Virtual I/O device functions + * Copyright (C) 2004 Linda Xie <lxie@us.ibm.com> + * + * All rights reserved. + * + * Send feedback to <lxie@us.ibm.com> + * + */ +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/sysfs.h> +#include <linux/of.h> +#include <linux/pci.h> +#include <linux/string.h> +#include <linux/slab.h> + +#include <asm/rtas.h> +#include "rpaphp.h" + +/* free up the memory used by a slot */ +void dealloc_slot_struct(struct slot *slot) +{ + of_node_put(slot->dn); + kfree(slot->name); + kfree(slot); +} + +struct slot *alloc_slot_struct(struct device_node *dn, + int drc_index, char *drc_name, int power_domain) +{ + struct slot *slot; + + slot = kzalloc(sizeof(struct slot), GFP_KERNEL); + if (!slot) + goto error_nomem; + slot->name = kstrdup(drc_name, GFP_KERNEL); + if (!slot->name) + goto error_slot; + slot->dn = of_node_get(dn); + slot->index = drc_index; + slot->power_domain = power_domain; + slot->hotplug_slot.ops = &rpaphp_hotplug_slot_ops; + + return (slot); + +error_slot: + kfree(slot); +error_nomem: + return NULL; +} + +static int is_registered(struct slot *slot) +{ + struct slot *tmp_slot; + + list_for_each_entry(tmp_slot, &rpaphp_slot_head, rpaphp_slot_list) { + if (!strcmp(tmp_slot->name, slot->name)) + return 1; + } + return 0; +} + +int rpaphp_deregister_slot(struct slot *slot) +{ + int retval = 0; + struct hotplug_slot *php_slot = &slot->hotplug_slot; + + dbg("%s - Entry: deregistering slot=%s\n", + __func__, slot->name); + + list_del(&slot->rpaphp_slot_list); + pci_hp_deregister(php_slot); + dealloc_slot_struct(slot); + + dbg("%s - Exit: rc[%d]\n", __func__, retval); + return retval; +} +EXPORT_SYMBOL_GPL(rpaphp_deregister_slot); + +int rpaphp_register_slot(struct slot *slot) +{ + struct hotplug_slot *php_slot = &slot->hotplug_slot; + struct device_node *child; + u32 my_index; + int retval; + int slotno = -1; + + dbg("%s registering slot:path[%pOF] index[%x], name[%s] pdomain[%x] type[%d]\n", + __func__, slot->dn, slot->index, slot->name, + slot->power_domain, slot->type); + + /* should not try to register the same slot twice */ + if (is_registered(slot)) { + err("rpaphp_register_slot: slot[%s] is already registered\n", slot->name); + return -EAGAIN; + } + + for_each_child_of_node(slot->dn, child) { + retval = of_property_read_u32(child, "ibm,my-drc-index", &my_index); + if (my_index == slot->index) { + slotno = PCI_SLOT(PCI_DN(child)->devfn); + of_node_put(child); + break; + } + } + + retval = pci_hp_register(php_slot, slot->bus, slotno, slot->name); + if (retval) { + err("pci_hp_register failed with error %d\n", retval); + return retval; + } + + /* add slot to our internal list */ + list_add(&slot->rpaphp_slot_list, &rpaphp_slot_head); + info("Slot [%s] registered\n", slot->name); + return 0; +} diff --git a/drivers/pci/hotplug/s390_pci_hpc.c b/drivers/pci/hotplug/s390_pci_hpc.c new file mode 100644 index 000000000..a89b7de72 --- /dev/null +++ b/drivers/pci/hotplug/s390_pci_hpc.c @@ -0,0 +1,122 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * PCI Hot Plug Controller Driver for System z + * + * Copyright 2012 IBM Corp. + * + * Author(s): + * Jan Glauber <jang@linux.vnet.ibm.com> + */ + +#define KMSG_COMPONENT "zpci" +#define pr_fmt(fmt) KMSG_COMPONENT ": " fmt + +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/pci.h> +#include <linux/pci_hotplug.h> +#include <asm/pci_debug.h> +#include <asm/sclp.h> + +#define SLOT_NAME_SIZE 10 + +static int enable_slot(struct hotplug_slot *hotplug_slot) +{ + struct zpci_dev *zdev = container_of(hotplug_slot, struct zpci_dev, + hotplug_slot); + int rc; + + if (zdev->state != ZPCI_FN_STATE_STANDBY) + return -EIO; + + rc = sclp_pci_configure(zdev->fid); + zpci_dbg(3, "conf fid:%x, rc:%d\n", zdev->fid, rc); + if (rc) + return rc; + zdev->state = ZPCI_FN_STATE_CONFIGURED; + + return zpci_scan_configured_device(zdev, zdev->fh); +} + +static int disable_slot(struct hotplug_slot *hotplug_slot) +{ + struct zpci_dev *zdev = container_of(hotplug_slot, struct zpci_dev, + hotplug_slot); + struct pci_dev *pdev; + + if (zdev->state != ZPCI_FN_STATE_CONFIGURED) + return -EIO; + + pdev = pci_get_slot(zdev->zbus->bus, zdev->devfn); + if (pdev && pci_num_vf(pdev)) { + pci_dev_put(pdev); + return -EBUSY; + } + pci_dev_put(pdev); + + return zpci_deconfigure_device(zdev); +} + +static int reset_slot(struct hotplug_slot *hotplug_slot, bool probe) +{ + struct zpci_dev *zdev = container_of(hotplug_slot, struct zpci_dev, + hotplug_slot); + + if (zdev->state != ZPCI_FN_STATE_CONFIGURED) + return -EIO; + /* + * We can't take the zdev->lock as reset_slot may be called during + * probing and/or device removal which already happens under the + * zdev->lock. Instead the user should use the higher level + * pci_reset_function() or pci_bus_reset() which hold the PCI device + * lock preventing concurrent removal. If not using these functions + * holding the PCI device lock is required. + */ + + /* As long as the function is configured we can reset */ + if (probe) + return 0; + + return zpci_hot_reset_device(zdev); +} + +static int get_power_status(struct hotplug_slot *hotplug_slot, u8 *value) +{ + struct zpci_dev *zdev = container_of(hotplug_slot, struct zpci_dev, + hotplug_slot); + + *value = zpci_is_device_configured(zdev) ? 1 : 0; + return 0; +} + +static int get_adapter_status(struct hotplug_slot *hotplug_slot, u8 *value) +{ + /* if the slot exits it always contains a function */ + *value = 1; + return 0; +} + +static const struct hotplug_slot_ops s390_hotplug_slot_ops = { + .enable_slot = enable_slot, + .disable_slot = disable_slot, + .reset_slot = reset_slot, + .get_power_status = get_power_status, + .get_adapter_status = get_adapter_status, +}; + +int zpci_init_slot(struct zpci_dev *zdev) +{ + char name[SLOT_NAME_SIZE]; + struct zpci_bus *zbus = zdev->zbus; + + zdev->hotplug_slot.ops = &s390_hotplug_slot_ops; + + snprintf(name, SLOT_NAME_SIZE, "%08x", zdev->fid); + return pci_hp_register(&zdev->hotplug_slot, zbus->bus, + zdev->devfn, name); +} + +void zpci_exit_slot(struct zpci_dev *zdev) +{ + pci_hp_deregister(&zdev->hotplug_slot); +} diff --git a/drivers/pci/hotplug/shpchp.h b/drivers/pci/hotplug/shpchp.h new file mode 100644 index 000000000..6e85885b5 --- /dev/null +++ b/drivers/pci/hotplug/shpchp.h @@ -0,0 +1,324 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * 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. + * + * Send feedback to <greg@kroah.com>,<kristen.c.accardi@intel.com> + * + */ +#ifndef _SHPCHP_H +#define _SHPCHP_H + +#include <linux/types.h> +#include <linux/pci.h> +#include <linux/pci_hotplug.h> +#include <linux/delay.h> +#include <linux/sched/signal.h> /* signal_pending(), struct timer_list */ +#include <linux/mutex.h> +#include <linux/workqueue.h> + +#if !defined(MODULE) + #define MY_NAME "shpchp" +#else + #define MY_NAME THIS_MODULE->name +#endif + +extern bool shpchp_poll_mode; +extern int shpchp_poll_time; +extern bool shpchp_debug; + +#define dbg(format, arg...) \ +do { \ + if (shpchp_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) + +#define ctrl_dbg(ctrl, format, arg...) \ + do { \ + if (shpchp_debug) \ + pci_printk(KERN_DEBUG, ctrl->pci_dev, \ + format, ## arg); \ + } while (0) +#define ctrl_err(ctrl, format, arg...) \ + pci_err(ctrl->pci_dev, format, ## arg) +#define ctrl_info(ctrl, format, arg...) \ + pci_info(ctrl->pci_dev, format, ## arg) +#define ctrl_warn(ctrl, format, arg...) \ + pci_warn(ctrl->pci_dev, format, ## arg) + + +#define SLOT_NAME_SIZE 10 +struct slot { + u8 bus; + u8 device; + u16 status; + u32 number; + u8 is_a_board; + u8 state; + u8 attention_save; + u8 presence_save; + u8 latch_save; + u8 pwr_save; + struct controller *ctrl; + const struct hpc_ops *hpc_ops; + struct hotplug_slot hotplug_slot; + struct list_head slot_list; + struct delayed_work work; /* work for button event */ + struct mutex lock; + struct workqueue_struct *wq; + u8 hp_slot; +}; + +struct event_info { + u32 event_type; + struct slot *p_slot; + struct work_struct work; +}; + +struct controller { + struct mutex crit_sect; /* critical section mutex */ + struct mutex cmd_lock; /* command lock */ + int num_slots; /* Number of slots on ctlr */ + int slot_num_inc; /* 1 or -1 */ + struct pci_dev *pci_dev; + struct list_head slot_list; + const struct hpc_ops *hpc_ops; + wait_queue_head_t queue; /* sleep & wake process */ + u8 slot_device_offset; + u32 pcix_misc2_reg; /* for amd pogo errata */ + u32 first_slot; /* First physical slot number */ + u32 cap_offset; + unsigned long mmio_base; + unsigned long mmio_size; + void __iomem *creg; + struct timer_list poll_timer; +}; + +/* Define AMD SHPC ID */ +#define PCI_DEVICE_ID_AMD_POGO_7458 0x7458 + +/* AMD PCI-X bridge registers */ +#define PCIX_MEM_BASE_LIMIT_OFFSET 0x1C +#define PCIX_MISCII_OFFSET 0x48 +#define PCIX_MISC_BRIDGE_ERRORS_OFFSET 0x80 + +/* AMD PCIX_MISCII masks and offsets */ +#define PERRNONFATALENABLE_MASK 0x00040000 +#define PERRFATALENABLE_MASK 0x00080000 +#define PERRFLOODENABLE_MASK 0x00100000 +#define SERRNONFATALENABLE_MASK 0x00200000 +#define SERRFATALENABLE_MASK 0x00400000 + +/* AMD PCIX_MISC_BRIDGE_ERRORS masks and offsets */ +#define PERR_OBSERVED_MASK 0x00000001 + +/* AMD PCIX_MEM_BASE_LIMIT masks */ +#define RSE_MASK 0x40000000 + +#define INT_BUTTON_IGNORE 0 +#define INT_PRESENCE_ON 1 +#define INT_PRESENCE_OFF 2 +#define INT_SWITCH_CLOSE 3 +#define INT_SWITCH_OPEN 4 +#define INT_POWER_FAULT 5 +#define INT_POWER_FAULT_CLEAR 6 +#define INT_BUTTON_PRESS 7 +#define INT_BUTTON_RELEASE 8 +#define INT_BUTTON_CANCEL 9 + +#define STATIC_STATE 0 +#define BLINKINGON_STATE 1 +#define BLINKINGOFF_STATE 2 +#define POWERON_STATE 3 +#define POWEROFF_STATE 4 + +/* Error messages */ +#define INTERLOCK_OPEN 0x00000002 +#define ADD_NOT_SUPPORTED 0x00000003 +#define CARD_FUNCTIONING 0x00000005 +#define ADAPTER_NOT_SAME 0x00000006 +#define NO_ADAPTER_PRESENT 0x00000009 +#define NOT_ENOUGH_RESOURCES 0x0000000B +#define DEVICE_TYPE_NOT_SUPPORTED 0x0000000C +#define WRONG_BUS_FREQUENCY 0x0000000D +#define POWER_FAILURE 0x0000000E + +int __must_check shpchp_create_ctrl_files(struct controller *ctrl); +void shpchp_remove_ctrl_files(struct controller *ctrl); +int shpchp_sysfs_enable_slot(struct slot *slot); +int shpchp_sysfs_disable_slot(struct slot *slot); +u8 shpchp_handle_attention_button(u8 hp_slot, struct controller *ctrl); +u8 shpchp_handle_switch_change(u8 hp_slot, struct controller *ctrl); +u8 shpchp_handle_presence_change(u8 hp_slot, struct controller *ctrl); +u8 shpchp_handle_power_fault(u8 hp_slot, struct controller *ctrl); +int shpchp_configure_device(struct slot *p_slot); +void shpchp_unconfigure_device(struct slot *p_slot); +void cleanup_slots(struct controller *ctrl); +void shpchp_queue_pushbutton_work(struct work_struct *work); +int shpc_init(struct controller *ctrl, struct pci_dev *pdev); + +static inline const char *slot_name(struct slot *slot) +{ + return hotplug_slot_name(&slot->hotplug_slot); +} + +struct ctrl_reg { + volatile u32 base_offset; + volatile u32 slot_avail1; + volatile u32 slot_avail2; + volatile u32 slot_config; + volatile u16 sec_bus_config; + volatile u8 msi_ctrl; + volatile u8 prog_interface; + volatile u16 cmd; + volatile u16 cmd_status; + volatile u32 intr_loc; + volatile u32 serr_loc; + volatile u32 serr_intr_enable; + volatile u32 slot1; +} __attribute__ ((packed)); + +/* offsets to the controller registers based on the above structure layout */ +enum ctrl_offsets { + BASE_OFFSET = offsetof(struct ctrl_reg, base_offset), + SLOT_AVAIL1 = offsetof(struct ctrl_reg, slot_avail1), + SLOT_AVAIL2 = offsetof(struct ctrl_reg, slot_avail2), + SLOT_CONFIG = offsetof(struct ctrl_reg, slot_config), + SEC_BUS_CONFIG = offsetof(struct ctrl_reg, sec_bus_config), + MSI_CTRL = offsetof(struct ctrl_reg, msi_ctrl), + PROG_INTERFACE = offsetof(struct ctrl_reg, prog_interface), + CMD = offsetof(struct ctrl_reg, cmd), + CMD_STATUS = offsetof(struct ctrl_reg, cmd_status), + INTR_LOC = offsetof(struct ctrl_reg, intr_loc), + SERR_LOC = offsetof(struct ctrl_reg, serr_loc), + SERR_INTR_ENABLE = offsetof(struct ctrl_reg, serr_intr_enable), + SLOT1 = offsetof(struct ctrl_reg, slot1), +}; + +static inline struct slot *get_slot(struct hotplug_slot *hotplug_slot) +{ + return container_of(hotplug_slot, struct slot, hotplug_slot); +} + +static inline struct slot *shpchp_find_slot(struct controller *ctrl, u8 device) +{ + struct slot *slot; + + list_for_each_entry(slot, &ctrl->slot_list, slot_list) { + if (slot->device == device) + return slot; + } + + ctrl_err(ctrl, "Slot (device=0x%02x) not found\n", device); + return NULL; +} + +static inline void amd_pogo_errata_save_misc_reg(struct slot *p_slot) +{ + u32 pcix_misc2_temp; + + /* save MiscII register */ + pci_read_config_dword(p_slot->ctrl->pci_dev, PCIX_MISCII_OFFSET, &pcix_misc2_temp); + + p_slot->ctrl->pcix_misc2_reg = pcix_misc2_temp; + + /* clear SERR/PERR enable bits */ + pcix_misc2_temp &= ~SERRFATALENABLE_MASK; + pcix_misc2_temp &= ~SERRNONFATALENABLE_MASK; + pcix_misc2_temp &= ~PERRFLOODENABLE_MASK; + pcix_misc2_temp &= ~PERRFATALENABLE_MASK; + pcix_misc2_temp &= ~PERRNONFATALENABLE_MASK; + pci_write_config_dword(p_slot->ctrl->pci_dev, PCIX_MISCII_OFFSET, pcix_misc2_temp); +} + +static inline void amd_pogo_errata_restore_misc_reg(struct slot *p_slot) +{ + u32 pcix_misc2_temp; + u32 pcix_bridge_errors_reg; + u32 pcix_mem_base_reg; + u8 perr_set; + u8 rse_set; + + /* write-one-to-clear Bridge_Errors[ PERR_OBSERVED ] */ + pci_read_config_dword(p_slot->ctrl->pci_dev, PCIX_MISC_BRIDGE_ERRORS_OFFSET, &pcix_bridge_errors_reg); + perr_set = pcix_bridge_errors_reg & PERR_OBSERVED_MASK; + if (perr_set) { + ctrl_dbg(p_slot->ctrl, + "Bridge_Errors[ PERR_OBSERVED = %08X] (W1C)\n", + perr_set); + + pci_write_config_dword(p_slot->ctrl->pci_dev, PCIX_MISC_BRIDGE_ERRORS_OFFSET, perr_set); + } + + /* write-one-to-clear Memory_Base_Limit[ RSE ] */ + pci_read_config_dword(p_slot->ctrl->pci_dev, PCIX_MEM_BASE_LIMIT_OFFSET, &pcix_mem_base_reg); + rse_set = pcix_mem_base_reg & RSE_MASK; + if (rse_set) { + ctrl_dbg(p_slot->ctrl, "Memory_Base_Limit[ RSE ] (W1C)\n"); + + pci_write_config_dword(p_slot->ctrl->pci_dev, PCIX_MEM_BASE_LIMIT_OFFSET, rse_set); + } + /* restore MiscII register */ + pci_read_config_dword(p_slot->ctrl->pci_dev, PCIX_MISCII_OFFSET, &pcix_misc2_temp); + + if (p_slot->ctrl->pcix_misc2_reg & SERRFATALENABLE_MASK) + pcix_misc2_temp |= SERRFATALENABLE_MASK; + else + pcix_misc2_temp &= ~SERRFATALENABLE_MASK; + + if (p_slot->ctrl->pcix_misc2_reg & SERRNONFATALENABLE_MASK) + pcix_misc2_temp |= SERRNONFATALENABLE_MASK; + else + pcix_misc2_temp &= ~SERRNONFATALENABLE_MASK; + + if (p_slot->ctrl->pcix_misc2_reg & PERRFLOODENABLE_MASK) + pcix_misc2_temp |= PERRFLOODENABLE_MASK; + else + pcix_misc2_temp &= ~PERRFLOODENABLE_MASK; + + if (p_slot->ctrl->pcix_misc2_reg & PERRFATALENABLE_MASK) + pcix_misc2_temp |= PERRFATALENABLE_MASK; + else + pcix_misc2_temp &= ~PERRFATALENABLE_MASK; + + if (p_slot->ctrl->pcix_misc2_reg & PERRNONFATALENABLE_MASK) + pcix_misc2_temp |= PERRNONFATALENABLE_MASK; + else + pcix_misc2_temp &= ~PERRNONFATALENABLE_MASK; + pci_write_config_dword(p_slot->ctrl->pci_dev, PCIX_MISCII_OFFSET, pcix_misc2_temp); +} + +struct hpc_ops { + int (*power_on_slot)(struct slot *slot); + int (*slot_enable)(struct slot *slot); + int (*slot_disable)(struct slot *slot); + int (*set_bus_speed_mode)(struct slot *slot, enum pci_bus_speed speed); + int (*get_power_status)(struct slot *slot, u8 *status); + int (*get_attention_status)(struct slot *slot, u8 *status); + int (*set_attention_status)(struct slot *slot, u8 status); + int (*get_latch_status)(struct slot *slot, u8 *status); + int (*get_adapter_status)(struct slot *slot, u8 *status); + int (*get_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 000000000..53692b048 --- /dev/null +++ b/drivers/pci/hotplug/shpchp_core.c @@ -0,0 +1,346 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * 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. + * + * Send feedback to <greg@kroah.com>, <kristen.c.accardi@intel.com> + * + */ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/kernel.h> +#include <linux/types.h> +#include <linux/slab.h> +#include <linux/pci.h> +#include "shpchp.h" + +/* Global variables */ +bool shpchp_debug; +bool shpchp_poll_mode; +int shpchp_poll_time; + +#define DRIVER_VERSION "0.4" +#define DRIVER_AUTHOR "Dan Zink <dan.zink@compaq.com>, Greg Kroah-Hartman <greg@kroah.com>, Dely Sy <dely.l.sy@intel.com>" +#define DRIVER_DESC "Standard Hot Plug PCI Controller Driver" + +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); + +module_param(shpchp_debug, bool, 0644); +module_param(shpchp_poll_mode, bool, 0644); +module_param(shpchp_poll_time, int, 0644); +MODULE_PARM_DESC(shpchp_debug, "Debugging mode enabled or not"); +MODULE_PARM_DESC(shpchp_poll_mode, "Using polling mechanism for hot-plug events or not"); +MODULE_PARM_DESC(shpchp_poll_time, "Polling mechanism frequency, in seconds"); + +#define SHPC_MODULE_NAME "shpchp" + +static int set_attention_status(struct hotplug_slot *slot, u8 value); +static int enable_slot(struct hotplug_slot *slot); +static int disable_slot(struct hotplug_slot *slot); +static int get_power_status(struct hotplug_slot *slot, u8 *value); +static int get_attention_status(struct hotplug_slot *slot, u8 *value); +static int get_latch_status(struct hotplug_slot *slot, u8 *value); +static int get_adapter_status(struct hotplug_slot *slot, u8 *value); + +static const struct hotplug_slot_ops shpchp_hotplug_slot_ops = { + .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, +}; + +static int init_slots(struct controller *ctrl) +{ + struct slot *slot; + struct hotplug_slot *hotplug_slot; + char name[SLOT_NAME_SIZE]; + int retval; + int i; + + for (i = 0; i < ctrl->num_slots; i++) { + slot = kzalloc(sizeof(*slot), GFP_KERNEL); + if (!slot) { + retval = -ENOMEM; + goto error; + } + + hotplug_slot = &slot->hotplug_slot; + + slot->hp_slot = i; + slot->ctrl = ctrl; + slot->bus = ctrl->pci_dev->subordinate->number; + slot->device = ctrl->slot_device_offset + i; + slot->hpc_ops = ctrl->hpc_ops; + slot->number = ctrl->first_slot + (ctrl->slot_num_inc * i); + + slot->wq = alloc_workqueue("shpchp-%d", 0, 0, slot->number); + if (!slot->wq) { + retval = -ENOMEM; + goto error_slot; + } + + mutex_init(&slot->lock); + INIT_DELAYED_WORK(&slot->work, shpchp_queue_pushbutton_work); + + /* register this slot with the hotplug pci core */ + snprintf(name, SLOT_NAME_SIZE, "%d", slot->number); + hotplug_slot->ops = &shpchp_hotplug_slot_ops; + + ctrl_dbg(ctrl, "Registering domain:bus:dev=%04x:%02x:%02x hp_slot=%x sun=%x slot_device_offset=%x\n", + pci_domain_nr(ctrl->pci_dev->subordinate), + slot->bus, slot->device, slot->hp_slot, slot->number, + ctrl->slot_device_offset); + retval = pci_hp_register(hotplug_slot, + ctrl->pci_dev->subordinate, slot->device, name); + if (retval) { + ctrl_err(ctrl, "pci_hp_register failed with error %d\n", + retval); + goto error_slotwq; + } + + get_power_status(hotplug_slot, &slot->pwr_save); + get_attention_status(hotplug_slot, &slot->attention_save); + get_latch_status(hotplug_slot, &slot->latch_save); + get_adapter_status(hotplug_slot, &slot->presence_save); + + list_add(&slot->slot_list, &ctrl->slot_list); + } + + return 0; +error_slotwq: + destroy_workqueue(slot->wq); +error_slot: + kfree(slot); +error: + return retval; +} + +void cleanup_slots(struct controller *ctrl) +{ + struct slot *slot, *next; + + list_for_each_entry_safe(slot, next, &ctrl->slot_list, slot_list) { + list_del(&slot->slot_list); + cancel_delayed_work(&slot->work); + destroy_workqueue(slot->wq); + pci_hp_deregister(&slot->hotplug_slot); + kfree(slot); + } +} + +/* + * set_attention_status - Turns the Amber LED for a slot on, off or blink + */ +static int set_attention_status(struct hotplug_slot *hotplug_slot, u8 status) +{ + struct slot *slot = get_slot(hotplug_slot); + + ctrl_dbg(slot->ctrl, "%s: physical_slot = %s\n", + __func__, slot_name(slot)); + + slot->attention_save = status; + slot->hpc_ops->set_attention_status(slot, status); + + return 0; +} + +static int enable_slot(struct hotplug_slot *hotplug_slot) +{ + struct slot *slot = get_slot(hotplug_slot); + + ctrl_dbg(slot->ctrl, "%s: physical_slot = %s\n", + __func__, slot_name(slot)); + + return shpchp_sysfs_enable_slot(slot); +} + +static int disable_slot(struct hotplug_slot *hotplug_slot) +{ + struct slot *slot = get_slot(hotplug_slot); + + ctrl_dbg(slot->ctrl, "%s: physical_slot = %s\n", + __func__, slot_name(slot)); + + return shpchp_sysfs_disable_slot(slot); +} + +static int get_power_status(struct hotplug_slot *hotplug_slot, u8 *value) +{ + struct slot *slot = get_slot(hotplug_slot); + int retval; + + ctrl_dbg(slot->ctrl, "%s: physical_slot = %s\n", + __func__, slot_name(slot)); + + retval = slot->hpc_ops->get_power_status(slot, value); + if (retval < 0) + *value = slot->pwr_save; + + return 0; +} + +static int get_attention_status(struct hotplug_slot *hotplug_slot, u8 *value) +{ + struct slot *slot = get_slot(hotplug_slot); + int retval; + + ctrl_dbg(slot->ctrl, "%s: physical_slot = %s\n", + __func__, slot_name(slot)); + + retval = slot->hpc_ops->get_attention_status(slot, value); + if (retval < 0) + *value = slot->attention_save; + + return 0; +} + +static int get_latch_status(struct hotplug_slot *hotplug_slot, u8 *value) +{ + struct slot *slot = get_slot(hotplug_slot); + int retval; + + ctrl_dbg(slot->ctrl, "%s: physical_slot = %s\n", + __func__, slot_name(slot)); + + retval = slot->hpc_ops->get_latch_status(slot, value); + if (retval < 0) + *value = slot->latch_save; + + return 0; +} + +static int get_adapter_status(struct hotplug_slot *hotplug_slot, u8 *value) +{ + struct slot *slot = get_slot(hotplug_slot); + int retval; + + ctrl_dbg(slot->ctrl, "%s: physical_slot = %s\n", + __func__, slot_name(slot)); + + retval = slot->hpc_ops->get_adapter_status(slot, value); + if (retval < 0) + *value = slot->presence_save; + + return 0; +} + +static bool shpc_capable(struct pci_dev *bridge) +{ + /* + * It is assumed that AMD GOLAM chips support SHPC but they do not + * have SHPC capability. + */ + if (bridge->vendor == PCI_VENDOR_ID_AMD && + bridge->device == PCI_DEVICE_ID_AMD_GOLAM_7450) + return true; + + if (pci_find_capability(bridge, PCI_CAP_ID_SHPC)) + return true; + + return false; +} + +static int shpc_probe(struct pci_dev *pdev, const struct pci_device_id *ent) +{ + int rc; + struct controller *ctrl; + + if (!shpc_capable(pdev)) + return -ENODEV; + + if (acpi_get_hp_hw_control_from_firmware(pdev)) + return -ENODEV; + + ctrl = kzalloc(sizeof(*ctrl), GFP_KERNEL); + if (!ctrl) + goto err_out_none; + + INIT_LIST_HEAD(&ctrl->slot_list); + + rc = shpc_init(ctrl, pdev); + if (rc) { + ctrl_dbg(ctrl, "Controller initialization failed\n"); + goto err_out_free_ctrl; + } + + pci_set_drvdata(pdev, ctrl); + + /* Setup the slot information structures */ + rc = init_slots(ctrl); + if (rc) { + ctrl_err(ctrl, "Slot initialization failed\n"); + goto err_out_release_ctlr; + } + + rc = shpchp_create_ctrl_files(ctrl); + if (rc) + goto err_cleanup_slots; + + pdev->shpc_managed = 1; + return 0; + +err_cleanup_slots: + cleanup_slots(ctrl); +err_out_release_ctlr: + ctrl->hpc_ops->release_ctlr(ctrl); +err_out_free_ctrl: + kfree(ctrl); +err_out_none: + return -ENODEV; +} + +static void shpc_remove(struct pci_dev *dev) +{ + struct controller *ctrl = pci_get_drvdata(dev); + + dev->shpc_managed = 0; + shpchp_remove_ctrl_files(ctrl); + ctrl->hpc_ops->release_ctlr(ctrl); + kfree(ctrl); +} + +static const struct pci_device_id shpcd_pci_tbl[] = { + {PCI_DEVICE_CLASS(PCI_CLASS_BRIDGE_PCI_NORMAL, ~0)}, + { /* end: all zeroes */ } +}; +MODULE_DEVICE_TABLE(pci, shpcd_pci_tbl); + +static struct pci_driver shpc_driver = { + .name = SHPC_MODULE_NAME, + .id_table = shpcd_pci_tbl, + .probe = shpc_probe, + .remove = shpc_remove, +}; + +static int __init shpcd_init(void) +{ + int retval; + + retval = pci_register_driver(&shpc_driver); + dbg("%s: pci_register_driver = %d\n", __func__, retval); + info(DRIVER_DESC " version: " DRIVER_VERSION "\n"); + + return retval; +} + +static void __exit shpcd_cleanup(void) +{ + dbg("unload_shpchpd()\n"); + pci_unregister_driver(&shpc_driver); + info(DRIVER_DESC " version: " DRIVER_VERSION " unloaded\n"); +} + +module_init(shpcd_init); +module_exit(shpcd_cleanup); diff --git a/drivers/pci/hotplug/shpchp_ctrl.c b/drivers/pci/hotplug/shpchp_ctrl.c new file mode 100644 index 000000000..6a6705e0c --- /dev/null +++ b/drivers/pci/hotplug/shpchp_ctrl.c @@ -0,0 +1,705 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * 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. + * + * Send feedback to <greg@kroah.com>, <kristen.c.accardi@intel.com> + * + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/types.h> +#include <linux/slab.h> +#include <linux/pci.h> +#include "../pci.h" +#include "shpchp.h" + +static void interrupt_event_handler(struct work_struct *work); +static int shpchp_enable_slot(struct slot *p_slot); +static int shpchp_disable_slot(struct slot *p_slot); + +static int queue_interrupt_event(struct slot *p_slot, u32 event_type) +{ + struct event_info *info; + + info = kmalloc(sizeof(*info), GFP_ATOMIC); + if (!info) + return -ENOMEM; + + info->event_type = event_type; + info->p_slot = p_slot; + INIT_WORK(&info->work, interrupt_event_handler); + + queue_work(p_slot->wq, &info->work); + + return 0; +} + +u8 shpchp_handle_attention_button(u8 hp_slot, struct controller *ctrl) +{ + struct slot *p_slot; + u32 event_type; + + /* Attention Button Change */ + ctrl_dbg(ctrl, "Attention button interrupt received\n"); + + p_slot = shpchp_find_slot(ctrl, hp_slot + ctrl->slot_device_offset); + p_slot->hpc_ops->get_adapter_status(p_slot, &(p_slot->presence_save)); + + /* + * Button pressed - See if need to TAKE ACTION!!! + */ + ctrl_info(ctrl, "Button pressed on Slot(%s)\n", slot_name(p_slot)); + event_type = INT_BUTTON_PRESS; + + queue_interrupt_event(p_slot, event_type); + + return 0; + +} + +u8 shpchp_handle_switch_change(u8 hp_slot, struct controller *ctrl) +{ + struct slot *p_slot; + u8 getstatus; + u32 event_type; + + /* Switch Change */ + ctrl_dbg(ctrl, "Switch interrupt received\n"); + + p_slot = shpchp_find_slot(ctrl, hp_slot + ctrl->slot_device_offset); + p_slot->hpc_ops->get_adapter_status(p_slot, &(p_slot->presence_save)); + p_slot->hpc_ops->get_latch_status(p_slot, &getstatus); + ctrl_dbg(ctrl, "Card present %x Power status %x\n", + p_slot->presence_save, p_slot->pwr_save); + + if (getstatus) { + /* + * Switch opened + */ + ctrl_info(ctrl, "Latch open on Slot(%s)\n", slot_name(p_slot)); + event_type = INT_SWITCH_OPEN; + if (p_slot->pwr_save && p_slot->presence_save) { + event_type = INT_POWER_FAULT; + ctrl_err(ctrl, "Surprise Removal of card\n"); + } + } else { + /* + * Switch closed + */ + ctrl_info(ctrl, "Latch close on Slot(%s)\n", slot_name(p_slot)); + event_type = INT_SWITCH_CLOSE; + } + + queue_interrupt_event(p_slot, event_type); + + return 1; +} + +u8 shpchp_handle_presence_change(u8 hp_slot, struct controller *ctrl) +{ + struct slot *p_slot; + u32 event_type; + + /* Presence Change */ + ctrl_dbg(ctrl, "Presence/Notify input change\n"); + + p_slot = shpchp_find_slot(ctrl, hp_slot + ctrl->slot_device_offset); + + /* + * Save the presence state + */ + p_slot->hpc_ops->get_adapter_status(p_slot, &(p_slot->presence_save)); + if (p_slot->presence_save) { + /* + * Card Present + */ + ctrl_info(ctrl, "Card present on Slot(%s)\n", + slot_name(p_slot)); + event_type = INT_PRESENCE_ON; + } else { + /* + * Not Present + */ + ctrl_info(ctrl, "Card not present on Slot(%s)\n", + slot_name(p_slot)); + event_type = INT_PRESENCE_OFF; + } + + queue_interrupt_event(p_slot, event_type); + + return 1; +} + +u8 shpchp_handle_power_fault(u8 hp_slot, struct controller *ctrl) +{ + struct slot *p_slot; + u32 event_type; + + /* Power fault */ + ctrl_dbg(ctrl, "Power fault interrupt received\n"); + + p_slot = shpchp_find_slot(ctrl, hp_slot + ctrl->slot_device_offset); + + if (!(p_slot->hpc_ops->query_power_fault(p_slot))) { + /* + * Power fault Cleared + */ + ctrl_info(ctrl, "Power fault cleared on Slot(%s)\n", + slot_name(p_slot)); + p_slot->status = 0x00; + event_type = INT_POWER_FAULT_CLEAR; + } else { + /* + * Power fault + */ + ctrl_info(ctrl, "Power fault on Slot(%s)\n", slot_name(p_slot)); + event_type = INT_POWER_FAULT; + /* set power fault status for this board */ + p_slot->status = 0xFF; + ctrl_info(ctrl, "Power fault bit %x set\n", hp_slot); + } + + queue_interrupt_event(p_slot, event_type); + + return 1; +} + +/* The following routines constitute the bulk of the + hotplug controller logic + */ +static int change_bus_speed(struct controller *ctrl, struct slot *p_slot, + enum pci_bus_speed speed) +{ + int rc = 0; + + ctrl_dbg(ctrl, "Change speed to %d\n", speed); + rc = p_slot->hpc_ops->set_bus_speed_mode(p_slot, speed); + if (rc) { + ctrl_err(ctrl, "%s: Issue of set bus speed mode command failed\n", + __func__); + return WRONG_BUS_FREQUENCY; + } + return rc; +} + +static int fix_bus_speed(struct controller *ctrl, struct slot *pslot, + u8 flag, enum pci_bus_speed asp, enum pci_bus_speed bsp, + enum pci_bus_speed msp) +{ + int rc = 0; + + /* + * If other slots on the same bus are occupied, we cannot + * change the bus speed. + */ + if (flag) { + if (asp < bsp) { + ctrl_err(ctrl, "Speed of bus %x and adapter %x mismatch\n", + bsp, asp); + rc = WRONG_BUS_FREQUENCY; + } + return rc; + } + + if (asp < msp) { + if (bsp != asp) + rc = change_bus_speed(ctrl, pslot, asp); + } else { + if (bsp != msp) + rc = change_bus_speed(ctrl, pslot, msp); + } + return rc; +} + +/** + * board_added - Called after a board has been added to the system. + * @p_slot: target &slot + * + * Turns power on for the board. + * Configures board. + */ +static int board_added(struct slot *p_slot) +{ + u8 hp_slot; + u8 slots_not_empty = 0; + int rc = 0; + enum pci_bus_speed asp, bsp, msp; + struct controller *ctrl = p_slot->ctrl; + struct pci_bus *parent = ctrl->pci_dev->subordinate; + + hp_slot = p_slot->device - ctrl->slot_device_offset; + + ctrl_dbg(ctrl, "%s: p_slot->device, slot_offset, hp_slot = %d, %d ,%d\n", + __func__, p_slot->device, ctrl->slot_device_offset, hp_slot); + + /* Power on slot without connecting to bus */ + rc = p_slot->hpc_ops->power_on_slot(p_slot); + if (rc) { + ctrl_err(ctrl, "Failed to power on slot\n"); + return -1; + } + + if ((ctrl->pci_dev->vendor == 0x8086) && (ctrl->pci_dev->device == 0x0332)) { + rc = p_slot->hpc_ops->set_bus_speed_mode(p_slot, PCI_SPEED_33MHz); + if (rc) { + ctrl_err(ctrl, "%s: Issue of set bus speed mode command failed\n", + __func__); + return WRONG_BUS_FREQUENCY; + } + + /* turn on board, blink green LED, turn off Amber LED */ + rc = p_slot->hpc_ops->slot_enable(p_slot); + if (rc) { + ctrl_err(ctrl, "Issue of Slot Enable command failed\n"); + return rc; + } + } + + rc = p_slot->hpc_ops->get_adapter_speed(p_slot, &asp); + if (rc) { + ctrl_err(ctrl, "Can't get adapter speed or bus mode mismatch\n"); + return WRONG_BUS_FREQUENCY; + } + + bsp = ctrl->pci_dev->subordinate->cur_bus_speed; + msp = ctrl->pci_dev->subordinate->max_bus_speed; + + /* Check if there are other slots or devices on the same bus */ + if (!list_empty(&ctrl->pci_dev->subordinate->devices)) + slots_not_empty = 1; + + ctrl_dbg(ctrl, "%s: slots_not_empty %d, adapter_speed %d, bus_speed %d, max_bus_speed %d\n", + __func__, slots_not_empty, asp, + bsp, msp); + + rc = fix_bus_speed(ctrl, p_slot, slots_not_empty, asp, bsp, msp); + if (rc) + return rc; + + /* turn on board, blink green LED, turn off Amber LED */ + rc = p_slot->hpc_ops->slot_enable(p_slot); + if (rc) { + ctrl_err(ctrl, "Issue of Slot Enable command failed\n"); + return rc; + } + + /* Wait for ~1 second */ + msleep(1000); + + ctrl_dbg(ctrl, "%s: slot status = %x\n", __func__, p_slot->status); + /* Check for a power fault */ + if (p_slot->status == 0xFF) { + /* power fault occurred, but it was benign */ + ctrl_dbg(ctrl, "%s: Power fault\n", __func__); + p_slot->status = 0; + goto err_exit; + } + + if (shpchp_configure_device(p_slot)) { + ctrl_err(ctrl, "Cannot add device at %04x:%02x:%02x\n", + pci_domain_nr(parent), p_slot->bus, p_slot->device); + goto err_exit; + } + + p_slot->status = 0; + p_slot->is_a_board = 0x01; + p_slot->pwr_save = 1; + + p_slot->hpc_ops->green_led_on(p_slot); + + return 0; + +err_exit: + /* turn off slot, turn on Amber LED, turn off Green LED */ + rc = p_slot->hpc_ops->slot_disable(p_slot); + if (rc) { + ctrl_err(ctrl, "%s: Issue of Slot Disable command failed\n", + __func__); + return rc; + } + + return(rc); +} + + +/** + * remove_board - Turns off slot and LEDs + * @p_slot: target &slot + */ +static int remove_board(struct slot *p_slot) +{ + struct controller *ctrl = p_slot->ctrl; + u8 hp_slot; + int rc; + + shpchp_unconfigure_device(p_slot); + + hp_slot = p_slot->device - ctrl->slot_device_offset; + p_slot = shpchp_find_slot(ctrl, hp_slot + ctrl->slot_device_offset); + + ctrl_dbg(ctrl, "%s: hp_slot = %d\n", __func__, hp_slot); + + /* Change status to shutdown */ + if (p_slot->is_a_board) + p_slot->status = 0x01; + + /* turn off slot, turn on Amber LED, turn off Green LED */ + rc = p_slot->hpc_ops->slot_disable(p_slot); + if (rc) { + ctrl_err(ctrl, "%s: Issue of Slot Disable command failed\n", + __func__); + return rc; + } + + rc = p_slot->hpc_ops->set_attention_status(p_slot, 0); + if (rc) { + ctrl_err(ctrl, "Issue of Set Attention command failed\n"); + return rc; + } + + p_slot->pwr_save = 0; + p_slot->is_a_board = 0; + + return 0; +} + + +struct pushbutton_work_info { + struct slot *p_slot; + struct work_struct work; +}; + +/** + * shpchp_pushbutton_thread - handle pushbutton events + * @work: &struct work_struct to be handled + * + * Scheduled procedure to handle blocking stuff for the pushbuttons. + * Handles all pending events and exits. + */ +static void shpchp_pushbutton_thread(struct work_struct *work) +{ + struct pushbutton_work_info *info = + container_of(work, struct pushbutton_work_info, work); + struct slot *p_slot = info->p_slot; + + mutex_lock(&p_slot->lock); + switch (p_slot->state) { + case POWEROFF_STATE: + mutex_unlock(&p_slot->lock); + shpchp_disable_slot(p_slot); + mutex_lock(&p_slot->lock); + p_slot->state = STATIC_STATE; + break; + case POWERON_STATE: + mutex_unlock(&p_slot->lock); + if (shpchp_enable_slot(p_slot)) + p_slot->hpc_ops->green_led_off(p_slot); + mutex_lock(&p_slot->lock); + p_slot->state = STATIC_STATE; + break; + default: + break; + } + mutex_unlock(&p_slot->lock); + + kfree(info); +} + +void shpchp_queue_pushbutton_work(struct work_struct *work) +{ + struct slot *p_slot = container_of(work, struct slot, work.work); + struct pushbutton_work_info *info; + + info = kmalloc(sizeof(*info), GFP_KERNEL); + if (!info) { + ctrl_err(p_slot->ctrl, "%s: Cannot allocate memory\n", + __func__); + return; + } + info->p_slot = p_slot; + INIT_WORK(&info->work, shpchp_pushbutton_thread); + + mutex_lock(&p_slot->lock); + switch (p_slot->state) { + case BLINKINGOFF_STATE: + p_slot->state = POWEROFF_STATE; + break; + case BLINKINGON_STATE: + p_slot->state = POWERON_STATE; + break; + default: + kfree(info); + goto out; + } + queue_work(p_slot->wq, &info->work); + out: + mutex_unlock(&p_slot->lock); +} + +static void update_slot_info(struct slot *slot) +{ + slot->hpc_ops->get_power_status(slot, &slot->pwr_save); + slot->hpc_ops->get_attention_status(slot, &slot->attention_save); + slot->hpc_ops->get_latch_status(slot, &slot->latch_save); + slot->hpc_ops->get_adapter_status(slot, &slot->presence_save); +} + +/* + * Note: This function must be called with slot->lock held + */ +static void handle_button_press_event(struct slot *p_slot) +{ + u8 getstatus; + struct controller *ctrl = p_slot->ctrl; + + switch (p_slot->state) { + case STATIC_STATE: + p_slot->hpc_ops->get_power_status(p_slot, &getstatus); + if (getstatus) { + p_slot->state = BLINKINGOFF_STATE; + ctrl_info(ctrl, "PCI slot #%s - powering off due to button press\n", + slot_name(p_slot)); + } else { + p_slot->state = BLINKINGON_STATE; + ctrl_info(ctrl, "PCI slot #%s - powering on due to button press\n", + slot_name(p_slot)); + } + /* blink green LED and turn off amber */ + p_slot->hpc_ops->green_led_blink(p_slot); + p_slot->hpc_ops->set_attention_status(p_slot, 0); + + queue_delayed_work(p_slot->wq, &p_slot->work, 5*HZ); + break; + case BLINKINGOFF_STATE: + case BLINKINGON_STATE: + /* + * Cancel if we are still blinking; this means that we + * press the attention again before the 5 sec. limit + * expires to cancel hot-add or hot-remove + */ + ctrl_info(ctrl, "Button cancel on Slot(%s)\n", + slot_name(p_slot)); + cancel_delayed_work(&p_slot->work); + if (p_slot->state == BLINKINGOFF_STATE) + p_slot->hpc_ops->green_led_on(p_slot); + else + p_slot->hpc_ops->green_led_off(p_slot); + p_slot->hpc_ops->set_attention_status(p_slot, 0); + ctrl_info(ctrl, "PCI slot #%s - action canceled due to button press\n", + slot_name(p_slot)); + p_slot->state = STATIC_STATE; + break; + case POWEROFF_STATE: + case POWERON_STATE: + /* + * Ignore if the slot is on power-on or power-off state; + * this means that the previous attention button action + * to hot-add or hot-remove is undergoing + */ + ctrl_info(ctrl, "Button ignore on Slot(%s)\n", + slot_name(p_slot)); + update_slot_info(p_slot); + break; + default: + ctrl_warn(ctrl, "Not a valid state\n"); + break; + } +} + +static void interrupt_event_handler(struct work_struct *work) +{ + struct event_info *info = container_of(work, struct event_info, work); + struct slot *p_slot = info->p_slot; + + mutex_lock(&p_slot->lock); + switch (info->event_type) { + case INT_BUTTON_PRESS: + handle_button_press_event(p_slot); + break; + case INT_POWER_FAULT: + ctrl_dbg(p_slot->ctrl, "%s: Power fault\n", __func__); + p_slot->hpc_ops->set_attention_status(p_slot, 1); + p_slot->hpc_ops->green_led_off(p_slot); + break; + default: + update_slot_info(p_slot); + break; + } + mutex_unlock(&p_slot->lock); + + kfree(info); +} + + +static int shpchp_enable_slot (struct slot *p_slot) +{ + u8 getstatus = 0; + int rc, retval = -ENODEV; + struct controller *ctrl = p_slot->ctrl; + + /* Check to see if (latch closed, card present, power off) */ + mutex_lock(&p_slot->ctrl->crit_sect); + rc = p_slot->hpc_ops->get_adapter_status(p_slot, &getstatus); + if (rc || !getstatus) { + ctrl_info(ctrl, "No adapter on slot(%s)\n", slot_name(p_slot)); + goto out; + } + rc = p_slot->hpc_ops->get_latch_status(p_slot, &getstatus); + if (rc || getstatus) { + ctrl_info(ctrl, "Latch open on slot(%s)\n", slot_name(p_slot)); + goto out; + } + rc = p_slot->hpc_ops->get_power_status(p_slot, &getstatus); + if (rc || getstatus) { + ctrl_info(ctrl, "Already enabled on slot(%s)\n", + slot_name(p_slot)); + goto out; + } + + p_slot->is_a_board = 1; + + /* We have to save the presence info for these slots */ + p_slot->hpc_ops->get_adapter_status(p_slot, &(p_slot->presence_save)); + p_slot->hpc_ops->get_power_status(p_slot, &(p_slot->pwr_save)); + ctrl_dbg(ctrl, "%s: p_slot->pwr_save %x\n", __func__, p_slot->pwr_save); + p_slot->hpc_ops->get_latch_status(p_slot, &getstatus); + + if ((p_slot->ctrl->pci_dev->vendor == PCI_VENDOR_ID_AMD && + p_slot->ctrl->pci_dev->device == PCI_DEVICE_ID_AMD_POGO_7458) + && p_slot->ctrl->num_slots == 1) { + /* handle AMD POGO errata; this must be done before enable */ + amd_pogo_errata_save_misc_reg(p_slot); + retval = board_added(p_slot); + /* handle AMD POGO errata; this must be done after enable */ + amd_pogo_errata_restore_misc_reg(p_slot); + } else + retval = board_added(p_slot); + + if (retval) { + p_slot->hpc_ops->get_adapter_status(p_slot, + &(p_slot->presence_save)); + p_slot->hpc_ops->get_latch_status(p_slot, &getstatus); + } + + update_slot_info(p_slot); + out: + mutex_unlock(&p_slot->ctrl->crit_sect); + return retval; +} + + +static int shpchp_disable_slot (struct slot *p_slot) +{ + u8 getstatus = 0; + int rc, retval = -ENODEV; + struct controller *ctrl = p_slot->ctrl; + + if (!p_slot->ctrl) + return -ENODEV; + + /* Check to see if (latch closed, card present, power on) */ + mutex_lock(&p_slot->ctrl->crit_sect); + + rc = p_slot->hpc_ops->get_adapter_status(p_slot, &getstatus); + if (rc || !getstatus) { + ctrl_info(ctrl, "No adapter on slot(%s)\n", slot_name(p_slot)); + goto out; + } + rc = p_slot->hpc_ops->get_latch_status(p_slot, &getstatus); + if (rc || getstatus) { + ctrl_info(ctrl, "Latch open on slot(%s)\n", slot_name(p_slot)); + goto out; + } + rc = p_slot->hpc_ops->get_power_status(p_slot, &getstatus); + if (rc || !getstatus) { + ctrl_info(ctrl, "Already disabled on slot(%s)\n", + slot_name(p_slot)); + goto out; + } + + retval = remove_board(p_slot); + update_slot_info(p_slot); + out: + mutex_unlock(&p_slot->ctrl->crit_sect); + return retval; +} + +int shpchp_sysfs_enable_slot(struct slot *p_slot) +{ + int retval = -ENODEV; + struct controller *ctrl = p_slot->ctrl; + + mutex_lock(&p_slot->lock); + switch (p_slot->state) { + case BLINKINGON_STATE: + cancel_delayed_work(&p_slot->work); + fallthrough; + case STATIC_STATE: + p_slot->state = POWERON_STATE; + mutex_unlock(&p_slot->lock); + retval = shpchp_enable_slot(p_slot); + mutex_lock(&p_slot->lock); + p_slot->state = STATIC_STATE; + break; + case POWERON_STATE: + ctrl_info(ctrl, "Slot %s is already in powering on state\n", + slot_name(p_slot)); + break; + case BLINKINGOFF_STATE: + case POWEROFF_STATE: + ctrl_info(ctrl, "Already enabled on slot %s\n", + slot_name(p_slot)); + break; + default: + ctrl_err(ctrl, "Not a valid state on slot %s\n", + slot_name(p_slot)); + break; + } + mutex_unlock(&p_slot->lock); + + return retval; +} + +int shpchp_sysfs_disable_slot(struct slot *p_slot) +{ + int retval = -ENODEV; + struct controller *ctrl = p_slot->ctrl; + + mutex_lock(&p_slot->lock); + switch (p_slot->state) { + case BLINKINGOFF_STATE: + cancel_delayed_work(&p_slot->work); + fallthrough; + case STATIC_STATE: + p_slot->state = POWEROFF_STATE; + mutex_unlock(&p_slot->lock); + retval = shpchp_disable_slot(p_slot); + mutex_lock(&p_slot->lock); + p_slot->state = STATIC_STATE; + break; + case POWEROFF_STATE: + ctrl_info(ctrl, "Slot %s is already in powering off state\n", + slot_name(p_slot)); + break; + case BLINKINGON_STATE: + case POWERON_STATE: + ctrl_info(ctrl, "Already disabled on slot %s\n", + slot_name(p_slot)); + break; + default: + ctrl_err(ctrl, "Not a valid state on slot %s\n", + slot_name(p_slot)); + break; + } + mutex_unlock(&p_slot->lock); + + return retval; +} diff --git a/drivers/pci/hotplug/shpchp_hpc.c b/drivers/pci/hotplug/shpchp_hpc.c new file mode 100644 index 000000000..bd7557ca4 --- /dev/null +++ b/drivers/pci/hotplug/shpchp_hpc.c @@ -0,0 +1,1091 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * 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. + * + * Send feedback to <greg@kroah.com>,<kristen.c.accardi@intel.com> + * + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/types.h> +#include <linux/pci.h> +#include <linux/interrupt.h> + +#include "shpchp.h" + +/* Slot Available Register I field definition */ +#define SLOT_33MHZ 0x0000001f +#define SLOT_66MHZ_PCIX 0x00001f00 +#define SLOT_100MHZ_PCIX 0x001f0000 +#define SLOT_133MHZ_PCIX 0x1f000000 + +/* Slot Available Register II field definition */ +#define SLOT_66MHZ 0x0000001f +#define SLOT_66MHZ_PCIX_266 0x00000f00 +#define SLOT_100MHZ_PCIX_266 0x0000f000 +#define SLOT_133MHZ_PCIX_266 0x000f0000 +#define SLOT_66MHZ_PCIX_533 0x00f00000 +#define SLOT_100MHZ_PCIX_533 0x0f000000 +#define SLOT_133MHZ_PCIX_533 0xf0000000 + +/* Slot Configuration */ +#define SLOT_NUM 0x0000001F +#define FIRST_DEV_NUM 0x00001F00 +#define PSN 0x07FF0000 +#define UPDOWN 0x20000000 +#define MRLSENSOR 0x40000000 +#define ATTN_BUTTON 0x80000000 + +/* + * Interrupt Locator Register definitions + */ +#define CMD_INTR_PENDING (1 << 0) +#define SLOT_INTR_PENDING(i) (1 << (i + 1)) + +/* + * Controller SERR-INT Register + */ +#define GLOBAL_INTR_MASK (1 << 0) +#define GLOBAL_SERR_MASK (1 << 1) +#define COMMAND_INTR_MASK (1 << 2) +#define ARBITER_SERR_MASK (1 << 3) +#define COMMAND_DETECTED (1 << 16) +#define ARBITER_DETECTED (1 << 17) +#define SERR_INTR_RSVDZ_MASK 0xfffc0000 + +/* + * Logical Slot Register definitions + */ +#define SLOT_REG(i) (SLOT1 + (4 * i)) + +#define SLOT_STATE_SHIFT (0) +#define SLOT_STATE_MASK (3 << 0) +#define SLOT_STATE_PWRONLY (1) +#define SLOT_STATE_ENABLED (2) +#define SLOT_STATE_DISABLED (3) +#define PWR_LED_STATE_SHIFT (2) +#define PWR_LED_STATE_MASK (3 << 2) +#define ATN_LED_STATE_SHIFT (4) +#define ATN_LED_STATE_MASK (3 << 4) +#define ATN_LED_STATE_ON (1) +#define ATN_LED_STATE_BLINK (2) +#define ATN_LED_STATE_OFF (3) +#define POWER_FAULT (1 << 6) +#define ATN_BUTTON (1 << 7) +#define MRL_SENSOR (1 << 8) +#define MHZ66_CAP (1 << 9) +#define PRSNT_SHIFT (10) +#define PRSNT_MASK (3 << 10) +#define PCIX_CAP_SHIFT (12) +#define PCIX_CAP_MASK_PI1 (3 << 12) +#define PCIX_CAP_MASK_PI2 (7 << 12) +#define PRSNT_CHANGE_DETECTED (1 << 16) +#define ISO_PFAULT_DETECTED (1 << 17) +#define BUTTON_PRESS_DETECTED (1 << 18) +#define MRL_CHANGE_DETECTED (1 << 19) +#define CON_PFAULT_DETECTED (1 << 20) +#define PRSNT_CHANGE_INTR_MASK (1 << 24) +#define ISO_PFAULT_INTR_MASK (1 << 25) +#define BUTTON_PRESS_INTR_MASK (1 << 26) +#define MRL_CHANGE_INTR_MASK (1 << 27) +#define CON_PFAULT_INTR_MASK (1 << 28) +#define MRL_CHANGE_SERR_MASK (1 << 29) +#define CON_PFAULT_SERR_MASK (1 << 30) +#define SLOT_REG_RSVDZ_MASK ((1 << 15) | (7 << 21)) + +/* + * SHPC Command Code definitions + * + * Slot Operation 00h - 3Fh + * Set Bus Segment Speed/Mode A 40h - 47h + * Power-Only All Slots 48h + * Enable All Slots 49h + * Set Bus Segment Speed/Mode B (PI=2) 50h - 5Fh + * Reserved Command Codes 60h - BFh + * Vendor Specific Commands C0h - FFh + */ +#define SET_SLOT_PWR 0x01 /* Slot Operation */ +#define SET_SLOT_ENABLE 0x02 +#define SET_SLOT_DISABLE 0x03 +#define SET_PWR_ON 0x04 +#define SET_PWR_BLINK 0x08 +#define SET_PWR_OFF 0x0c +#define SET_ATTN_ON 0x10 +#define SET_ATTN_BLINK 0x20 +#define SET_ATTN_OFF 0x30 +#define SETA_PCI_33MHZ 0x40 /* Set Bus Segment Speed/Mode A */ +#define SETA_PCI_66MHZ 0x41 +#define SETA_PCIX_66MHZ 0x42 +#define SETA_PCIX_100MHZ 0x43 +#define SETA_PCIX_133MHZ 0x44 +#define SETA_RESERVED1 0x45 +#define SETA_RESERVED2 0x46 +#define SETA_RESERVED3 0x47 +#define SET_PWR_ONLY_ALL 0x48 /* Power-Only All Slots */ +#define SET_ENABLE_ALL 0x49 /* Enable All Slots */ +#define SETB_PCI_33MHZ 0x50 /* Set Bus Segment Speed/Mode B */ +#define SETB_PCI_66MHZ 0x51 +#define SETB_PCIX_66MHZ_PM 0x52 +#define SETB_PCIX_100MHZ_PM 0x53 +#define SETB_PCIX_133MHZ_PM 0x54 +#define SETB_PCIX_66MHZ_EM 0x55 +#define SETB_PCIX_100MHZ_EM 0x56 +#define SETB_PCIX_133MHZ_EM 0x57 +#define SETB_PCIX_66MHZ_266 0x58 +#define SETB_PCIX_100MHZ_266 0x59 +#define SETB_PCIX_133MHZ_266 0x5a +#define SETB_PCIX_66MHZ_533 0x5b +#define SETB_PCIX_100MHZ_533 0x5c +#define SETB_PCIX_133MHZ_533 0x5d +#define SETB_RESERVED1 0x5e +#define SETB_RESERVED2 0x5f + +/* + * SHPC controller command error code + */ +#define SWITCH_OPEN 0x1 +#define INVALID_CMD 0x2 +#define INVALID_SPEED_MODE 0x4 + +/* + * For accessing SHPC Working Register Set via PCI Configuration Space + */ +#define DWORD_SELECT 0x2 +#define DWORD_DATA 0x4 + +/* Field Offset in Logical Slot Register - byte boundary */ +#define SLOT_EVENT_LATCH 0x2 +#define SLOT_SERR_INT_MASK 0x3 + +static irqreturn_t shpc_isr(int irq, void *dev_id); +static void start_int_poll_timer(struct controller *ctrl, int sec); +static int hpc_check_cmd_status(struct controller *ctrl); + +static inline u8 shpc_readb(struct controller *ctrl, int reg) +{ + return readb(ctrl->creg + reg); +} + +static inline u16 shpc_readw(struct controller *ctrl, int reg) +{ + return readw(ctrl->creg + reg); +} + +static inline void shpc_writew(struct controller *ctrl, int reg, u16 val) +{ + writew(val, ctrl->creg + reg); +} + +static inline u32 shpc_readl(struct controller *ctrl, int reg) +{ + return readl(ctrl->creg + reg); +} + +static inline void shpc_writel(struct controller *ctrl, int reg, u32 val) +{ + writel(val, ctrl->creg + reg); +} + +static inline int shpc_indirect_read(struct controller *ctrl, int index, + u32 *value) +{ + int rc; + u32 cap_offset = ctrl->cap_offset; + struct pci_dev *pdev = ctrl->pci_dev; + + rc = pci_write_config_byte(pdev, cap_offset + DWORD_SELECT, index); + if (rc) + return rc; + return pci_read_config_dword(pdev, cap_offset + DWORD_DATA, value); +} + +/* + * This is the interrupt polling timeout function. + */ +static void int_poll_timeout(struct timer_list *t) +{ + struct controller *ctrl = from_timer(ctrl, t, poll_timer); + + /* Poll for interrupt events. regs == NULL => polling */ + shpc_isr(0, ctrl); + + if (!shpchp_poll_time) + shpchp_poll_time = 2; /* default polling interval is 2 sec */ + + start_int_poll_timer(ctrl, shpchp_poll_time); +} + +/* + * This function starts the interrupt polling timer. + */ +static void start_int_poll_timer(struct controller *ctrl, int sec) +{ + /* Clamp to sane value */ + if ((sec <= 0) || (sec > 60)) + sec = 2; + + ctrl->poll_timer.expires = jiffies + sec * HZ; + add_timer(&ctrl->poll_timer); +} + +static inline int is_ctrl_busy(struct controller *ctrl) +{ + u16 cmd_status = shpc_readw(ctrl, CMD_STATUS); + return cmd_status & 0x1; +} + +/* + * Returns 1 if SHPC finishes executing a command within 1 sec, + * otherwise returns 0. + */ +static inline int shpc_poll_ctrl_busy(struct controller *ctrl) +{ + int i; + + if (!is_ctrl_busy(ctrl)) + return 1; + + /* Check every 0.1 sec for a total of 1 sec */ + for (i = 0; i < 10; i++) { + msleep(100); + if (!is_ctrl_busy(ctrl)) + return 1; + } + + return 0; +} + +static inline int shpc_wait_cmd(struct controller *ctrl) +{ + int retval = 0; + unsigned long timeout = msecs_to_jiffies(1000); + int rc; + + if (shpchp_poll_mode) + rc = shpc_poll_ctrl_busy(ctrl); + else + rc = wait_event_interruptible_timeout(ctrl->queue, + !is_ctrl_busy(ctrl), timeout); + if (!rc && is_ctrl_busy(ctrl)) { + retval = -EIO; + ctrl_err(ctrl, "Command not completed in 1000 msec\n"); + } else if (rc < 0) { + retval = -EINTR; + ctrl_info(ctrl, "Command was interrupted by a signal\n"); + } + + return retval; +} + +static int shpc_write_cmd(struct slot *slot, u8 t_slot, u8 cmd) +{ + struct controller *ctrl = slot->ctrl; + u16 cmd_status; + int retval = 0; + u16 temp_word; + + mutex_lock(&slot->ctrl->cmd_lock); + + if (!shpc_poll_ctrl_busy(ctrl)) { + /* After 1 sec and the controller is still busy */ + ctrl_err(ctrl, "Controller is still busy after 1 sec\n"); + retval = -EBUSY; + goto out; + } + + ++t_slot; + temp_word = (t_slot << 8) | (cmd & 0xFF); + ctrl_dbg(ctrl, "%s: t_slot %x cmd %x\n", __func__, t_slot, cmd); + + /* To make sure the Controller Busy bit is 0 before we send out the + * command. + */ + shpc_writew(ctrl, CMD, temp_word); + + /* + * Wait for command completion. + */ + retval = shpc_wait_cmd(slot->ctrl); + if (retval) + goto out; + + cmd_status = hpc_check_cmd_status(slot->ctrl); + if (cmd_status) { + ctrl_err(ctrl, "Failed to issued command 0x%x (error code = %d)\n", + cmd, cmd_status); + retval = -EIO; + } + out: + mutex_unlock(&slot->ctrl->cmd_lock); + return retval; +} + +static int hpc_check_cmd_status(struct controller *ctrl) +{ + int retval = 0; + u16 cmd_status = shpc_readw(ctrl, CMD_STATUS) & 0x000F; + + switch (cmd_status >> 1) { + case 0: + retval = 0; + break; + case 1: + retval = SWITCH_OPEN; + ctrl_err(ctrl, "Switch opened!\n"); + break; + case 2: + retval = INVALID_CMD; + ctrl_err(ctrl, "Invalid HPC command!\n"); + break; + case 4: + retval = INVALID_SPEED_MODE; + ctrl_err(ctrl, "Invalid bus speed/mode!\n"); + break; + default: + retval = cmd_status; + } + + return retval; +} + + +static int hpc_get_attention_status(struct slot *slot, u8 *status) +{ + struct controller *ctrl = slot->ctrl; + u32 slot_reg = shpc_readl(ctrl, SLOT_REG(slot->hp_slot)); + u8 state = (slot_reg & ATN_LED_STATE_MASK) >> ATN_LED_STATE_SHIFT; + + switch (state) { + case ATN_LED_STATE_ON: + *status = 1; /* On */ + break; + case ATN_LED_STATE_BLINK: + *status = 2; /* Blink */ + break; + case ATN_LED_STATE_OFF: + *status = 0; /* Off */ + break; + default: + *status = 0xFF; /* Reserved */ + break; + } + + return 0; +} + +static int hpc_get_power_status(struct slot *slot, u8 *status) +{ + struct controller *ctrl = slot->ctrl; + u32 slot_reg = shpc_readl(ctrl, SLOT_REG(slot->hp_slot)); + u8 state = (slot_reg & SLOT_STATE_MASK) >> SLOT_STATE_SHIFT; + + switch (state) { + case SLOT_STATE_PWRONLY: + *status = 2; /* Powered only */ + break; + case SLOT_STATE_ENABLED: + *status = 1; /* Enabled */ + break; + case SLOT_STATE_DISABLED: + *status = 0; /* Disabled */ + break; + default: + *status = 0xFF; /* Reserved */ + break; + } + + return 0; +} + + +static int hpc_get_latch_status(struct slot *slot, u8 *status) +{ + struct controller *ctrl = slot->ctrl; + u32 slot_reg = shpc_readl(ctrl, SLOT_REG(slot->hp_slot)); + + *status = !!(slot_reg & MRL_SENSOR); /* 0 -> close; 1 -> open */ + + return 0; +} + +static int hpc_get_adapter_status(struct slot *slot, u8 *status) +{ + struct controller *ctrl = slot->ctrl; + u32 slot_reg = shpc_readl(ctrl, SLOT_REG(slot->hp_slot)); + u8 state = (slot_reg & PRSNT_MASK) >> PRSNT_SHIFT; + + *status = (state != 0x3) ? 1 : 0; + + return 0; +} + +static int hpc_get_prog_int(struct slot *slot, u8 *prog_int) +{ + struct controller *ctrl = slot->ctrl; + + *prog_int = shpc_readb(ctrl, PROG_INTERFACE); + + return 0; +} + +static int hpc_get_adapter_speed(struct slot *slot, enum pci_bus_speed *value) +{ + int retval = 0; + struct controller *ctrl = slot->ctrl; + u32 slot_reg = shpc_readl(ctrl, SLOT_REG(slot->hp_slot)); + u8 m66_cap = !!(slot_reg & MHZ66_CAP); + u8 pi, pcix_cap; + + retval = hpc_get_prog_int(slot, &pi); + if (retval) + return retval; + + switch (pi) { + case 1: + pcix_cap = (slot_reg & PCIX_CAP_MASK_PI1) >> PCIX_CAP_SHIFT; + break; + case 2: + pcix_cap = (slot_reg & PCIX_CAP_MASK_PI2) >> PCIX_CAP_SHIFT; + break; + default: + return -ENODEV; + } + + ctrl_dbg(ctrl, "%s: slot_reg = %x, pcix_cap = %x, m66_cap = %x\n", + __func__, slot_reg, pcix_cap, m66_cap); + + switch (pcix_cap) { + case 0x0: + *value = m66_cap ? PCI_SPEED_66MHz : PCI_SPEED_33MHz; + break; + case 0x1: + *value = PCI_SPEED_66MHz_PCIX; + break; + case 0x3: + *value = PCI_SPEED_133MHz_PCIX; + break; + case 0x4: + *value = PCI_SPEED_133MHz_PCIX_266; + break; + case 0x5: + *value = PCI_SPEED_133MHz_PCIX_533; + break; + case 0x2: + default: + *value = PCI_SPEED_UNKNOWN; + retval = -ENODEV; + break; + } + + ctrl_dbg(ctrl, "Adapter speed = %d\n", *value); + return retval; +} + +static int hpc_get_mode1_ECC_cap(struct slot *slot, u8 *mode) +{ + int retval = 0; + struct controller *ctrl = slot->ctrl; + u16 sec_bus_status = shpc_readw(ctrl, SEC_BUS_CONFIG); + u8 pi = shpc_readb(ctrl, PROG_INTERFACE); + + if (pi == 2) { + *mode = (sec_bus_status & 0x0100) >> 8; + } else { + retval = -1; + } + + ctrl_dbg(ctrl, "Mode 1 ECC cap = %d\n", *mode); + return retval; +} + +static int hpc_query_power_fault(struct slot *slot) +{ + struct controller *ctrl = slot->ctrl; + u32 slot_reg = shpc_readl(ctrl, SLOT_REG(slot->hp_slot)); + + /* Note: Logic 0 => fault */ + return !(slot_reg & POWER_FAULT); +} + +static int hpc_set_attention_status(struct slot *slot, u8 value) +{ + u8 slot_cmd = 0; + + switch (value) { + case 0: + slot_cmd = SET_ATTN_OFF; /* OFF */ + break; + case 1: + slot_cmd = SET_ATTN_ON; /* ON */ + break; + case 2: + slot_cmd = SET_ATTN_BLINK; /* BLINK */ + break; + default: + return -1; + } + + return shpc_write_cmd(slot, slot->hp_slot, slot_cmd); +} + + +static void hpc_set_green_led_on(struct slot *slot) +{ + shpc_write_cmd(slot, slot->hp_slot, SET_PWR_ON); +} + +static void hpc_set_green_led_off(struct slot *slot) +{ + shpc_write_cmd(slot, slot->hp_slot, SET_PWR_OFF); +} + +static void hpc_set_green_led_blink(struct slot *slot) +{ + shpc_write_cmd(slot, slot->hp_slot, SET_PWR_BLINK); +} + +static void hpc_release_ctlr(struct controller *ctrl) +{ + int i; + u32 slot_reg, serr_int; + + /* + * Mask event interrupts and SERRs of all slots + */ + for (i = 0; i < ctrl->num_slots; i++) { + slot_reg = shpc_readl(ctrl, SLOT_REG(i)); + slot_reg |= (PRSNT_CHANGE_INTR_MASK | ISO_PFAULT_INTR_MASK | + BUTTON_PRESS_INTR_MASK | MRL_CHANGE_INTR_MASK | + CON_PFAULT_INTR_MASK | MRL_CHANGE_SERR_MASK | + CON_PFAULT_SERR_MASK); + slot_reg &= ~SLOT_REG_RSVDZ_MASK; + shpc_writel(ctrl, SLOT_REG(i), slot_reg); + } + + cleanup_slots(ctrl); + + /* + * Mask SERR and System Interrupt generation + */ + serr_int = shpc_readl(ctrl, SERR_INTR_ENABLE); + serr_int |= (GLOBAL_INTR_MASK | GLOBAL_SERR_MASK | + COMMAND_INTR_MASK | ARBITER_SERR_MASK); + serr_int &= ~SERR_INTR_RSVDZ_MASK; + shpc_writel(ctrl, SERR_INTR_ENABLE, serr_int); + + if (shpchp_poll_mode) + del_timer(&ctrl->poll_timer); + else { + free_irq(ctrl->pci_dev->irq, ctrl); + pci_disable_msi(ctrl->pci_dev); + } + + iounmap(ctrl->creg); + release_mem_region(ctrl->mmio_base, ctrl->mmio_size); +} + +static int hpc_power_on_slot(struct slot *slot) +{ + int retval; + + retval = shpc_write_cmd(slot, slot->hp_slot, SET_SLOT_PWR); + if (retval) + ctrl_err(slot->ctrl, "%s: Write command failed!\n", __func__); + + return retval; +} + +static int hpc_slot_enable(struct slot *slot) +{ + int retval; + + /* Slot - Enable, Power Indicator - Blink, Attention Indicator - Off */ + retval = shpc_write_cmd(slot, slot->hp_slot, + SET_SLOT_ENABLE | SET_PWR_BLINK | SET_ATTN_OFF); + if (retval) + ctrl_err(slot->ctrl, "%s: Write command failed!\n", __func__); + + return retval; +} + +static int hpc_slot_disable(struct slot *slot) +{ + int retval; + + /* Slot - Disable, Power Indicator - Off, Attention Indicator - On */ + retval = shpc_write_cmd(slot, slot->hp_slot, + SET_SLOT_DISABLE | SET_PWR_OFF | SET_ATTN_ON); + if (retval) + ctrl_err(slot->ctrl, "%s: Write command failed!\n", __func__); + + return retval; +} + +static int shpc_get_cur_bus_speed(struct controller *ctrl) +{ + int retval = 0; + struct pci_bus *bus = ctrl->pci_dev->subordinate; + enum pci_bus_speed bus_speed = PCI_SPEED_UNKNOWN; + u16 sec_bus_reg = shpc_readw(ctrl, SEC_BUS_CONFIG); + u8 pi = shpc_readb(ctrl, PROG_INTERFACE); + u8 speed_mode = (pi == 2) ? (sec_bus_reg & 0xF) : (sec_bus_reg & 0x7); + + if ((pi == 1) && (speed_mode > 4)) { + retval = -ENODEV; + goto out; + } + + switch (speed_mode) { + case 0x0: + bus_speed = PCI_SPEED_33MHz; + break; + case 0x1: + bus_speed = PCI_SPEED_66MHz; + break; + case 0x2: + bus_speed = PCI_SPEED_66MHz_PCIX; + break; + case 0x3: + bus_speed = PCI_SPEED_100MHz_PCIX; + break; + case 0x4: + bus_speed = PCI_SPEED_133MHz_PCIX; + break; + case 0x5: + bus_speed = PCI_SPEED_66MHz_PCIX_ECC; + break; + case 0x6: + bus_speed = PCI_SPEED_100MHz_PCIX_ECC; + break; + case 0x7: + bus_speed = PCI_SPEED_133MHz_PCIX_ECC; + break; + case 0x8: + bus_speed = PCI_SPEED_66MHz_PCIX_266; + break; + case 0x9: + 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; + default: + retval = -ENODEV; + break; + } + + out: + bus->cur_bus_speed = bus_speed; + dbg("Current bus speed = %d\n", bus_speed); + return retval; +} + + +static int hpc_set_bus_speed_mode(struct slot *slot, enum pci_bus_speed value) +{ + int retval; + struct controller *ctrl = slot->ctrl; + u8 pi, cmd; + + pi = shpc_readb(ctrl, PROG_INTERFACE); + if ((pi == 1) && (value > PCI_SPEED_133MHz_PCIX)) + return -EINVAL; + + switch (value) { + case PCI_SPEED_33MHz: + cmd = SETA_PCI_33MHZ; + break; + case PCI_SPEED_66MHz: + cmd = SETA_PCI_66MHZ; + break; + case PCI_SPEED_66MHz_PCIX: + cmd = SETA_PCIX_66MHZ; + break; + case PCI_SPEED_100MHz_PCIX: + cmd = SETA_PCIX_100MHZ; + break; + case PCI_SPEED_133MHz_PCIX: + cmd = SETA_PCIX_133MHZ; + break; + case PCI_SPEED_66MHz_PCIX_ECC: + cmd = SETB_PCIX_66MHZ_EM; + break; + case PCI_SPEED_100MHz_PCIX_ECC: + cmd = SETB_PCIX_100MHZ_EM; + break; + case PCI_SPEED_133MHz_PCIX_ECC: + cmd = SETB_PCIX_133MHZ_EM; + break; + case PCI_SPEED_66MHz_PCIX_266: + cmd = SETB_PCIX_66MHZ_266; + break; + case PCI_SPEED_100MHz_PCIX_266: + cmd = SETB_PCIX_100MHZ_266; + break; + case PCI_SPEED_133MHz_PCIX_266: + cmd = SETB_PCIX_133MHZ_266; + break; + case PCI_SPEED_66MHz_PCIX_533: + cmd = SETB_PCIX_66MHZ_533; + break; + case PCI_SPEED_100MHz_PCIX_533: + cmd = SETB_PCIX_100MHZ_533; + break; + case PCI_SPEED_133MHz_PCIX_533: + cmd = SETB_PCIX_133MHZ_533; + break; + default: + return -EINVAL; + } + + retval = shpc_write_cmd(slot, 0, cmd); + if (retval) + ctrl_err(ctrl, "%s: Write command failed!\n", __func__); + else + shpc_get_cur_bus_speed(ctrl); + + return retval; +} + +static irqreturn_t shpc_isr(int irq, void *dev_id) +{ + struct controller *ctrl = (struct controller *)dev_id; + u32 serr_int, slot_reg, intr_loc, intr_loc2; + int hp_slot; + + /* Check to see if it was our interrupt */ + intr_loc = shpc_readl(ctrl, INTR_LOC); + if (!intr_loc) + return IRQ_NONE; + + ctrl_dbg(ctrl, "%s: intr_loc = %x\n", __func__, intr_loc); + + if (!shpchp_poll_mode) { + /* + * Mask Global Interrupt Mask - see implementation + * note on p. 139 of SHPC spec rev 1.0 + */ + serr_int = shpc_readl(ctrl, SERR_INTR_ENABLE); + serr_int |= GLOBAL_INTR_MASK; + serr_int &= ~SERR_INTR_RSVDZ_MASK; + shpc_writel(ctrl, SERR_INTR_ENABLE, serr_int); + + intr_loc2 = shpc_readl(ctrl, INTR_LOC); + ctrl_dbg(ctrl, "%s: intr_loc2 = %x\n", __func__, intr_loc2); + } + + if (intr_loc & CMD_INTR_PENDING) { + /* + * Command Complete Interrupt Pending + * RO only - clear by writing 1 to the Command Completion + * Detect bit in Controller SERR-INT register + */ + serr_int = shpc_readl(ctrl, SERR_INTR_ENABLE); + serr_int &= ~SERR_INTR_RSVDZ_MASK; + shpc_writel(ctrl, SERR_INTR_ENABLE, serr_int); + + wake_up_interruptible(&ctrl->queue); + } + + if (!(intr_loc & ~CMD_INTR_PENDING)) + goto out; + + for (hp_slot = 0; hp_slot < ctrl->num_slots; hp_slot++) { + /* To find out which slot has interrupt pending */ + if (!(intr_loc & SLOT_INTR_PENDING(hp_slot))) + continue; + + slot_reg = shpc_readl(ctrl, SLOT_REG(hp_slot)); + ctrl_dbg(ctrl, "Slot %x with intr, slot register = %x\n", + hp_slot, slot_reg); + + if (slot_reg & MRL_CHANGE_DETECTED) + shpchp_handle_switch_change(hp_slot, ctrl); + + if (slot_reg & BUTTON_PRESS_DETECTED) + shpchp_handle_attention_button(hp_slot, ctrl); + + if (slot_reg & PRSNT_CHANGE_DETECTED) + shpchp_handle_presence_change(hp_slot, ctrl); + + if (slot_reg & (ISO_PFAULT_DETECTED | CON_PFAULT_DETECTED)) + shpchp_handle_power_fault(hp_slot, ctrl); + + /* Clear all slot events */ + slot_reg &= ~SLOT_REG_RSVDZ_MASK; + shpc_writel(ctrl, SLOT_REG(hp_slot), slot_reg); + } + out: + if (!shpchp_poll_mode) { + /* Unmask Global Interrupt Mask */ + serr_int = shpc_readl(ctrl, SERR_INTR_ENABLE); + serr_int &= ~(GLOBAL_INTR_MASK | SERR_INTR_RSVDZ_MASK); + shpc_writel(ctrl, SERR_INTR_ENABLE, serr_int); + } + + return IRQ_HANDLED; +} + +static int shpc_get_max_bus_speed(struct controller *ctrl) +{ + int retval = 0; + struct pci_bus *bus = ctrl->pci_dev->subordinate; + enum pci_bus_speed bus_speed = PCI_SPEED_UNKNOWN; + u8 pi = shpc_readb(ctrl, PROG_INTERFACE); + u32 slot_avail1 = shpc_readl(ctrl, SLOT_AVAIL1); + u32 slot_avail2 = shpc_readl(ctrl, SLOT_AVAIL2); + + if (pi == 2) { + if (slot_avail2 & SLOT_133MHZ_PCIX_533) + bus_speed = PCI_SPEED_133MHz_PCIX_533; + else if (slot_avail2 & SLOT_100MHZ_PCIX_533) + bus_speed = PCI_SPEED_100MHz_PCIX_533; + else if (slot_avail2 & SLOT_66MHZ_PCIX_533) + bus_speed = PCI_SPEED_66MHz_PCIX_533; + else if (slot_avail2 & SLOT_133MHZ_PCIX_266) + bus_speed = PCI_SPEED_133MHz_PCIX_266; + else if (slot_avail2 & SLOT_100MHZ_PCIX_266) + bus_speed = PCI_SPEED_100MHz_PCIX_266; + else if (slot_avail2 & SLOT_66MHZ_PCIX_266) + bus_speed = PCI_SPEED_66MHz_PCIX_266; + } + + if (bus_speed == PCI_SPEED_UNKNOWN) { + if (slot_avail1 & SLOT_133MHZ_PCIX) + bus_speed = PCI_SPEED_133MHz_PCIX; + else if (slot_avail1 & SLOT_100MHZ_PCIX) + bus_speed = PCI_SPEED_100MHz_PCIX; + else if (slot_avail1 & SLOT_66MHZ_PCIX) + bus_speed = PCI_SPEED_66MHz_PCIX; + else if (slot_avail2 & SLOT_66MHZ) + bus_speed = PCI_SPEED_66MHz; + else if (slot_avail1 & SLOT_33MHZ) + bus_speed = PCI_SPEED_33MHz; + else + retval = -ENODEV; + } + + bus->max_bus_speed = bus_speed; + ctrl_dbg(ctrl, "Max bus speed = %d\n", bus_speed); + + return retval; +} + +static const struct hpc_ops shpchp_hpc_ops = { + .power_on_slot = hpc_power_on_slot, + .slot_enable = hpc_slot_enable, + .slot_disable = hpc_slot_disable, + .set_bus_speed_mode = hpc_set_bus_speed_mode, + .set_attention_status = hpc_set_attention_status, + .get_power_status = hpc_get_power_status, + .get_attention_status = hpc_get_attention_status, + .get_latch_status = hpc_get_latch_status, + .get_adapter_status = hpc_get_adapter_status, + + .get_adapter_speed = hpc_get_adapter_speed, + .get_mode1_ECC_cap = hpc_get_mode1_ECC_cap, + .get_prog_int = hpc_get_prog_int, + + .query_power_fault = hpc_query_power_fault, + .green_led_on = hpc_set_green_led_on, + .green_led_off = hpc_set_green_led_off, + .green_led_blink = hpc_set_green_led_blink, + + .release_ctlr = hpc_release_ctlr, +}; + +int shpc_init(struct controller *ctrl, struct pci_dev *pdev) +{ + int rc = -1, num_slots = 0; + u8 hp_slot; + u32 shpc_base_offset; + u32 tempdword, slot_reg, slot_config; + u8 i; + + ctrl->pci_dev = pdev; /* pci_dev of the P2P bridge */ + ctrl_dbg(ctrl, "Hotplug Controller:\n"); + + if (pdev->vendor == PCI_VENDOR_ID_AMD && + pdev->device == PCI_DEVICE_ID_AMD_GOLAM_7450) { + /* amd shpc driver doesn't use Base Offset; assume 0 */ + ctrl->mmio_base = pci_resource_start(pdev, 0); + ctrl->mmio_size = pci_resource_len(pdev, 0); + } else { + ctrl->cap_offset = pci_find_capability(pdev, PCI_CAP_ID_SHPC); + if (!ctrl->cap_offset) { + ctrl_err(ctrl, "Cannot find PCI capability\n"); + goto abort; + } + ctrl_dbg(ctrl, " cap_offset = %x\n", ctrl->cap_offset); + + rc = shpc_indirect_read(ctrl, 0, &shpc_base_offset); + if (rc) { + ctrl_err(ctrl, "Cannot read base_offset\n"); + goto abort; + } + + rc = shpc_indirect_read(ctrl, 3, &tempdword); + if (rc) { + ctrl_err(ctrl, "Cannot read slot config\n"); + goto abort; + } + num_slots = tempdword & SLOT_NUM; + ctrl_dbg(ctrl, " num_slots (indirect) %x\n", num_slots); + + for (i = 0; i < 9 + num_slots; i++) { + rc = shpc_indirect_read(ctrl, i, &tempdword); + if (rc) { + ctrl_err(ctrl, "Cannot read creg (index = %d)\n", + i); + goto abort; + } + ctrl_dbg(ctrl, " offset %d: value %x\n", i, tempdword); + } + + ctrl->mmio_base = + pci_resource_start(pdev, 0) + shpc_base_offset; + ctrl->mmio_size = 0x24 + 0x4 * num_slots; + } + + ctrl_info(ctrl, "HPC vendor_id %x device_id %x ss_vid %x ss_did %x\n", + pdev->vendor, pdev->device, pdev->subsystem_vendor, + pdev->subsystem_device); + + rc = pci_enable_device(pdev); + if (rc) { + ctrl_err(ctrl, "pci_enable_device failed\n"); + goto abort; + } + + if (!request_mem_region(ctrl->mmio_base, ctrl->mmio_size, MY_NAME)) { + ctrl_err(ctrl, "Cannot reserve MMIO region\n"); + rc = -1; + goto abort; + } + + ctrl->creg = ioremap(ctrl->mmio_base, ctrl->mmio_size); + if (!ctrl->creg) { + ctrl_err(ctrl, "Cannot remap MMIO region %lx @ %lx\n", + ctrl->mmio_size, ctrl->mmio_base); + release_mem_region(ctrl->mmio_base, ctrl->mmio_size); + rc = -1; + goto abort; + } + ctrl_dbg(ctrl, "ctrl->creg %p\n", ctrl->creg); + + mutex_init(&ctrl->crit_sect); + mutex_init(&ctrl->cmd_lock); + + /* Setup wait queue */ + init_waitqueue_head(&ctrl->queue); + + ctrl->hpc_ops = &shpchp_hpc_ops; + + /* Return PCI Controller Info */ + slot_config = shpc_readl(ctrl, SLOT_CONFIG); + ctrl->slot_device_offset = (slot_config & FIRST_DEV_NUM) >> 8; + ctrl->num_slots = slot_config & SLOT_NUM; + ctrl->first_slot = (slot_config & PSN) >> 16; + ctrl->slot_num_inc = ((slot_config & UPDOWN) >> 29) ? 1 : -1; + + /* Mask Global Interrupt Mask & Command Complete Interrupt Mask */ + tempdword = shpc_readl(ctrl, SERR_INTR_ENABLE); + ctrl_dbg(ctrl, "SERR_INTR_ENABLE = %x\n", tempdword); + tempdword |= (GLOBAL_INTR_MASK | GLOBAL_SERR_MASK | + COMMAND_INTR_MASK | ARBITER_SERR_MASK); + tempdword &= ~SERR_INTR_RSVDZ_MASK; + shpc_writel(ctrl, SERR_INTR_ENABLE, tempdword); + tempdword = shpc_readl(ctrl, SERR_INTR_ENABLE); + ctrl_dbg(ctrl, "SERR_INTR_ENABLE = %x\n", tempdword); + + /* Mask the MRL sensor SERR Mask of individual slot in + * Slot SERR-INT Mask & clear all the existing event if any + */ + for (hp_slot = 0; hp_slot < ctrl->num_slots; hp_slot++) { + slot_reg = shpc_readl(ctrl, SLOT_REG(hp_slot)); + ctrl_dbg(ctrl, "Default Logical Slot Register %d value %x\n", + hp_slot, slot_reg); + slot_reg |= (PRSNT_CHANGE_INTR_MASK | ISO_PFAULT_INTR_MASK | + BUTTON_PRESS_INTR_MASK | MRL_CHANGE_INTR_MASK | + CON_PFAULT_INTR_MASK | MRL_CHANGE_SERR_MASK | + CON_PFAULT_SERR_MASK); + slot_reg &= ~SLOT_REG_RSVDZ_MASK; + shpc_writel(ctrl, SLOT_REG(hp_slot), slot_reg); + } + + if (shpchp_poll_mode) { + /* Install interrupt polling timer. Start with 10 sec delay */ + timer_setup(&ctrl->poll_timer, int_poll_timeout, 0); + start_int_poll_timer(ctrl, 10); + } else { + /* Installs the interrupt handler */ + rc = pci_enable_msi(pdev); + if (rc) { + ctrl_info(ctrl, "Can't get msi for the hotplug controller\n"); + ctrl_info(ctrl, "Use INTx for the hotplug controller\n"); + } else { + pci_set_master(pdev); + } + + rc = request_irq(ctrl->pci_dev->irq, shpc_isr, IRQF_SHARED, + MY_NAME, (void *)ctrl); + ctrl_dbg(ctrl, "request_irq %d (returns %d)\n", + ctrl->pci_dev->irq, rc); + if (rc) { + ctrl_err(ctrl, "Can't get irq %d for the hotplug controller\n", + ctrl->pci_dev->irq); + goto abort_iounmap; + } + } + ctrl_dbg(ctrl, "HPC at %s irq=%x\n", pci_name(pdev), pdev->irq); + + shpc_get_max_bus_speed(ctrl); + shpc_get_cur_bus_speed(ctrl); + + /* + * Unmask all event interrupts of all slots + */ + for (hp_slot = 0; hp_slot < ctrl->num_slots; hp_slot++) { + slot_reg = shpc_readl(ctrl, SLOT_REG(hp_slot)); + ctrl_dbg(ctrl, "Default Logical Slot Register %d value %x\n", + hp_slot, slot_reg); + slot_reg &= ~(PRSNT_CHANGE_INTR_MASK | ISO_PFAULT_INTR_MASK | + BUTTON_PRESS_INTR_MASK | MRL_CHANGE_INTR_MASK | + CON_PFAULT_INTR_MASK | SLOT_REG_RSVDZ_MASK); + shpc_writel(ctrl, SLOT_REG(hp_slot), slot_reg); + } + if (!shpchp_poll_mode) { + /* Unmask all general input interrupts and SERR */ + tempdword = shpc_readl(ctrl, SERR_INTR_ENABLE); + tempdword &= ~(GLOBAL_INTR_MASK | COMMAND_INTR_MASK | + SERR_INTR_RSVDZ_MASK); + shpc_writel(ctrl, SERR_INTR_ENABLE, tempdword); + tempdword = shpc_readl(ctrl, SERR_INTR_ENABLE); + ctrl_dbg(ctrl, "SERR_INTR_ENABLE = %x\n", tempdword); + } + + return 0; + + /* We end up here for the many possible ways to fail this API. */ +abort_iounmap: + iounmap(ctrl->creg); +abort: + return rc; +} diff --git a/drivers/pci/hotplug/shpchp_pci.c b/drivers/pci/hotplug/shpchp_pci.c new file mode 100644 index 000000000..36db0c3c4 --- /dev/null +++ b/drivers/pci/hotplug/shpchp_pci.c @@ -0,0 +1,85 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * 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. + * + * Send feedback to <greg@kroah.com>, <kristen.c.accardi@intel.com> + * + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/types.h> +#include <linux/pci.h> +#include "../pci.h" +#include "shpchp.h" + +int shpchp_configure_device(struct slot *p_slot) +{ + struct pci_dev *dev; + struct controller *ctrl = p_slot->ctrl; + struct pci_dev *bridge = ctrl->pci_dev; + struct pci_bus *parent = bridge->subordinate; + int num, ret = 0; + + pci_lock_rescan_remove(); + + dev = pci_get_slot(parent, PCI_DEVFN(p_slot->device, 0)); + if (dev) { + ctrl_err(ctrl, "Device %s already exists at %04x:%02x:%02x, cannot hot-add\n", + pci_name(dev), pci_domain_nr(parent), + p_slot->bus, p_slot->device); + pci_dev_put(dev); + ret = -EINVAL; + goto out; + } + + num = pci_scan_slot(parent, PCI_DEVFN(p_slot->device, 0)); + if (num == 0) { + ctrl_err(ctrl, "No new device found\n"); + ret = -ENODEV; + goto out; + } + + for_each_pci_bridge(dev, parent) { + if (PCI_SLOT(dev->devfn) == p_slot->device) + pci_hp_add_bridge(dev); + } + + pci_assign_unassigned_bridge_resources(bridge); + pcie_bus_configure_settings(parent); + pci_bus_add_devices(parent); + + out: + pci_unlock_rescan_remove(); + return ret; +} + +void shpchp_unconfigure_device(struct slot *p_slot) +{ + struct pci_bus *parent = p_slot->ctrl->pci_dev->subordinate; + struct pci_dev *dev, *temp; + struct controller *ctrl = p_slot->ctrl; + + ctrl_dbg(ctrl, "%s: domain:bus:dev = %04x:%02x:%02x\n", + __func__, pci_domain_nr(parent), p_slot->bus, p_slot->device); + + pci_lock_rescan_remove(); + + list_for_each_entry_safe(dev, temp, &parent->devices, bus_list) { + if (PCI_SLOT(dev->devfn) != p_slot->device) + continue; + + pci_dev_get(dev); + pci_stop_and_remove_bus_device(dev); + pci_dev_put(dev); + } + + pci_unlock_rescan_remove(); +} diff --git a/drivers/pci/hotplug/shpchp_sysfs.c b/drivers/pci/hotplug/shpchp_sysfs.c new file mode 100644 index 000000000..64beed7a2 --- /dev/null +++ b/drivers/pci/hotplug/shpchp_sysfs.c @@ -0,0 +1,86 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * 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. + * + * Send feedback to <greg@kroah.com> + * + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/types.h> +#include <linux/pci.h> +#include "shpchp.h" + + +/* A few routines that create sysfs entries for the hot plug controller */ + +static ssize_t show_ctrl(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct pci_dev *pdev; + int index, busnr; + struct resource *res; + struct pci_bus *bus; + size_t len = 0; + + pdev = to_pci_dev(dev); + bus = pdev->subordinate; + + len += sysfs_emit_at(buf, len, "Free resources: memory\n"); + pci_bus_for_each_resource(bus, res, index) { + if (res && (res->flags & IORESOURCE_MEM) && + !(res->flags & IORESOURCE_PREFETCH)) { + len += sysfs_emit_at(buf, len, + "start = %8.8llx, length = %8.8llx\n", + (unsigned long long)res->start, + (unsigned long long)resource_size(res)); + } + } + len += sysfs_emit_at(buf, len, "Free resources: prefetchable memory\n"); + pci_bus_for_each_resource(bus, res, index) { + if (res && (res->flags & IORESOURCE_MEM) && + (res->flags & IORESOURCE_PREFETCH)) { + len += sysfs_emit_at(buf, len, + "start = %8.8llx, length = %8.8llx\n", + (unsigned long long)res->start, + (unsigned long long)resource_size(res)); + } + } + len += sysfs_emit_at(buf, len, "Free resources: IO\n"); + pci_bus_for_each_resource(bus, res, index) { + if (res && (res->flags & IORESOURCE_IO)) { + len += sysfs_emit_at(buf, len, + "start = %8.8llx, length = %8.8llx\n", + (unsigned long long)res->start, + (unsigned long long)resource_size(res)); + } + } + len += sysfs_emit_at(buf, len, "Free resources: bus numbers\n"); + for (busnr = bus->busn_res.start; busnr <= bus->busn_res.end; busnr++) { + if (!pci_find_bus(pci_domain_nr(bus), busnr)) + break; + } + if (busnr < bus->busn_res.end) + len += sysfs_emit_at(buf, len, + "start = %8.8x, length = %8.8x\n", + busnr, (int)(bus->busn_res.end - busnr)); + + return len; +} +static DEVICE_ATTR(ctrl, S_IRUGO, show_ctrl, NULL); + +int shpchp_create_ctrl_files(struct controller *ctrl) +{ + return device_create_file(&ctrl->pci_dev->dev, &dev_attr_ctrl); +} + +void shpchp_remove_ctrl_files(struct controller *ctrl) +{ + device_remove_file(&ctrl->pci_dev->dev, &dev_attr_ctrl); +} |