Commit 5ef9998c authored by Alexander Duyck's avatar Alexander Duyck Committed by Lee Jones
Browse files

platform/x86: Intel PMT Crashlog capability driver



Add support for the Intel Platform Monitoring Technology crashlog
interface. This interface provides a few sysfs values to allow for
controlling the crashlog telemetry interface as well as a character
driver to allow for mapping the crashlog memory region so that it can be
accessed after a crashlog has been recorded.

This driver is meant to only support the server version of the crashlog
which is identified as crash_type 1 with a version of zero. Currently no
other types are supported.

Signed-off-by: default avatarAlexander Duyck <alexander.h.duyck@linux.intel.com>
Signed-off-by: default avatarDavid E. Box <david.e.box@linux.intel.com>
Reviewed-by: default avatarHans de Goede <hdegoede@redhat.com>
Signed-off-by: default avatarLee Jones <lee.jones@linaro.org>
parent 68fe8e6e
Loading
Loading
Loading
Loading
+65 −0
Original line number Diff line number Diff line
@@ -52,3 +52,68 @@ 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.
+11 −0
Original line number Diff line number Diff line
@@ -1385,6 +1385,17 @@ config INTEL_PMT_TELEMETRY
	  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
+1 −0
Original line number Diff line number Diff line
@@ -142,6 +142,7 @@ 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
+328 −0
Original line number Diff line number Diff line
// 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");