Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
+186
View File
@@ -0,0 +1,186 @@
# SPDX-License-Identifier: GPL-2.0
#
# PCI Hotplug support
#
menuconfig HOTPLUG_PCI
bool "Support for PCI Hotplug"
depends on PCI && SYSFS
default y if USB4
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.
Thunderbolt/USB4 PCIe tunneling depends on native PCIe hotplug.
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_AMPERE_ALTRA
tristate "ACPI PCI Hotplug driver Ampere Altra extensions"
depends on HOTPLUG_PCI_ACPI
depends on HAVE_ARM_SMCCC_DISCOVERY
help
Say Y here if you have an Ampere Altra system.
To compile this driver as a module, choose M here: the
module will be called acpiphp_ampere_altra.
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 a 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_OCTEONEP
bool "Marvell OCTEON PCI Hotplug driver"
depends on HOTPLUG_PCI
help
Say Y here if you have an OCTEON PCIe device with a hotplug
controller. This driver enables the non-controller functions of the
device to be registered as hotplug slots.
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
+74
View File
@@ -0,0 +1,74 @@
# 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
obj-$(CONFIG_HOTPLUG_PCI_OCTEONEP) += octep_hp.o
# acpiphp_ibm extends acpiphp, so should be linked afterwards.
obj-$(CONFIG_HOTPLUG_PCI_ACPI_AMPERE_ALTRA) += acpiphp_ampere_altra.o
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
+52
View File
@@ -0,0 +1,52 @@
Contributions are solicited in particular to remedy the following issues:
cpcihp:
* Returned code from pci_hp_add_bridge() is not checked.
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?
* Returned code from pci_hp_add_bridge() is not checked.
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?
* Returned code from pci_hp_add_bridge() is not checked.
shpchp:
* 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.
@@ -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");
+187
View File
@@ -0,0 +1,187 @@
/* 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_latch_status(struct acpiphp_slot *slot);
u8 acpiphp_get_adapter_status(struct acpiphp_slot *slot);
/* variables */
extern bool acpiphp_disabled;
#endif /* _ACPIPHP_H */
@@ -0,0 +1,128 @@
// SPDX-License-Identifier: GPL-2.0
/*
* ACPI PCI Hot Plug Extension for Ampere Altra. Allows control of
* attention LEDs via requests to system firmware.
*
* Copyright (C) 2023 Ampere Computing LLC
*/
#define pr_fmt(fmt) "acpiphp_ampere_altra: " fmt
#include <linux/init.h>
#include <linux/module.h>
#include <linux/pci.h>
#include <linux/pci_hotplug.h>
#include <linux/platform_device.h>
#include "acpiphp.h"
#define HANDLE_OPEN 0xb0200000
#define HANDLE_CLOSE 0xb0300000
#define REQUEST 0xf0700000
#define LED_CMD 0x00000004
#define LED_ATTENTION 0x00000002
#define LED_SET_ON 0x00000001
#define LED_SET_OFF 0x00000002
#define LED_SET_BLINK 0x00000003
static u32 led_service_id[4];
static int led_status(u8 status)
{
switch (status) {
case 1: return LED_SET_ON;
case 2: return LED_SET_BLINK;
default: return LED_SET_OFF;
}
}
static int set_attention_status(struct hotplug_slot *slot, u8 status)
{
struct arm_smccc_res res;
struct pci_bus *bus;
struct pci_dev *root_port;
unsigned long flags;
u32 handle;
int ret = 0;
bus = slot->pci_slot->bus;
root_port = pcie_find_root_port(bus->self);
if (!root_port)
return -ENODEV;
local_irq_save(flags);
arm_smccc_smc(HANDLE_OPEN, led_service_id[0], led_service_id[1],
led_service_id[2], led_service_id[3], 0, 0, 0, &res);
if (res.a0) {
ret = -ENODEV;
goto out;
}
handle = res.a1 & 0xffff0000;
arm_smccc_smc(REQUEST, LED_CMD, led_status(status), LED_ATTENTION,
(PCI_SLOT(root_port->devfn) << 4) | (pci_domain_nr(bus) & 0xf),
0, 0, handle, &res);
if (res.a0)
ret = -ENODEV;
arm_smccc_smc(HANDLE_CLOSE, handle, 0, 0, 0, 0, 0, 0, &res);
out:
local_irq_restore(flags);
return ret;
}
static int get_attention_status(struct hotplug_slot *slot, u8 *status)
{
return -EINVAL;
}
static struct acpiphp_attention_info ampere_altra_attn = {
.set_attn = set_attention_status,
.get_attn = get_attention_status,
.owner = THIS_MODULE,
};
static int altra_led_probe(struct platform_device *pdev)
{
struct fwnode_handle *fwnode = dev_fwnode(&pdev->dev);
int ret;
ret = fwnode_property_read_u32_array(fwnode, "uuid", led_service_id, 4);
if (ret) {
dev_err(&pdev->dev, "can't find uuid\n");
return ret;
}
ret = acpiphp_register_attention(&ampere_altra_attn);
if (ret) {
dev_err(&pdev->dev, "can't register driver\n");
return ret;
}
return 0;
}
static void altra_led_remove(struct platform_device *pdev)
{
acpiphp_unregister_attention(&ampere_altra_attn);
}
static const struct acpi_device_id altra_led_ids[] = {
{ "AMPC0008", 0 },
{ }
};
MODULE_DEVICE_TABLE(acpi, altra_led_ids);
static struct platform_driver altra_led_driver = {
.driver = {
.name = "ampere-altra-leds",
.acpi_match_table = altra_led_ids,
},
.probe = altra_led_probe,
.remove = altra_led_remove,
};
module_platform_driver(altra_led_driver);
MODULE_AUTHOR("D Scott Phillips <scott@os.amperecomputing.com>");
MODULE_DESCRIPTION("ACPI PCI Hot Plug Extension for Ampere Altra");
MODULE_LICENSE("GPL");
@@ -0,0 +1,310 @@
// 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_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->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_obj(*slot);
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"
: "");
}
File diff suppressed because it is too large Load Diff
@@ -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,
const 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_obj(union apci_descriptor);
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", &params, &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(&note->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,
const 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, &current_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);
@@ -0,0 +1,99 @@
/* 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);
};
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);
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 */
@@ -0,0 +1,631 @@
// 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);
dbg("%s - physical_slot = %s", __func__, slot_name(slot));
return 0;
}
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);
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)
{
return 1;
}
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_obj(struct slot);
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;
}
@@ -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;
}
@@ -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)");
@@ -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");
@@ -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 */
+729
View File
@@ -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
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -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_obj(struct pci_resource);
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_obj(struct pci_resource);
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_obj(struct pci_resource);
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_obj(struct pci_resource);
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;
}
@@ -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
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,207 @@
// 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;
};
#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_obj(*dbg);
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;
}
+749
View File
@@ -0,0 +1,749 @@
/* 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>
#include <linux/pci_regs.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);
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 (PCI_HEADER_TYPE_MFD|PCI_HEADER_TYPE_NORMAL)
#define PCI_HEADER_TYPE_MULTIBRIDGE (PCI_HEADER_TYPE_MFD|PCI_HEADER_TYPE_BRIDGE)
#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
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+427
View File
@@ -0,0 +1,427 @@
// SPDX-License-Identifier: GPL-2.0-only
/* Copyright (C) 2024 Marvell. */
#include <linux/cleanup.h>
#include <linux/container_of.h>
#include <linux/delay.h>
#include <linux/dev_printk.h>
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/io-64-nonatomic-lo-hi.h>
#include <linux/kernel.h>
#include <linux/list.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/pci.h>
#include <linux/pci_hotplug.h>
#include <linux/slab.h>
#include <linux/spinlock.h>
#include <linux/workqueue.h>
#define OCTEP_HP_INTR_OFFSET(x) (0x20400 + ((x) << 4))
#define OCTEP_HP_INTR_VECTOR(x) (16 + (x))
#define OCTEP_HP_DRV_NAME "octep_hp"
/*
* Type of MSI-X interrupts. OCTEP_HP_INTR_VECTOR() and
* OCTEP_HP_INTR_OFFSET() generate the vector and offset for an interrupt
* type.
*/
enum octep_hp_intr_type {
OCTEP_HP_INTR_INVALID = -1,
OCTEP_HP_INTR_ENA = 0,
OCTEP_HP_INTR_DIS = 1,
OCTEP_HP_INTR_MAX = 2,
};
struct octep_hp_cmd {
struct list_head list;
enum octep_hp_intr_type intr_type;
u64 intr_val;
};
struct octep_hp_slot {
struct list_head list;
struct hotplug_slot slot;
u16 slot_number;
struct pci_dev *hp_pdev;
unsigned int hp_devfn;
struct octep_hp_controller *ctrl;
};
struct octep_hp_intr_info {
enum octep_hp_intr_type type;
int number;
char name[16];
};
struct octep_hp_controller {
void __iomem *base;
struct pci_dev *pdev;
struct octep_hp_intr_info intr[OCTEP_HP_INTR_MAX];
struct work_struct work;
struct list_head slot_list;
struct mutex slot_lock; /* Protects slot_list */
struct list_head hp_cmd_list;
spinlock_t hp_cmd_lock; /* Protects hp_cmd_list */
};
static void octep_hp_enable_pdev(struct octep_hp_controller *hp_ctrl,
struct octep_hp_slot *hp_slot)
{
guard(mutex)(&hp_ctrl->slot_lock);
if (hp_slot->hp_pdev) {
pci_dbg(hp_slot->hp_pdev, "Slot %s is already enabled\n",
hotplug_slot_name(&hp_slot->slot));
return;
}
/* Scan the device and add it to the bus */
hp_slot->hp_pdev = pci_scan_single_device(hp_ctrl->pdev->bus,
hp_slot->hp_devfn);
pci_bus_assign_resources(hp_ctrl->pdev->bus);
pci_bus_add_device(hp_slot->hp_pdev);
dev_dbg(&hp_slot->hp_pdev->dev, "Enabled slot %s\n",
hotplug_slot_name(&hp_slot->slot));
}
static void octep_hp_disable_pdev(struct octep_hp_controller *hp_ctrl,
struct octep_hp_slot *hp_slot)
{
guard(mutex)(&hp_ctrl->slot_lock);
if (!hp_slot->hp_pdev) {
pci_dbg(hp_ctrl->pdev, "Slot %s is already disabled\n",
hotplug_slot_name(&hp_slot->slot));
return;
}
pci_dbg(hp_slot->hp_pdev, "Disabling slot %s\n",
hotplug_slot_name(&hp_slot->slot));
/* Remove the device from the bus */
pci_stop_and_remove_bus_device_locked(hp_slot->hp_pdev);
hp_slot->hp_pdev = NULL;
}
static int octep_hp_enable_slot(struct hotplug_slot *slot)
{
struct octep_hp_slot *hp_slot =
container_of(slot, struct octep_hp_slot, slot);
octep_hp_enable_pdev(hp_slot->ctrl, hp_slot);
return 0;
}
static int octep_hp_disable_slot(struct hotplug_slot *slot)
{
struct octep_hp_slot *hp_slot =
container_of(slot, struct octep_hp_slot, slot);
octep_hp_disable_pdev(hp_slot->ctrl, hp_slot);
return 0;
}
static struct hotplug_slot_ops octep_hp_slot_ops = {
.enable_slot = octep_hp_enable_slot,
.disable_slot = octep_hp_disable_slot,
};
#define SLOT_NAME_SIZE 16
static struct octep_hp_slot *
octep_hp_register_slot(struct octep_hp_controller *hp_ctrl,
struct pci_dev *pdev, u16 slot_number)
{
char slot_name[SLOT_NAME_SIZE];
struct octep_hp_slot *hp_slot;
int ret;
hp_slot = kzalloc_obj(*hp_slot);
if (!hp_slot)
return ERR_PTR(-ENOMEM);
hp_slot->ctrl = hp_ctrl;
hp_slot->hp_pdev = pdev;
hp_slot->hp_devfn = pdev->devfn;
hp_slot->slot_number = slot_number;
hp_slot->slot.ops = &octep_hp_slot_ops;
snprintf(slot_name, sizeof(slot_name), "octep_hp_%u", slot_number);
ret = pci_hp_register(&hp_slot->slot, hp_ctrl->pdev->bus,
PCI_SLOT(pdev->devfn), slot_name);
if (ret) {
kfree(hp_slot);
return ERR_PTR(ret);
}
pci_info(pdev, "Registered slot %s for device %s\n",
slot_name, pci_name(pdev));
list_add_tail(&hp_slot->list, &hp_ctrl->slot_list);
octep_hp_disable_pdev(hp_ctrl, hp_slot);
return hp_slot;
}
static void octep_hp_deregister_slot(void *data)
{
struct octep_hp_slot *hp_slot = data;
struct octep_hp_controller *hp_ctrl = hp_slot->ctrl;
pci_hp_deregister(&hp_slot->slot);
octep_hp_enable_pdev(hp_ctrl, hp_slot);
list_del(&hp_slot->list);
kfree(hp_slot);
}
static const char *octep_hp_cmd_name(enum octep_hp_intr_type type)
{
switch (type) {
case OCTEP_HP_INTR_ENA:
return "hotplug enable";
case OCTEP_HP_INTR_DIS:
return "hotplug disable";
default:
return "invalid";
}
}
static void octep_hp_cmd_handler(struct octep_hp_controller *hp_ctrl,
struct octep_hp_cmd *hp_cmd)
{
struct octep_hp_slot *hp_slot;
/*
* Enable or disable the slots based on the slot mask.
* intr_val is a bit mask where each bit represents a slot.
*/
list_for_each_entry(hp_slot, &hp_ctrl->slot_list, list) {
if (!(hp_cmd->intr_val & BIT(hp_slot->slot_number)))
continue;
pci_info(hp_ctrl->pdev, "Received %s command for slot %s\n",
octep_hp_cmd_name(hp_cmd->intr_type),
hotplug_slot_name(&hp_slot->slot));
switch (hp_cmd->intr_type) {
case OCTEP_HP_INTR_ENA:
octep_hp_enable_pdev(hp_ctrl, hp_slot);
break;
case OCTEP_HP_INTR_DIS:
octep_hp_disable_pdev(hp_ctrl, hp_slot);
break;
default:
break;
}
}
}
static void octep_hp_work_handler(struct work_struct *work)
{
struct octep_hp_controller *hp_ctrl;
struct octep_hp_cmd *hp_cmd;
unsigned long flags;
hp_ctrl = container_of(work, struct octep_hp_controller, work);
/* Process all the hotplug commands */
spin_lock_irqsave(&hp_ctrl->hp_cmd_lock, flags);
while (!list_empty(&hp_ctrl->hp_cmd_list)) {
hp_cmd = list_first_entry(&hp_ctrl->hp_cmd_list,
struct octep_hp_cmd, list);
list_del(&hp_cmd->list);
spin_unlock_irqrestore(&hp_ctrl->hp_cmd_lock, flags);
octep_hp_cmd_handler(hp_ctrl, hp_cmd);
kfree(hp_cmd);
spin_lock_irqsave(&hp_ctrl->hp_cmd_lock, flags);
}
spin_unlock_irqrestore(&hp_ctrl->hp_cmd_lock, flags);
}
static enum octep_hp_intr_type octep_hp_intr_type(struct octep_hp_intr_info *intr,
int irq)
{
enum octep_hp_intr_type type;
for (type = OCTEP_HP_INTR_ENA; type < OCTEP_HP_INTR_MAX; type++) {
if (intr[type].number == irq)
return type;
}
return OCTEP_HP_INTR_INVALID;
}
static irqreturn_t octep_hp_intr_handler(int irq, void *data)
{
struct octep_hp_controller *hp_ctrl = data;
struct pci_dev *pdev = hp_ctrl->pdev;
enum octep_hp_intr_type type;
struct octep_hp_cmd *hp_cmd;
u64 intr_val;
type = octep_hp_intr_type(hp_ctrl->intr, irq);
if (type == OCTEP_HP_INTR_INVALID) {
pci_err(pdev, "Invalid interrupt %d\n", irq);
return IRQ_HANDLED;
}
/* Read and clear the interrupt */
intr_val = readq(hp_ctrl->base + OCTEP_HP_INTR_OFFSET(type));
writeq(intr_val, hp_ctrl->base + OCTEP_HP_INTR_OFFSET(type));
hp_cmd = kzalloc_obj(*hp_cmd, GFP_ATOMIC);
if (!hp_cmd)
return IRQ_HANDLED;
hp_cmd->intr_val = intr_val;
hp_cmd->intr_type = type;
/* Add the command to the list and schedule the work */
spin_lock(&hp_ctrl->hp_cmd_lock);
list_add_tail(&hp_cmd->list, &hp_ctrl->hp_cmd_list);
spin_unlock(&hp_ctrl->hp_cmd_lock);
schedule_work(&hp_ctrl->work);
return IRQ_HANDLED;
}
static void octep_hp_irq_cleanup(void *data)
{
struct octep_hp_controller *hp_ctrl = data;
pci_free_irq_vectors(hp_ctrl->pdev);
flush_work(&hp_ctrl->work);
}
static int octep_hp_request_irq(struct octep_hp_controller *hp_ctrl,
enum octep_hp_intr_type type)
{
struct pci_dev *pdev = hp_ctrl->pdev;
struct octep_hp_intr_info *intr;
int irq;
irq = pci_irq_vector(pdev, OCTEP_HP_INTR_VECTOR(type));
if (irq < 0)
return irq;
intr = &hp_ctrl->intr[type];
intr->number = irq;
intr->type = type;
snprintf(intr->name, sizeof(intr->name), "octep_hp_%d", type);
return devm_request_irq(&pdev->dev, irq, octep_hp_intr_handler,
IRQF_SHARED, intr->name, hp_ctrl);
}
static int octep_hp_controller_setup(struct pci_dev *pdev,
struct octep_hp_controller *hp_ctrl)
{
struct device *dev = &pdev->dev;
enum octep_hp_intr_type type;
int ret;
ret = pcim_enable_device(pdev);
if (ret)
return dev_err_probe(dev, ret, "Failed to enable PCI device\n");
hp_ctrl->base = pcim_iomap_region(pdev, 0, OCTEP_HP_DRV_NAME);
if (IS_ERR(hp_ctrl->base))
return dev_err_probe(dev, PTR_ERR(hp_ctrl->base),
"Failed to map PCI device region\n");
pci_set_master(pdev);
pci_set_drvdata(pdev, hp_ctrl);
INIT_LIST_HEAD(&hp_ctrl->slot_list);
INIT_LIST_HEAD(&hp_ctrl->hp_cmd_list);
mutex_init(&hp_ctrl->slot_lock);
spin_lock_init(&hp_ctrl->hp_cmd_lock);
INIT_WORK(&hp_ctrl->work, octep_hp_work_handler);
hp_ctrl->pdev = pdev;
ret = pci_alloc_irq_vectors(pdev, 1,
OCTEP_HP_INTR_VECTOR(OCTEP_HP_INTR_MAX),
PCI_IRQ_MSIX);
if (ret < 0)
return dev_err_probe(dev, ret, "Failed to alloc MSI-X vectors\n");
ret = devm_add_action(&pdev->dev, octep_hp_irq_cleanup, hp_ctrl);
if (ret)
return dev_err_probe(&pdev->dev, ret, "Failed to add IRQ cleanup action\n");
for (type = OCTEP_HP_INTR_ENA; type < OCTEP_HP_INTR_MAX; type++) {
ret = octep_hp_request_irq(hp_ctrl, type);
if (ret)
return dev_err_probe(dev, ret,
"Failed to request IRQ for vector %d\n",
OCTEP_HP_INTR_VECTOR(type));
}
return 0;
}
static int octep_hp_pci_probe(struct pci_dev *pdev,
const struct pci_device_id *id)
{
struct octep_hp_controller *hp_ctrl;
struct pci_dev *tmp_pdev, *next;
struct octep_hp_slot *hp_slot;
u16 slot_number = 0;
int ret;
hp_ctrl = devm_kzalloc(&pdev->dev, sizeof(*hp_ctrl), GFP_KERNEL);
if (!hp_ctrl)
return -ENOMEM;
ret = octep_hp_controller_setup(pdev, hp_ctrl);
if (ret)
return ret;
/*
* Register all hotplug slots. Hotplug controller is the first function
* of the PCI device. The hotplug slots are the remaining functions of
* the PCI device. The hotplug slot functions are logically removed from
* the bus during probing and are re-enabled by the driver when a
* hotplug event is received.
*/
list_for_each_entry_safe(tmp_pdev, next, &pdev->bus->devices, bus_list) {
if (tmp_pdev == pdev)
continue;
hp_slot = octep_hp_register_slot(hp_ctrl, tmp_pdev, slot_number);
if (IS_ERR(hp_slot))
return dev_err_probe(&pdev->dev, PTR_ERR(hp_slot),
"Failed to register hotplug slot %u\n",
slot_number);
ret = devm_add_action(&pdev->dev, octep_hp_deregister_slot,
hp_slot);
if (ret)
return dev_err_probe(&pdev->dev, ret,
"Failed to add action for deregistering slot %u\n",
slot_number);
slot_number++;
}
return 0;
}
#define PCI_DEVICE_ID_CAVIUM_OCTEP_HP_CTLR 0xa0e3
static struct pci_device_id octep_hp_pci_map[] = {
{ PCI_DEVICE(PCI_VENDOR_ID_CAVIUM, PCI_DEVICE_ID_CAVIUM_OCTEP_HP_CTLR) },
{ },
};
static struct pci_driver octep_hp = {
.name = OCTEP_HP_DRV_NAME,
.id_table = octep_hp_pci_map,
.probe = octep_hp_pci_probe,
};
module_pci_driver(octep_hp);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Marvell");
MODULE_DESCRIPTION("Marvell OCTEON PCI Hotplug driver");
@@ -0,0 +1,579 @@
// 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>
#include <linux/moduleparam.h>
#include <linux/kernel.h>
#include <linux/types.h>
#include <linux/kobject.h>
#include <linux/sysfs.h>
#include <linux/init.h>
#include <linux/pci.h>
#include <linux/pci_hotplug.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;
/* 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 (ops->get_##name) \
retval = ops->get_##name(slot, value); \
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);
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;
}
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 (ops->set_attention_status)
retval = ops->set_attention_status(slot, attention);
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 (slot->ops->hardware_test)
retval = slot->ops->hardware_test(slot, test);
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 hotplug_slot *slot)
{
if ((slot->ops->enable_slot) ||
(slot->ops->disable_slot) ||
(slot->ops->get_power_status))
return true;
return false;
}
static bool has_attention_file(struct hotplug_slot *slot)
{
if ((slot->ops->set_attention_status) ||
(slot->ops->get_attention_status))
return true;
return false;
}
static bool has_latch_file(struct hotplug_slot *slot)
{
if (slot->ops->get_latch_status)
return true;
return false;
}
static bool has_adapter_file(struct hotplug_slot *slot)
{
if (slot->ops->get_adapter_status)
return true;
return false;
}
static bool has_test_file(struct hotplug_slot *slot)
{
if (slot->ops->hardware_test)
return true;
return false;
}
static int fs_add_slot(struct hotplug_slot *slot, struct pci_slot *pci_slot)
{
struct kobject *kobj;
int retval = 0;
/* Create symbolic link to the hotplug driver module */
kobj = kset_find_obj(module_kset, slot->mod_name);
if (kobj) {
retval = sysfs_create_link(&pci_slot->kobj, kobj, "module");
if (retval)
dev_err(&pci_slot->bus->dev,
"Error creating sysfs link (%d)\n", retval);
kobject_put(kobj);
}
if (has_power_file(slot)) {
retval = sysfs_create_file(&pci_slot->kobj,
&hotplug_slot_attr_power.attr);
if (retval)
goto exit_power;
}
if (has_attention_file(slot)) {
retval = sysfs_create_file(&pci_slot->kobj,
&hotplug_slot_attr_attention.attr);
if (retval)
goto exit_attention;
}
if (has_latch_file(slot)) {
retval = sysfs_create_file(&pci_slot->kobj,
&hotplug_slot_attr_latch.attr);
if (retval)
goto exit_latch;
}
if (has_adapter_file(slot)) {
retval = sysfs_create_file(&pci_slot->kobj,
&hotplug_slot_attr_presence.attr);
if (retval)
goto exit_adapter;
}
if (has_test_file(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(slot))
sysfs_remove_file(&pci_slot->kobj,
&hotplug_slot_attr_presence.attr);
exit_adapter:
if (has_latch_file(slot))
sysfs_remove_file(&pci_slot->kobj, &hotplug_slot_attr_latch.attr);
exit_latch:
if (has_attention_file(slot))
sysfs_remove_file(&pci_slot->kobj,
&hotplug_slot_attr_attention.attr);
exit_attention:
if (has_power_file(slot))
sysfs_remove_file(&pci_slot->kobj, &hotplug_slot_attr_power.attr);
exit_power:
sysfs_remove_link(&pci_slot->kobj, "module");
exit:
return retval;
}
static void fs_remove_slot(struct hotplug_slot *slot, struct pci_slot *pci_slot)
{
if (has_power_file(slot))
sysfs_remove_file(&pci_slot->kobj, &hotplug_slot_attr_power.attr);
if (has_attention_file(slot))
sysfs_remove_file(&pci_slot->kobj,
&hotplug_slot_attr_attention.attr);
if (has_latch_file(slot))
sysfs_remove_file(&pci_slot->kobj, &hotplug_slot_attr_latch.attr);
if (has_adapter_file(slot))
sysfs_remove_file(&pci_slot->kobj,
&hotplug_slot_attr_presence.attr);
if (has_test_file(slot))
sysfs_remove_file(&pci_slot->kobj, &hotplug_slot_attr_test.attr);
sysfs_remove_link(&pci_slot->kobj, "module");
}
/**
* __pci_hp_register - register a hotplug_slot with the PCI hotplug subsystem
* @slot: pointer to the &struct hotplug_slot to register
* @bus: bus this slot is on
* @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;
int result;
if (WARN_ON(!slot))
return -EINVAL;
pci_slot = slot->pci_slot;
result = fs_add_slot(slot, pci_slot);
if (result)
return result;
kobject_uevent(&pci_slot->kobj, KOBJ_ADD);
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().
*/
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.
*/
void pci_hp_del(struct hotplug_slot *slot)
{
if (WARN_ON(!slot))
return;
fs_remove_slot(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.
*/
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 DECLARE_WAIT_QUEUE_HEAD(pci_hp_link_change_wq);
/**
* pci_hp_ignore_link_change - begin code section causing spurious link changes
* @pdev: PCI hotplug bridge
*
* Mark the beginning of a code section causing spurious link changes on the
* Secondary Bus of @pdev, e.g. as a side effect of a Secondary Bus Reset,
* D3cold transition, firmware update or FPGA reconfiguration.
*
* Hotplug drivers can thus check whether such a code section is executing
* concurrently, await it with pci_hp_spurious_link_change() and ignore the
* resulting link change events.
*
* Must be paired with pci_hp_unignore_link_change(). May be called both
* from the PCI core and from Endpoint drivers. May be called for bridges
* which are not hotplug-capable, in which case it has no effect because
* no hotplug driver is bound to the bridge.
*/
void pci_hp_ignore_link_change(struct pci_dev *pdev)
{
set_bit(PCI_LINK_CHANGING, &pdev->priv_flags);
smp_mb__after_atomic(); /* pairs with implied barrier of wait_event() */
}
/**
* pci_hp_unignore_link_change - end code section causing spurious link changes
* @pdev: PCI hotplug bridge
*
* Mark the end of a code section causing spurious link changes on the
* Secondary Bus of @pdev. Must be paired with pci_hp_ignore_link_change().
*/
void pci_hp_unignore_link_change(struct pci_dev *pdev)
{
set_bit(PCI_LINK_CHANGED, &pdev->priv_flags);
mb(); /* ensure pci_hp_spurious_link_change() sees either bit set */
clear_bit(PCI_LINK_CHANGING, &pdev->priv_flags);
wake_up_all(&pci_hp_link_change_wq);
}
/**
* pci_hp_spurious_link_change - check for spurious link changes
* @pdev: PCI hotplug bridge
*
* Check whether a code section is executing concurrently which is causing
* spurious link changes on the Secondary Bus of @pdev. Await the end of the
* code section if so.
*
* May be called by hotplug drivers to check whether a link change is spurious
* and can be ignored.
*
* Because a genuine link change may have occurred in-between a spurious link
* change and the invocation of this function, hotplug drivers should perform
* sanity checks such as retrieving the current link state and bringing down
* the slot if the link is down.
*
* Return: %true if such a code section has been executing concurrently,
* otherwise %false. Also return %true if such a code section has not been
* executing concurrently, but at least once since the last invocation of this
* function.
*/
bool pci_hp_spurious_link_change(struct pci_dev *pdev)
{
wait_event(pci_hp_link_change_wq,
!test_bit(PCI_LINK_CHANGING, &pdev->priv_flags));
return test_and_clear_bit(PCI_LINK_CHANGED, &pdev->priv_flags);
}
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");
+212
View File
@@ -0,0 +1,212 @@
/* 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
* @dsn: cached copy of Device Serial Number of Function 0 in the hotplug slot
* (PCIe r6.2 sec 7.9.3); used to determine whether a hotplugged device
* was replaced with a different one during system sleep
* @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;
u64 dsn;
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);
bool pciehp_device_replaced(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 */
@@ -0,0 +1,384 @@
// 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/bitfield.h>
#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_obj(*ops);
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,
PCI_SLOT_ALL_DEVICES, 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 = FIELD_PREP(PCI_EXP_SLTCTL_AIC, status);
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);
/*
* If hotplugged device was replaced with a different one
* during system sleep, mark the old device disconnected
* (to prevent its driver from accessing the new device)
* and synthesize a Presence Detect Changed event.
*/
if (pciehp_device_replaced(ctrl)) {
ctrl_dbg(ctrl, "device replaced during system sleep\n");
pci_walk_bus(ctrl->pcie->port->subordinate,
pci_dev_set_disconnected, NULL);
pciehp_request(ctrl, PCI_EXP_SLTSTA_PDC);
}
}
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;
}
@@ -0,0 +1,464 @@
// 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 <trace/events/pci.h>
#include "../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);
/* Don't carry LBMS indications across */
pcie_reset_lbms(ctrl->pcie->port);
}
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): Button press: will power off in 5 sec\n",
slot_name(ctrl));
} else {
ctrl->state = BLINKINGON_STATE;
ctrl_info(ctrl, "Slot(%s): Button press: will power on in 5 sec\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
*/
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);
ctrl_info(ctrl, "Slot(%s): Button press: canceling request to power off\n",
slot_name(ctrl));
} 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): Button press: canceling request to power on\n",
slot_name(ctrl));
}
break;
default:
ctrl_err(ctrl, "Slot(%s): Button press: 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));
trace_pci_hp_event(pci_name(ctrl->pcie->port),
slot_name(ctrl),
PCI_HOTPLUG_LINK_DOWN);
}
if (events & PCI_EXP_SLTSTA_PDC) {
ctrl_info(ctrl, "Slot(%s): Card not present\n",
slot_name(ctrl));
trace_pci_hp_event(pci_name(ctrl->pcie->port),
slot_name(ctrl),
PCI_HOTPLUG_CARD_NOT_PRESENT);
}
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));
trace_pci_hp_event(pci_name(ctrl->pcie->port),
slot_name(ctrl),
PCI_HOTPLUG_CARD_NOT_PRESENT);
}
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));
trace_pci_hp_event(pci_name(ctrl->pcie->port),
slot_name(ctrl),
PCI_HOTPLUG_CARD_PRESENT);
}
if (link_active) {
ctrl_info(ctrl, "Slot(%s): Link Up\n", slot_name(ctrl));
trace_pci_hp_event(pci_name(ctrl->pcie->port),
slot_name(ctrl),
PCI_HOTPLUG_LINK_UP);
}
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;
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,141 @@
// 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);
dev = pci_get_slot(parent, PCI_DEVFN(0, 0));
ctrl->dsn = pci_get_dsn(dev);
pci_dev_put(dev);
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();
}
File diff suppressed because it is too large Load Diff
@@ -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
@@ -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");
@@ -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);
}
+99
View File
@@ -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 */
@@ -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_property_present(dn->parent, "ibm,drc-info"))
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_property_present(dn, "ibm,drc-info"))
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);
@@ -0,0 +1,196 @@
// 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"
/*
* RTAS call get-sensor-state(DR_ENTITY_SENSE) return values as per PAPR:
* -- generic return codes ---
* -1: Hardware Error
* -2: RTAS_BUSY
* -3: Invalid sensor. RTAS Parameter Error.
* -- rtas_get_sensor function specific return codes ---
* -9000: Need DR entity to be powered up and unisolated before RTAS call
* -9001: Need DR entity to be powered up, but not unisolated, before RTAS call
* -9002: DR entity unusable
* 990x: Extended delay - where x is a number in the range of 0-5
*/
#define RTAS_SLOT_UNISOLATED -9000
#define RTAS_SLOT_NOT_UNISOLATED -9001
#define RTAS_SLOT_NOT_USABLE -9002
static int rtas_get_sensor_errno(int rtas_rc)
{
switch (rtas_rc) {
case 0:
/* Success case */
return 0;
case RTAS_SLOT_UNISOLATED:
case RTAS_SLOT_NOT_UNISOLATED:
return -EFAULT;
case RTAS_SLOT_NOT_USABLE:
return -ENODEV;
case RTAS_BUSY:
case RTAS_EXTENDED_DELAY_MIN...RTAS_EXTENDED_DELAY_MAX:
return -EBUSY;
default:
return rtas_error_rc(rtas_rc);
}
}
/*
* get_adapter_status() can be called by the EEH handler during EEH recovery.
* On certain PHB failures, the RTAS call rtas_call(get-sensor-state) returns
* extended busy error (9902) until PHB is recovered by pHyp. The RTAS call
* interface rtas_get_sensor() loops over the RTAS call on extended delay
* return code (9902) until the return value is either success (0) or error
* (-1). This causes the EEH handler to get stuck for ~6 seconds before it
* could notify that the PCI error has been detected and stop any active
* operations. This sometimes causes EEH recovery to fail. To avoid this issue,
* invoke rtas_call(get-sensor-state) directly if the respective PE is in EEH
* recovery state and return -EBUSY error based on RTAS return status. This
* will help the EEH handler to notify the driver about the PCI error
* immediately and successfully proceed with EEH recovery steps.
*/
static int __rpaphp_get_sensor_state(struct slot *slot, int *state)
{
int rc;
int token = rtas_token("get-sensor-state");
struct pci_dn *pdn;
struct eeh_pe *pe;
struct pci_controller *phb = PCI_DN(slot->dn)->phb;
if (token == RTAS_UNKNOWN_SERVICE)
return -ENOENT;
/*
* Fallback to existing method for empty slot or PE isn't in EEH
* recovery.
*/
pdn = list_first_entry_or_null(&PCI_DN(phb->dn)->child_list,
struct pci_dn, list);
if (!pdn)
goto fallback;
pe = eeh_dev_to_pe(pdn->edev);
if (pe && (pe->state & EEH_PE_RECOVERING)) {
rc = rtas_call(token, 2, 2, state, DR_ENTITY_SENSE,
slot->index);
return rtas_get_sensor_errno(rc);
}
fallback:
return rtas_get_sensor(DR_ENTITY_SENSE, slot->index, state);
}
int rpaphp_get_sensor_state(struct slot *slot, int *state)
{
int rc;
int setlevel;
rc = __rpaphp_get_sensor_state(slot, 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 = __rpaphp_get_sensor_state(slot, 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;
}
@@ -0,0 +1,117 @@
// 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_obj(struct slot);
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;
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_scoped(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);
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;
}
@@ -0,0 +1,141 @@
// 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 pr_fmt(fmt) "zpci: " 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;
mutex_lock(&zdev->state_lock);
if (zdev->state != ZPCI_FN_STATE_STANDBY) {
rc = -EIO;
goto out;
}
rc = sclp_pci_configure(zdev->fid);
zpci_dbg(3, "conf fid:%x, rc:%d\n", zdev->fid, rc);
if (rc)
goto out;
zdev->state = ZPCI_FN_STATE_CONFIGURED;
rc = zpci_scan_configured_device(zdev, zdev->fh);
out:
mutex_unlock(&zdev->state_lock);
return rc;
}
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 = NULL;
int rc;
mutex_lock(&zdev->state_lock);
if (zdev->state != ZPCI_FN_STATE_CONFIGURED) {
rc = -EIO;
goto out;
}
pdev = pci_get_slot(zdev->zbus->bus, zdev->devfn);
if (pdev && pci_num_vf(pdev)) {
rc = -EBUSY;
goto out;
}
rc = zpci_deconfigure_device(zdev);
out:
if (pdev)
pci_dev_put(pdev);
mutex_unlock(&zdev->state_lock);
return rc;
}
static int reset_slot(struct hotplug_slot *hotplug_slot, bool probe)
{
struct zpci_dev *zdev = container_of(hotplug_slot, struct zpci_dev,
hotplug_slot);
int rc = -EIO;
/*
* If we can't get the zdev->state_lock the device state is
* currently undergoing a transition and we bail out - just
* the same as if the device's state is not configured at all.
*/
if (!mutex_trylock(&zdev->state_lock))
return rc;
/* We can reset only if the function is configured */
if (zdev->state != ZPCI_FN_STATE_CONFIGURED)
goto out;
if (probe) {
rc = 0;
goto out;
}
rc = zpci_hot_reset_device(zdev);
out:
mutex_unlock(&zdev->state_lock);
return rc;
}
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 exists 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);
}
+303
View File
@@ -0,0 +1,303 @@
/* 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 ctrl_dbg(ctrl, format, arg...) \
pci_dbg(ctrl->pci_dev, format, ## arg)
#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;
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;
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);
}
int shpchp_power_on_slot(struct slot *slot);
int shpchp_slot_enable(struct slot *slot);
int shpchp_slot_disable(struct slot *slot);
int shpchp_set_bus_speed_mode(struct slot *slot, enum pci_bus_speed speed);
int shpchp_get_power_status(struct slot *slot, u8 *status);
int shpchp_get_attention_status(struct slot *slot, u8 *status);
int shpchp_set_attention_status(struct slot *slot, u8 status);
int shpchp_get_latch_status(struct slot *slot, u8 *status);
int shpchp_get_adapter_status(struct slot *slot, u8 *status);
int shpchp_get_adapter_speed(struct slot *slot, enum pci_bus_speed *speed);
int shpchp_get_prog_int(struct slot *slot, u8 *prog_int);
int shpchp_query_power_fault(struct slot *slot);
void shpchp_green_led_on(struct slot *slot);
void shpchp_green_led_off(struct slot *slot);
void shpchp_green_led_blink(struct slot *slot);
void shpchp_release_ctlr(struct controller *ctrl);
int shpchp_check_cmd_status(struct controller *ctrl);
#endif /* _SHPCHP_H */
@@ -0,0 +1,334 @@
// 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_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_param(shpchp_poll_mode, bool, 0644);
module_param(shpchp_poll_time, int, 0644);
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_obj(*slot);
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->number = ctrl->first_slot + (ctrl->slot_num_inc * i);
slot->wq = alloc_workqueue("shpchp-%d", WQ_PERCPU, 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;
shpchp_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 = shpchp_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 = shpchp_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 = shpchp_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 = shpchp_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_obj(*ctrl);
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:
shpchp_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);
shpchp_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)
{
return pci_register_driver(&shpc_driver);
}
static void __exit shpcd_cleanup(void)
{
pci_unregister_driver(&shpc_driver);
}
module_init(shpcd_init);
module_exit(shpcd_cleanup);
@@ -0,0 +1,704 @@
// 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_obj(*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);
shpchp_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);
shpchp_get_adapter_status(p_slot, &p_slot->presence_save);
shpchp_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
*/
shpchp_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 (!(shpchp_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 = shpchp_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 = shpchp_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 = shpchp_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 = shpchp_slot_enable(p_slot);
if (rc) {
ctrl_err(ctrl, "Issue of Slot Enable command failed\n");
return rc;
}
}
rc = shpchp_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 = shpchp_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;
shpchp_green_led_on(p_slot);
return 0;
err_exit:
/* turn off slot, turn on Amber LED, turn off Green LED */
rc = shpchp_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 = shpchp_slot_disable(p_slot);
if (rc) {
ctrl_err(ctrl, "%s: Issue of Slot Disable command failed\n",
__func__);
return rc;
}
rc = shpchp_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))
shpchp_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_obj(*info);
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)
{
shpchp_get_power_status(slot, &slot->pwr_save);
shpchp_get_attention_status(slot, &slot->attention_save);
shpchp_get_latch_status(slot, &slot->latch_save);
shpchp_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:
shpchp_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 */
shpchp_green_led_blink(p_slot);
shpchp_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)
shpchp_green_led_on(p_slot);
else
shpchp_green_led_off(p_slot);
shpchp_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__);
shpchp_set_attention_status(p_slot, 1);
shpchp_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 = shpchp_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 = shpchp_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 = shpchp_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 */
shpchp_get_adapter_status(p_slot, &p_slot->presence_save);
shpchp_get_power_status(p_slot, &p_slot->pwr_save);
ctrl_dbg(ctrl, "%s: p_slot->pwr_save %x\n", __func__, p_slot->pwr_save);
shpchp_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) {
shpchp_get_adapter_status(p_slot, &p_slot->presence_save);
shpchp_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 = shpchp_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 = shpchp_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 = shpchp_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;
}
File diff suppressed because it is too large Load Diff
@@ -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();
}
@@ -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;
struct resource *res;
struct pci_bus *bus;
size_t len = 0;
int busnr;
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) {
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) {
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) {
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);
}