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
+10
View File
@@ -0,0 +1,10 @@
# SPDX-License-Identifier: GPL-2.0
#
# Makefile for the s390 PCI subsystem.
#
obj-$(CONFIG_PCI) += pci.o pci_irq.o pci_clp.o \
pci_event.o pci_debug.o pci_insn.o pci_mmio.o \
pci_bus.o pci_kvm_hook.o pci_report.o pci_fixup.o
obj-$(CONFIG_PCI_IOV) += pci_iov.o
obj-$(CONFIG_SYSFS) += pci_sysfs.o
File diff suppressed because it is too large Load Diff
+483
View File
@@ -0,0 +1,483 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright IBM Corp. 2020
*
* Author(s):
* Pierre Morel <pmorel@linux.ibm.com>
*
*/
#define pr_fmt(fmt) "zpci: " fmt
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/err.h>
#include <linux/delay.h>
#include <linux/seq_file.h>
#include <linux/irqdomain.h>
#include <linux/jump_label.h>
#include <linux/pci.h>
#include <linux/printk.h>
#include <linux/dma-direct.h>
#include <asm/pci_clp.h>
#include <asm/pci_dma.h>
#include "pci_bus.h"
#include "pci_iov.h"
static LIST_HEAD(zbus_list);
static DEFINE_MUTEX(zbus_list_lock);
static int zpci_nb_devices;
/* zpci_bus_prepare_device - Prepare a zPCI function for scanning
* @zdev: the zPCI function to be prepared
*
* The PCI resources for the function are set up and added to its zbus and the
* function is enabled. The function must be added to a zbus which must have
* a PCI bus created. If an error occurs the zPCI function is not enabled.
*
* Return: 0 on success, an error code otherwise
*/
static int zpci_bus_prepare_device(struct zpci_dev *zdev)
{
int rc, i;
if (!zdev_enabled(zdev)) {
rc = zpci_enable_device(zdev);
if (rc) {
pr_err("Enabling PCI function %08x failed\n", zdev->fid);
return rc;
}
}
if (!zdev->has_resources) {
zpci_setup_bus_resources(zdev);
for (i = 0; i < PCI_STD_NUM_BARS; i++) {
if (zdev->bars[i].res)
pci_bus_add_resource(zdev->zbus->bus, zdev->bars[i].res);
}
}
return 0;
}
/* zpci_bus_scan_device - Scan a single device adding it to the PCI core
* @zdev: the zdev to be scanned
*
* Scans the PCI function making it available to the common PCI code.
*
* Return: 0 on success, an error value otherwise
*/
int zpci_bus_scan_device(struct zpci_dev *zdev)
{
struct pci_dev *pdev;
int rc;
rc = zpci_bus_prepare_device(zdev);
if (rc)
return rc;
pdev = pci_scan_single_device(zdev->zbus->bus, zdev->devfn);
if (!pdev)
return -ENODEV;
pci_lock_rescan_remove();
pci_bus_add_device(pdev);
pci_unlock_rescan_remove();
return 0;
}
/* zpci_bus_remove_device - Removes the given zdev from the PCI core
* @zdev: the zdev to be removed from the PCI core
* @set_error: if true the device's error state is set to permanent failure
*
* Sets a zPCI device to a configured but offline state; the zPCI
* device is still accessible through its hotplug slot and the zPCI
* API but is removed from the common code PCI bus, making it
* no longer available to drivers.
*/
void zpci_bus_remove_device(struct zpci_dev *zdev, bool set_error)
{
struct zpci_bus *zbus = zdev->zbus;
struct pci_dev *pdev;
if (!zdev->zbus->bus)
return;
pdev = pci_get_slot(zbus->bus, zdev->devfn);
if (pdev) {
if (set_error)
pdev->error_state = pci_channel_io_perm_failure;
if (pdev->is_virtfn) {
zpci_iov_remove_virtfn(pdev, zdev->vfn);
/* balance pci_get_slot */
pci_dev_put(pdev);
return;
}
pci_stop_and_remove_bus_device_locked(pdev);
/* balance pci_get_slot */
pci_dev_put(pdev);
}
}
/* zpci_bus_scan_bus - Scan all configured zPCI functions on the bus
* @zbus: the zbus to be scanned
*
* Enables and scans all PCI functions on the bus making them available to the
* common PCI code. If a PCI function fails to be initialized an error will be
* returned but attempts will still be made for all other functions on the bus.
*
* Return: 0 on success, an error value otherwise
*/
int zpci_bus_scan_bus(struct zpci_bus *zbus)
{
struct zpci_dev *zdev;
int devfn, rc, ret = 0;
for (devfn = 0; devfn < ZPCI_FUNCTIONS_PER_BUS; devfn++) {
zdev = zbus->function[devfn];
if (zdev && zdev->state == ZPCI_FN_STATE_CONFIGURED) {
rc = zpci_bus_prepare_device(zdev);
if (rc)
ret = -EIO;
}
}
pci_lock_rescan_remove();
pci_scan_child_bus(zbus->bus);
pci_bus_add_devices(zbus->bus);
pci_unlock_rescan_remove();
return ret;
}
static bool zpci_bus_is_multifunction_root(struct zpci_dev *zdev)
{
return !s390_pci_no_rid && zdev->rid_available &&
!zdev->vfn;
}
/* zpci_bus_create_pci_bus - Create the PCI bus associated with this zbus
* @zbus: the zbus holding the zdevices
* @fr: PCI root function that will determine the bus's domain, and bus speed
* @ops: the pci operations
*
* The PCI function @fr determines the domain (its UID), multifunction property
* and maximum bus speed of the entire bus.
*
* Return: 0 on success, an error code otherwise
*/
static int zpci_bus_create_pci_bus(struct zpci_bus *zbus, struct zpci_dev *fr, struct pci_ops *ops)
{
struct pci_bus *bus;
int domain;
domain = zpci_alloc_domain((u16)fr->uid);
if (domain < 0)
return domain;
zbus->domain_nr = domain;
zbus->multifunction = zpci_bus_is_multifunction_root(fr);
zbus->max_bus_speed = fr->max_bus_speed;
if (zpci_create_parent_msi_domain(zbus))
goto out_free_domain;
/*
* Note that the zbus->resources are taken over and zbus->resources
* is empty after a successful call
*/
bus = pci_create_root_bus(NULL, ZPCI_BUS_NR, ops, zbus, &zbus->resources);
if (!bus)
goto out_remove_msi_domain;
zbus->bus = bus;
dev_set_msi_domain(&zbus->bus->dev, zbus->msi_parent_domain);
return 0;
out_remove_msi_domain:
zpci_remove_parent_msi_domain(zbus);
out_free_domain:
zpci_free_domain(zbus->domain_nr);
return -ENOMEM;
}
/**
* zpci_bus_release - Un-initialize resources associated with the zbus and
* free memory
* @kref: refcount * that is part of struct zpci_bus
*
* MUST be called with `zbus_list_lock` held, but the lock is released during
* run of the function.
*/
static inline void zpci_bus_release(struct kref *kref)
__releases(&zbus_list_lock)
{
struct zpci_bus *zbus = container_of(kref, struct zpci_bus, kref);
lockdep_assert_held(&zbus_list_lock);
list_del(&zbus->bus_next);
mutex_unlock(&zbus_list_lock);
/*
* At this point no-one should see this object, or be able to get a new
* reference to it.
*/
if (zbus->bus) {
pci_lock_rescan_remove();
pci_stop_root_bus(zbus->bus);
zpci_free_domain(zbus->domain_nr);
pci_free_resource_list(&zbus->resources);
pci_remove_root_bus(zbus->bus);
pci_unlock_rescan_remove();
}
zpci_remove_parent_msi_domain(zbus);
kfree(zbus);
}
static inline void __zpci_bus_get(struct zpci_bus *zbus)
{
lockdep_assert_held(&zbus_list_lock);
kref_get(&zbus->kref);
}
static inline void zpci_bus_put(struct zpci_bus *zbus)
{
kref_put_mutex(&zbus->kref, zpci_bus_release, &zbus_list_lock);
}
static struct zpci_bus *zpci_bus_get(int topo, bool topo_is_tid)
{
struct zpci_bus *zbus;
mutex_lock(&zbus_list_lock);
list_for_each_entry(zbus, &zbus_list, bus_next) {
if (!zbus->multifunction)
continue;
if (topo_is_tid == zbus->topo_is_tid && topo == zbus->topo) {
__zpci_bus_get(zbus);
goto out_unlock;
}
}
zbus = NULL;
out_unlock:
mutex_unlock(&zbus_list_lock);
return zbus;
}
/**
* zpci_bus_get_next - get the next zbus object from given position in the list
* @pos: current position/cursor in the global zbus list
*
* Acquires and releases references as the cursor iterates (might also free/
* release the cursor). Is tolerant of concurrent operations on the list.
*
* To begin the iteration, set *@pos to %NULL before calling the function.
*
* *@pos is set to %NULL in cases where either the list is empty, or *@pos is
* the last element in the list.
*
* Context: Process context. May sleep.
*/
void zpci_bus_get_next(struct zpci_bus **pos)
{
struct zpci_bus *curp = *pos, *next = NULL;
mutex_lock(&zbus_list_lock);
if (curp)
next = list_next_entry(curp, bus_next);
else
next = list_first_entry(&zbus_list, typeof(*curp), bus_next);
if (list_entry_is_head(next, &zbus_list, bus_next))
next = NULL;
if (next)
__zpci_bus_get(next);
*pos = next;
mutex_unlock(&zbus_list_lock);
/* zpci_bus_put() might drop refcount to 0 and locks zbus_list_lock */
if (curp)
zpci_bus_put(curp);
}
static struct zpci_bus *zpci_bus_alloc(int topo, bool topo_is_tid)
{
struct zpci_bus *zbus;
zbus = kzalloc_obj(*zbus);
if (!zbus)
return NULL;
zbus->topo = topo;
zbus->topo_is_tid = topo_is_tid;
INIT_LIST_HEAD(&zbus->bus_next);
kref_init(&zbus->kref);
INIT_LIST_HEAD(&zbus->resources);
zbus->bus_resource.start = 0;
zbus->bus_resource.end = ZPCI_BUS_NR;
zbus->bus_resource.flags = IORESOURCE_BUS;
pci_add_resource(&zbus->resources, &zbus->bus_resource);
mutex_lock(&zbus_list_lock);
list_add_tail(&zbus->bus_next, &zbus_list);
mutex_unlock(&zbus_list_lock);
return zbus;
}
static void pci_dma_range_setup(struct pci_dev *pdev)
{
struct zpci_dev *zdev = to_zpci(pdev);
u64 aligned_end, size;
dma_addr_t dma_start;
int ret;
dma_start = PAGE_ALIGN(zdev->start_dma);
aligned_end = PAGE_ALIGN_DOWN(zdev->end_dma + 1);
if (aligned_end >= dma_start)
size = aligned_end - dma_start;
else
size = 0;
WARN_ON_ONCE(size == 0);
ret = dma_direct_set_offset(&pdev->dev, 0, dma_start, size);
if (ret)
pr_err("Failed to allocate DMA range map for %s\n", pci_name(pdev));
}
void pcibios_bus_add_device(struct pci_dev *pdev)
{
struct zpci_dev *zdev = to_zpci(pdev);
pci_dma_range_setup(pdev);
/*
* With pdev->no_vf_scan the common PCI probing code does not
* perform PF/VF linking.
*/
if (zdev->vfn) {
zpci_iov_setup_virtfn(zdev->zbus, pdev, zdev->vfn);
pdev->no_command_memory = 1;
}
}
static int zpci_bus_add_device(struct zpci_bus *zbus, struct zpci_dev *zdev)
{
int rc = -EINVAL;
if (zbus->multifunction) {
if (!zdev->rid_available) {
WARN_ONCE(1, "rid_available not set for multifunction\n");
return rc;
}
zdev->devfn = zdev->rid & ZPCI_RID_MASK_DEVFN;
}
if (zbus->function[zdev->devfn]) {
pr_err("devfn %04x is already assigned\n", zdev->devfn);
return rc;
}
zdev->zbus = zbus;
zbus->function[zdev->devfn] = zdev;
zpci_nb_devices++;
rc = zpci_init_slot(zdev);
if (rc)
goto error;
zdev->has_hp_slot = 1;
return 0;
error:
zbus->function[zdev->devfn] = NULL;
zdev->zbus = NULL;
zpci_nb_devices--;
return rc;
}
static bool zpci_bus_is_isolated_vf(struct zpci_bus *zbus, struct zpci_dev *zdev)
{
struct pci_dev *pdev;
if (!zdev->vfn)
return false;
pdev = zpci_iov_find_parent_pf(zbus, zdev);
if (!pdev)
return true;
pci_dev_put(pdev);
return false;
}
int zpci_bus_device_register(struct zpci_dev *zdev, struct pci_ops *ops)
{
bool topo_is_tid = zdev->tid_avail;
struct zpci_bus *zbus = NULL;
int topo, rc = -EBADF;
if (zpci_nb_devices == ZPCI_NR_DEVICES) {
pr_warn("Adding PCI function %08x failed because the configured limit of %d is reached\n",
zdev->fid, ZPCI_NR_DEVICES);
return -ENOSPC;
}
topo = topo_is_tid ? zdev->tid : zdev->pchid;
zbus = zpci_bus_get(topo, topo_is_tid);
/*
* An isolated VF gets its own domain/bus even if there exists
* a matching domain/bus already
*/
if (zbus && zpci_bus_is_isolated_vf(zbus, zdev)) {
zpci_bus_put(zbus);
zbus = NULL;
}
if (!zbus) {
zbus = zpci_bus_alloc(topo, topo_is_tid);
if (!zbus)
return -ENOMEM;
}
if (!zbus->bus) {
/* The UID of the first PCI function registered with a zpci_bus
* is used as the domain number for that bus. Currently there
* is exactly one zpci_bus per domain.
*/
rc = zpci_bus_create_pci_bus(zbus, zdev, ops);
if (rc)
goto error;
}
rc = zpci_bus_add_device(zbus, zdev);
if (rc)
goto error;
return 0;
error:
pr_err("Adding PCI function %08x failed\n", zdev->fid);
zpci_bus_put(zbus);
return rc;
}
void zpci_bus_device_unregister(struct zpci_dev *zdev)
{
struct zpci_bus *zbus = zdev->zbus;
zpci_nb_devices--;
zbus->function[zdev->devfn] = NULL;
zpci_bus_put(zbus);
}
+57
View File
@@ -0,0 +1,57 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Copyright IBM Corp. 2020
*
* Author(s):
* Pierre Morel <pmorel@linux.ibm.com>
*
*/
#ifndef __S390_PCI_BUS_H
#define __S390_PCI_BUS_H
#include <linux/pci.h>
int zpci_bus_device_register(struct zpci_dev *zdev, struct pci_ops *ops);
void zpci_bus_device_unregister(struct zpci_dev *zdev);
int zpci_bus_scan_bus(struct zpci_bus *zbus);
void zpci_bus_get_next(struct zpci_bus **pos);
/**
* zpci_bus_for_each - iterate over all the registered zbus objects
* @pos: a struct zpci_bus * as cursor
*
* Acquires and releases references as the cursor iterates over the registered
* objects. Is tolerant against concurrent removals of objects.
*
* Context: Process context. May sleep.
*/
#define zpci_bus_for_each(pos) \
for ((pos) = NULL, zpci_bus_get_next(&(pos)); (pos) != NULL; \
zpci_bus_get_next(&(pos)))
int zpci_bus_scan_device(struct zpci_dev *zdev);
void zpci_bus_remove_device(struct zpci_dev *zdev, bool set_error);
void zpci_release_device(struct kref *kref);
void zpci_zdev_put(struct zpci_dev *zdev);
static inline void zpci_zdev_get(struct zpci_dev *zdev)
{
kref_get(&zdev->kref);
}
int zpci_alloc_domain(int domain);
void zpci_free_domain(int domain);
int zpci_setup_bus_resources(struct zpci_dev *zdev);
static inline struct zpci_dev *zdev_from_bus(struct pci_bus *bus,
unsigned int devfn)
{
struct zpci_bus *zbus = bus->sysdata;
return (devfn >= ZPCI_FUNCTIONS_PER_BUS) ? NULL : zbus->function[devfn];
}
#endif /* __S390_PCI_BUS_H */
+678
View File
@@ -0,0 +1,678 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright IBM Corp. 2012
*
* Author(s):
* Jan Glauber <jang@linux.vnet.ibm.com>
*/
#define pr_fmt(fmt) "zpci: " fmt
#include <linux/kernel.h>
#include <linux/miscdevice.h>
#include <linux/slab.h>
#include <linux/err.h>
#include <linux/delay.h>
#include <linux/pci.h>
#include <linux/uaccess.h>
#include <asm/asm-extable.h>
#include <asm/pci_debug.h>
#include <asm/pci_clp.h>
#include <asm/asm.h>
#include <asm/clp.h>
#include <uapi/asm/clp.h>
#include "pci_bus.h"
bool zpci_unique_uid;
void update_uid_checking(bool new)
{
if (zpci_unique_uid != new)
zpci_dbg(3, "uid checking:%d\n", new);
zpci_unique_uid = new;
}
static inline void zpci_err_clp(unsigned int rsp, int rc)
{
struct {
unsigned int rsp;
int rc;
} __packed data = {rsp, rc};
zpci_err_hex(&data, sizeof(data));
}
/*
* Call Logical Processor with c=1, lps=0 and command 1
* to get the bit mask of installed logical processors
*/
static inline int clp_get_ilp(unsigned long *ilp)
{
unsigned long mask;
int cc, exception;
exception = 1;
asm_inline volatile (
" .insn rrf,0xb9a00000,%[mask],%[cmd],8,0\n"
"0: lhi %[exc],0\n"
"1:\n"
CC_IPM(cc)
EX_TABLE(0b, 1b)
: CC_OUT(cc, cc), [mask] "=d" (mask), [exc] "+d" (exception)
: [cmd] "a" (1)
: CC_CLOBBER);
*ilp = mask;
return exception ? 3 : CC_TRANSFORM(cc);
}
/*
* Call Logical Processor with c=0, the give constant lps and an lpcb request.
*/
static __always_inline int clp_req(void *data, unsigned int lps)
{
struct { u8 _[CLP_BLK_SIZE]; } *req = data;
int cc, exception;
u64 ignored;
exception = 1;
asm_inline volatile (
" .insn rrf,0xb9a00000,%[ign],%[req],0,%[lps]\n"
"0: lhi %[exc],0\n"
"1:\n"
CC_IPM(cc)
EX_TABLE(0b, 1b)
: CC_OUT(cc, cc), [ign] "=d" (ignored), "+m" (*req), [exc] "+d" (exception)
: [req] "a" (req), [lps] "i" (lps)
: CC_CLOBBER);
return exception ? 3 : CC_TRANSFORM(cc);
}
static void *clp_alloc_block(gfp_t gfp_mask)
{
return (void *) __get_free_pages(gfp_mask, get_order(CLP_BLK_SIZE));
}
static void clp_free_block(void *ptr)
{
free_pages((unsigned long) ptr, get_order(CLP_BLK_SIZE));
}
static void clp_store_query_pci_fngrp(struct zpci_dev *zdev,
struct clp_rsp_query_pci_grp *response)
{
zdev->tlb_refresh = response->refresh;
zdev->dma_mask = response->dasm;
zdev->msi_addr = response->msia;
zdev->max_msi = response->noi;
zdev->fmb_update = response->mui;
zdev->version = response->version;
zdev->maxstbl = response->maxstbl;
zdev->dtsm = response->dtsm;
zdev->rtr_avail = response->rtr;
switch (response->version) {
case 1:
zdev->max_bus_speed = PCIE_SPEED_5_0GT;
break;
default:
zdev->max_bus_speed = PCI_SPEED_UNKNOWN;
break;
}
}
static int clp_query_pci_fngrp(struct zpci_dev *zdev, u8 pfgid)
{
struct clp_req_rsp_query_pci_grp *rrb;
int rc;
rrb = clp_alloc_block(GFP_KERNEL);
if (!rrb)
return -ENOMEM;
memset(rrb, 0, sizeof(*rrb));
rrb->request.hdr.len = sizeof(rrb->request);
rrb->request.hdr.cmd = CLP_QUERY_PCI_FNGRP;
rrb->response.hdr.len = sizeof(rrb->response);
rrb->request.pfgid = pfgid;
rc = clp_req(rrb, CLP_LPS_PCI);
if (!rc && rrb->response.hdr.rsp == CLP_RC_OK)
clp_store_query_pci_fngrp(zdev, &rrb->response);
else {
zpci_err("Q PCI FGRP:\n");
zpci_err_clp(rrb->response.hdr.rsp, rc);
rc = -EIO;
}
clp_free_block(rrb);
return rc;
}
static int clp_store_query_pci_fn(struct zpci_dev *zdev,
struct clp_rsp_query_pci *response)
{
int i;
for (i = 0; i < PCI_STD_NUM_BARS; i++) {
zdev->bars[i].val = le32_to_cpu(response->bar[i]);
zdev->bars[i].size = response->bar_size[i];
}
zdev->start_dma = response->sdma;
zdev->end_dma = response->edma;
zdev->pchid = response->pchid;
zdev->pfgid = response->pfgid;
zdev->pft = response->pft;
zdev->vfn = response->vfn;
zdev->port = response->port;
zdev->fidparm = response->fidparm;
zdev->uid = response->uid;
zdev->fmb_length = sizeof(u32) * response->fmb_len;
zdev->is_physfn = response->is_physfn;
zdev->rid_available = response->rid_avail;
if (zdev->rid_available)
zdev->rid = response->rid;
zdev->tid_avail = response->tid_avail;
if (zdev->tid_avail)
zdev->tid = response->tid;
memcpy(zdev->pfip, response->pfip, sizeof(zdev->pfip));
if (response->util_str_avail) {
memcpy(zdev->util_str, response->util_str,
sizeof(zdev->util_str));
zdev->util_str_avail = 1;
}
zdev->mio_capable = response->mio_addr_avail;
for (i = 0; i < PCI_STD_NUM_BARS; i++) {
if (!(response->mio.valid & (1 << (PCI_STD_NUM_BARS - i - 1))))
continue;
zdev->bars[i].mio_wb = (void __iomem *) response->mio.addr[i].wb;
zdev->bars[i].mio_wt = (void __iomem *) response->mio.addr[i].wt;
}
return 0;
}
int clp_query_pci_fn(struct zpci_dev *zdev)
{
struct clp_req_rsp_query_pci *rrb;
int rc;
rrb = clp_alloc_block(GFP_KERNEL);
if (!rrb)
return -ENOMEM;
memset(rrb, 0, sizeof(*rrb));
rrb->request.hdr.len = sizeof(rrb->request);
rrb->request.hdr.cmd = CLP_QUERY_PCI_FN;
rrb->response.hdr.len = sizeof(rrb->response);
rrb->request.fh = zdev->fh;
rc = clp_req(rrb, CLP_LPS_PCI);
if (!rc && rrb->response.hdr.rsp == CLP_RC_OK) {
rc = clp_store_query_pci_fn(zdev, &rrb->response);
if (rc)
goto out;
rc = clp_query_pci_fngrp(zdev, rrb->response.pfgid);
} else {
zpci_err("Q PCI FN:\n");
zpci_err_clp(rrb->response.hdr.rsp, rc);
rc = -EIO;
}
out:
clp_free_block(rrb);
return rc;
}
/**
* clp_set_pci_fn() - Execute a command on a PCI function
* @zdev: Function that will be affected
* @fh: Out parameter for updated function handle
* @nr_dma_as: DMA address space number
* @command: The command code to execute
*
* Returns: 0 on success, < 0 for Linux errors (e.g. -ENOMEM), and
* > 0 for non-success platform responses
*/
static int clp_set_pci_fn(struct zpci_dev *zdev, u32 *fh, u8 nr_dma_as, u8 command)
{
struct clp_req_rsp_set_pci *rrb;
int rc, retries = 100;
u32 gisa = 0;
*fh = 0;
rrb = clp_alloc_block(GFP_KERNEL);
if (!rrb)
return -ENOMEM;
if (command != CLP_SET_DISABLE_PCI_FN)
gisa = zdev->gisa;
do {
memset(rrb, 0, sizeof(*rrb));
rrb->request.hdr.len = sizeof(rrb->request);
rrb->request.hdr.cmd = CLP_SET_PCI_FN;
rrb->response.hdr.len = sizeof(rrb->response);
rrb->request.fh = zdev->fh;
rrb->request.oc = command;
rrb->request.ndas = nr_dma_as;
rrb->request.gisa = gisa;
rc = clp_req(rrb, CLP_LPS_PCI);
if (rrb->response.hdr.rsp == CLP_RC_SETPCIFN_BUSY) {
retries--;
if (retries < 0)
break;
msleep(20);
}
} while (rrb->response.hdr.rsp == CLP_RC_SETPCIFN_BUSY);
if (!rc && rrb->response.hdr.rsp == CLP_RC_OK) {
*fh = rrb->response.fh;
} else {
zpci_err("Set PCI FN:\n");
zpci_err_clp(rrb->response.hdr.rsp, rc);
if (!rc)
rc = rrb->response.hdr.rsp;
}
clp_free_block(rrb);
return rc;
}
int clp_setup_writeback_mio(void)
{
struct clp_req_rsp_slpc_pci *rrb;
u8 wb_bit_pos;
int rc;
rrb = clp_alloc_block(GFP_KERNEL);
if (!rrb)
return -ENOMEM;
memset(rrb, 0, sizeof(*rrb));
rrb->request.hdr.len = sizeof(rrb->request);
rrb->request.hdr.cmd = CLP_SLPC;
rrb->response.hdr.len = sizeof(rrb->response);
rc = clp_req(rrb, CLP_LPS_PCI);
if (!rc && rrb->response.hdr.rsp == CLP_RC_OK) {
if (rrb->response.vwb) {
wb_bit_pos = rrb->response.mio_wb;
set_bit_inv(wb_bit_pos, &mio_wb_bit_mask);
zpci_dbg(3, "wb bit: %d\n", wb_bit_pos);
} else {
zpci_dbg(3, "wb bit: n.a.\n");
}
} else {
zpci_err("SLPC PCI:\n");
zpci_err_clp(rrb->response.hdr.rsp, rc);
rc = -EIO;
}
clp_free_block(rrb);
return rc;
}
int clp_enable_fh(struct zpci_dev *zdev, u32 *fh, u8 nr_dma_as)
{
int rc;
rc = clp_set_pci_fn(zdev, fh, nr_dma_as, CLP_SET_ENABLE_PCI_FN);
zpci_dbg(3, "ena fid:%x, fh:%x, rc:%d\n", zdev->fid, *fh, rc);
if (!rc && zpci_use_mio(zdev)) {
rc = clp_set_pci_fn(zdev, fh, nr_dma_as, CLP_SET_ENABLE_MIO);
zpci_dbg(3, "ena mio fid:%x, fh:%x, rc:%d\n",
zdev->fid, *fh, rc);
if (rc)
clp_disable_fh(zdev, fh);
}
return rc;
}
int clp_disable_fh(struct zpci_dev *zdev, u32 *fh)
{
int rc;
if (!zdev_enabled(zdev))
return 0;
rc = clp_set_pci_fn(zdev, fh, 0, CLP_SET_DISABLE_PCI_FN);
zpci_dbg(3, "dis fid:%x, fh:%x, rc:%d\n", zdev->fid, *fh, rc);
return rc;
}
static int clp_list_pci_req(struct clp_req_rsp_list_pci *rrb,
u64 *resume_token, int *nentries)
{
int rc;
memset(rrb, 0, sizeof(*rrb));
rrb->request.hdr.len = sizeof(rrb->request);
rrb->request.hdr.cmd = CLP_LIST_PCI;
/* store as many entries as possible */
rrb->response.hdr.len = CLP_BLK_SIZE - LIST_PCI_HDR_LEN;
rrb->request.resume_token = *resume_token;
/* Get PCI function handle list */
rc = clp_req(rrb, CLP_LPS_PCI);
if (rc || rrb->response.hdr.rsp != CLP_RC_OK) {
zpci_err("List PCI FN:\n");
zpci_err_clp(rrb->response.hdr.rsp, rc);
return -EIO;
}
update_uid_checking(rrb->response.uid_checking);
WARN_ON_ONCE(rrb->response.entry_size !=
sizeof(struct clp_fh_list_entry));
*nentries = (rrb->response.hdr.len - LIST_PCI_HDR_LEN) /
rrb->response.entry_size;
*resume_token = rrb->response.resume_token;
return rc;
}
static int clp_list_pci(struct clp_req_rsp_list_pci *rrb, void *data,
void (*cb)(struct clp_fh_list_entry *, void *))
{
u64 resume_token = 0;
int nentries, i, rc;
do {
rc = clp_list_pci_req(rrb, &resume_token, &nentries);
if (rc)
return rc;
for (i = 0; i < nentries; i++)
cb(&rrb->response.fh_list[i], data);
} while (resume_token);
return rc;
}
static int clp_find_pci(struct clp_req_rsp_list_pci *rrb, u32 fid,
struct clp_fh_list_entry *entry)
{
struct clp_fh_list_entry *fh_list;
u64 resume_token = 0;
int nentries, i, rc;
do {
rc = clp_list_pci_req(rrb, &resume_token, &nentries);
if (rc)
return rc;
fh_list = rrb->response.fh_list;
for (i = 0; i < nentries; i++) {
if (fh_list[i].fid == fid) {
*entry = fh_list[i];
return 0;
}
}
} while (resume_token);
return -ENODEV;
}
static void __clp_add(struct clp_fh_list_entry *entry, void *data)
{
struct list_head *scan_list = data;
struct zpci_dev *zdev;
if (!entry->vendor_id)
return;
zdev = get_zdev_by_fid(entry->fid);
if (zdev) {
zpci_zdev_put(zdev);
return;
}
zdev = zpci_create_device(entry->fid, entry->fh, entry->config_state);
if (IS_ERR(zdev))
return;
list_add_tail(&zdev->entry, scan_list);
}
int clp_scan_pci_devices(struct list_head *scan_list)
{
struct clp_req_rsp_list_pci *rrb;
int rc;
rrb = clp_alloc_block(GFP_KERNEL);
if (!rrb)
return -ENOMEM;
rc = clp_list_pci(rrb, scan_list, __clp_add);
clp_free_block(rrb);
return rc;
}
/*
* Get the current function handle of the function matching @fid
*/
int clp_refresh_fh(u32 fid, u32 *fh)
{
struct clp_req_rsp_list_pci *rrb;
struct clp_fh_list_entry entry;
int rc;
rrb = clp_alloc_block(GFP_NOWAIT);
if (!rrb)
return -ENOMEM;
rc = clp_find_pci(rrb, fid, &entry);
if (!rc)
*fh = entry.fh;
clp_free_block(rrb);
return rc;
}
int clp_get_state(u32 fid, enum zpci_state *state)
{
struct clp_req_rsp_list_pci *rrb;
struct clp_fh_list_entry entry;
int rc;
rrb = clp_alloc_block(GFP_ATOMIC);
if (!rrb)
return -ENOMEM;
rc = clp_find_pci(rrb, fid, &entry);
if (!rc) {
*state = entry.config_state;
} else if (rc == -ENODEV) {
*state = ZPCI_FN_STATE_RESERVED;
rc = 0;
}
clp_free_block(rrb);
return rc;
}
static int clp_base_slpc(struct clp_req *req, struct clp_req_rsp_slpc *lpcb)
{
unsigned long limit = PAGE_SIZE - sizeof(lpcb->request);
if (lpcb->request.hdr.len != sizeof(lpcb->request) ||
lpcb->response.hdr.len > limit)
return -EINVAL;
return clp_req(lpcb, CLP_LPS_BASE) ? -EOPNOTSUPP : 0;
}
static int clp_base_command(struct clp_req *req, struct clp_req_hdr *lpcb)
{
switch (lpcb->cmd) {
case 0x0001: /* store logical-processor characteristics */
return clp_base_slpc(req, (void *) lpcb);
default:
return -EINVAL;
}
}
static int clp_pci_slpc(struct clp_req *req, struct clp_req_rsp_slpc_pci *lpcb)
{
unsigned long limit = PAGE_SIZE - sizeof(lpcb->request);
if (lpcb->request.hdr.len != sizeof(lpcb->request) ||
lpcb->response.hdr.len > limit)
return -EINVAL;
return clp_req(lpcb, CLP_LPS_PCI) ? -EOPNOTSUPP : 0;
}
static int clp_pci_list(struct clp_req *req, struct clp_req_rsp_list_pci *lpcb)
{
unsigned long limit = PAGE_SIZE - sizeof(lpcb->request);
if (lpcb->request.hdr.len != sizeof(lpcb->request) ||
lpcb->response.hdr.len > limit)
return -EINVAL;
if (lpcb->request.reserved2 != 0)
return -EINVAL;
return clp_req(lpcb, CLP_LPS_PCI) ? -EOPNOTSUPP : 0;
}
static int clp_pci_query(struct clp_req *req,
struct clp_req_rsp_query_pci *lpcb)
{
unsigned long limit = PAGE_SIZE - sizeof(lpcb->request);
if (lpcb->request.hdr.len != sizeof(lpcb->request) ||
lpcb->response.hdr.len > limit)
return -EINVAL;
if (lpcb->request.reserved2 != 0 || lpcb->request.reserved3 != 0)
return -EINVAL;
return clp_req(lpcb, CLP_LPS_PCI) ? -EOPNOTSUPP : 0;
}
static int clp_pci_query_grp(struct clp_req *req,
struct clp_req_rsp_query_pci_grp *lpcb)
{
unsigned long limit = PAGE_SIZE - sizeof(lpcb->request);
if (lpcb->request.hdr.len != sizeof(lpcb->request) ||
lpcb->response.hdr.len > limit)
return -EINVAL;
if (lpcb->request.reserved2 != 0 || lpcb->request.reserved3 != 0 ||
lpcb->request.reserved4 != 0)
return -EINVAL;
return clp_req(lpcb, CLP_LPS_PCI) ? -EOPNOTSUPP : 0;
}
static int clp_pci_command(struct clp_req *req, struct clp_req_hdr *lpcb)
{
switch (lpcb->cmd) {
case 0x0001: /* store logical-processor characteristics */
return clp_pci_slpc(req, (void *) lpcb);
case 0x0002: /* list PCI functions */
return clp_pci_list(req, (void *) lpcb);
case 0x0003: /* query PCI function */
return clp_pci_query(req, (void *) lpcb);
case 0x0004: /* query PCI function group */
return clp_pci_query_grp(req, (void *) lpcb);
default:
return -EINVAL;
}
}
static int clp_normal_command(struct clp_req *req)
{
struct clp_req_hdr *lpcb;
void __user *uptr;
int rc;
rc = -EINVAL;
if (req->lps != 0 && req->lps != 2)
goto out;
rc = -ENOMEM;
lpcb = clp_alloc_block(GFP_KERNEL);
if (!lpcb)
goto out;
rc = -EFAULT;
uptr = (void __force __user *)(unsigned long) req->data_p;
if (copy_from_user(lpcb, uptr, PAGE_SIZE) != 0)
goto out_free;
rc = -EINVAL;
if (lpcb->fmt != 0 || lpcb->reserved1 != 0 || lpcb->reserved2 != 0)
goto out_free;
switch (req->lps) {
case 0:
rc = clp_base_command(req, lpcb);
break;
case 2:
rc = clp_pci_command(req, lpcb);
break;
}
if (rc)
goto out_free;
rc = -EFAULT;
if (copy_to_user(uptr, lpcb, PAGE_SIZE) != 0)
goto out_free;
rc = 0;
out_free:
clp_free_block(lpcb);
out:
return rc;
}
static int clp_immediate_command(struct clp_req *req)
{
void __user *uptr;
unsigned long ilp;
int exists;
if (req->cmd > 1 || clp_get_ilp(&ilp) != 0)
return -EINVAL;
uptr = (void __force __user *)(unsigned long) req->data_p;
if (req->cmd == 0) {
/* Command code 0: test for a specific processor */
exists = test_bit_inv(req->lps, &ilp);
return put_user(exists, (int __user *) uptr);
}
/* Command code 1: return bit mask of installed processors */
return put_user(ilp, (unsigned long __user *) uptr);
}
static long clp_misc_ioctl(struct file *filp, unsigned int cmd,
unsigned long arg)
{
struct clp_req req;
void __user *argp;
if (cmd != CLP_SYNC)
return -EINVAL;
argp = (void __user *)arg;
if (copy_from_user(&req, argp, sizeof(req)))
return -EFAULT;
if (req.r != 0)
return -EINVAL;
return req.c ? clp_immediate_command(&req) : clp_normal_command(&req);
}
static int clp_misc_release(struct inode *inode, struct file *filp)
{
return 0;
}
static const struct file_operations clp_misc_fops = {
.owner = THIS_MODULE,
.open = nonseekable_open,
.release = clp_misc_release,
.unlocked_ioctl = clp_misc_ioctl,
};
static struct miscdevice clp_misc_device = {
.minor = MISC_DYNAMIC_MINOR,
.name = "clp",
.fops = &clp_misc_fops,
};
builtin_misc_device(clp_misc_device);
+221
View File
@@ -0,0 +1,221 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright IBM Corp. 2012,2015
*
* Author(s):
* Jan Glauber <jang@linux.vnet.ibm.com>
*/
#define pr_fmt(fmt) "zpci: " fmt
#include <linux/kernel.h>
#include <linux/seq_file.h>
#include <linux/debugfs.h>
#include <linux/export.h>
#include <linux/pci.h>
#include <asm/debug.h>
#include <asm/pci_dma.h>
static struct dentry *debugfs_root;
debug_info_t *pci_debug_msg_id;
EXPORT_SYMBOL_GPL(pci_debug_msg_id);
debug_info_t *pci_debug_err_id;
EXPORT_SYMBOL_GPL(pci_debug_err_id);
static char *pci_common_names[] = {
"Load operations",
"Store operations",
"Store block operations",
"Refresh operations",
};
static char *pci_fmt0_names[] = {
"DMA read bytes",
"DMA write bytes",
};
static char *pci_fmt1_names[] = {
"Received bytes",
"Received packets",
"Transmitted bytes",
"Transmitted packets",
};
static char *pci_fmt2_names[] = {
"Consumed work units",
"Maximum work units",
};
static char *pci_fmt3_names[] = {
"Transmitted bytes",
};
static char *pci_sw_names[] = {
"Mapped pages",
"Unmapped pages",
"Global RPCITs",
"Sync Map RPCITs",
"Sync RPCITs",
};
static void pci_fmb_show(struct seq_file *m, char *name[], int length,
u64 *data)
{
int i;
for (i = 0; i < length; i++, data++)
seq_printf(m, "%26s:\t%llu\n", name[i], *data);
}
static void pci_sw_counter_show(struct seq_file *m)
{
struct zpci_dev *zdev = m->private;
struct zpci_iommu_ctrs *ctrs;
atomic64_t *counter;
unsigned long flags;
int i;
spin_lock_irqsave(&zdev->dom_lock, flags);
ctrs = zpci_get_iommu_ctrs(m->private);
if (!ctrs)
goto unlock;
counter = &ctrs->mapped_pages;
for (i = 0; i < ARRAY_SIZE(pci_sw_names); i++, counter++)
seq_printf(m, "%26s:\t%llu\n", pci_sw_names[i],
atomic64_read(counter));
unlock:
spin_unlock_irqrestore(&zdev->dom_lock, flags);
}
static int pci_perf_show(struct seq_file *m, void *v)
{
struct zpci_dev *zdev = m->private;
if (!zdev)
return 0;
mutex_lock(&zdev->fmb_lock);
if (!zdev->fmb) {
mutex_unlock(&zdev->fmb_lock);
seq_puts(m, "FMB statistics disabled\n");
return 0;
}
/* header */
seq_printf(m, "Update interval: %u ms\n", zdev->fmb_update);
seq_printf(m, "Samples: %u\n", zdev->fmb->samples);
seq_printf(m, "Last update TOD: %Lx\n", zdev->fmb->last_update);
pci_fmb_show(m, pci_common_names, ARRAY_SIZE(pci_common_names),
&zdev->fmb->ld_ops);
switch (zdev->fmb->format) {
case 0:
if (!(zdev->fmb->fmt_ind & ZPCI_FMB_DMA_COUNTER_VALID))
break;
pci_fmb_show(m, pci_fmt0_names, ARRAY_SIZE(pci_fmt0_names),
&zdev->fmb->fmt0.dma_rbytes);
break;
case 1:
pci_fmb_show(m, pci_fmt1_names, ARRAY_SIZE(pci_fmt1_names),
&zdev->fmb->fmt1.rx_bytes);
break;
case 2:
pci_fmb_show(m, pci_fmt2_names, ARRAY_SIZE(pci_fmt2_names),
&zdev->fmb->fmt2.consumed_work_units);
break;
case 3:
pci_fmb_show(m, pci_fmt3_names, ARRAY_SIZE(pci_fmt3_names),
&zdev->fmb->fmt3.tx_bytes);
break;
default:
seq_puts(m, "Unknown format\n");
}
pci_sw_counter_show(m);
mutex_unlock(&zdev->fmb_lock);
return 0;
}
static ssize_t pci_perf_seq_write(struct file *file, const char __user *ubuf,
size_t count, loff_t *off)
{
struct zpci_dev *zdev = ((struct seq_file *) file->private_data)->private;
unsigned long val;
int rc;
if (!zdev)
return 0;
rc = kstrtoul_from_user(ubuf, count, 10, &val);
if (rc)
return rc;
mutex_lock(&zdev->fmb_lock);
switch (val) {
case 0:
rc = zpci_fmb_disable_device(zdev);
break;
case 1:
rc = zpci_fmb_enable_device(zdev);
break;
}
mutex_unlock(&zdev->fmb_lock);
return rc ? rc : count;
}
static int pci_perf_seq_open(struct inode *inode, struct file *filp)
{
return single_open(filp, pci_perf_show,
file_inode(filp)->i_private);
}
static const struct file_operations debugfs_pci_perf_fops = {
.open = pci_perf_seq_open,
.read = seq_read,
.write = pci_perf_seq_write,
.llseek = seq_lseek,
.release = single_release,
};
void zpci_debug_init_device(struct zpci_dev *zdev, const char *name)
{
zdev->debugfs_dev = debugfs_create_dir(name, debugfs_root);
debugfs_create_file("statistics", S_IFREG | S_IRUGO | S_IWUSR,
zdev->debugfs_dev, zdev, &debugfs_pci_perf_fops);
}
void zpci_debug_exit_device(struct zpci_dev *zdev)
{
debugfs_remove_recursive(zdev->debugfs_dev);
}
int __init zpci_debug_init(void)
{
/* event trace buffer */
pci_debug_msg_id = debug_register("pci_msg", 8, 1, 8 * sizeof(long));
if (!pci_debug_msg_id)
return -EINVAL;
debug_register_view(pci_debug_msg_id, &debug_sprintf_view);
debug_set_level(pci_debug_msg_id, 3);
/* error log */
pci_debug_err_id = debug_register("pci_error", 2, 1, 16);
if (!pci_debug_err_id)
return -EINVAL;
debug_register_view(pci_debug_err_id, &debug_hex_ascii_view);
debug_set_level(pci_debug_err_id, 3);
debugfs_root = debugfs_create_dir("pci", NULL);
return 0;
}
void zpci_debug_exit(void)
{
debug_unregister(pci_debug_msg_id);
debug_unregister(pci_debug_err_id);
debugfs_remove(debugfs_root);
}
+479
View File
@@ -0,0 +1,479 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright IBM Corp. 2012
*
* Author(s):
* Jan Glauber <jang@linux.vnet.ibm.com>
*/
#define pr_fmt(fmt) "zpci: " fmt
#include <linux/kernel.h>
#include <linux/pci.h>
#include <asm/pci_debug.h>
#include <asm/pci_dma.h>
#include <asm/sclp.h>
#include "pci_bus.h"
#include "pci_report.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 inline bool ers_result_indicates_abort(pci_ers_result_t ers_res)
{
switch (ers_res) {
case PCI_ERS_RESULT_CAN_RECOVER:
case PCI_ERS_RESULT_RECOVERED:
case PCI_ERS_RESULT_NEED_RESET:
case PCI_ERS_RESULT_NONE:
return false;
default:
return true;
}
}
static bool is_passed_through(struct pci_dev *pdev)
{
struct zpci_dev *zdev = to_zpci(pdev);
bool ret;
mutex_lock(&zdev->kzdev_lock);
ret = !!zdev->kzdev;
mutex_unlock(&zdev->kzdev_lock);
return ret;
}
static bool is_driver_supported(struct pci_driver *driver)
{
if (!driver || !driver->err_handler)
return false;
if (!driver->err_handler->error_detected)
return false;
return true;
}
static pci_ers_result_t zpci_event_notify_error_detected(struct pci_dev *pdev,
struct pci_driver *driver)
{
pci_ers_result_t ers_res = PCI_ERS_RESULT_DISCONNECT;
ers_res = driver->err_handler->error_detected(pdev, pdev->error_state);
pci_uevent_ers(pdev, ers_res);
if (ers_result_indicates_abort(ers_res))
pr_info("%s: Automatic recovery failed after initial reporting\n", pci_name(pdev));
else if (ers_res == PCI_ERS_RESULT_NEED_RESET)
pr_debug("%s: Driver needs reset to recover\n", pci_name(pdev));
return ers_res;
}
static pci_ers_result_t zpci_event_do_error_state_clear(struct pci_dev *pdev,
struct pci_driver *driver)
{
pci_ers_result_t ers_res = PCI_ERS_RESULT_DISCONNECT;
struct zpci_dev *zdev = to_zpci(pdev);
int rc;
/* The underlying device may have been disabled by the event */
if (!zdev_enabled(zdev))
return PCI_ERS_RESULT_NEED_RESET;
pr_info("%s: Unblocking device access for examination\n", pci_name(pdev));
rc = zpci_reset_load_store_blocked(zdev);
if (rc) {
pr_err("%s: Unblocking device access failed\n", pci_name(pdev));
/* Let's try a full reset instead */
return PCI_ERS_RESULT_NEED_RESET;
}
if (driver->err_handler->mmio_enabled)
ers_res = driver->err_handler->mmio_enabled(pdev);
else
ers_res = PCI_ERS_RESULT_NONE;
if (ers_result_indicates_abort(ers_res)) {
pr_info("%s: Automatic recovery failed after MMIO re-enable\n",
pci_name(pdev));
return ers_res;
} else if (ers_res == PCI_ERS_RESULT_NEED_RESET) {
pr_debug("%s: Driver needs reset to recover\n", pci_name(pdev));
return ers_res;
}
pr_debug("%s: Unblocking DMA\n", pci_name(pdev));
rc = zpci_clear_error_state(zdev);
if (!rc) {
pdev->error_state = pci_channel_io_normal;
} else {
pr_err("%s: Unblocking DMA failed\n", pci_name(pdev));
/* Let's try a full reset instead */
return PCI_ERS_RESULT_NEED_RESET;
}
return ers_res;
}
static pci_ers_result_t zpci_event_do_reset(struct pci_dev *pdev,
struct pci_driver *driver)
{
pci_ers_result_t ers_res = PCI_ERS_RESULT_DISCONNECT;
pr_info("%s: Initiating reset\n", pci_name(pdev));
if (zpci_hot_reset_device(to_zpci(pdev))) {
pr_err("%s: The reset request failed\n", pci_name(pdev));
return ers_res;
}
pdev->error_state = pci_channel_io_normal;
if (driver->err_handler->slot_reset)
ers_res = driver->err_handler->slot_reset(pdev);
else
ers_res = PCI_ERS_RESULT_NONE;
if (ers_result_indicates_abort(ers_res)) {
pr_info("%s: Automatic recovery failed after slot reset\n", pci_name(pdev));
return ers_res;
}
return ers_res;
}
/* zpci_event_attempt_error_recovery - Try to recover the given PCI function
* @pdev: PCI function to recover currently in the error state
*
* We follow the scheme outlined in Documentation/PCI/pci-error-recovery.rst.
* With the simplification that recovery always happens per function
* and the platform determines which functions are affected for
* multi-function devices.
*/
static pci_ers_result_t zpci_event_attempt_error_recovery(struct pci_dev *pdev)
{
pci_ers_result_t ers_res = PCI_ERS_RESULT_DISCONNECT;
struct zpci_dev *zdev = to_zpci(pdev);
char *status_str = "success";
struct pci_driver *driver;
/*
* Ensure that the PCI function is not removed concurrently, no driver
* is unbound or probed and that userspace can't access its
* configuration space while we perform recovery.
*/
device_lock(&pdev->dev);
if (pdev->error_state == pci_channel_io_perm_failure) {
ers_res = PCI_ERS_RESULT_DISCONNECT;
goto out_unlock;
}
pdev->error_state = pci_channel_io_frozen;
if (is_passed_through(pdev)) {
pr_info("%s: Cannot be recovered in the host because it is a pass-through device\n",
pci_name(pdev));
status_str = "failed (pass-through)";
goto out_unlock;
}
driver = to_pci_driver(pdev->dev.driver);
if (!is_driver_supported(driver)) {
if (!driver) {
pr_info("%s: Cannot be recovered because no driver is bound to the device\n",
pci_name(pdev));
status_str = "failed (no driver)";
} else {
pr_info("%s: The %s driver bound to the device does not support error recovery\n",
pci_name(pdev),
driver->name);
status_str = "failed (no driver support)";
}
goto out_unlock;
}
ers_res = zpci_event_notify_error_detected(pdev, driver);
if (ers_result_indicates_abort(ers_res)) {
status_str = "failed (abort on detection)";
goto out_unlock;
}
if (ers_res != PCI_ERS_RESULT_NEED_RESET) {
ers_res = zpci_event_do_error_state_clear(pdev, driver);
if (ers_result_indicates_abort(ers_res)) {
status_str = "failed (abort on MMIO enable)";
goto out_unlock;
}
}
if (ers_res == PCI_ERS_RESULT_NEED_RESET)
ers_res = zpci_event_do_reset(pdev, driver);
/*
* ers_res can be PCI_ERS_RESULT_NONE either because the driver
* decided to return it, indicating that it abstains from voting
* on how to recover, or because it didn't implement the callback.
* Both cases assume, that if there is nothing else causing a
* disconnect, we recovered successfully.
*/
if (ers_res == PCI_ERS_RESULT_NONE)
ers_res = PCI_ERS_RESULT_RECOVERED;
if (ers_res != PCI_ERS_RESULT_RECOVERED) {
pci_uevent_ers(pdev, PCI_ERS_RESULT_DISCONNECT);
pr_err("%s: Automatic recovery failed; operator intervention is required\n",
pci_name(pdev));
status_str = "failed (driver can't recover)";
goto out_unlock;
}
pr_info("%s: The device is ready to resume operations\n", pci_name(pdev));
if (driver->err_handler->resume)
driver->err_handler->resume(pdev);
pci_uevent_ers(pdev, PCI_ERS_RESULT_RECOVERED);
out_unlock:
device_unlock(&pdev->dev);
zpci_report_status(zdev, "recovery", status_str);
return ers_res;
}
/* zpci_event_io_failure - Report PCI channel failure state to driver
* @pdev: PCI function for which to report
* @es: PCI channel failure state to report
*/
static void zpci_event_io_failure(struct pci_dev *pdev, pci_channel_state_t es)
{
struct pci_driver *driver;
pci_dev_lock(pdev);
pdev->error_state = es;
/**
* While vfio-pci's error_detected callback notifies user-space QEMU
* reacts to this by freezing the guest. In an s390 environment PCI
* errors are rarely fatal so this is overkill. Instead in the future
* we will inject the error event and let the guest recover the device
* itself.
*/
if (is_passed_through(pdev))
goto out;
driver = to_pci_driver(pdev->dev.driver);
if (driver && driver->err_handler && driver->err_handler->error_detected)
driver->err_handler->error_detected(pdev, pdev->error_state);
out:
pci_dev_unlock(pdev);
}
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;
pci_ers_result_t ers_res;
u32 fh = 0;
int rc;
zpci_dbg(3, "err fid:%x, fh:%x, pec:%x\n",
ccdf->fid, ccdf->fh, ccdf->pec);
zpci_err("error CCDF:\n");
zpci_err_hex(ccdf, sizeof(*ccdf));
if (zdev) {
mutex_lock(&zdev->state_lock);
rc = clp_refresh_fh(zdev->fid, &fh);
if (rc)
goto no_pdev;
if (!fh || ccdf->fh != fh) {
/* Ignore events with stale handles */
zpci_dbg(3, "err fid:%x, fh:%x (stale %x)\n",
ccdf->fid, fh, ccdf->fh);
goto no_pdev;
}
zpci_update_fh(zdev, ccdf->fh);
if (zdev->zbus->bus)
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;
switch (ccdf->pec) {
case 0x002a: /* Error event concerns FMB */
case 0x002b:
case 0x002c:
break;
case 0x0040: /* Service Action or Error Recovery Failed */
case 0x003b:
zpci_event_io_failure(pdev, pci_channel_io_perm_failure);
break;
default: /* PCI function left in the error state attempt to recover */
ers_res = zpci_event_attempt_error_recovery(pdev);
if (ers_res != PCI_ERS_RESULT_RECOVERED)
zpci_event_io_failure(pdev, pci_channel_io_perm_failure);
break;
}
pci_dev_put(pdev);
no_pdev:
if (zdev)
mutex_unlock(&zdev->state_lock);
zpci_zdev_put(zdev);
}
void zpci_event_error(void *data)
{
if (zpci_is_enabled())
__zpci_event_error(data);
}
static void zpci_event_hard_deconfigured(struct zpci_dev *zdev, u32 fh)
{
zpci_update_fh(zdev, fh);
/* Give the driver a hint that the function is
* already unusable.
*/
zpci_bus_remove_device(zdev, true);
/* Even though the device is already gone we still
* need to free zPCI resources as part of the disable.
*/
if (zdev_enabled(zdev))
zpci_disable_device(zdev);
zdev->state = ZPCI_FN_STATE_STANDBY;
}
static void zpci_event_reappear(struct zpci_dev *zdev)
{
lockdep_assert_held(&zdev->state_lock);
/*
* The zdev is in the reserved state. This means that it was presumed to
* go away but there are still undropped references. Now, the platform
* announced its availability again. Bring back the lingering zdev
* to standby. This is safe because we hold a temporary reference
* now so that it won't go away. Account for the re-appearance of the
* underlying device by incrementing the reference count.
*/
zdev->state = ZPCI_FN_STATE_STANDBY;
zpci_zdev_get(zdev);
zpci_dbg(1, "rea fid:%x, fh:%x\n", zdev->fid, zdev->fh);
}
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;
zpci_dbg(3, "avl fid:%x, fh:%x, pec:%x\n",
ccdf->fid, ccdf->fh, ccdf->pec);
if (existing_zdev)
mutex_lock(&zdev->state_lock);
switch (ccdf->pec) {
case 0x0301: /* Reserved|Standby -> Configured */
if (!zdev) {
zdev = zpci_create_device(ccdf->fid, ccdf->fh, ZPCI_FN_STATE_CONFIGURED);
if (IS_ERR(zdev))
break;
if (zpci_add_device(zdev)) {
kfree(zdev);
break;
}
} else {
if (zdev->state == ZPCI_FN_STATE_RESERVED)
zpci_event_reappear(zdev);
/* the configuration request may be stale */
else if (zdev->state != ZPCI_FN_STATE_STANDBY)
break;
zdev->state = ZPCI_FN_STATE_CONFIGURED;
}
zpci_scan_configured_device(zdev, ccdf->fh);
break;
case 0x0302: /* Reserved -> Standby */
if (!zdev) {
zdev = zpci_create_device(ccdf->fid, ccdf->fh, ZPCI_FN_STATE_STANDBY);
if (IS_ERR(zdev))
break;
if (zpci_add_device(zdev)) {
kfree(zdev);
break;
}
} else {
if (zdev->state == ZPCI_FN_STATE_RESERVED)
zpci_event_reappear(zdev);
zpci_update_fh(zdev, ccdf->fh);
}
break;
case 0x0303: /* Deconfiguration requested */
if (zdev) {
/* The event may have been queued before we configured
* the device.
*/
if (zdev->state != ZPCI_FN_STATE_CONFIGURED)
break;
zpci_update_fh(zdev, ccdf->fh);
zpci_deconfigure_device(zdev);
}
break;
case 0x0304: /* Configured -> Standby|Reserved */
if (zdev) {
/* The event may have been queued before we configured
* the device.:
*/
if (zdev->state == ZPCI_FN_STATE_CONFIGURED)
zpci_event_hard_deconfigured(zdev, ccdf->fh);
/* The 0x0304 event may immediately reserve the device */
if (!clp_get_state(zdev->fid, &state) &&
state == ZPCI_FN_STATE_RESERVED) {
zpci_device_reserved(zdev);
}
}
break;
case 0x0306: /* 0x308 or 0x302 for multiple devices */
zpci_remove_reserved_devices();
zpci_scan_devices();
break;
case 0x0308: /* Standby -> Reserved */
if (!zdev)
break;
zpci_device_reserved(zdev);
break;
default:
break;
}
if (existing_zdev) {
mutex_unlock(&zdev->state_lock);
zpci_zdev_put(zdev);
}
}
void zpci_event_availability(void *data)
{
if (zpci_is_enabled())
__zpci_event_availability(data);
}
+23
View File
@@ -0,0 +1,23 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Exceptions for specific devices,
*
* Copyright IBM Corp. 2025
*
* Author(s):
* Niklas Schnelle <schnelle@linux.ibm.com>
*/
#include <linux/pci.h>
static void zpci_ism_bar_no_mmap(struct pci_dev *pdev)
{
/*
* ISM's BAR is special. Drivers written for ISM know
* how to handle this but others need to be aware of their
* special nature e.g. to prevent attempts to mmap() it.
*/
pdev->non_mappable_bars = 1;
}
DECLARE_PCI_FIXUP_EARLY(PCI_VENDOR_ID_IBM,
PCI_DEVICE_ID_IBM_ISM,
zpci_ism_bar_no_mmap);
+453
View File
@@ -0,0 +1,453 @@
// SPDX-License-Identifier: GPL-2.0
/*
* s390 specific pci instructions
*
* Copyright IBM Corp. 2013
*/
#include <linux/export.h>
#include <linux/errno.h>
#include <linux/delay.h>
#include <linux/jump_label.h>
#include <asm/asm-extable.h>
#include <asm/facility.h>
#include <asm/pci_insn.h>
#include <asm/pci_debug.h>
#include <asm/pci_io.h>
#include <asm/processor.h>
#include <asm/asm.h>
#define ZPCI_INSN_BUSY_DELAY 1 /* 1 microsecond */
struct zpci_err_insn_data {
u8 insn;
u8 cc;
u8 status;
union {
struct {
u64 req;
u64 offset;
};
struct {
u64 addr;
u64 len;
};
};
} __packed;
static inline void zpci_err_insn_req(int lvl, u8 insn, u8 cc, u8 status,
u64 req, u64 offset)
{
struct zpci_err_insn_data data = {
.insn = insn, .cc = cc, .status = status,
.req = req, .offset = offset};
zpci_err_hex_level(lvl, &data, sizeof(data));
}
static inline void zpci_err_insn_addr(int lvl, u8 insn, u8 cc, u8 status,
u64 addr, u64 len)
{
struct zpci_err_insn_data data = {
.insn = insn, .cc = cc, .status = status,
.addr = addr, .len = len};
zpci_err_hex_level(lvl, &data, sizeof(data));
}
/* Modify PCI Function Controls */
static inline u8 __mpcifc(u64 req, struct zpci_fib *fib, u8 *status)
{
int cc;
asm volatile (
" .insn rxy,0xe300000000d0,%[req],%[fib]\n"
CC_IPM(cc)
: CC_OUT(cc, cc), [req] "+d" (req), [fib] "+Q" (*fib)
:
: CC_CLOBBER);
*status = req >> 24 & 0xff;
return CC_TRANSFORM(cc);
}
u8 zpci_mod_fc(u64 req, struct zpci_fib *fib, u8 *status)
{
bool retried = false;
u8 cc;
do {
cc = __mpcifc(req, fib, status);
if (cc == 2) {
msleep(ZPCI_INSN_BUSY_DELAY);
if (!retried) {
zpci_err_insn_req(1, 'M', cc, *status, req, 0);
retried = true;
}
}
} while (cc == 2);
if (cc)
zpci_err_insn_req(0, 'M', cc, *status, req, 0);
else if (retried)
zpci_err_insn_req(1, 'M', cc, *status, req, 0);
return cc;
}
EXPORT_SYMBOL_GPL(zpci_mod_fc);
/* Refresh PCI Translations */
static inline u8 __rpcit(u64 fn, u64 addr, u64 range, u8 *status)
{
union register_pair addr_range = {.even = addr, .odd = range};
int cc;
asm volatile (
" .insn rre,0xb9d30000,%[fn],%[addr_range]\n"
CC_IPM(cc)
: CC_OUT(cc, cc), [fn] "+d" (fn)
: [addr_range] "d" (addr_range.pair)
: CC_CLOBBER);
*status = fn >> 24 & 0xff;
return CC_TRANSFORM(cc);
}
int zpci_refresh_trans(u64 fn, u64 addr, u64 range)
{
bool retried = false;
u8 cc, status;
do {
cc = __rpcit(fn, addr, range, &status);
if (cc == 2) {
udelay(ZPCI_INSN_BUSY_DELAY);
if (!retried) {
zpci_err_insn_addr(1, 'R', cc, status, addr, range);
retried = true;
}
}
} while (cc == 2);
if (cc)
zpci_err_insn_addr(0, 'R', cc, status, addr, range);
else if (retried)
zpci_err_insn_addr(1, 'R', cc, status, addr, range);
if (cc == 1 && (status == 4 || status == 16))
return -ENOMEM;
return (cc) ? -EIO : 0;
}
/* Set Interruption Controls */
int zpci_set_irq_ctrl(u16 ctl, u8 isc, union zpci_sic_iib *iib)
{
if (!test_facility(72))
return -EIO;
asm volatile(
".insn rsy,0xeb00000000d1,%[ctl],%[isc],%[iib]"
: : [ctl] "d" (ctl), [isc] "d" (isc << 27), [iib] "Q" (*iib));
return 0;
}
EXPORT_SYMBOL_GPL(zpci_set_irq_ctrl);
/* PCI Load */
static inline int ____pcilg(u64 *data, u64 req, u64 offset, u8 *status)
{
union register_pair req_off = {.even = req, .odd = offset};
int cc, exception;
u64 __data;
exception = 1;
asm_inline volatile (
" .insn rre,0xb9d20000,%[data],%[req_off]\n"
"0: lhi %[exc],0\n"
"1:\n"
CC_IPM(cc)
EX_TABLE(0b, 1b)
: CC_OUT(cc, cc), [data] "=d" (__data),
[req_off] "+d" (req_off.pair), [exc] "+d" (exception)
:
: CC_CLOBBER);
*status = req_off.even >> 24 & 0xff;
*data = __data;
return exception ? -ENXIO : CC_TRANSFORM(cc);
}
static inline int __pcilg(u64 *data, u64 req, u64 offset, u8 *status)
{
u64 __data;
int cc;
cc = ____pcilg(&__data, req, offset, status);
if (!cc)
*data = __data;
return cc;
}
int __zpci_load(u64 *data, u64 req, u64 offset)
{
bool retried = false;
u8 status;
int cc;
do {
cc = __pcilg(data, req, offset, &status);
if (cc == 2) {
udelay(ZPCI_INSN_BUSY_DELAY);
if (!retried) {
zpci_err_insn_req(1, 'l', cc, status, req, offset);
retried = true;
}
}
} while (cc == 2);
if (cc)
zpci_err_insn_req(0, 'l', cc, status, req, offset);
else if (retried)
zpci_err_insn_req(1, 'l', cc, status, req, offset);
return (cc > 0) ? -EIO : cc;
}
EXPORT_SYMBOL_GPL(__zpci_load);
static inline int zpci_load_fh(u64 *data, const volatile void __iomem *addr,
unsigned long len)
{
struct zpci_iomap_entry *entry = &zpci_iomap_start[ZPCI_IDX(addr)];
u64 req = ZPCI_CREATE_REQ(READ_ONCE(entry->fh), entry->bar, len);
return __zpci_load(data, req, ZPCI_OFFSET(addr));
}
static inline int __pcilg_mio(u64 *data, u64 ioaddr, u64 len, u8 *status)
{
union register_pair ioaddr_len = {.even = ioaddr, .odd = len};
int cc, exception;
u64 __data;
exception = 1;
asm_inline volatile (
" .insn rre,0xb9d60000,%[data],%[ioaddr_len]\n"
"0: lhi %[exc],0\n"
"1:\n"
CC_IPM(cc)
EX_TABLE(0b, 1b)
: CC_OUT(cc, cc), [data] "=d" (__data),
[ioaddr_len] "+d" (ioaddr_len.pair), [exc] "+d" (exception)
:
: CC_CLOBBER);
*status = ioaddr_len.odd >> 24 & 0xff;
*data = __data;
return exception ? -ENXIO : CC_TRANSFORM(cc);
}
int zpci_load(u64 *data, const volatile void __iomem *addr, unsigned long len)
{
u8 status;
int cc;
if (!static_branch_unlikely(&have_mio))
return zpci_load_fh(data, addr, len);
cc = __pcilg_mio(data, (__force u64) addr, len, &status);
if (cc)
zpci_err_insn_addr(0, 'L', cc, status, (__force u64) addr, len);
return (cc > 0) ? -EIO : cc;
}
EXPORT_SYMBOL_GPL(zpci_load);
/* PCI Store */
static inline int __pcistg(u64 data, u64 req, u64 offset, u8 *status)
{
union register_pair req_off = {.even = req, .odd = offset};
int cc, exception;
exception = 1;
asm_inline volatile (
" .insn rre,0xb9d00000,%[data],%[req_off]\n"
"0: lhi %[exc],0\n"
"1:\n"
CC_IPM(cc)
EX_TABLE(0b, 1b)
: CC_OUT(cc, cc), [req_off] "+d" (req_off.pair), [exc] "+d" (exception)
: [data] "d" (data)
: CC_CLOBBER);
*status = req_off.even >> 24 & 0xff;
return exception ? -ENXIO : CC_TRANSFORM(cc);
}
int __zpci_store(u64 data, u64 req, u64 offset)
{
bool retried = false;
u8 status;
int cc;
do {
cc = __pcistg(data, req, offset, &status);
if (cc == 2) {
udelay(ZPCI_INSN_BUSY_DELAY);
if (!retried) {
zpci_err_insn_req(1, 's', cc, status, req, offset);
retried = true;
}
}
} while (cc == 2);
if (cc)
zpci_err_insn_req(0, 's', cc, status, req, offset);
else if (retried)
zpci_err_insn_req(1, 's', cc, status, req, offset);
return (cc > 0) ? -EIO : cc;
}
EXPORT_SYMBOL_GPL(__zpci_store);
static inline int zpci_store_fh(const volatile void __iomem *addr, u64 data,
unsigned long len)
{
struct zpci_iomap_entry *entry = &zpci_iomap_start[ZPCI_IDX(addr)];
u64 req = ZPCI_CREATE_REQ(READ_ONCE(entry->fh), entry->bar, len);
return __zpci_store(data, req, ZPCI_OFFSET(addr));
}
static inline int __pcistg_mio(u64 data, u64 ioaddr, u64 len, u8 *status)
{
union register_pair ioaddr_len = {.even = ioaddr, .odd = len};
int cc, exception;
exception = 1;
asm_inline volatile (
" .insn rre,0xb9d40000,%[data],%[ioaddr_len]\n"
"0: lhi %[exc],0\n"
"1:\n"
CC_IPM(cc)
EX_TABLE(0b, 1b)
: CC_OUT(cc, cc), [ioaddr_len] "+d" (ioaddr_len.pair), [exc] "+d" (exception)
: [data] "d" (data)
: CC_CLOBBER_LIST("memory"));
*status = ioaddr_len.odd >> 24 & 0xff;
return exception ? -ENXIO : CC_TRANSFORM(cc);
}
int zpci_store(const volatile void __iomem *addr, u64 data, unsigned long len)
{
u8 status;
int cc;
if (!static_branch_unlikely(&have_mio))
return zpci_store_fh(addr, data, len);
cc = __pcistg_mio(data, (__force u64) addr, len, &status);
if (cc)
zpci_err_insn_addr(0, 'S', cc, status, (__force u64) addr, len);
return (cc > 0) ? -EIO : cc;
}
EXPORT_SYMBOL_GPL(zpci_store);
/* PCI Store Block */
static inline int __pcistb(const u64 *data, u64 req, u64 offset, u8 *status)
{
int cc, exception;
exception = 1;
asm_inline volatile (
" .insn rsy,0xeb00000000d0,%[req],%[offset],%[data]\n"
"0: lhi %[exc],0\n"
"1:\n"
CC_IPM(cc)
EX_TABLE(0b, 1b)
: CC_OUT(cc, cc), [req] "+d" (req), [exc] "+d" (exception)
: [offset] "d" (offset), [data] "Q" (*data)
: CC_CLOBBER);
*status = req >> 24 & 0xff;
return exception ? -ENXIO : CC_TRANSFORM(cc);
}
int __zpci_store_block(const u64 *data, u64 req, u64 offset)
{
bool retried = false;
u8 status;
int cc;
do {
cc = __pcistb(data, req, offset, &status);
if (cc == 2) {
udelay(ZPCI_INSN_BUSY_DELAY);
if (!retried) {
zpci_err_insn_req(0, 'b', cc, status, req, offset);
retried = true;
}
}
} while (cc == 2);
if (cc)
zpci_err_insn_req(0, 'b', cc, status, req, offset);
else if (retried)
zpci_err_insn_req(1, 'b', cc, status, req, offset);
return (cc > 0) ? -EIO : cc;
}
EXPORT_SYMBOL_GPL(__zpci_store_block);
static inline int zpci_write_block_fh(volatile void __iomem *dst,
const void *src, unsigned long len)
{
struct zpci_iomap_entry *entry = &zpci_iomap_start[ZPCI_IDX(dst)];
u64 req = ZPCI_CREATE_REQ(entry->fh, entry->bar, len);
u64 offset = ZPCI_OFFSET(dst);
return __zpci_store_block(src, req, offset);
}
static inline int __pcistb_mio(const u64 *data, u64 ioaddr, u64 len, u8 *status)
{
int cc, exception;
exception = 1;
asm_inline volatile (
" .insn rsy,0xeb00000000d4,%[len],%[ioaddr],%[data]\n"
"0: lhi %[exc],0\n"
"1:\n"
CC_IPM(cc)
EX_TABLE(0b, 1b)
: CC_OUT(cc, cc), [len] "+d" (len), [exc] "+d" (exception)
: [ioaddr] "d" (ioaddr), [data] "Q" (*data)
: CC_CLOBBER);
*status = len >> 24 & 0xff;
return exception ? -ENXIO : CC_TRANSFORM(cc);
}
int zpci_write_block(volatile void __iomem *dst,
const void *src, unsigned long len)
{
u8 status;
int cc;
if (!static_branch_unlikely(&have_mio))
return zpci_write_block_fh(dst, src, len);
cc = __pcistb_mio(src, (__force u64) dst, len, &status);
if (cc)
zpci_err_insn_addr(0, 'B', cc, status, (__force u64) dst, len);
return (cc > 0) ? -EIO : cc;
}
EXPORT_SYMBOL_GPL(zpci_write_block);
static inline void __pciwb_mio(void)
{
asm volatile (".insn rre,0xb9d50000,0,0");
}
void zpci_barrier(void)
{
if (static_branch_likely(&have_mio))
__pciwb_mio();
}
EXPORT_SYMBOL_GPL(zpci_barrier);
+126
View File
@@ -0,0 +1,126 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright IBM Corp. 2020
*
* Author(s):
* Niklas Schnelle <schnelle@linux.ibm.com>
*
*/
#define pr_fmt(fmt) "zpci: " fmt
#include <linux/kernel.h>
#include <linux/pci.h>
#include "pci_iov.h"
static struct resource iov_res = {
.name = "PCI IOV res",
.start = 0,
.end = -1,
.flags = IORESOURCE_MEM,
};
void zpci_iov_map_resources(struct pci_dev *pdev)
{
resource_size_t len;
int i;
for (i = 0; i < PCI_SRIOV_NUM_BARS; i++) {
int bar = i + PCI_IOV_RESOURCES;
len = pci_resource_len(pdev, bar);
if (!len)
continue;
pdev->resource[bar].parent = &iov_res;
}
}
void zpci_iov_remove_virtfn(struct pci_dev *pdev, int vfn)
{
pci_lock_rescan_remove();
/* Linux' vfid's start at 0 vfn at 1 */
pci_iov_remove_virtfn(pdev->physfn, vfn - 1);
pci_unlock_rescan_remove();
}
static int zpci_iov_link_virtfn(struct pci_dev *pdev, struct pci_dev *virtfn, int vfid)
{
int rc;
rc = pci_iov_sysfs_link(pdev, virtfn, vfid);
if (rc)
return rc;
virtfn->is_virtfn = 1;
virtfn->multifunction = 0;
virtfn->physfn = pci_dev_get(pdev);
return 0;
}
/**
* zpci_iov_find_parent_pf - Find the parent PF, if any, of the given function
* @zbus: The bus that the PCI function is on, or would be added on
* @zdev: The PCI function
*
* Finds the parent PF, if it exists and is configured, of the given PCI function
* and increments its refcount. Th PF is searched for on the provided bus so the
* caller has to ensure that this is the correct bus to search. This function may
* be used before adding the PCI function to a zbus.
*
* Return: Pointer to the struct pci_dev of the parent PF or NULL if it not
* found. If the function is not a VF or has no RequesterID information,
* NULL is returned as well.
*/
struct pci_dev *zpci_iov_find_parent_pf(struct zpci_bus *zbus, struct zpci_dev *zdev)
{
int i, vfid, devfn, cand_devfn;
struct pci_dev *pdev;
if (!zbus->multifunction)
return NULL;
/* Non-VFs and VFs without RID available don't have a parent */
if (!zdev->vfn || !zdev->rid_available)
return NULL;
/* Linux vfid starts at 0 vfn at 1 */
vfid = zdev->vfn - 1;
devfn = zdev->rid & ZPCI_RID_MASK_DEVFN;
/*
* If the parent PF for the given VF is also configured in the
* instance, it must be on the same zbus.
* We can then identify the parent PF by checking what
* devfn the VF would have if it belonged to that PF using the PF's
* stride and offset. Only if this candidate devfn matches the
* actual devfn will we link both functions.
*/
for (i = 0; i < ZPCI_FUNCTIONS_PER_BUS; i++) {
zdev = zbus->function[i];
if (zdev && zdev->is_physfn) {
pdev = pci_get_slot(zbus->bus, zdev->devfn);
if (!pdev)
continue;
cand_devfn = pci_iov_virtfn_devfn(pdev, vfid);
if (cand_devfn == devfn)
return pdev;
/* balance pci_get_slot() */
pci_dev_put(pdev);
}
}
return NULL;
}
int zpci_iov_setup_virtfn(struct zpci_bus *zbus, struct pci_dev *virtfn, int vfn)
{
struct zpci_dev *zdev = to_zpci(virtfn);
struct pci_dev *pdev_pf;
int rc = 0;
pdev_pf = zpci_iov_find_parent_pf(zbus, zdev);
if (pdev_pf) {
/* Linux' vfids start at 0 while zdev->vfn starts at 1 */
rc = zpci_iov_link_virtfn(pdev_pf, virtfn, zdev->vfn - 1);
pci_dev_put(pdev_pf);
}
return rc;
}
+39
View File
@@ -0,0 +1,39 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Copyright IBM Corp. 2020
*
* Author(s):
* Niklas Schnelle <schnelle@linux.ibm.com>
*
*/
#ifndef __S390_PCI_IOV_H
#define __S390_PCI_IOV_H
#include <linux/pci.h>
#ifdef CONFIG_PCI_IOV
void zpci_iov_remove_virtfn(struct pci_dev *pdev, int vfn);
void zpci_iov_map_resources(struct pci_dev *pdev);
int zpci_iov_setup_virtfn(struct zpci_bus *zbus, struct pci_dev *virtfn, int vfn);
struct pci_dev *zpci_iov_find_parent_pf(struct zpci_bus *zbus, struct zpci_dev *zdev);
#else /* CONFIG_PCI_IOV */
static inline void zpci_iov_remove_virtfn(struct pci_dev *pdev, int vfn) {}
static inline void zpci_iov_map_resources(struct pci_dev *pdev) {}
static inline int zpci_iov_setup_virtfn(struct zpci_bus *zbus, struct pci_dev *virtfn, int vfn)
{
return 0;
}
static inline struct pci_dev *zpci_iov_find_parent_pf(struct zpci_bus *zbus, struct zpci_dev *zdev)
{
return NULL;
}
#endif /* CONFIG_PCI_IOV */
#endif /* __S390_PCI_IOV_h */
+663
View File
@@ -0,0 +1,663 @@
// SPDX-License-Identifier: GPL-2.0
#define pr_fmt(fmt) "zpci: " fmt
#include <linux/kernel.h>
#include <linux/irq.h>
#include <linux/kernel_stat.h>
#include <linux/pci.h>
#include <linux/msi.h>
#include <linux/irqchip/irq-msi-lib.h>
#include <linux/smp.h>
#include <asm/isc.h>
#include <asm/airq.h>
#include <asm/tpi.h>
static enum {FLOATING, DIRECTED} irq_delivery;
/*
* summary bit vector
* FLOATING - summary bit per function
* DIRECTED - summary bit per cpu (only used in fallback path)
*/
static struct airq_iv *zpci_sbv;
/*
* interrupt bit vectors
* FLOATING - interrupt bit vector per function
* DIRECTED - interrupt bit vector per cpu
*/
static struct airq_iv **zpci_ibv;
/* Modify PCI: Register floating adapter interruptions */
static int zpci_set_airq(struct zpci_dev *zdev)
{
u64 req = ZPCI_CREATE_REQ(zdev->fh, 0, ZPCI_MOD_FC_REG_INT);
struct zpci_fib fib = {0};
u8 status;
fib.fmt0.isc = PCI_ISC;
fib.fmt0.sum = 1; /* enable summary notifications */
fib.fmt0.noi = airq_iv_end(zdev->aibv);
fib.fmt0.aibv = virt_to_phys(zdev->aibv->vector);
fib.fmt0.aibvo = 0; /* each zdev has its own interrupt vector */
fib.fmt0.aisb = virt_to_phys(zpci_sbv->vector) + (zdev->aisb / 64) * 8;
fib.fmt0.aisbo = zdev->aisb & 63;
fib.gd = zdev->gisa;
return zpci_mod_fc(req, &fib, &status) ? -EIO : 0;
}
/* Modify PCI: Unregister floating adapter interruptions */
static int zpci_clear_airq(struct zpci_dev *zdev)
{
u64 req = ZPCI_CREATE_REQ(zdev->fh, 0, ZPCI_MOD_FC_DEREG_INT);
struct zpci_fib fib = {0};
u8 cc, status;
fib.gd = zdev->gisa;
cc = zpci_mod_fc(req, &fib, &status);
if (cc == 3 || (cc == 1 && status == 24))
/* Function already gone or IRQs already deregistered. */
cc = 0;
return cc ? -EIO : 0;
}
/* Modify PCI: Register CPU directed interruptions */
static int zpci_set_directed_irq(struct zpci_dev *zdev)
{
u64 req = ZPCI_CREATE_REQ(zdev->fh, 0, ZPCI_MOD_FC_REG_INT_D);
struct zpci_fib fib = {0};
u8 status;
fib.fmt = 1;
fib.fmt1.noi = zdev->msi_nr_irqs;
fib.fmt1.dibvo = zdev->msi_first_bit;
fib.gd = zdev->gisa;
return zpci_mod_fc(req, &fib, &status) ? -EIO : 0;
}
/* Modify PCI: Unregister CPU directed interruptions */
static int zpci_clear_directed_irq(struct zpci_dev *zdev)
{
u64 req = ZPCI_CREATE_REQ(zdev->fh, 0, ZPCI_MOD_FC_DEREG_INT_D);
struct zpci_fib fib = {0};
u8 cc, status;
fib.fmt = 1;
fib.gd = zdev->gisa;
cc = zpci_mod_fc(req, &fib, &status);
if (cc == 3 || (cc == 1 && status == 24))
/* Function already gone or IRQs already deregistered. */
cc = 0;
return cc ? -EIO : 0;
}
/* Register adapter interruptions */
int zpci_set_irq(struct zpci_dev *zdev)
{
int rc;
if (irq_delivery == DIRECTED)
rc = zpci_set_directed_irq(zdev);
else
rc = zpci_set_airq(zdev);
return rc;
}
/* Clear adapter interruptions */
static int zpci_clear_irq(struct zpci_dev *zdev)
{
int rc;
if (irq_delivery == DIRECTED)
rc = zpci_clear_directed_irq(zdev);
else
rc = zpci_clear_airq(zdev);
return rc;
}
static int zpci_set_irq_affinity(struct irq_data *data, const struct cpumask *dest,
bool force)
{
irq_data_update_affinity(data, dest);
return IRQ_SET_MASK_OK;
}
/*
* Encode the hwirq number for the parent domain. The encoding must be unique
* for each IRQ of each device in the parent domain, so it uses the devfn to
* identify the device and the msi_index to identify the IRQ within that device.
*/
static inline u32 zpci_encode_hwirq(u8 devfn, u16 msi_index)
{
return (devfn << 16) | msi_index;
}
static inline u16 zpci_decode_hwirq_msi_index(irq_hw_number_t hwirq)
{
return hwirq & 0xffff;
}
static void zpci_compose_msi_msg(struct irq_data *data, struct msi_msg *msg)
{
struct msi_desc *desc = irq_data_get_msi_desc(data);
struct zpci_dev *zdev = to_zpci_dev(desc->dev);
if (irq_delivery == DIRECTED) {
int cpu = cpumask_first(irq_data_get_affinity_mask(data));
msg->address_lo = zdev->msi_addr & 0xff0000ff;
msg->address_lo |= (smp_cpu_get_cpu_address(cpu) << 8);
} else {
msg->address_lo = zdev->msi_addr & 0xffffffff;
}
msg->address_hi = zdev->msi_addr >> 32;
msg->data = zpci_decode_hwirq_msi_index(data->hwirq);
}
static struct irq_chip zpci_irq_chip = {
.name = "PCI-MSI",
.irq_compose_msi_msg = zpci_compose_msi_msg,
};
static void zpci_handle_cpu_local_irq(bool rescan)
{
struct airq_iv *dibv = zpci_ibv[smp_processor_id()];
union zpci_sic_iib iib = {{0}};
struct irq_domain *msi_domain;
irq_hw_number_t hwirq;
unsigned long bit;
int irqs_on = 0;
for (bit = 0;;) {
/* Scan the directed IRQ bit vector */
bit = airq_iv_scan(dibv, bit, airq_iv_end(dibv));
if (bit == -1UL) {
if (!rescan || irqs_on++)
/* End of second scan with interrupts on. */
break;
/* First scan complete, re-enable interrupts. */
if (zpci_set_irq_ctrl(SIC_IRQ_MODE_D_SINGLE, PCI_ISC, &iib))
break;
bit = 0;
continue;
}
inc_irq_stat(IRQIO_MSI);
hwirq = airq_iv_get_data(dibv, bit);
msi_domain = (struct irq_domain *)airq_iv_get_ptr(dibv, bit);
generic_handle_domain_irq(msi_domain, hwirq);
}
}
struct cpu_irq_data {
call_single_data_t csd;
atomic_t scheduled;
};
static DEFINE_PER_CPU_SHARED_ALIGNED(struct cpu_irq_data, irq_data);
static void zpci_handle_remote_irq(void *data)
{
atomic_t *scheduled = data;
do {
zpci_handle_cpu_local_irq(false);
} while (atomic_dec_return(scheduled));
}
static void zpci_handle_fallback_irq(void)
{
struct cpu_irq_data *cpu_data;
union zpci_sic_iib iib = {{0}};
unsigned long cpu;
int irqs_on = 0;
for (cpu = 0;;) {
cpu = airq_iv_scan(zpci_sbv, cpu, airq_iv_end(zpci_sbv));
if (cpu == -1UL) {
if (irqs_on++)
/* End of second scan with interrupts on. */
break;
/* First scan complete, re-enable interrupts. */
if (zpci_set_irq_ctrl(SIC_IRQ_MODE_SINGLE, PCI_ISC, &iib))
break;
cpu = 0;
continue;
}
cpu_data = &per_cpu(irq_data, cpu);
if (atomic_inc_return(&cpu_data->scheduled) > 1)
continue;
INIT_CSD(&cpu_data->csd, zpci_handle_remote_irq, &cpu_data->scheduled);
smp_call_function_single_async(cpu, &cpu_data->csd);
}
}
static void zpci_directed_irq_handler(struct airq_struct *airq,
struct tpi_info *tpi_info)
{
bool floating = !tpi_info->directed_irq;
if (floating) {
inc_irq_stat(IRQIO_PCF);
zpci_handle_fallback_irq();
} else {
inc_irq_stat(IRQIO_PCD);
zpci_handle_cpu_local_irq(true);
}
}
static void zpci_floating_irq_handler(struct airq_struct *airq,
struct tpi_info *tpi_info)
{
union zpci_sic_iib iib = {{0}};
struct irq_domain *msi_domain;
irq_hw_number_t hwirq;
unsigned long si, ai;
struct airq_iv *aibv;
int irqs_on = 0;
inc_irq_stat(IRQIO_PCF);
for (si = 0;;) {
/* Scan adapter summary indicator bit vector */
si = airq_iv_scan(zpci_sbv, si, airq_iv_end(zpci_sbv));
if (si == -1UL) {
if (irqs_on++)
/* End of second scan with interrupts on. */
break;
/* First scan complete, re-enable interrupts. */
if (zpci_set_irq_ctrl(SIC_IRQ_MODE_SINGLE, PCI_ISC, &iib))
break;
si = 0;
continue;
}
/* Scan the adapter interrupt vector for this device. */
aibv = zpci_ibv[si];
for (ai = 0;;) {
ai = airq_iv_scan(aibv, ai, airq_iv_end(aibv));
if (ai == -1UL)
break;
inc_irq_stat(IRQIO_MSI);
airq_iv_lock(aibv, ai);
hwirq = airq_iv_get_data(aibv, ai);
msi_domain = (struct irq_domain *)airq_iv_get_ptr(aibv, ai);
generic_handle_domain_irq(msi_domain, hwirq);
airq_iv_unlock(aibv, ai);
}
}
}
static int __alloc_airq(struct zpci_dev *zdev, int msi_vecs,
unsigned long *bit)
{
if (irq_delivery == DIRECTED) {
/* Allocate cpu vector bits */
*bit = airq_iv_alloc(zpci_ibv[0], msi_vecs);
if (*bit == -1UL)
return -EIO;
} else {
/* Allocate adapter summary indicator bit */
*bit = airq_iv_alloc_bit(zpci_sbv);
if (*bit == -1UL)
return -EIO;
zdev->aisb = *bit;
/* Create adapter interrupt vector */
zdev->aibv = airq_iv_create(msi_vecs,
AIRQ_IV_PTR | AIRQ_IV_DATA | AIRQ_IV_BITLOCK,
NULL);
if (!zdev->aibv)
return -ENOMEM;
/* Wire up shortcut pointer */
zpci_ibv[*bit] = zdev->aibv;
/* Each function has its own interrupt vector */
*bit = 0;
}
return 0;
}
bool arch_restore_msi_irqs(struct pci_dev *pdev)
{
struct zpci_dev *zdev = to_zpci(pdev);
zpci_set_irq(zdev);
return true;
}
static struct airq_struct zpci_airq = {
.handler = zpci_floating_irq_handler,
.isc = PCI_ISC,
};
static void zpci_msi_teardown_directed(struct zpci_dev *zdev)
{
airq_iv_free(zpci_ibv[0], zdev->msi_first_bit, zdev->max_msi);
zdev->msi_first_bit = -1U;
zdev->msi_nr_irqs = 0;
}
static void zpci_msi_teardown_floating(struct zpci_dev *zdev)
{
airq_iv_release(zdev->aibv);
zdev->aibv = NULL;
airq_iv_free_bit(zpci_sbv, zdev->aisb);
zdev->aisb = -1UL;
zdev->msi_first_bit = -1U;
zdev->msi_nr_irqs = 0;
}
static void zpci_msi_teardown(struct irq_domain *domain, msi_alloc_info_t *arg)
{
struct zpci_dev *zdev = to_zpci_dev(domain->dev);
zpci_clear_irq(zdev);
if (irq_delivery == DIRECTED)
zpci_msi_teardown_directed(zdev);
else
zpci_msi_teardown_floating(zdev);
}
static int zpci_msi_prepare(struct irq_domain *domain,
struct device *dev, int nvec,
msi_alloc_info_t *info)
{
struct zpci_dev *zdev = to_zpci_dev(dev);
struct pci_dev *pdev = to_pci_dev(dev);
unsigned long bit;
int msi_vecs, rc;
msi_vecs = min_t(unsigned int, nvec, zdev->max_msi);
if (msi_vecs < nvec) {
pr_info("%s requested %d IRQs, allocate system limit of %d\n",
pci_name(pdev), nvec, zdev->max_msi);
}
rc = __alloc_airq(zdev, msi_vecs, &bit);
if (rc) {
pr_err("Allocating adapter IRQs for %s failed\n", pci_name(pdev));
return rc;
}
zdev->msi_first_bit = bit;
zdev->msi_nr_irqs = msi_vecs;
rc = zpci_set_irq(zdev);
if (rc) {
pr_err("Registering adapter IRQs for %s failed\n",
pci_name(pdev));
if (irq_delivery == DIRECTED)
zpci_msi_teardown_directed(zdev);
else
zpci_msi_teardown_floating(zdev);
return rc;
}
return 0;
}
static int zpci_msi_domain_alloc(struct irq_domain *domain, unsigned int virq,
unsigned int nr_irqs, void *args)
{
struct msi_desc *desc = ((msi_alloc_info_t *)args)->desc;
struct zpci_dev *zdev = to_zpci_dev(desc->dev);
struct zpci_bus *zbus = zdev->zbus;
unsigned int cpu, hwirq;
unsigned long bit;
int i;
bit = zdev->msi_first_bit + desc->msi_index;
hwirq = zpci_encode_hwirq(zdev->devfn, desc->msi_index);
if (desc->msi_index + nr_irqs > zdev->max_msi)
return -EINVAL;
for (i = 0; i < nr_irqs; i++) {
irq_domain_set_info(domain, virq + i, hwirq + i,
&zpci_irq_chip, zdev,
handle_percpu_irq, NULL, NULL);
if (irq_delivery == DIRECTED) {
for_each_possible_cpu(cpu) {
airq_iv_set_ptr(zpci_ibv[cpu], bit + i,
(unsigned long)zbus->msi_parent_domain);
airq_iv_set_data(zpci_ibv[cpu], bit + i, hwirq + i);
}
} else {
airq_iv_set_ptr(zdev->aibv, bit + i,
(unsigned long)zbus->msi_parent_domain);
airq_iv_set_data(zdev->aibv, bit + i, hwirq + i);
}
}
return 0;
}
static void zpci_msi_clear_airq(struct irq_data *d, int i)
{
struct msi_desc *desc = irq_data_get_msi_desc(d);
struct zpci_dev *zdev = to_zpci_dev(desc->dev);
unsigned long bit;
unsigned int cpu;
u16 msi_index;
msi_index = zpci_decode_hwirq_msi_index(d->hwirq);
bit = zdev->msi_first_bit + msi_index;
if (irq_delivery == DIRECTED) {
for_each_possible_cpu(cpu) {
airq_iv_set_ptr(zpci_ibv[cpu], bit + i, 0);
airq_iv_set_data(zpci_ibv[cpu], bit + i, 0);
}
} else {
airq_iv_set_ptr(zdev->aibv, bit + i, 0);
airq_iv_set_data(zdev->aibv, bit + i, 0);
}
}
static void zpci_msi_domain_free(struct irq_domain *domain, unsigned int virq,
unsigned int nr_irqs)
{
struct irq_data *d;
int i;
for (i = 0; i < nr_irqs; i++) {
d = irq_domain_get_irq_data(domain, virq + i);
zpci_msi_clear_airq(d, i);
irq_domain_reset_irq_data(d);
}
}
static const struct irq_domain_ops zpci_msi_domain_ops = {
.alloc = zpci_msi_domain_alloc,
.free = zpci_msi_domain_free,
};
static bool zpci_init_dev_msi_info(struct device *dev, struct irq_domain *domain,
struct irq_domain *real_parent,
struct msi_domain_info *info)
{
if (!msi_lib_init_dev_msi_info(dev, domain, real_parent, info))
return false;
info->ops->msi_prepare = zpci_msi_prepare;
info->ops->msi_teardown = zpci_msi_teardown;
return true;
}
static struct msi_parent_ops zpci_msi_parent_ops = {
.supported_flags = MSI_GENERIC_FLAGS_MASK |
MSI_FLAG_PCI_MSIX |
MSI_FLAG_MULTI_PCI_MSI,
.required_flags = MSI_FLAG_USE_DEF_DOM_OPS |
MSI_FLAG_USE_DEF_CHIP_OPS,
.init_dev_msi_info = zpci_init_dev_msi_info,
};
int zpci_create_parent_msi_domain(struct zpci_bus *zbus)
{
char fwnode_name[18];
snprintf(fwnode_name, sizeof(fwnode_name), "ZPCI_MSI_DOM_%04x", zbus->domain_nr);
struct irq_domain_info info = {
.fwnode = irq_domain_alloc_named_fwnode(fwnode_name),
.ops = &zpci_msi_domain_ops,
};
if (!info.fwnode) {
pr_err("Failed to allocate fwnode for MSI IRQ domain\n");
return -ENOMEM;
}
if (irq_delivery == FLOATING)
zpci_msi_parent_ops.required_flags |= MSI_FLAG_NO_AFFINITY;
zbus->msi_parent_domain = msi_create_parent_irq_domain(&info, &zpci_msi_parent_ops);
if (!zbus->msi_parent_domain) {
irq_domain_free_fwnode(info.fwnode);
pr_err("Failed to create MSI IRQ domain\n");
return -ENOMEM;
}
return 0;
}
void zpci_remove_parent_msi_domain(struct zpci_bus *zbus)
{
struct fwnode_handle *fn;
fn = zbus->msi_parent_domain->fwnode;
irq_domain_remove(zbus->msi_parent_domain);
irq_domain_free_fwnode(fn);
}
static void __init cpu_enable_directed_irq(void *unused)
{
union zpci_sic_iib iib = {{0}};
union zpci_sic_iib ziib = {{0}};
iib.cdiib.dibv_addr = virt_to_phys(zpci_ibv[smp_processor_id()]->vector);
zpci_set_irq_ctrl(SIC_IRQ_MODE_SET_CPU, 0, &iib);
zpci_set_irq_ctrl(SIC_IRQ_MODE_D_SINGLE, PCI_ISC, &ziib);
}
static int __init zpci_directed_irq_init(void)
{
union zpci_sic_iib iib = {{0}};
unsigned int cpu;
zpci_sbv = airq_iv_create(num_possible_cpus(), 0, NULL);
if (!zpci_sbv)
return -ENOMEM;
iib.diib.isc = PCI_ISC;
iib.diib.nr_cpus = num_possible_cpus();
iib.diib.disb_addr = virt_to_phys(zpci_sbv->vector);
zpci_set_irq_ctrl(SIC_IRQ_MODE_DIRECT, 0, &iib);
zpci_ibv = kzalloc_objs(*zpci_ibv, num_possible_cpus());
if (!zpci_ibv)
return -ENOMEM;
for_each_possible_cpu(cpu) {
/*
* Per CPU IRQ vectors look the same but bit-allocation
* is only done on the first vector.
*/
zpci_ibv[cpu] = airq_iv_create(cache_line_size() * BITS_PER_BYTE,
AIRQ_IV_PTR |
AIRQ_IV_DATA |
AIRQ_IV_CACHELINE |
(!cpu ? AIRQ_IV_ALLOC : 0), NULL);
if (!zpci_ibv[cpu])
return -ENOMEM;
}
on_each_cpu(cpu_enable_directed_irq, NULL, 1);
zpci_irq_chip.irq_set_affinity = zpci_set_irq_affinity;
return 0;
}
static int __init zpci_floating_irq_init(void)
{
zpci_ibv = kzalloc_objs(*zpci_ibv, ZPCI_NR_DEVICES);
if (!zpci_ibv)
return -ENOMEM;
zpci_sbv = airq_iv_create(ZPCI_NR_DEVICES, AIRQ_IV_ALLOC, NULL);
if (!zpci_sbv)
goto out_free;
return 0;
out_free:
kfree(zpci_ibv);
return -ENOMEM;
}
int __init zpci_irq_init(void)
{
union zpci_sic_iib iib = {{0}};
int rc;
irq_delivery = sclp.has_dirq ? DIRECTED : FLOATING;
if (s390_pci_force_floating)
irq_delivery = FLOATING;
if (irq_delivery == DIRECTED)
zpci_airq.handler = zpci_directed_irq_handler;
rc = register_adapter_interrupt(&zpci_airq);
if (rc)
goto out;
/* Set summary to 1 to be called every time for the ISC. */
*zpci_airq.lsi_ptr = 1;
switch (irq_delivery) {
case FLOATING:
rc = zpci_floating_irq_init();
break;
case DIRECTED:
rc = zpci_directed_irq_init();
break;
}
if (rc)
goto out_airq;
/*
* Enable floating IRQs (with suppression after one IRQ). When using
* directed IRQs this enables the fallback path.
*/
zpci_set_irq_ctrl(SIC_IRQ_MODE_SINGLE, PCI_ISC, &iib);
return 0;
out_airq:
unregister_adapter_interrupt(&zpci_airq);
out:
return rc;
}
void __init zpci_irq_exit(void)
{
unsigned int cpu;
if (irq_delivery == DIRECTED) {
for_each_possible_cpu(cpu) {
airq_iv_release(zpci_ibv[cpu]);
}
}
kfree(zpci_ibv);
if (zpci_sbv)
airq_iv_release(zpci_sbv);
unregister_adapter_interrupt(&zpci_airq);
}
+13
View File
@@ -0,0 +1,13 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* VFIO ZPCI devices support
*
* Copyright (C) IBM Corp. 2022. All rights reserved.
* Author(s): Pierre Morel <pmorel@linux.ibm.com>
*/
#include <linux/kvm_host.h>
#include <linux/export.h>
struct zpci_kvm_hook zpci_kvm_hook;
EXPORT_SYMBOL_GPL(zpci_kvm_hook);
+364
View File
@@ -0,0 +1,364 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Access to PCI I/O memory from user space programs.
*
* Copyright IBM Corp. 2014
* Author(s): Alexey Ishchuk <aishchuk@linux.vnet.ibm.com>
*/
#include <linux/kernel.h>
#include <linux/syscalls.h>
#include <linux/init.h>
#include <linux/mm.h>
#include <linux/errno.h>
#include <linux/pci.h>
#include <asm/asm-extable.h>
#include <asm/pci_io.h>
#include <asm/pci_debug.h>
#include <asm/asm.h>
static inline void zpci_err_mmio(u8 cc, u8 status, u64 offset)
{
struct {
u64 offset;
u8 cc;
u8 status;
} data = {offset, cc, status};
zpci_err_hex(&data, sizeof(data));
}
static inline int __pcistb_mio_inuser(
void __iomem *ioaddr, const void __user *src,
u64 len, u8 *status)
{
int cc, exception;
bool sacf_flag;
exception = 1;
sacf_flag = enable_sacf_uaccess();
asm_inline volatile (
" sacf 256\n"
"0: .insn rsy,0xeb00000000d4,%[len],%[ioaddr],%[src]\n"
"1: lhi %[exc],0\n"
"2: sacf 768\n"
CC_IPM(cc)
EX_TABLE(0b, 2b) EX_TABLE(1b, 2b)
: CC_OUT(cc, cc), [len] "+d" (len), [exc] "+d" (exception)
: [ioaddr] "a" (ioaddr), [src] "Q" (*((u8 __force *)src))
: CC_CLOBBER_LIST("memory"));
disable_sacf_uaccess(sacf_flag);
*status = len >> 24 & 0xff;
return exception ? -ENXIO : CC_TRANSFORM(cc);
}
static inline int __pcistg_mio_inuser(
void __iomem *ioaddr, const void __user *src,
u64 ulen, u8 *status)
{
union register_pair ioaddr_len = {.even = (u64 __force)ioaddr, .odd = ulen};
int cc, exception;
bool sacf_flag;
u64 val = 0;
u64 cnt = ulen;
u8 tmp;
/*
* copy 0 < @len <= 8 bytes from @src into the right most bytes of
* a register, then store it to PCI at @ioaddr while in secondary
* address space. pcistg then uses the user mappings.
*/
exception = 1;
sacf_flag = enable_sacf_uaccess();
asm_inline volatile (
" sacf 256\n"
"0: llgc %[tmp],0(%[src])\n"
"4: sllg %[val],%[val],8\n"
" aghi %[src],1\n"
" ogr %[val],%[tmp]\n"
" brctg %[cnt],0b\n"
"1: .insn rre,0xb9d40000,%[val],%[ioaddr_len]\n"
"2: lhi %[exc],0\n"
"3: sacf 768\n"
CC_IPM(cc)
EX_TABLE(0b, 3b) EX_TABLE(4b, 3b) EX_TABLE(1b, 3b) EX_TABLE(2b, 3b)
: [src] "+a" (src), [cnt] "+d" (cnt),
[val] "+d" (val), [tmp] "=d" (tmp), [exc] "+d" (exception),
CC_OUT(cc, cc), [ioaddr_len] "+&d" (ioaddr_len.pair)
:
: CC_CLOBBER_LIST("memory"));
disable_sacf_uaccess(sacf_flag);
*status = ioaddr_len.odd >> 24 & 0xff;
cc = exception ? -ENXIO : CC_TRANSFORM(cc);
/* did we read everything from user memory? */
if (!cc && cnt != 0)
cc = -EFAULT;
return cc;
}
static inline int __memcpy_toio_inuser(void __iomem *dst,
const void __user *src, size_t n)
{
int size, rc = 0;
u8 status = 0;
if (!src)
return -EINVAL;
while (n > 0) {
size = zpci_get_max_io_size((u64 __force) dst,
(u64 __force) src, n,
ZPCI_MAX_WRITE_SIZE);
if (size > 8) /* main path */
rc = __pcistb_mio_inuser(dst, src, size, &status);
else
rc = __pcistg_mio_inuser(dst, src, size, &status);
if (rc)
break;
src += size;
dst += size;
n -= size;
}
if (rc)
zpci_err_mmio(rc, status, (__force u64) dst);
return rc;
}
SYSCALL_DEFINE3(s390_pci_mmio_write, unsigned long, mmio_addr,
const void __user *, user_buffer, size_t, length)
{
struct follow_pfnmap_args args = { };
u8 local_buf[64];
void __iomem *io_addr;
void *buf;
struct vm_area_struct *vma;
long ret;
if (!zpci_is_enabled())
return -ENODEV;
if (length <= 0 || PAGE_SIZE - (mmio_addr & ~PAGE_MASK) < length)
return -EINVAL;
/*
* We only support write access to MIO capable devices if we are on
* a MIO enabled system. Otherwise we would have to check for every
* address if it is a special ZPCI_ADDR and would have to do
* a pfn lookup which we don't need for MIO capable devices. Currently
* ISM devices are the only devices without MIO support and there is no
* known need for accessing these from userspace.
*/
if (static_branch_likely(&have_mio)) {
ret = __memcpy_toio_inuser((void __iomem *) mmio_addr,
user_buffer,
length);
return ret;
}
if (length > 64) {
buf = kmalloc(length, GFP_KERNEL);
if (!buf)
return -ENOMEM;
} else
buf = local_buf;
ret = -EFAULT;
if (copy_from_user(buf, user_buffer, length))
goto out_free;
mmap_read_lock(current->mm);
ret = -EINVAL;
vma = vma_lookup(current->mm, mmio_addr);
if (!vma)
goto out_unlock_mmap;
if (!(vma->vm_flags & (VM_IO | VM_PFNMAP)))
goto out_unlock_mmap;
ret = -EACCES;
if (!(vma->vm_flags & VM_WRITE))
goto out_unlock_mmap;
args.address = mmio_addr;
args.vma = vma;
ret = follow_pfnmap_start(&args);
if (ret) {
fixup_user_fault(current->mm, mmio_addr, FAULT_FLAG_WRITE, NULL);
ret = follow_pfnmap_start(&args);
if (ret)
goto out_unlock_mmap;
}
ret = -EFAULT;
io_addr = (void __iomem *)((args.pfn << PAGE_SHIFT) |
(mmio_addr & ~PAGE_MASK));
if ((unsigned long) io_addr < ZPCI_IOMAP_ADDR_BASE)
goto out_unlock_pt;
ret = zpci_memcpy_toio(io_addr, buf, length);
out_unlock_pt:
follow_pfnmap_end(&args);
out_unlock_mmap:
mmap_read_unlock(current->mm);
out_free:
if (buf != local_buf)
kfree(buf);
return ret;
}
static inline int __pcilg_mio_inuser(
void __user *dst, const void __iomem *ioaddr,
u64 ulen, u8 *status)
{
union register_pair ioaddr_len = {.even = (u64 __force)ioaddr, .odd = ulen};
bool sacf_flag;
u64 cnt = ulen;
int shift = ulen * 8;
int cc, exception;
u64 val, tmp;
/*
* read 0 < @len <= 8 bytes from the PCI memory mapped at @ioaddr (in
* user space) into a register using pcilg then store these bytes at
* user address @dst
*/
exception = 1;
sacf_flag = enable_sacf_uaccess();
asm_inline volatile (
" sacf 256\n"
"0: .insn rre,0xb9d60000,%[val],%[ioaddr_len]\n"
"1: lhi %[exc],0\n"
" jne 4f\n"
"2: ahi %[shift],-8\n"
" srlg %[tmp],%[val],0(%[shift])\n"
"3: stc %[tmp],0(%[dst])\n"
"5: aghi %[dst],1\n"
" brctg %[cnt],2b\n"
/*
* Use xr to clear exc and set condition code to zero
* to ensure flag output is correct for this branch.
*/
" xr %[exc],%[exc]\n"
"4: sacf 768\n"
CC_IPM(cc)
EX_TABLE(0b, 4b) EX_TABLE(1b, 4b) EX_TABLE(3b, 4b) EX_TABLE(5b, 4b)
: [ioaddr_len] "+&d" (ioaddr_len.pair), [exc] "+d" (exception),
CC_OUT(cc, cc), [val] "=d" (val),
[dst] "+a" (dst), [cnt] "+d" (cnt), [tmp] "=d" (tmp),
[shift] "+a" (shift)
:
: CC_CLOBBER_LIST("memory"));
disable_sacf_uaccess(sacf_flag);
cc = exception ? -ENXIO : CC_TRANSFORM(cc);
/* did we write everything to the user space buffer? */
if (!cc && cnt != 0)
cc = -EFAULT;
*status = ioaddr_len.odd >> 24 & 0xff;
return cc;
}
static inline int __memcpy_fromio_inuser(void __user *dst,
const void __iomem *src,
unsigned long n)
{
int size, rc = 0;
u8 status;
while (n > 0) {
size = zpci_get_max_io_size((u64 __force) src,
(u64 __force) dst, n,
ZPCI_MAX_READ_SIZE);
rc = __pcilg_mio_inuser(dst, src, size, &status);
if (rc)
break;
src += size;
dst += size;
n -= size;
}
if (rc)
zpci_err_mmio(rc, status, (__force u64) dst);
return rc;
}
SYSCALL_DEFINE3(s390_pci_mmio_read, unsigned long, mmio_addr,
void __user *, user_buffer, size_t, length)
{
struct follow_pfnmap_args args = { };
u8 local_buf[64];
void __iomem *io_addr;
void *buf;
struct vm_area_struct *vma;
long ret;
if (!zpci_is_enabled())
return -ENODEV;
if (length <= 0 || PAGE_SIZE - (mmio_addr & ~PAGE_MASK) < length)
return -EINVAL;
/*
* We only support read access to MIO capable devices if we are on
* a MIO enabled system. Otherwise we would have to check for every
* address if it is a special ZPCI_ADDR and would have to do
* a pfn lookup which we don't need for MIO capable devices. Currently
* ISM devices are the only devices without MIO support and there is no
* known need for accessing these from userspace.
*/
if (static_branch_likely(&have_mio)) {
ret = __memcpy_fromio_inuser(
user_buffer, (const void __iomem *)mmio_addr,
length);
return ret;
}
if (length > 64) {
buf = kmalloc(length, GFP_KERNEL);
if (!buf)
return -ENOMEM;
} else {
buf = local_buf;
}
mmap_read_lock(current->mm);
ret = -EINVAL;
vma = vma_lookup(current->mm, mmio_addr);
if (!vma)
goto out_unlock_mmap;
if (!(vma->vm_flags & (VM_IO | VM_PFNMAP)))
goto out_unlock_mmap;
ret = -EACCES;
if (!(vma->vm_flags & VM_READ))
goto out_unlock_mmap;
args.vma = vma;
args.address = mmio_addr;
ret = follow_pfnmap_start(&args);
if (ret) {
fixup_user_fault(current->mm, mmio_addr, 0, NULL);
ret = follow_pfnmap_start(&args);
if (ret)
goto out_unlock_mmap;
}
io_addr = (void __iomem *)((args.pfn << PAGE_SHIFT) |
(mmio_addr & ~PAGE_MASK));
if ((unsigned long) io_addr < ZPCI_IOMAP_ADDR_BASE) {
ret = -EFAULT;
goto out_unlock_pt;
}
ret = zpci_memcpy_fromio(buf, io_addr, length);
out_unlock_pt:
follow_pfnmap_end(&args);
out_unlock_mmap:
mmap_read_unlock(current->mm);
if (!ret && copy_to_user(user_buffer, buf, length))
ret = -EFAULT;
if (buf != local_buf)
kfree(buf);
return ret;
}
+157
View File
@@ -0,0 +1,157 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright IBM Corp. 2024
*
* Author(s):
* Niklas Schnelle <schnelle@linux.ibm.com>
*
*/
#define pr_fmt(fmt) "zpci: " fmt
#include <linux/kernel.h>
#include <linux/sprintf.h>
#include <linux/pci.h>
#include <asm/sclp.h>
#include <asm/debug.h>
#include <asm/pci_debug.h>
#include "pci_report.h"
#define ZPCI_ERR_LOG_ID_KERNEL_REPORT 0x4714
struct zpci_report_error_data {
u64 timestamp;
u64 err_log_id;
char log_data[];
} __packed;
#define ZPCI_REPORT_SIZE (PAGE_SIZE - sizeof(struct err_notify_sccb))
#define ZPCI_REPORT_DATA_SIZE (ZPCI_REPORT_SIZE - sizeof(struct zpci_report_error_data))
struct zpci_report_error {
struct zpci_report_error_header header;
struct zpci_report_error_data data;
} __packed;
static const char *zpci_state_str(pci_channel_state_t state)
{
switch (state) {
case pci_channel_io_normal:
return "normal";
case pci_channel_io_frozen:
return "frozen";
case pci_channel_io_perm_failure:
return "permanent-failure";
default:
return "invalid";
};
}
static int debug_log_header_fn(debug_info_t *id, struct debug_view *view,
int area, debug_entry_t *entry, char *out_buf,
size_t out_buf_size)
{
unsigned long sec, usec;
unsigned int level;
char *except_str;
int rc = 0;
level = entry->level;
sec = entry->clock;
usec = do_div(sec, USEC_PER_SEC);
if (entry->exception)
except_str = "*";
else
except_str = "-";
rc += scnprintf(out_buf, out_buf_size, "%011ld:%06lu %1u %1s %04u ",
sec, usec, level, except_str,
entry->cpu);
return rc;
}
static int debug_prolog_header(debug_info_t *id, struct debug_view *view,
char *out_buf, size_t out_buf_size)
{
return scnprintf(out_buf, out_buf_size, "sec:usec level except cpu msg\n");
}
static struct debug_view debug_log_view = {
"pci_msg_log",
&debug_prolog_header,
&debug_log_header_fn,
&debug_sprintf_format_fn,
NULL,
NULL
};
/**
* zpci_report_status - Report the status of operations on a PCI device
* @zdev: The PCI device for which to report status
* @operation: A string representing the operation reported
* @status: A string representing the status of the operation
*
* This function creates a human readable report about an operation such as
* PCI device recovery and forwards this to the platform using the SCLP Write
* Event Data mechanism. Besides the operation and status strings the report
* also contains additional information about the device deemed useful for
* debug such as the currently bound device driver, if any, and error state.
* Additionally a string representation of pci_debug_msg_id, or as much as fits,
* is also included.
*
* Return: 0 on success an error code < 0 otherwise.
*/
int zpci_report_status(struct zpci_dev *zdev, const char *operation, const char *status)
{
struct zpci_report_error *report;
struct pci_driver *driver = NULL;
struct pci_dev *pdev = NULL;
char *buf, *end;
int ret;
if (!zdev || !zdev->zbus)
return -ENODEV;
/* Protected virtualization hosts get nothing from us */
if (prot_virt_guest)
return -ENODATA;
report = (void *)get_zeroed_page(GFP_KERNEL);
if (!report)
return -ENOMEM;
if (zdev->zbus->bus)
pdev = pci_get_slot(zdev->zbus->bus, zdev->devfn);
if (pdev)
driver = to_pci_driver(pdev->dev.driver);
buf = report->data.log_data;
end = report->data.log_data + ZPCI_REPORT_DATA_SIZE;
buf += scnprintf(buf, end - buf, "report: %s\n", operation);
buf += scnprintf(buf, end - buf, "status: %s\n", status);
buf += scnprintf(buf, end - buf, "state: %s\n",
(pdev) ? zpci_state_str(pdev->error_state) : "n/a");
buf += scnprintf(buf, end - buf, "driver: %s\n", (driver) ? driver->name : "n/a");
ret = debug_dump(pci_debug_msg_id, &debug_log_view, buf, end - buf, true);
if (ret < 0)
pr_err("Reading PCI debug messages failed with code %d\n", ret);
else
buf += ret;
report->header.version = 1;
report->header.action = SCLP_ERRNOTIFY_AQ_INFO_LOG;
report->header.length = buf - (char *)&report->data;
report->data.timestamp = ktime_get_clocktai_seconds();
report->data.err_log_id = ZPCI_ERR_LOG_ID_KERNEL_REPORT;
ret = sclp_pci_report(&report->header, zdev->fh, zdev->fid);
if (ret)
pr_err("Reporting PCI status failed with code %d\n", ret);
else
pr_info("Reported PCI device status\n");
free_page((unsigned long)report);
return ret;
}
+16
View File
@@ -0,0 +1,16 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Copyright IBM Corp. 2024
*
* Author(s):
* Niklas Schnelle <schnelle@linux.ibm.com>
*
*/
#ifndef __S390_PCI_REPORT_H
#define __S390_PCI_REPORT_H
struct zpci_dev;
int zpci_report_status(struct zpci_dev *zdev, const char *operation, const char *status);
#endif /* __S390_PCI_REPORT_H */
+279
View File
@@ -0,0 +1,279 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright IBM Corp. 2012
*
* Author(s):
* Jan Glauber <jang@linux.vnet.ibm.com>
*/
#define pr_fmt(fmt) "zpci: " fmt
#include <linux/kernel.h>
#include <linux/stat.h>
#include <linux/pci.h>
#include "../../../drivers/pci/pci.h"
#include <asm/sclp.h>
#define zpci_attr(name, fmt, member) \
static ssize_t name##_show(struct device *dev, \
struct device_attribute *attr, char *buf) \
{ \
struct zpci_dev *zdev = to_zpci(to_pci_dev(dev)); \
\
return sysfs_emit(buf, fmt, zdev->member); \
} \
static DEVICE_ATTR_RO(name)
zpci_attr(function_id, "0x%08x\n", fid);
zpci_attr(function_handle, "0x%08x\n", fh);
zpci_attr(pchid, "0x%04x\n", pchid);
zpci_attr(pfgid, "0x%02x\n", pfgid);
zpci_attr(vfn, "0x%04x\n", vfn);
zpci_attr(pft, "0x%02x\n", pft);
zpci_attr(port, "%d\n", port);
zpci_attr(fidparm, "0x%02x\n", fidparm);
zpci_attr(uid, "0x%x\n", uid);
zpci_attr(segment0, "0x%02x\n", pfip[0]);
zpci_attr(segment1, "0x%02x\n", pfip[1]);
zpci_attr(segment2, "0x%02x\n", pfip[2]);
zpci_attr(segment3, "0x%02x\n", pfip[3]);
#define ZPCI_FW_ATTR_RO(_name) \
static struct kobj_attribute _name##_attr = __ATTR_RO(_name)
static ssize_t mio_enabled_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct zpci_dev *zdev = to_zpci(to_pci_dev(dev));
return sysfs_emit(buf, zpci_use_mio(zdev) ? "1\n" : "0\n");
}
static DEVICE_ATTR_RO(mio_enabled);
static int _do_recover(struct pci_dev *pdev, struct zpci_dev *zdev)
{
int ret;
pci_stop_and_remove_bus_device(pdev);
if (zdev_enabled(zdev)) {
ret = zpci_disable_device(zdev);
/*
* Due to a z/VM vs LPAR inconsistency in the error
* state the FH may indicate an enabled device but
* disable says the device is already disabled don't
* treat it as an error here.
*/
if (ret == -EINVAL)
ret = 0;
if (ret)
return ret;
}
ret = zpci_reenable_device(zdev);
return ret;
}
static ssize_t recover_store(struct device *dev, struct device_attribute *attr,
const char *buf, size_t count)
{
struct kernfs_node *kn;
struct pci_dev *pdev = to_pci_dev(dev);
struct zpci_dev *zdev = to_zpci(pdev);
int ret = 0;
/* Can't use device_remove_self() here as that would lead us to lock
* the pci_rescan_remove_lock while holding the device' kernfs lock.
* This would create a possible deadlock with disable_slot() which is
* not directly protected by the device' kernfs lock but takes it
* during the device removal which happens under
* pci_rescan_remove_lock.
*
* This is analogous to sdev_store_delete() in
* drivers/scsi/scsi_sysfs.c
*/
kn = sysfs_break_active_protection(&dev->kobj, &attr->attr);
WARN_ON_ONCE(!kn);
/* Device needs to be configured and state must not change */
mutex_lock(&zdev->state_lock);
if (zdev->state != ZPCI_FN_STATE_CONFIGURED)
goto out;
/* device_remove_file() serializes concurrent calls ignoring all but
* the first
*/
device_remove_file(dev, attr);
/* A concurrent call to recover_store() may slip between
* sysfs_break_active_protection() and the sysfs file removal.
* Once it unblocks from pci_lock_rescan_remove() the original pdev
* will already be removed.
*/
pci_lock_rescan_remove();
if (pci_dev_is_added(pdev)) {
ret = _do_recover(pdev, zdev);
}
pci_rescan_bus(zdev->zbus->bus);
pci_unlock_rescan_remove();
out:
mutex_unlock(&zdev->state_lock);
if (kn)
sysfs_unbreak_active_protection(kn);
return ret ? ret : count;
}
static DEVICE_ATTR_WO(recover);
static ssize_t util_string_read(struct file *filp, struct kobject *kobj,
const struct bin_attribute *attr, char *buf,
loff_t off, size_t count)
{
struct device *dev = kobj_to_dev(kobj);
struct pci_dev *pdev = to_pci_dev(dev);
struct zpci_dev *zdev = to_zpci(pdev);
return memory_read_from_buffer(buf, count, &off, zdev->util_str,
sizeof(zdev->util_str));
}
static const BIN_ATTR_RO(util_string, CLP_UTIL_STR_LEN);
static ssize_t report_error_write(struct file *filp, struct kobject *kobj,
const struct bin_attribute *attr, char *buf,
loff_t off, size_t count)
{
struct zpci_report_error_header *report = (void *) buf;
struct device *dev = kobj_to_dev(kobj);
struct pci_dev *pdev = to_pci_dev(dev);
struct zpci_dev *zdev = to_zpci(pdev);
int ret;
if (off || (count < sizeof(*report)))
return -EINVAL;
ret = sclp_pci_report(report, zdev->fh, zdev->fid);
return ret ? ret : count;
}
static const BIN_ATTR(report_error, S_IWUSR, NULL, report_error_write, PAGE_SIZE);
static ssize_t uid_is_unique_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
return sysfs_emit(buf, "%d\n", zpci_unique_uid ? 1 : 0);
}
static DEVICE_ATTR_RO(uid_is_unique);
static ssize_t uid_checking_show(struct kobject *kobj,
struct kobj_attribute *attr, char *buf)
{
return sysfs_emit(buf, "%d\n", zpci_unique_uid ? 1 : 0);
}
ZPCI_FW_ATTR_RO(uid_checking);
/* analogous to smbios index */
static ssize_t index_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct zpci_dev *zdev = to_zpci(to_pci_dev(dev));
u32 index = ~0;
if (zpci_unique_uid)
index = zdev->uid;
return sysfs_emit(buf, "%u\n", index);
}
static DEVICE_ATTR_RO(index);
static ssize_t zpci_uid_slot_show(struct pci_slot *slot, char *buf)
{
struct zpci_dev *zdev = container_of(slot->hotplug, struct zpci_dev,
hotplug_slot);
return sysfs_emit(buf, "0x%x\n", zdev->uid);
}
static struct pci_slot_attribute zpci_slot_attr_uid =
__ATTR(uid, 0444, zpci_uid_slot_show, NULL);
static umode_t zpci_index_is_visible(struct kobject *kobj,
struct attribute *attr, int n)
{
return zpci_unique_uid ? attr->mode : 0;
}
static struct attribute *zpci_ident_attrs[] = {
&dev_attr_index.attr,
NULL,
};
const struct attribute_group zpci_ident_attr_group = {
.attrs = zpci_ident_attrs,
.is_visible = zpci_index_is_visible,
};
static const struct bin_attribute *const zpci_bin_attrs[] = {
&bin_attr_util_string,
&bin_attr_report_error,
NULL,
};
static struct attribute *zpci_dev_attrs[] = {
&dev_attr_function_id.attr,
&dev_attr_function_handle.attr,
&dev_attr_pchid.attr,
&dev_attr_pfgid.attr,
&dev_attr_pft.attr,
&dev_attr_port.attr,
&dev_attr_fidparm.attr,
&dev_attr_vfn.attr,
&dev_attr_uid.attr,
&dev_attr_recover.attr,
&dev_attr_mio_enabled.attr,
&dev_attr_uid_is_unique.attr,
NULL,
};
const struct attribute_group zpci_attr_group = {
.attrs = zpci_dev_attrs,
.bin_attrs = zpci_bin_attrs,
};
static struct attribute *pfip_attrs[] = {
&dev_attr_segment0.attr,
&dev_attr_segment1.attr,
&dev_attr_segment2.attr,
&dev_attr_segment3.attr,
NULL,
};
const struct attribute_group pfip_attr_group = {
.name = "pfip",
.attrs = pfip_attrs,
};
static struct attribute *zpci_slot_attrs[] = {
&zpci_slot_attr_uid.attr,
NULL,
};
const struct attribute_group zpci_slot_attr_group = {
.attrs = zpci_slot_attrs,
};
static struct attribute *clp_fw_attrs[] = {
&uid_checking_attr.attr,
NULL,
};
static struct attribute_group clp_fw_attr_group = {
.name = "clp",
.attrs = clp_fw_attrs,
};
int __init __zpci_fw_sysfs_init(void)
{
return sysfs_create_group(firmware_kobj, &clp_fw_attr_group);
}