Immutable branch between MFD and x86 due for the v5.11 merge window
-----BEGIN PGP SIGNATURE----- iQIzBAABCgAdFiEEdrbJNaO+IJqU8IdIUa+KL4f8d2EFAl+ijTcACgkQUa+KL4f8 d2E+3w//Q6gB4sQ0wrhrs+R04sP9FTj/A1XFGMitIFhESsEClcvIKzB2Qh/ZjEk4 tHObkjIbA1FYaZdxq0MKw1kBbH400CIe3GO1Bkla2E4hONTx2bqJWqPmp099Yibl dMUijTSdefRrVMGUXTmSVftIPvv1lxDmc7YfmD+Y6E/MLgA9wqjXo+eOfbW6S8x/ SoSqK86WGNJpGCaUuK1diZ/3MkZG7/PT13HKxU5cGAkXTpSjPREB9IVdxEVHLo7q lvOzx3FGajWqEhDhswUZqSq7hGlH9NxbM456fBu3KbUvurXjPsotBeLmu2/5+2SW XBrEKT31a3On3KG7FZe/F3wg85etJkVHx7C1qycAAfKRbcwu9F9Z1g0VrbwDfTDq mkWHbzT3qXd50gTGXpCSUeDDTLBVXP65eZUHCdyUOIDUUphx8PIlsyPsqfy0hUxe SEMlCfAmQ5owlBT2EGC/6v4pENJn0IjJFMl+KJsPraIyjqS5ftfcr07BjTM65h+w Znq3gbKYKT//xODLCkvG6NDYpvr6LsqWYPoRHKg0Gd6FvgKKjl/Bfkq9ZmeFDint v2qKBfB+PtGKnKhnHmI5sseaUhsxy44jQ72wmI0InSwMoEj5pZFqs0EXfJyu/YJl q/gu9h3u+cJWc1D41LqrOIUeyjW2/K512cKf7tAU1QpceYumgQs= =l+RW -----END PGP SIGNATURE----- Merge tag 'ib-mfd-x86-v5.11' into review-hans Immutable branch between MFD and x86 due for the v5.11 merge window
This commit is contained in:
commit
91de32fe6d
119
Documentation/ABI/testing/sysfs-class-intel_pmt
Normal file
119
Documentation/ABI/testing/sysfs-class-intel_pmt
Normal file
@ -0,0 +1,119 @@
|
||||
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
|
||||
|
223
drivers/mfd/intel_pmt.c
Normal file
223
drivers/mfd/intel_pmt.c
Normal file
@ -0,0 +1,223 @@
|
||||
// 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
|
||||
|
297
drivers/platform/x86/intel_pmt_class.c
Normal file
297
drivers/platform/x86/intel_pmt_class.c
Normal file
@ -0,0 +1,297 @@
|
||||
// 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");
|
52
drivers/platform/x86/intel_pmt_class.h
Normal file
52
drivers/platform/x86/intel_pmt_class.h
Normal file
@ -0,0 +1,52 @@
|
||||
/* 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
|
328
drivers/platform/x86/intel_pmt_crashlog.c
Normal file
328
drivers/platform/x86/intel_pmt_crashlog.c
Normal file
@ -0,0 +1,328 @@
|
||||
// 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");
|
160
drivers/platform/x86/intel_pmt_telemetry.c
Normal file
160
drivers/platform/x86/intel_pmt_telemetry.c
Normal file
@ -0,0 +1,160 @@
|
||||
// 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 */
|
||||
|
Loading…
Reference in New Issue
Block a user