Commit 91de32fe authored by Hans de Goede's avatar Hans de Goede

Merge tag 'ib-mfd-x86-v5.11' into review-hans

Immutable branch between MFD and x86 due for the v5.11 merge window
parents 83f7a38e 5ef9998c
What: /sys/class/intel_pmt/
Date: October 2020
KernelVersion: 5.10
Contact: David Box <david.e.box@linux.intel.com>
Description:
The intel_pmt/ class directory contains information for
devices that expose hardware telemetry using Intel Platform
Monitoring Technology (PMT)
What: /sys/class/intel_pmt/telem<x>
Date: October 2020
KernelVersion: 5.10
Contact: David Box <david.e.box@linux.intel.com>
Description:
The telem<x> directory contains files describing an instance of
a PMT telemetry device that exposes hardware telemetry. Each
telem<x> directory has an associated telem file. This file
may be opened and mapped or read to access the telemetry space
of the device. The register layout of the telemetry space is
determined from an XML file that matches the PCI device id and
GUID for the device.
What: /sys/class/intel_pmt/telem<x>/telem
Date: October 2020
KernelVersion: 5.10
Contact: David Box <david.e.box@linux.intel.com>
Description:
(RO) The telemetry data for this telemetry device. This file
may be mapped or read to obtain the data.
What: /sys/class/intel_pmt/telem<x>/guid
Date: October 2020
KernelVersion: 5.10
Contact: David Box <david.e.box@linux.intel.com>
Description:
(RO) The GUID for this telemetry device. The GUID identifies
the version of the XML file for the parent device that is to
be used to get the register layout.
What: /sys/class/intel_pmt/telem<x>/size
Date: October 2020
KernelVersion: 5.10
Contact: David Box <david.e.box@linux.intel.com>
Description:
(RO) The size of telemetry region in bytes that corresponds to
the mapping size for the telem file.
What: /sys/class/intel_pmt/telem<x>/offset
Date: October 2020
KernelVersion: 5.10
Contact: David Box <david.e.box@linux.intel.com>
Description:
(RO) The offset of telemetry region in bytes that corresponds to
the mapping for the telem file.
What: /sys/class/intel_pmt/crashlog<x>
Date: October 2020
KernelVersion: 5.10
Contact: Alexander Duyck <alexander.h.duyck@linux.intel.com>
Description:
The crashlog<x> directory contains files for configuring an
instance of a PMT crashlog device that can perform crash data
recording. Each crashlog<x> device has an associated crashlog
file. This file can be opened and mapped or read to access the
resulting crashlog buffer. The register layout for the buffer
can be determined from an XML file of specified GUID for the
parent device.
What: /sys/class/intel_pmt/crashlog<x>/crashlog
Date: October 2020
KernelVersion: 5.10
Contact: David Box <david.e.box@linux.intel.com>
Description:
(RO) The crashlog buffer for this crashlog device. This file
may be mapped or read to obtain the data.
What: /sys/class/intel_pmt/crashlog<x>/guid
Date: October 2020
KernelVersion: 5.10
Contact: Alexander Duyck <alexander.h.duyck@linux.intel.com>
Description:
(RO) The GUID for this crashlog device. The GUID identifies the
version of the XML file for the parent device that should be
used to determine the register layout.
What: /sys/class/intel_pmt/crashlog<x>/size
Date: October 2020
KernelVersion: 5.10
Contact: Alexander Duyck <alexander.h.duyck@linux.intel.com>
Description:
(RO) The length of the result buffer in bytes that corresponds
to the size for the crashlog buffer.
What: /sys/class/intel_pmt/crashlog<x>/offset
Date: October 2020
KernelVersion: 5.10
Contact: Alexander Duyck <alexander.h.duyck@linux.intel.com>
Description:
(RO) The offset of the buffer in bytes that corresponds
to the mapping for the crashlog device.
What: /sys/class/intel_pmt/crashlog<x>/enable
Date: October 2020
KernelVersion: 5.10
Contact: Alexander Duyck <alexander.h.duyck@linux.intel.com>
Description:
(RW) Boolean value controlling if the crashlog functionality
is enabled for the crashlog device.
What: /sys/class/intel_pmt/crashlog<x>/trigger
Date: October 2020
KernelVersion: 5.10
Contact: Alexander Duyck <alexander.h.duyck@linux.intel.com>
Description:
(RW) Boolean value controlling the triggering of the crashlog
device node. When read it provides data on if the crashlog has
been triggered. When written to it can be used to either clear
the current trigger by writing false, or to trigger a new
event if the trigger is not currently set.
......@@ -9030,6 +9030,12 @@ F: drivers/mfd/intel_soc_pmic*
F: include/linux/mfd/intel_msic.h
F: include/linux/mfd/intel_soc_pmic*
INTEL PMT DRIVER
M: "David E. Box" <david.e.box@linux.intel.com>
S: Maintained
F: drivers/mfd/intel_pmt.c
F: drivers/platform/x86/intel_pmt_*
INTEL PRO/WIRELESS 2100, 2200BG, 2915ABG NETWORK CONNECTION SUPPORT
M: Stanislav Yakovlev <stas.yakovlev@gmail.com>
L: linux-wireless@vger.kernel.org
......
......@@ -682,6 +682,16 @@ config MFD_INTEL_PMC_BXT
Register and P-unit access. In addition this creates devices
for iTCO watchdog and telemetry that are part of the PMC.
config MFD_INTEL_PMT
tristate "Intel Platform Monitoring Technology (PMT) support"
depends on PCI
select MFD_CORE
help
The Intel Platform Monitoring Technology (PMT) is an interface that
provides access to hardware monitor registers. This driver supports
Telemetry, Watcher, and Crashlog PMT capabilities/devices for
platforms starting from Tiger Lake.
config MFD_IPAQ_MICRO
bool "Atmel Micro ASIC (iPAQ h3100/h3600/h3700) Support"
depends on SA1100_H3100 || SA1100_H3600
......
......@@ -216,6 +216,7 @@ obj-$(CONFIG_MFD_INTEL_LPSS_PCI) += intel-lpss-pci.o
obj-$(CONFIG_MFD_INTEL_LPSS_ACPI) += intel-lpss-acpi.o
obj-$(CONFIG_MFD_INTEL_MSIC) += intel_msic.o
obj-$(CONFIG_MFD_INTEL_PMC_BXT) += intel_pmc_bxt.o
obj-$(CONFIG_MFD_INTEL_PMT) += intel_pmt.o
obj-$(CONFIG_MFD_PALMAS) += palmas.o
obj-$(CONFIG_MFD_VIPERBOARD) += viperboard.o
obj-$(CONFIG_MFD_RC5T583) += rc5t583.o rc5t583-irq.o
......
// SPDX-License-Identifier: GPL-2.0
/*
* Intel Platform Monitoring Technology PMT driver
*
* Copyright (c) 2020, Intel Corporation.
* All Rights Reserved.
*
* Author: David E. Box <david.e.box@linux.intel.com>
*/
#include <linux/bits.h>
#include <linux/kernel.h>
#include <linux/mfd/core.h>
#include <linux/module.h>
#include <linux/pci.h>
#include <linux/platform_device.h>
#include <linux/pm.h>
#include <linux/pm_runtime.h>
#include <linux/types.h>
/* Intel DVSEC capability vendor space offsets */
#define INTEL_DVSEC_ENTRIES 0xA
#define INTEL_DVSEC_SIZE 0xB
#define INTEL_DVSEC_TABLE 0xC
#define INTEL_DVSEC_TABLE_BAR(x) ((x) & GENMASK(2, 0))
#define INTEL_DVSEC_TABLE_OFFSET(x) ((x) & GENMASK(31, 3))
#define INTEL_DVSEC_ENTRY_SIZE 4
/* PMT capabilities */
#define DVSEC_INTEL_ID_TELEMETRY 2
#define DVSEC_INTEL_ID_WATCHER 3
#define DVSEC_INTEL_ID_CRASHLOG 4
struct intel_dvsec_header {
u16 length;
u16 id;
u8 num_entries;
u8 entry_size;
u8 tbir;
u32 offset;
};
enum pmt_quirks {
/* Watcher capability not supported */
PMT_QUIRK_NO_WATCHER = BIT(0),
/* Crashlog capability not supported */
PMT_QUIRK_NO_CRASHLOG = BIT(1),
/* Use shift instead of mask to read discovery table offset */
PMT_QUIRK_TABLE_SHIFT = BIT(2),
};
struct pmt_platform_info {
unsigned long quirks;
};
static const struct pmt_platform_info tgl_info = {
.quirks = PMT_QUIRK_NO_WATCHER | PMT_QUIRK_NO_CRASHLOG |
PMT_QUIRK_TABLE_SHIFT,
};
static int pmt_add_dev(struct pci_dev *pdev, struct intel_dvsec_header *header,
unsigned long quirks)
{
struct device *dev = &pdev->dev;
struct resource *res, *tmp;
struct mfd_cell *cell;
const char *name;
int count = header->num_entries;
int size = header->entry_size;
int id = header->id;
int i;
switch (id) {
case DVSEC_INTEL_ID_TELEMETRY:
name = "pmt_telemetry";
break;
case DVSEC_INTEL_ID_WATCHER:
if (quirks & PMT_QUIRK_NO_WATCHER) {
dev_info(dev, "Watcher not supported\n");
return 0;
}
name = "pmt_watcher";
break;
case DVSEC_INTEL_ID_CRASHLOG:
if (quirks & PMT_QUIRK_NO_CRASHLOG) {
dev_info(dev, "Crashlog not supported\n");
return 0;
}
name = "pmt_crashlog";
break;
default:
dev_err(dev, "Unrecognized PMT capability: %d\n", id);
return -EINVAL;
}
if (!header->num_entries || !header->entry_size) {
dev_err(dev, "Invalid count or size for %s header\n", name);
return -EINVAL;
}
cell = devm_kzalloc(dev, sizeof(*cell), GFP_KERNEL);
if (!cell)
return -ENOMEM;
res = devm_kcalloc(dev, count, sizeof(*res), GFP_KERNEL);
if (!res)
return -ENOMEM;
if (quirks & PMT_QUIRK_TABLE_SHIFT)
header->offset >>= 3;
/*
* The PMT DVSEC contains the starting offset and count for a block of
* discovery tables, each providing access to monitoring facilities for
* a section of the device. Create a resource list of these tables to
* provide to the driver.
*/
for (i = 0, tmp = res; i < count; i++, tmp++) {
tmp->start = pdev->resource[header->tbir].start +
header->offset + i * (size << 2);
tmp->end = tmp->start + (size << 2) - 1;
tmp->flags = IORESOURCE_MEM;
}
cell->resources = res;
cell->num_resources = count;
cell->name = name;
return devm_mfd_add_devices(dev, PLATFORM_DEVID_AUTO, cell, 1, NULL, 0,
NULL);
}
static int pmt_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id)
{
struct pmt_platform_info *info;
unsigned long quirks = 0;
bool found_devices = false;
int ret, pos = 0;
ret = pcim_enable_device(pdev);
if (ret)
return ret;
info = (struct pmt_platform_info *)id->driver_data;
if (info)
quirks = info->quirks;
do {
struct intel_dvsec_header header;
u32 table;
u16 vid;
pos = pci_find_next_ext_capability(pdev, pos, PCI_EXT_CAP_ID_DVSEC);
if (!pos)
break;
pci_read_config_word(pdev, pos + PCI_DVSEC_HEADER1, &vid);
if (vid != PCI_VENDOR_ID_INTEL)
continue;
pci_read_config_word(pdev, pos + PCI_DVSEC_HEADER2,
&header.id);
pci_read_config_byte(pdev, pos + INTEL_DVSEC_ENTRIES,
&header.num_entries);
pci_read_config_byte(pdev, pos + INTEL_DVSEC_SIZE,
&header.entry_size);
pci_read_config_dword(pdev, pos + INTEL_DVSEC_TABLE,
&table);
header.tbir = INTEL_DVSEC_TABLE_BAR(table);
header.offset = INTEL_DVSEC_TABLE_OFFSET(table);
ret = pmt_add_dev(pdev, &header, quirks);
if (ret) {
dev_warn(&pdev->dev,
"Failed to add device for DVSEC id %d\n",
header.id);
continue;
}
found_devices = true;
} while (true);
if (!found_devices)
return -ENODEV;
pm_runtime_put(&pdev->dev);
pm_runtime_allow(&pdev->dev);
return 0;
}
static void pmt_pci_remove(struct pci_dev *pdev)
{
pm_runtime_forbid(&pdev->dev);
pm_runtime_get_sync(&pdev->dev);
}
#define PCI_DEVICE_ID_INTEL_PMT_ADL 0x467d
#define PCI_DEVICE_ID_INTEL_PMT_OOBMSM 0x09a7
#define PCI_DEVICE_ID_INTEL_PMT_TGL 0x9a0d
static const struct pci_device_id pmt_pci_ids[] = {
{ PCI_DEVICE_DATA(INTEL, PMT_ADL, &tgl_info) },
{ PCI_DEVICE_DATA(INTEL, PMT_OOBMSM, NULL) },
{ PCI_DEVICE_DATA(INTEL, PMT_TGL, &tgl_info) },
{ }
};
MODULE_DEVICE_TABLE(pci, pmt_pci_ids);
static struct pci_driver pmt_pci_driver = {
.name = "intel-pmt",
.id_table = pmt_pci_ids,
.probe = pmt_pci_probe,
.remove = pmt_pci_remove,
};
module_pci_driver(pmt_pci_driver);
MODULE_AUTHOR("David E. Box <david.e.box@linux.intel.com>");
MODULE_DESCRIPTION("Intel Platform Monitoring Technology PMT driver");
MODULE_LICENSE("GPL v2");
......@@ -1343,6 +1343,40 @@ config INTEL_PMC_CORE
- LTR Ignore
- MPHY/PLL gating status (Sunrisepoint PCH only)
config INTEL_PMT_CLASS
tristate "Intel Platform Monitoring Technology (PMT) Class driver"
help
The Intel Platform Monitoring Technology (PMT) class driver provides
the basic sysfs interface and file hierarchy uses by PMT devices.
For more information, see:
<file:Documentation/ABI/testing/sysfs-class-intel_pmt>
To compile this driver as a module, choose M here: the module
will be called intel_pmt_class.
config INTEL_PMT_TELEMETRY
tristate "Intel Platform Monitoring Technology (PMT) Telemetry driver"
select INTEL_PMT_CLASS
help
The Intel Platform Monitory Technology (PMT) Telemetry driver provides
access to hardware telemetry metrics on devices that support the
feature.
To compile this driver as a module, choose M here: the module
will be called intel_pmt_telemetry.
config INTEL_PMT_CRASHLOG
tristate "Intel Platform Monitoring Technology (PMT) Crashlog driver"
select INTEL_PMT_CLASS
help
The Intel Platform Monitoring Technology (PMT) crashlog driver provides
access to hardware crashlog capabilities on devices that support the
feature.
To compile this driver as a module, choose M here: the module
will be called intel_pmt_crashlog.
config INTEL_PUNIT_IPC
tristate "Intel P-Unit IPC Driver"
help
......
......@@ -135,6 +135,9 @@ obj-$(CONFIG_INTEL_MFLD_THERMAL) += intel_mid_thermal.o
obj-$(CONFIG_INTEL_MID_POWER_BUTTON) += intel_mid_powerbtn.o
obj-$(CONFIG_INTEL_MRFLD_PWRBTN) += intel_mrfld_pwrbtn.o
obj-$(CONFIG_INTEL_PMC_CORE) += intel_pmc_core.o intel_pmc_core_pltdrv.o
obj-$(CONFIG_INTEL_PMT_CLASS) += intel_pmt_class.o
obj-$(CONFIG_INTEL_PMT_TELEMETRY) += intel_pmt_telemetry.o
obj-$(CONFIG_INTEL_PMT_CRASHLOG) += intel_pmt_crashlog.o
obj-$(CONFIG_INTEL_PUNIT_IPC) += intel_punit_ipc.o
obj-$(CONFIG_INTEL_SCU_IPC) += intel_scu_ipc.o
obj-$(CONFIG_INTEL_SCU_PCI) += intel_scu_pcidrv.o
......
// SPDX-License-Identifier: GPL-2.0
/*
* Intel Platform Monitory Technology Telemetry driver
*
* Copyright (c) 2020, Intel Corporation.
* All Rights Reserved.
*
* Author: "Alexander Duyck" <alexander.h.duyck@linux.intel.com>
*/
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/mm.h>
#include <linux/pci.h>
#include "intel_pmt_class.h"
#define PMT_XA_START 0
#define PMT_XA_MAX INT_MAX
#define PMT_XA_LIMIT XA_LIMIT(PMT_XA_START, PMT_XA_MAX)
/*
* sysfs
*/
static ssize_t
intel_pmt_read(struct file *filp, struct kobject *kobj,
struct bin_attribute *attr, char *buf, loff_t off,
size_t count)
{
struct intel_pmt_entry *entry = container_of(attr,
struct intel_pmt_entry,
pmt_bin_attr);
if (off < 0)
return -EINVAL;
if (off >= entry->size)
return 0;
if (count > entry->size - off)
count = entry->size - off;
memcpy_fromio(buf, entry->base + off, count);
return count;
}
static int
intel_pmt_mmap(struct file *filp, struct kobject *kobj,
struct bin_attribute *attr, struct vm_area_struct *vma)
{
struct intel_pmt_entry *entry = container_of(attr,
struct intel_pmt_entry,
pmt_bin_attr);
unsigned long vsize = vma->vm_end - vma->vm_start;
struct device *dev = kobj_to_dev(kobj);
unsigned long phys = entry->base_addr;
unsigned long pfn = PFN_DOWN(phys);
unsigned long psize;
if (vma->vm_flags & (VM_WRITE | VM_MAYWRITE))
return -EROFS;
psize = (PFN_UP(entry->base_addr + entry->size) - pfn) * PAGE_SIZE;
if (vsize > psize) {
dev_err(dev, "Requested mmap size is too large\n");
return -EINVAL;
}
vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot);
if (io_remap_pfn_range(vma, vma->vm_start, pfn,
vsize, vma->vm_page_prot))
return -EAGAIN;
return 0;
}
static ssize_t
guid_show(struct device *dev, struct device_attribute *attr, char *buf)
{
struct intel_pmt_entry *entry = dev_get_drvdata(dev);
return sprintf(buf, "0x%x\n", entry->guid);
}
static DEVICE_ATTR_RO(guid);
static ssize_t size_show(struct device *dev, struct device_attribute *attr,
char *buf)
{
struct intel_pmt_entry *entry = dev_get_drvdata(dev);
return sprintf(buf, "%zu\n", entry->size);
}
static DEVICE_ATTR_RO(size);
static ssize_t
offset_show(struct device *dev, struct device_attribute *attr, char *buf)
{
struct intel_pmt_entry *entry = dev_get_drvdata(dev);
return sprintf(buf, "%lu\n", offset_in_page(entry->base_addr));
}
static DEVICE_ATTR_RO(offset);
static struct attribute *intel_pmt_attrs[] = {
&dev_attr_guid.attr,
&dev_attr_size.attr,
&dev_attr_offset.attr,
NULL
};
ATTRIBUTE_GROUPS(intel_pmt);
static struct class intel_pmt_class = {
.name = "intel_pmt",
.owner = THIS_MODULE,
.dev_groups = intel_pmt_groups,
};
static int intel_pmt_populate_entry(struct intel_pmt_entry *entry,
struct intel_pmt_header *header,
struct device *dev,
struct resource *disc_res)
{
struct pci_dev *pci_dev = to_pci_dev(dev->parent);
u8 bir;
/*
* The base offset should always be 8 byte aligned.
*
* For non-local access types the lower 3 bits of base offset
* contains the index of the base address register where the
* telemetry can be found.
*/
bir = GET_BIR(header->base_offset);
/* Local access and BARID only for now */
switch (header->access_type) {
case ACCESS_LOCAL:
if (bir) {
dev_err(dev,
"Unsupported BAR index %d for access type %d\n",
bir, header->access_type);
return -EINVAL;
}
/*
* For access_type LOCAL, the base address is as follows:
* base address = end of discovery region + base offset
*/
entry->base_addr = disc_res->end + 1 + header->base_offset;
break;
case ACCESS_BARID:
/*
* If another BAR was specified then the base offset
* represents the offset within that BAR. SO retrieve the
* address from the parent PCI device and add offset.
*/
entry->base_addr = pci_resource_start(pci_dev, bir) +
GET_ADDRESS(header->base_offset);
break;
default:
dev_err(dev, "Unsupported access type %d\n",
header->access_type);
return -EINVAL;
}
entry->guid = header->guid;
entry->size = header->size;
return 0;
}
static int intel_pmt_dev_register(struct intel_pmt_entry *entry,
struct intel_pmt_namespace *ns,
struct device *parent)
{
struct resource res;
struct device *dev;
int ret;
ret = xa_alloc(ns->xa, &entry->devid, entry, PMT_XA_LIMIT, GFP_KERNEL);
if (ret)
return ret;
dev = device_create(&intel_pmt_class, parent, MKDEV(0, 0), entry,
"%s%d", ns->name, entry->devid);
if (IS_ERR(dev)) {
dev_err(parent, "Could not create %s%d device node\n",
ns->name, entry->devid);
ret = PTR_ERR(dev);
goto fail_dev_create;
}
entry->kobj = &dev->kobj;
if (ns->attr_grp) {
ret = sysfs_create_group(entry->kobj, ns->attr_grp);
if (ret)
goto fail_sysfs;
}
/* if size is 0 assume no data buffer, so no file needed */
if (!entry->size)
return 0;
res.start = entry->base_addr;
res.end = res.start + entry->size - 1;
res.flags = IORESOURCE_MEM;
entry->base = devm_ioremap_resource(dev, &res);
if (IS_ERR(entry->base)) {
ret = PTR_ERR(entry->base);
goto fail_ioremap;
}
sysfs_bin_attr_init(&entry->pmt_bin_attr);
entry->pmt_bin_attr.attr.name = ns->name;
entry->pmt_bin_attr.attr.mode = 0440;
entry->pmt_bin_attr.mmap = intel_pmt_mmap;
entry->pmt_bin_attr.read = intel_pmt_read;
entry->pmt_bin_attr.size = entry->size;
ret = sysfs_create_bin_file(&dev->kobj, &entry->pmt_bin_attr);
if (!ret)
return 0;
fail_ioremap:
sysfs_remove_group(entry->kobj, ns->attr_grp);
fail_sysfs:
device_unregister(dev);
fail_dev_create:
xa_erase(ns->xa, entry->devid);
return ret;
}
int intel_pmt_dev_create(struct intel_pmt_entry *entry,
struct intel_pmt_namespace *ns,
struct platform_device *pdev, int idx)
{
struct intel_pmt_header header;
struct resource *disc_res;
int ret = -ENODEV;
disc_res = platform_get_resource(pdev, IORESOURCE_MEM, idx);
if (!disc_res)
return ret;
entry->disc_table = devm_platform_ioremap_resource(pdev, idx);
if (IS_ERR(entry->disc_table))
return PTR_ERR(entry->disc_table);
ret = ns->pmt_header_decode(entry, &header, &pdev->dev);
if (ret)
return ret;
ret = intel_pmt_populate_entry(entry, &header, &pdev->dev, disc_res);
if (ret)
return ret;
return intel_pmt_dev_register(entry, ns, &pdev->dev);
}
EXPORT_SYMBOL_GPL(intel_pmt_dev_create);
void intel_pmt_dev_destroy(struct intel_pmt_entry *entry,
struct intel_pmt_namespace *ns)
{
struct device *dev = kobj_to_dev(entry->kobj);
if (entry->size)
sysfs_remove_bin_file(entry->kobj, &entry->pmt_bin_attr);
if (ns->attr_grp)
sysfs_remove_group(entry->kobj, ns->attr_grp);
device_unregister(dev);
xa_erase(ns->xa, entry->devid);
}
EXPORT_SYMBOL_GPL(intel_pmt_dev_destroy);
static int __init pmt_class_init(void)
{
return class_register(&intel_pmt_class);
}
static void __exit pmt_class_exit(void)
{
class_unregister(&intel_pmt_class);
}
module_init(pmt_class_init);
module_exit(pmt_class_exit);
MODULE_AUTHOR("Alexander Duyck <alexander.h.duyck@linux.intel.com>");
MODULE_DESCRIPTION("Intel PMT Class driver");
MODULE_LICENSE("GPL v2");
/* SPDX-License-Identifier: GPL-2.0 */
#ifndef _INTEL_PMT_CLASS_H
#define _INTEL_PMT_CLASS_H
#include <linux/platform_device.h>
#include <linux/xarray.h>
#include <linux/types.h>
#include <linux/bits.h>
#include <linux/err.h>
#include <linux/io.h>
/* PMT access types */
#define ACCESS_BARID 2
#define ACCESS_LOCAL 3
/* PMT discovery base address/offset register layout */
#define GET_BIR(v) ((v) & GENMASK(2, 0))
#define GET_ADDRESS(v) ((v) & GENMASK(31, 3))
struct intel_pmt_entry {
struct bin_attribute pmt_bin_attr;
struct kobject *kobj;
void __iomem *disc_table;
void __iomem *base;
unsigned long base_addr;
size_t size;
u32 guid;
int devid;
};
struct intel_pmt_header {
u32 base_offset;
u32 size;
u32 guid;
u8 access_type;
};
struct intel_pmt_namespace {
const char *name;
struct xarray *xa;
const struct attribute_group *attr_grp;
int (*pmt_header_decode)(struct intel_pmt_entry *entry,
struct intel_pmt_header *header,
struct device *dev);
};
int intel_pmt_dev_create(struct intel_pmt_entry *entry,
struct intel_pmt_namespace *ns,
struct platform_device *pdev, int idx);
void intel_pmt_dev_destroy(struct intel_pmt_entry *entry,
struct intel_pmt_namespace *ns);
#endif
// SPDX-License-Identifier: GPL-2.0
/*
* Intel Platform Monitoring Technology Crashlog driver
*
* Copyright (c) 2020, Intel Corporation.
* All Rights Reserved.
*
* Author: "Alexander Duyck" <alexander.h.duyck@linux.intel.com>
*/
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/pci.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/overflow.h>
#include "intel_pmt_class.h"
#define DRV_NAME "pmt_crashlog"
/* Crashlog discovery header types */
#define CRASH_TYPE_OOBMSM 1
/* Control Flags */
#define CRASHLOG_FLAG_DISABLE BIT(27)
/*
* Bits 28 and 29 control the state of bit 31.
*
* Bit 28 will clear bit 31, if set, allowing a new crashlog to be captured.
* Bit 29 will immediately trigger a crashlog to be generated, setting bit 31.
* Bit 30 is read-only and reserved as 0.
* Bit 31 is the read-only status with a 1 indicating log is complete.
*/
#define CRASHLOG_FLAG_TRIGGER_CLEAR BIT(28)
#define CRASHLOG_FLAG_TRIGGER_EXECUTE BIT(29)
#define CRASHLOG_FLAG_TRIGGER_COMPLETE BIT(31)
#define CRASHLOG_FLAG_TRIGGER_MASK GENMASK(31, 28)
/* Crashlog Discovery Header */
#define CONTROL_OFFSET 0x0
#define GUID_OFFSET 0x4
#define BASE_OFFSET 0x8
#define SIZE_OFFSET 0xC
#define GET_ACCESS(v) ((v) & GENMASK(3, 0))
#define GET_TYPE(v) (((v) & GENMASK(7, 4)) >> 4)
#define GET_VERSION(v) (((v) & GENMASK(19, 16)) >> 16)
/* size is in bytes */
#define GET_SIZE(v) ((v) * sizeof(u32))
struct crashlog_entry {
/* entry must be first member of struct */
struct intel_pmt_entry entry;
struct mutex control_mutex;
};
struct pmt_crashlog_priv {
int num_entries;
struct crashlog_entry entry[];
};
/*
* I/O
*/
static bool pmt_crashlog_complete(struct intel_pmt_entry *entry)
{
u32 control = readl(entry->disc_table + CONTROL_OFFSET);
/* return current value of the crashlog complete flag */
return !!(control & CRASHLOG_FLAG_TRIGGER_COMPLETE);
}
static bool pmt_crashlog_disabled(struct intel_pmt_entry *entry)
{
u32 control = readl(entry->disc_table + CONTROL_OFFSET);
/* return current value of the crashlog disabled flag */
return !!(control & CRASHLOG_FLAG_DISABLE);
}
static bool pmt_crashlog_supported(struct intel_pmt_entry *entry)
{
u32 discovery_header = readl(entry->disc_table + CONTROL_OFFSET);
u32 crash_type, version;
crash_type = GET_TYPE(discovery_header);
version = GET_VERSION(discovery_header);
/*
* Currently we only recognize OOBMSM version 0 devices.
* We can ignore all other crashlog devices in the system.
*/
return crash_type == CRASH_TYPE_OOBMSM && version == 0;
}
static void pmt_crashlog_set_disable(struct intel_pmt_entry *entry,
bool disable)
{
u32 control = readl(entry->disc_table + CONTROL_OFFSET);
/* clear trigger bits so we are only modifying disable flag */
control &= ~CRASHLOG_FLAG_TRIGGER_MASK;
if (disable)
control |= CRASHLOG_FLAG_DISABLE;
else
control &= ~CRASHLOG_FLAG_DISABLE;
writel(control, entry->disc_table + CONTROL_OFFSET);
}
static void pmt_crashlog_set_clear(struct intel_pmt_entry *entry)
{
u32 control = readl(entry->disc_table + CONTROL_OFFSET);
control &= ~CRASHLOG_FLAG_TRIGGER_MASK;
control |= CRASHLOG_FLAG_TRIGGER_CLEAR;
writel(control, entry->disc_table + CONTROL_OFFSET);
}
static void pmt_crashlog_set_execute(struct intel_pmt_entry *entry)
{
u32 control = readl(entry->disc_table + CONTROL_OFFSET);
control &= ~CRASHLOG_FLAG_TRIGGER_MASK;
control |= CRASHLOG_FLAG_TRIGGER_EXECUTE;
writel(control, entry->disc_table + CONTROL_OFFSET);
}
/*
* sysfs
*/
static ssize_t
enable_show(struct device *dev, struct device_attribute *attr, char *buf)
{
struct intel_pmt_entry *entry = dev_get_drvdata(dev);
int enabled = !pmt_crashlog_disabled(entry);
return sprintf(buf, "%d\n", enabled);
}
static ssize_t
enable_store(struct device *dev, struct device_attribute *attr,
const char *buf, size_t count)
{
struct crashlog_entry *entry;
bool enabled;
int result;
entry = dev_get_drvdata(dev);
result = kstrtobool(buf, &enabled);
if (result)
return result;
mutex_lock(&entry->control_mutex);
pmt_crashlog_set_disable(&entry->entry, !enabled);
mutex_unlock(&entry->control_mutex);
return count;
}
static DEVICE_ATTR_RW(enable);
static ssize_t
trigger_show(struct device *dev, struct device_attribute *attr, char *buf)
{
struct intel_pmt_entry *entry;
int trigger;
entry = dev_get_drvdata(dev);
trigger = pmt_crashlog_complete(entry);
return sprintf(buf, "%d\n", trigger);
}
static ssize_t
trigger_store(struct device *dev, struct device_attribute *attr,
const char *buf, size_t count)
{
struct crashlog_entry *entry;
bool trigger;
int result;
entry = dev_get_drvdata(dev);
result = kstrtobool(buf, &trigger);
if (result)
return result;
mutex_lock(&entry->control_mutex);
if (!trigger) {
pmt_crashlog_set_clear(&entry->entry);
} else if (pmt_crashlog_complete(&entry->entry)) {
/* we cannot trigger a new crash if one is still pending */
result = -EEXIST;
goto err;
} else if (pmt_crashlog_disabled(&entry->entry)) {
/* if device is currently disabled, return busy */
result = -EBUSY;
goto err;
} else {
pmt_crashlog_set_execute(&entry->entry);
}
result = count;
err:
mutex_unlock(&entry->control_mutex);
return result;
}
static DEVICE_ATTR_RW(trigger);
static struct attribute *pmt_crashlog_attrs[] = {
&dev_attr_enable.attr,
&dev_attr_trigger.attr,
NULL
};
static struct attribute_group pmt_crashlog_group = {
.attrs = pmt_crashlog_attrs,
};
static int pmt_crashlog_header_decode(struct intel_pmt_entry *entry,
struct intel_pmt_header *header,
struct device *dev)
{
void __iomem *disc_table = entry->disc_table;
struct crashlog_entry *crashlog;
if (!pmt_crashlog_supported(entry))
return 1;
/* initialize control mutex */
crashlog = container_of(entry, struct crashlog_entry, entry);
mutex_init(&crashlog->control_mutex);
header->access_type = GET_ACCESS(readl(disc_table));
header->guid = readl(disc_table + GUID_OFFSET);
header->base_offset = readl(disc_table + BASE_OFFSET);
/* Size is measured in DWORDS, but accessor returns bytes */
header->size = GET_SIZE(readl(disc_table + SIZE_OFFSET));
return 0;
}
static DEFINE_XARRAY_ALLOC(crashlog_array);
static struct intel_pmt_namespace pmt_crashlog_ns = {
.name = "crashlog",
.xa = &crashlog_array,
.attr_grp = &pmt_crashlog_group,
.pmt_header_decode = pmt_crashlog_header_decode,
};
/*
* initialization
*/
static int pmt_crashlog_remove(struct platform_device *pdev)
{
struct pmt_crashlog_priv *priv = platform_get_drvdata(pdev);
int i;
for (i = 0; i < priv->num_entries; i++)
intel_pmt_dev_destroy(&priv->entry[i].entry, &pmt_crashlog_ns);
return 0;
}
static int pmt_crashlog_probe(struct platform_device *pdev)
{
struct pmt_crashlog_priv *priv;
size_t size;
int i, ret;
size = struct_size(priv, entry, pdev->num_resources);
priv = devm_kzalloc(&pdev->dev, size, GFP_KERNEL);
if (!priv)
return -ENOMEM;
platform_set_drvdata(pdev, priv);
for (i = 0; i < pdev->num_resources; i++) {
struct intel_pmt_entry *entry = &priv->entry[i].entry;
ret = intel_pmt_dev_create(entry, &pmt_crashlog_ns, pdev, i);
if (ret < 0)
goto abort_probe;
if (ret)
continue;
priv->num_entries++;
}
return 0;
abort_probe:
pmt_crashlog_remove(pdev);
return ret;
}
static struct platform_driver pmt_crashlog_driver = {
.driver = {
.name = DRV_NAME,
},
.remove = pmt_crashlog_remove,
.probe = pmt_crashlog_probe,
};
static int __init pmt_crashlog_init(void)
{
return platform_driver_register(&pmt_crashlog_driver);
}
static void __exit pmt_crashlog_exit(void)
{
platform_driver_unregister(&pmt_crashlog_driver);
xa_destroy(&crashlog_array);
}
module_init(pmt_crashlog_init);
module_exit(pmt_crashlog_exit);
MODULE_AUTHOR("Alexander Duyck <alexander.h.duyck@linux.intel.com>");
MODULE_DESCRIPTION("Intel PMT Crashlog driver");
MODULE_ALIAS("platform:" DRV_NAME);
MODULE_LICENSE("GPL v2");
// SPDX-License-Identifier: GPL-2.0
/*
* Intel Platform Monitory Technology Telemetry driver
*
* Copyright (c) 2020, Intel Corporation.
* All Rights Reserved.
*
* Author: "David E. Box" <david.e.box@linux.intel.com>
*/
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/pci.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/overflow.h>
#include "intel_pmt_class.h"
#define TELEM_DEV_NAME "pmt_telemetry"
#define TELEM_SIZE_OFFSET 0x0
#define TELEM_GUID_OFFSET 0x4
#define TELEM_BASE_OFFSET 0x8
#define TELEM_ACCESS(v) ((v) & GENMASK(3, 0))
/* size is in bytes */
#define TELEM_SIZE(v) (((v) & GENMASK(27, 12)) >> 10)
/* Used by client hardware to identify a fixed telemetry entry*/
#define TELEM_CLIENT_FIXED_BLOCK_GUID 0x10000000
struct pmt_telem_priv {
int num_entries;
struct intel_pmt_entry entry[];
};
/*
* Early implementations of PMT on client platforms have some
* differences from the server platforms (which use the Out Of Band
* Management Services Module OOBMSM). This list tracks those
* platforms as needed to handle those differences. Newer client
* platforms are expected to be fully compatible with server.
*/
static const struct pci_device_id pmt_telem_early_client_pci_ids[] = {
{ PCI_VDEVICE(INTEL, 0x9a0d) }, /* TGL */
{ PCI_VDEVICE(INTEL, 0x467d) }, /* ADL */
{ }
};
static bool intel_pmt_is_early_client_hw(struct device *dev)
{
struct pci_dev *parent = to_pci_dev(dev->parent);
return !!pci_match_id(pmt_telem_early_client_pci_ids, parent);
}
static bool pmt_telem_region_overlaps(struct intel_pmt_entry *entry,
struct device *dev)
{
u32 guid = readl(entry->disc_table + TELEM_GUID_OFFSET);
if (guid != TELEM_CLIENT_FIXED_BLOCK_GUID)
return false;
return intel_pmt_is_early_client_hw(dev);
}
static int pmt_telem_header_decode(struct intel_pmt_entry *entry,
struct intel_pmt_header *header,
struct device *dev)
{
void __iomem *disc_table = entry->disc_table;
if (pmt_telem_region_overlaps(entry, dev))
return 1;
header->access_type = TELEM_ACCESS(readl(disc_table));
header->guid = readl(disc_table + TELEM_GUID_OFFSET);
header->base_offset = readl(disc_table + TELEM_BASE_OFFSET);
/* Size is measured in DWORDS, but accessor returns bytes */
header->size = TELEM_SIZE(readl(disc_table));
return 0;
}
static DEFINE_XARRAY_ALLOC(telem_array);
static struct intel_pmt_namespace pmt_telem_ns = {
.name = "telem",
.xa = &telem_array,
.pmt_header_decode = pmt_telem_header_decode,
};
static int pmt_telem_remove(struct platform_device *pdev)
{
struct pmt_telem_priv *priv = platform_get_drvdata(pdev);
int i;
for (i = 0; i < priv->num_entries; i++)
intel_pmt_dev_destroy(&priv->entry[i], &pmt_telem_ns);
return 0;
}
static int pmt_telem_probe(struct platform_device *pdev)
{
struct pmt_telem_priv *priv;
size_t size;
int i, ret;
size = struct_size(priv, entry, pdev->num_resources);
priv = devm_kzalloc(&pdev->dev, size, GFP_KERNEL);
if (!priv)
return -ENOMEM;
platform_set_drvdata(pdev, priv);
for (i = 0; i < pdev->num_resources; i++) {
struct intel_pmt_entry *entry = &priv->entry[i];
ret = intel_pmt_dev_create(entry, &pmt_telem_ns, pdev, i);
if (ret < 0)
goto abort_probe;
if (ret)
continue;
priv->num_entries++;
}
return 0;
abort_probe:
pmt_telem_remove(pdev);
return ret;
}
static struct platform_driver pmt_telem_driver = {
.driver = {
.name = TELEM_DEV_NAME,
},
.remove = pmt_telem_remove,
.probe = pmt_telem_probe,
};
static int __init pmt_telem_init(void)
{
return platform_driver_register(&pmt_telem_driver);
}
module_init(pmt_telem_init);
static void __exit pmt_telem_exit(void)
{
platform_driver_unregister(&pmt_telem_driver);
xa_destroy(&telem_array);
}
module_exit(pmt_telem_exit);
MODULE_AUTHOR("David E. Box <david.e.box@linux.intel.com>");
MODULE_DESCRIPTION("Intel PMT Telemetry driver");
MODULE_ALIAS("platform:" TELEM_DEV_NAME);
MODULE_LICENSE("GPL v2");
......@@ -723,6 +723,7 @@
#define PCI_EXT_CAP_ID_DPC 0x1D /* Downstream Port Containment */
#define PCI_EXT_CAP_ID_L1SS 0x1E /* L1 PM Substates */
#define PCI_EXT_CAP_ID_PTM 0x1F /* Precision Time Measurement */
#define PCI_EXT_CAP_ID_DVSEC 0x23 /* Designated Vendor-Specific */
#define PCI_EXT_CAP_ID_DLF 0x25 /* Data Link Feature */
#define PCI_EXT_CAP_ID_PL_16GT 0x26 /* Physical Layer 16.0 GT/s */
#define PCI_EXT_CAP_ID_MAX PCI_EXT_CAP_ID_PL_16GT
......@@ -1066,6 +1067,10 @@
#define PCI_L1SS_CTL1_LTR_L12_TH_SCALE 0xe0000000 /* LTR_L1.2_THRESHOLD_Scale */
#define PCI_L1SS_CTL2 0x0c /* Control 2 Register */
/* Designated Vendor-Specific (DVSEC, PCI_EXT_CAP_ID_DVSEC) */
#define PCI_DVSEC_HEADER1 0x4 /* Designated Vendor-Specific Header1 */
#define PCI_DVSEC_HEADER2 0x8 /* Designated Vendor-Specific Header2 */
/* Data Link Feature */
#define PCI_DLF_CAP 0x04 /* Capabilities Register */
#define PCI_DLF_EXCHANGE_ENABLE 0x80000000 /* Data Link Feature Exchange Enable */
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment