bcb5d6c769
There's a number of tasks that need the state of a zpci device to be stable. Other tasks need to be synchronized as they change the state. State changes could be generated by the system as availability or error events, or be requested by the user through manipulations in sysfs. Some other actions accessible through sysfs - like device resets - need the state to be stable. Unsynchronized state handling could lead to unusable devices. This has been observed in cases of concurrent state changes through systemd udev rules and DPM boot control. Some breakage can be provoked by artificial tests, e.g. through repetitively injecting "recover" on a PCI function through sysfs while running a "hotplug remove/add" in a loop through a PCI slot's "power" attribute in sysfs. After a few iterations this could result in a kernel oops. So introduce a new mutex "state_lock" to guard the state property of the struct zpci_dev. Acquire this lock in all task that modify the state: - hotplug add and remove, through the PCI hotplug slot entry, - avaiability events, as reported by the platform, - error events, as reported by the platform, - during device resets, explicit through sysfs requests or implict through the common PCI layer. Break out an inner _do_recover() routine out of recover_store() to separte the necessary synchronizations from the actual manipulations of the zpci_dev required for the reset. With the following changes I was able to run the inject loops for hours without hitting an error. Signed-off-by: Gerd Bayer <gbayer@linux.ibm.com> Reviewed-by: Niklas Schnelle <schnelle@linux.ibm.com> Signed-off-by: Heiko Carstens <hca@linux.ibm.com>
144 lines
3.2 KiB
C
144 lines
3.2 KiB
C
// SPDX-License-Identifier: GPL-2.0+
|
|
/*
|
|
* PCI Hot Plug Controller Driver for System z
|
|
*
|
|
* Copyright 2012 IBM Corp.
|
|
*
|
|
* Author(s):
|
|
* Jan Glauber <jang@linux.vnet.ibm.com>
|
|
*/
|
|
|
|
#define KMSG_COMPONENT "zpci"
|
|
#define pr_fmt(fmt) KMSG_COMPONENT ": " fmt
|
|
|
|
#include <linux/kernel.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/pci.h>
|
|
#include <linux/pci_hotplug.h>
|
|
#include <asm/pci_debug.h>
|
|
#include <asm/sclp.h>
|
|
|
|
#define SLOT_NAME_SIZE 10
|
|
|
|
static int enable_slot(struct hotplug_slot *hotplug_slot)
|
|
{
|
|
struct zpci_dev *zdev = container_of(hotplug_slot, struct zpci_dev,
|
|
hotplug_slot);
|
|
int rc;
|
|
|
|
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)) {
|
|
pci_dev_put(pdev);
|
|
rc = -EBUSY;
|
|
goto out;
|
|
}
|
|
|
|
rc = zpci_deconfigure_device(zdev);
|
|
out:
|
|
mutex_unlock(&zdev->state_lock);
|
|
if (pdev)
|
|
pci_dev_put(pdev);
|
|
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 exits it always contains a function */
|
|
*value = 1;
|
|
return 0;
|
|
}
|
|
|
|
static const struct hotplug_slot_ops s390_hotplug_slot_ops = {
|
|
.enable_slot = enable_slot,
|
|
.disable_slot = disable_slot,
|
|
.reset_slot = reset_slot,
|
|
.get_power_status = get_power_status,
|
|
.get_adapter_status = get_adapter_status,
|
|
};
|
|
|
|
int zpci_init_slot(struct zpci_dev *zdev)
|
|
{
|
|
char name[SLOT_NAME_SIZE];
|
|
struct zpci_bus *zbus = zdev->zbus;
|
|
|
|
zdev->hotplug_slot.ops = &s390_hotplug_slot_ops;
|
|
|
|
snprintf(name, SLOT_NAME_SIZE, "%08x", zdev->fid);
|
|
return pci_hp_register(&zdev->hotplug_slot, zbus->bus,
|
|
zdev->devfn, name);
|
|
}
|
|
|
|
void zpci_exit_slot(struct zpci_dev *zdev)
|
|
{
|
|
pci_hp_deregister(&zdev->hotplug_slot);
|
|
}
|