
commit a46044a92add6a400f4dada7b943b30221f7cc80 upstream. Since commit 2a671f77ee49 ("s390/pci: fix use after free of zpci_dev") the reference count of a zpci_dev is incremented between pcibios_add_device() and pcibios_release_device() which was supposed to prevent the zpci_dev from being freed while the common PCI code has access to it. It was missed however that the handling of zPCI availability events assumed that once zpci_zdev_put() was called no later availability event would still see the device. With the previously mentioned commit however this assumption no longer holds and we must make sure that we only drop the initial long-lived reference the zPCI subsystem holds exactly once. Do so by introducing a zpci_device_reserved() function that handles when a device is reserved. Here we make sure the zpci_dev will not be considered for further events by removing it from the zpci_list. This also means that the device actually stays in the ZPCI_FN_STATE_RESERVED state between the time we know it has been reserved and the final reference going away. We thus need to consider it a real state instead of just a conceptual state after the removal. The final cleanup of PCI resources, removal from zbus, and destruction of the IOMMU stays in zpci_release_device() to make sure holders of the reference do see valid data until the release. Fixes: 2a671f77ee49 ("s390/pci: fix use after free of zpci_dev") Cc: stable@vger.kernel.org Signed-off-by: Niklas Schnelle <schnelle@linux.ibm.com> Signed-off-by: Vasily Gorbik <gor@linux.ibm.com> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
146 lines
3.1 KiB
C
146 lines
3.1 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 zpci_fn_configured(enum zpci_state state)
|
|
{
|
|
return state == ZPCI_FN_STATE_CONFIGURED ||
|
|
state == ZPCI_FN_STATE_ONLINE;
|
|
}
|
|
|
|
static inline int zdev_configure(struct zpci_dev *zdev)
|
|
{
|
|
int ret = sclp_pci_configure(zdev->fid);
|
|
|
|
zpci_dbg(3, "conf fid:%x, rc:%d\n", zdev->fid, ret);
|
|
if (!ret)
|
|
zdev->state = ZPCI_FN_STATE_CONFIGURED;
|
|
|
|
return ret;
|
|
}
|
|
|
|
static inline int zdev_deconfigure(struct zpci_dev *zdev)
|
|
{
|
|
int ret = sclp_pci_deconfigure(zdev->fid);
|
|
|
|
zpci_dbg(3, "deconf fid:%x, rc:%d\n", zdev->fid, ret);
|
|
if (!ret)
|
|
zdev->state = ZPCI_FN_STATE_STANDBY;
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int enable_slot(struct hotplug_slot *hotplug_slot)
|
|
{
|
|
struct zpci_dev *zdev = container_of(hotplug_slot, struct zpci_dev,
|
|
hotplug_slot);
|
|
struct zpci_bus *zbus = zdev->zbus;
|
|
int rc;
|
|
|
|
if (zdev->state != ZPCI_FN_STATE_STANDBY)
|
|
return -EIO;
|
|
|
|
rc = zdev_configure(zdev);
|
|
if (rc)
|
|
return rc;
|
|
|
|
rc = zpci_enable_device(zdev);
|
|
if (rc)
|
|
goto out_deconfigure;
|
|
|
|
pci_scan_slot(zbus->bus, zdev->devfn);
|
|
pci_lock_rescan_remove();
|
|
pci_bus_add_devices(zbus->bus);
|
|
pci_unlock_rescan_remove();
|
|
|
|
return rc;
|
|
|
|
out_deconfigure:
|
|
zdev_deconfigure(zdev);
|
|
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;
|
|
int rc;
|
|
|
|
if (!zpci_fn_configured(zdev->state))
|
|
return -EIO;
|
|
|
|
pdev = pci_get_slot(zdev->zbus->bus, zdev->devfn);
|
|
if (pdev && pci_num_vf(pdev)) {
|
|
pci_dev_put(pdev);
|
|
return -EBUSY;
|
|
}
|
|
pci_dev_put(pdev);
|
|
|
|
zpci_remove_device(zdev, false);
|
|
|
|
rc = zpci_disable_device(zdev);
|
|
if (rc)
|
|
return rc;
|
|
|
|
return zdev_deconfigure(zdev);
|
|
}
|
|
|
|
static int get_power_status(struct hotplug_slot *hotplug_slot, u8 *value)
|
|
{
|
|
struct zpci_dev *zdev = container_of(hotplug_slot, struct zpci_dev,
|
|
hotplug_slot);
|
|
|
|
*value = zpci_is_device_configured(zdev) ? 1 : 0;
|
|
return 0;
|
|
}
|
|
|
|
static int get_adapter_status(struct hotplug_slot *hotplug_slot, u8 *value)
|
|
{
|
|
/* if the slot exits it always contains a function */
|
|
*value = 1;
|
|
return 0;
|
|
}
|
|
|
|
static const struct hotplug_slot_ops s390_hotplug_slot_ops = {
|
|
.enable_slot = enable_slot,
|
|
.disable_slot = disable_slot,
|
|
.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);
|
|
}
|