forked from luck/tmp_suning_uos_patched
f0931ee125
[ Upstream commit c122383d221dfa2f41cfe5e672540595de986fde ] Currently zpci_dev uses kref based reference counting but only accounts for one original reference plus one reference from an added pci_dev to its underlying zpci_dev. Counting just the original reference worked until the pci_dev reference was added in commit 2a671f77ee49 ("s390/pci: fix use after free of zpci_dev") because once a zpci_dev goes away, i.e. enters the reserved state, it would immediately get released. However with the pci_dev reference this is no longer the case and the zpci_dev may still appear in multiple availability events indicating that it was reserved. This was solved by detecting when the zpci_dev is already on its way out but still hanging around. This has however shown some light on how unusual our zpci_dev reference counting is. Improve upon this by modelling zpci_dev reference counting on pci_dev. Analogous to pci_get_slot() increment the reference count in get_zdev_by_fid(). Thus all users of get_zdev_by_fid() must drop the reference once they are done with the zpci_dev. Similar to pci_scan_single_device(), zpci_create_device() returns the device with an initial count of 1 and the device added to the zpci_list (analogous to the PCI bus' device_list). In turn users of zpci_create_device() must only drop the reference once the device is gone from the point of view of the zPCI subsystem, it might still be referenced by the common PCI subsystem though. Reviewed-by: Matthew Rosato <mjrosato@linux.ibm.com> Signed-off-by: Niklas Schnelle <schnelle@linux.ibm.com> Signed-off-by: Vasily Gorbik <gor@linux.ibm.com> Signed-off-by: Sasha Levin <sashal@kernel.org>
176 lines
3.9 KiB
C
176 lines
3.9 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
/*
|
|
* Copyright IBM Corp. 2012
|
|
*
|
|
* 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/pci.h>
|
|
#include <asm/pci_debug.h>
|
|
#include <asm/sclp.h>
|
|
|
|
#include "pci_bus.h"
|
|
|
|
/* Content Code Description for PCI Function Error */
|
|
struct zpci_ccdf_err {
|
|
u32 reserved1;
|
|
u32 fh; /* function handle */
|
|
u32 fid; /* function id */
|
|
u32 ett : 4; /* expected table type */
|
|
u32 mvn : 12; /* MSI vector number */
|
|
u32 dmaas : 8; /* DMA address space */
|
|
u32 : 6;
|
|
u32 q : 1; /* event qualifier */
|
|
u32 rw : 1; /* read/write */
|
|
u64 faddr; /* failing address */
|
|
u32 reserved3;
|
|
u16 reserved4;
|
|
u16 pec; /* PCI event code */
|
|
} __packed;
|
|
|
|
/* Content Code Description for PCI Function Availability */
|
|
struct zpci_ccdf_avail {
|
|
u32 reserved1;
|
|
u32 fh; /* function handle */
|
|
u32 fid; /* function id */
|
|
u32 reserved2;
|
|
u32 reserved3;
|
|
u32 reserved4;
|
|
u32 reserved5;
|
|
u16 reserved6;
|
|
u16 pec; /* PCI event code */
|
|
} __packed;
|
|
|
|
static void __zpci_event_error(struct zpci_ccdf_err *ccdf)
|
|
{
|
|
struct zpci_dev *zdev = get_zdev_by_fid(ccdf->fid);
|
|
struct pci_dev *pdev = NULL;
|
|
|
|
zpci_err("error CCDF:\n");
|
|
zpci_err_hex(ccdf, sizeof(*ccdf));
|
|
|
|
if (zdev)
|
|
pdev = pci_get_slot(zdev->zbus->bus, zdev->devfn);
|
|
|
|
pr_err("%s: Event 0x%x reports an error for PCI function 0x%x\n",
|
|
pdev ? pci_name(pdev) : "n/a", ccdf->pec, ccdf->fid);
|
|
|
|
if (!pdev)
|
|
goto no_pdev;
|
|
|
|
pdev->error_state = pci_channel_io_perm_failure;
|
|
pci_dev_put(pdev);
|
|
no_pdev:
|
|
zpci_zdev_put(zdev);
|
|
}
|
|
|
|
void zpci_event_error(void *data)
|
|
{
|
|
if (zpci_is_enabled())
|
|
__zpci_event_error(data);
|
|
}
|
|
|
|
static void __zpci_event_availability(struct zpci_ccdf_avail *ccdf)
|
|
{
|
|
struct zpci_dev *zdev = get_zdev_by_fid(ccdf->fid);
|
|
bool existing_zdev = !!zdev;
|
|
enum zpci_state state;
|
|
struct pci_dev *pdev;
|
|
int ret;
|
|
|
|
zpci_err("avail CCDF:\n");
|
|
zpci_err_hex(ccdf, sizeof(*ccdf));
|
|
|
|
switch (ccdf->pec) {
|
|
case 0x0301: /* Reserved|Standby -> Configured */
|
|
if (!zdev) {
|
|
zpci_create_device(ccdf->fid, ccdf->fh, ZPCI_FN_STATE_CONFIGURED);
|
|
break;
|
|
}
|
|
/* the configuration request may be stale */
|
|
if (zdev->state != ZPCI_FN_STATE_STANDBY)
|
|
break;
|
|
zdev->fh = ccdf->fh;
|
|
zdev->state = ZPCI_FN_STATE_CONFIGURED;
|
|
ret = zpci_enable_device(zdev);
|
|
if (ret)
|
|
break;
|
|
|
|
/* the PCI function will be scanned once function 0 appears */
|
|
if (!zdev->zbus->bus)
|
|
break;
|
|
|
|
pdev = pci_scan_single_device(zdev->zbus->bus, zdev->devfn);
|
|
if (!pdev)
|
|
break;
|
|
|
|
pci_bus_add_device(pdev);
|
|
pci_lock_rescan_remove();
|
|
pci_bus_add_devices(zdev->zbus->bus);
|
|
pci_unlock_rescan_remove();
|
|
break;
|
|
case 0x0302: /* Reserved -> Standby */
|
|
if (!zdev) {
|
|
zpci_create_device(ccdf->fid, ccdf->fh, ZPCI_FN_STATE_STANDBY);
|
|
break;
|
|
}
|
|
zdev->fh = ccdf->fh;
|
|
break;
|
|
case 0x0303: /* Deconfiguration requested */
|
|
if (!zdev)
|
|
break;
|
|
zpci_remove_device(zdev, false);
|
|
|
|
ret = zpci_disable_device(zdev);
|
|
if (ret)
|
|
break;
|
|
|
|
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;
|
|
|
|
break;
|
|
case 0x0304: /* Configured -> Standby|Reserved */
|
|
if (!zdev)
|
|
break;
|
|
/* Give the driver a hint that the function is
|
|
* already unusable.
|
|
*/
|
|
zpci_remove_device(zdev, true);
|
|
|
|
zdev->fh = ccdf->fh;
|
|
zpci_disable_device(zdev);
|
|
zdev->state = ZPCI_FN_STATE_STANDBY;
|
|
if (!clp_get_state(ccdf->fid, &state) &&
|
|
state == ZPCI_FN_STATE_RESERVED) {
|
|
zpci_device_reserved(zdev);
|
|
}
|
|
break;
|
|
case 0x0306: /* 0x308 or 0x302 for multiple devices */
|
|
zpci_remove_reserved_devices();
|
|
clp_scan_pci_devices();
|
|
break;
|
|
case 0x0308: /* Standby -> Reserved */
|
|
if (!zdev)
|
|
break;
|
|
zpci_device_reserved(zdev);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
if (existing_zdev)
|
|
zpci_zdev_put(zdev);
|
|
}
|
|
|
|
void zpci_event_availability(void *data)
|
|
{
|
|
if (zpci_is_enabled())
|
|
__zpci_event_availability(data);
|
|
}
|