Skip to content

Commit

Permalink
s390/pci: PCI hotplug support via SCLP
Browse files Browse the repository at this point in the history
Add SCLP PCI configure/deconfigure and implement a PCI hotplug
controller (s390_pci_hpc). The hotplug controller creates a slot
for every PCI function in stand-by or configured state. The PCI
functions are named after the PCI function ID (fid). By writing to
the power attribute in /sys/bus/pci/slots/<fid>/power the PCI function
is moved to stand-by or configured state. If moved to the configured
state the device is automatically scanned by the s390 PCI layer.

Signed-off-by: Jan Glauber <jang@linux.vnet.ibm.com>
Signed-off-by: Martin Schwidefsky <schwidefsky@de.ibm.com>
  • Loading branch information
Jan Glauber authored and Martin Schwidefsky committed Nov 30, 2012
1 parent cbc0dd1 commit 7441b06
Show file tree
Hide file tree
Showing 8 changed files with 351 additions and 2 deletions.
11 changes: 11 additions & 0 deletions arch/s390/include/asm/pci.h
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,11 @@ struct zpci_dev {
enum pci_bus_speed max_bus_speed;
};

struct pci_hp_callback_ops {
int (*create_slot) (struct zpci_dev *zdev);
void (*remove_slot) (struct zpci_dev *zdev);
};

static inline bool zdev_enabled(struct zpci_dev *zdev)
{
return (zdev->fh & (1UL << 31)) ? true : false;
Expand Down Expand Up @@ -140,4 +145,10 @@ bool zpci_fid_present(u32);
int zpci_dma_init(void);
void zpci_dma_exit(void);

/* Hotplug */
extern struct mutex zpci_list_lock;
extern struct list_head zpci_list;
extern struct pci_hp_callback_ops hotplug_ops;
extern unsigned int pci_probe;

#endif
2 changes: 2 additions & 0 deletions arch/s390/include/asm/sclp.h
Original file line number Diff line number Diff line change
Expand Up @@ -55,5 +55,7 @@ int sclp_chp_read_info(struct sclp_chp_info *info);
void sclp_get_ipl_info(struct sclp_ipl_info *info);
bool sclp_has_linemode(void);
bool sclp_has_vt220(void);
int sclp_pci_configure(u32 fid);
int sclp_pci_deconfigure(u32 fid);

#endif /* _ASM_S390_SCLP_H */
9 changes: 9 additions & 0 deletions arch/s390/pci/pci.c
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,12 @@

/* list of all detected zpci devices */
LIST_HEAD(zpci_list);
EXPORT_SYMBOL_GPL(zpci_list);
DEFINE_MUTEX(zpci_list_lock);
EXPORT_SYMBOL_GPL(zpci_list_lock);

struct pci_hp_callback_ops hotplug_ops;
EXPORT_SYMBOL_GPL(hotplug_ops);

static DECLARE_BITMAP(zpci_domain, ZPCI_NR_DEVICES);
static DEFINE_SPINLOCK(zpci_domain_lock);
Expand Down Expand Up @@ -935,6 +940,8 @@ int zpci_create_device(struct zpci_dev *zdev)

mutex_lock(&zpci_list_lock);
list_add_tail(&zdev->entry, &zpci_list);
if (hotplug_ops.create_slot)
hotplug_ops.create_slot(zdev);
mutex_unlock(&zpci_list_lock);

if (zdev->state == ZPCI_FN_STATE_STANDBY)
Expand All @@ -948,6 +955,8 @@ int zpci_create_device(struct zpci_dev *zdev)
out_start:
mutex_lock(&zpci_list_lock);
list_del(&zdev->entry);
if (hotplug_ops.remove_slot)
hotplug_ops.remove_slot(zdev);
mutex_unlock(&zpci_list_lock);
out_bus:
zpci_free_domain(zdev);
Expand Down
11 changes: 11 additions & 0 deletions drivers/pci/hotplug/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -151,4 +151,15 @@ config HOTPLUG_PCI_SGI

When in doubt, say N.

config HOTPLUG_PCI_S390
tristate "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
1 change: 1 addition & 0 deletions drivers/pci/hotplug/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ obj-$(CONFIG_HOTPLUG_PCI_RPA) += rpaphp.o
obj-$(CONFIG_HOTPLUG_PCI_RPA_DLPAR) += rpadlpar_io.o
obj-$(CONFIG_HOTPLUG_PCI_SGI) += sgi_hotplug.o
obj-$(CONFIG_HOTPLUG_PCI_ACPI) += acpiphp.o
obj-$(CONFIG_HOTPLUG_PCI_S390) += s390_pci_hpc.o

# acpiphp_ibm extends acpiphp, so should be linked afterwards.

Expand Down
252 changes: 252 additions & 0 deletions drivers/pci/hotplug/s390_pci_hpc.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,252 @@
/*
* PCI Hot Plug Controller Driver for System z
*
* Copyright 2012 IBM Corp.
*
* Author(s):
* Jan Glauber <jang@linux.vnet.ibm.com>
*/

#define COMPONENT "zPCI hpc"
#define pr_fmt(fmt) COMPONENT ": " fmt

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/pci.h>
#include <linux/pci_hotplug.h>
#include <linux/init.h>
#include <asm/sclp.h>

#define SLOT_NAME_SIZE 10
static LIST_HEAD(s390_hotplug_slot_list);

MODULE_AUTHOR("Jan Glauber <jang@linux.vnet.ibm.com");
MODULE_DESCRIPTION("Hot Plug PCI Controller for System z");
MODULE_LICENSE("GPL");

static int zpci_fn_configured(enum zpci_state state)
{
return state == ZPCI_FN_STATE_CONFIGURED ||
state == ZPCI_FN_STATE_ONLINE;
}

/*
* struct slot - slot information for each *physical* slot
*/
struct slot {
struct list_head slot_list;
struct hotplug_slot *hotplug_slot;
struct zpci_dev *zdev;
};

static int enable_slot(struct hotplug_slot *hotplug_slot)
{
struct slot *slot = hotplug_slot->private;
int rc;

if (slot->zdev->state != ZPCI_FN_STATE_STANDBY)
return -EIO;

rc = sclp_pci_configure(slot->zdev->fid);
if (!rc) {
slot->zdev->state = ZPCI_FN_STATE_CONFIGURED;
/* automatically scan the device after is was configured */
zpci_enable_device(slot->zdev);
zpci_scan_device(slot->zdev);
}
return rc;
}

static int disable_slot(struct hotplug_slot *hotplug_slot)
{
struct slot *slot = hotplug_slot->private;
int rc;

if (!zpci_fn_configured(slot->zdev->state))
return -EIO;

/* TODO: we rely on the user to unbind/remove the device, is that plausible
* or do we need to trigger that here?
*/
rc = sclp_pci_deconfigure(slot->zdev->fid);
if (!rc) {
/* Fixme: better call List-PCI to find the disabled FH
for the FID since the FH should be opaque... */
slot->zdev->fh &= 0x7fffffff;
slot->zdev->state = ZPCI_FN_STATE_STANDBY;
}
return rc;
}

static int get_power_status(struct hotplug_slot *hotplug_slot, u8 *value)
{
struct slot *slot = hotplug_slot->private;

switch (slot->zdev->state) {
case ZPCI_FN_STATE_STANDBY:
*value = 0;
break;
default:
*value = 1;
break;
}
return 0;
}

static int get_adapter_status(struct hotplug_slot *hotplug_slot, u8 *value)
{
/* if the slot exits it always contains a function */
*value = 1;
return 0;
}

static void release_slot(struct hotplug_slot *hotplug_slot)
{
struct slot *slot = hotplug_slot->private;

pr_debug("%s - physical_slot = %s\n", __func__, hotplug_slot_name(hotplug_slot));
kfree(slot->hotplug_slot->info);
kfree(slot->hotplug_slot);
kfree(slot);
}

static struct hotplug_slot_ops s390_hotplug_slot_ops = {
.enable_slot = enable_slot,
.disable_slot = disable_slot,
.get_power_status = get_power_status,
.get_adapter_status = get_adapter_status,
};

static int init_pci_slot(struct zpci_dev *zdev)
{
struct hotplug_slot *hotplug_slot;
struct hotplug_slot_info *info;
char name[SLOT_NAME_SIZE];
struct slot *slot;
int rc;

if (!zdev)
return 0;

slot = kzalloc(sizeof(*slot), GFP_KERNEL);
if (!slot)
goto error;

hotplug_slot = kzalloc(sizeof(*hotplug_slot), GFP_KERNEL);
if (!hotplug_slot)
goto error_hp;
hotplug_slot->private = slot;

slot->hotplug_slot = hotplug_slot;
slot->zdev = zdev;

info = kzalloc(sizeof(*info), GFP_KERNEL);
if (!info)
goto error_info;
hotplug_slot->info = info;

hotplug_slot->ops = &s390_hotplug_slot_ops;
hotplug_slot->release = &release_slot;

get_power_status(hotplug_slot, &info->power_status);
get_adapter_status(hotplug_slot, &info->adapter_status);

snprintf(name, SLOT_NAME_SIZE, "%08x", zdev->fid);
rc = pci_hp_register(slot->hotplug_slot, zdev->bus,
ZPCI_DEVFN, name);
if (rc) {
pr_err("pci_hp_register failed with error %d\n", rc);
goto error_reg;
}
list_add(&slot->slot_list, &s390_hotplug_slot_list);
return 0;

error_reg:
kfree(info);
error_info:
kfree(hotplug_slot);
error_hp:
kfree(slot);
error:
return -ENOMEM;
}

static int __init init_pci_slots(void)
{
struct zpci_dev *zdev;
int device = 0;

/*
* Create a structure for each slot, and register that slot
* with the pci_hotplug subsystem.
*/
mutex_lock(&zpci_list_lock);
list_for_each_entry(zdev, &zpci_list, entry) {
init_pci_slot(zdev);
device++;
}

mutex_unlock(&zpci_list_lock);
return (device) ? 0 : -ENODEV;
}

static void exit_pci_slot(struct zpci_dev *zdev)
{
struct list_head *tmp, *n;
struct slot *slot;

list_for_each_safe(tmp, n, &s390_hotplug_slot_list) {
slot = list_entry(tmp, struct slot, slot_list);
if (slot->zdev != zdev)
continue;
list_del(&slot->slot_list);
pci_hp_deregister(slot->hotplug_slot);
}
}

static void __exit exit_pci_slots(void)
{
struct list_head *tmp, *n;
struct slot *slot;

/*
* Unregister all of our slots with the pci_hotplug subsystem.
* Memory will be freed in release_slot() callback after slot's
* lifespan is finished.
*/
list_for_each_safe(tmp, n, &s390_hotplug_slot_list) {
slot = list_entry(tmp, struct slot, slot_list);
list_del(&slot->slot_list);
pci_hp_deregister(slot->hotplug_slot);
}
}

static int __init pci_hotplug_s390_init(void)
{
/*
* Do specific initialization stuff for your driver here
* like initializing your controller hardware (if any) and
* determining the number of slots you have in the system
* right now.
*/

if (!pci_probe)
return -EOPNOTSUPP;

/* register callbacks for slot handling from arch code */
mutex_lock(&zpci_list_lock);
hotplug_ops.create_slot = init_pci_slot;
hotplug_ops.remove_slot = exit_pci_slot;
mutex_unlock(&zpci_list_lock);
pr_info("registered hotplug slot callbacks\n");
return init_pci_slots();
}

static void __exit pci_hotplug_s390_exit(void)
{
exit_pci_slots();
}

module_init(pci_hotplug_s390_init);
module_exit(pci_hotplug_s390_exit);
3 changes: 2 additions & 1 deletion drivers/s390/char/sclp.h
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright IBM Corp. 1999, 2009
* Copyright IBM Corp. 1999,2012
*
* Author(s): Martin Peschke <mpeschke@de.ibm.com>
* Martin Schwidefsky <schwidefsky@de.ibm.com>
Expand Down Expand Up @@ -103,6 +103,7 @@ extern u64 sclp_facilities;
#define SCLP_HAS_CHP_RECONFIG (sclp_facilities & 0x2000000000000000ULL)
#define SCLP_HAS_CPU_INFO (sclp_facilities & 0x0800000000000000ULL)
#define SCLP_HAS_CPU_RECONFIG (sclp_facilities & 0x0400000000000000ULL)
#define SCLP_HAS_PCI_RECONFIG (sclp_facilities & 0x0000000040000000ULL)


struct gds_subvector {
Expand Down
Loading

0 comments on commit 7441b06

Please sign in to comment.