forked from Minki/linux
9a99649f2a
For each PCI function we need to maintain arch specific data in struct zpci_dev which also contains a pointer to struct pci_dev. When a function is registered or deregistered (which is triggered by PCI common code) we need to adjust that pointer which could interfere with the machine check handler (triggered by FW) using zpci_dev->pdev. Since multiple instances of the same pdev could exist at a time this can't be solved with locking. Fix that by ditching the pdev pointer and use a bus walk to reach struct pci_dev (only one instance of a pdev can be registered at the bus at a time). Signed-off-by: Sebastian Ott <sebott@linux.vnet.ibm.com> Reviewed-by: Gerald Schaefer <gerald.schaefer@de.ibm.com> Signed-off-by: Martin Schwidefsky <schwidefsky@de.ibm.com>
177 lines
4.0 KiB
C
177 lines
4.0 KiB
C
/*
|
|
* Copyright IBM Corp. 2012
|
|
*
|
|
* Author(s):
|
|
* Jan Glauber <jang@linux.vnet.ibm.com>
|
|
*/
|
|
|
|
#define KMSG_COMPONENT "zpci"
|
|
#define pr_fmt(fmt) KMSG_COMPONENT ": " fmt
|
|
|
|
#include <linux/kernel.h>
|
|
#include <linux/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_perf_names[] = {
|
|
/* hardware counters */
|
|
"Load operations",
|
|
"Store operations",
|
|
"Store block operations",
|
|
"Refresh operations",
|
|
"DMA read bytes",
|
|
"DMA write bytes",
|
|
};
|
|
|
|
static char *pci_sw_names[] = {
|
|
"Allocated pages",
|
|
"Mapped pages",
|
|
"Unmapped pages",
|
|
};
|
|
|
|
static void pci_sw_counter_show(struct seq_file *m)
|
|
{
|
|
struct zpci_dev *zdev = m->private;
|
|
atomic64_t *counter = &zdev->allocated_pages;
|
|
int i;
|
|
|
|
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));
|
|
}
|
|
|
|
static int pci_perf_show(struct seq_file *m, void *v)
|
|
{
|
|
struct zpci_dev *zdev = m->private;
|
|
u64 *stat;
|
|
int i;
|
|
|
|
if (!zdev)
|
|
return 0;
|
|
|
|
mutex_lock(&zdev->lock);
|
|
if (!zdev->fmb) {
|
|
mutex_unlock(&zdev->lock);
|
|
seq_puts(m, "FMB statistics disabled\n");
|
|
return 0;
|
|
}
|
|
|
|
/* header */
|
|
seq_printf(m, "FMB @ %p\n", zdev->fmb);
|
|
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);
|
|
|
|
/* hardware counters */
|
|
stat = (u64 *) &zdev->fmb->ld_ops;
|
|
for (i = 0; i < 4; i++)
|
|
seq_printf(m, "%26s:\t%llu\n",
|
|
pci_perf_names[i], *(stat + i));
|
|
if (zdev->fmb->dma_valid)
|
|
for (i = 4; i < 6; i++)
|
|
seq_printf(m, "%26s:\t%llu\n",
|
|
pci_perf_names[i], *(stat + i));
|
|
|
|
pci_sw_counter_show(m);
|
|
mutex_unlock(&zdev->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->lock);
|
|
switch (val) {
|
|
case 0:
|
|
rc = zpci_fmb_disable_device(zdev);
|
|
break;
|
|
case 1:
|
|
rc = zpci_fmb_enable_device(zdev);
|
|
break;
|
|
}
|
|
mutex_unlock(&zdev->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);
|
|
if (IS_ERR(zdev->debugfs_dev))
|
|
zdev->debugfs_dev = NULL;
|
|
|
|
zdev->debugfs_perf = debugfs_create_file("statistics",
|
|
S_IFREG | S_IRUGO | S_IWUSR,
|
|
zdev->debugfs_dev, zdev,
|
|
&debugfs_pci_perf_fops);
|
|
if (IS_ERR(zdev->debugfs_perf))
|
|
zdev->debugfs_perf = NULL;
|
|
}
|
|
|
|
void zpci_debug_exit_device(struct zpci_dev *zdev)
|
|
{
|
|
debugfs_remove(zdev->debugfs_perf);
|
|
debugfs_remove(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, 6);
|
|
|
|
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);
|
|
}
|