mirror of
git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2025-08-05 16:54:27 +00:00

The Battlemage GPU has the type 1 version 2 crashlog feature. Update the crashlog driver to support this crashlog version. Signed-off-by: Michael J. Ruhl <michael.j.ruhl@intel.com> Link: https://lore.kernel.org/r/20250713172943.7335-14-michael.j.ruhl@intel.com [ij: make crashlog_type1_ver2 static] Reviewed-by: Ilpo Järvinen <ilpo.jarvinen@linux.intel.com> Signed-off-by: Ilpo Järvinen <ilpo.jarvinen@linux.intel.com>
614 lines
15 KiB
C
614 lines
15 KiB
C
// 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/auxiliary_bus.h>
|
|
#include <linux/cleanup.h>
|
|
#include <linux/intel_vsec.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/module.h>
|
|
#include <linux/mutex.h>
|
|
#include <linux/pci.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/uaccess.h>
|
|
#include <linux/overflow.h>
|
|
|
|
#include "class.h"
|
|
|
|
/* Crashlog discovery header types */
|
|
#define CRASH_TYPE_OOBMSM 1
|
|
|
|
/* 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))
|
|
|
|
/*
|
|
* Type 1 Version 0
|
|
* status and control registers are combined.
|
|
*
|
|
* Bits 29 and 30 control the state of bit 31.
|
|
* Bit 29 will clear bit 31, if set, allowing a new crashlog to be captured.
|
|
* Bit 30 will immediately trigger a crashlog to be generated, setting bit 31.
|
|
* Bit 31 is the read-only status with a 1 indicating log is complete.
|
|
*/
|
|
#define TYPE1_VER0_STATUS_OFFSET 0x00
|
|
#define TYPE1_VER0_CONTROL_OFFSET 0x00
|
|
|
|
#define TYPE1_VER0_DISABLE BIT(28)
|
|
#define TYPE1_VER0_CLEAR BIT(29)
|
|
#define TYPE1_VER0_EXECUTE BIT(30)
|
|
#define TYPE1_VER0_COMPLETE BIT(31)
|
|
#define TYPE1_VER0_TRIGGER_MASK GENMASK(31, 28)
|
|
|
|
/*
|
|
* Type 1 Version 2
|
|
* status and control are different registers
|
|
*/
|
|
#define TYPE1_VER2_STATUS_OFFSET 0x00
|
|
#define TYPE1_VER2_CONTROL_OFFSET 0x14
|
|
|
|
/* status register */
|
|
#define TYPE1_VER2_CLEAR_SUPPORT BIT(20)
|
|
#define TYPE1_VER2_REARMED BIT(25)
|
|
#define TYPE1_VER2_ERROR BIT(26)
|
|
#define TYPE1_VER2_CONSUMED BIT(27)
|
|
#define TYPE1_VER2_DISABLED BIT(28)
|
|
#define TYPE1_VER2_CLEARED BIT(29)
|
|
#define TYPE1_VER2_IN_PROGRESS BIT(30)
|
|
#define TYPE1_VER2_COMPLETE BIT(31)
|
|
|
|
/* control register */
|
|
#define TYPE1_VER2_CONSUME BIT(25)
|
|
#define TYPE1_VER2_REARM BIT(28)
|
|
#define TYPE1_VER2_EXECUTE BIT(29)
|
|
#define TYPE1_VER2_CLEAR BIT(30)
|
|
#define TYPE1_VER2_DISABLE BIT(31)
|
|
#define TYPE1_VER2_TRIGGER_MASK \
|
|
(TYPE1_VER2_EXECUTE | TYPE1_VER2_CLEAR | TYPE1_VER2_DISABLE)
|
|
|
|
/* After offset, order alphabetically, not bit ordered */
|
|
struct crashlog_status {
|
|
u32 offset;
|
|
u32 clear_supported;
|
|
u32 cleared;
|
|
u32 complete;
|
|
u32 consumed;
|
|
u32 disabled;
|
|
u32 error;
|
|
u32 in_progress;
|
|
u32 rearmed;
|
|
};
|
|
|
|
struct crashlog_control {
|
|
u32 offset;
|
|
u32 trigger_mask;
|
|
u32 clear;
|
|
u32 consume;
|
|
u32 disable;
|
|
u32 manual;
|
|
u32 rearm;
|
|
};
|
|
|
|
struct crashlog_info {
|
|
const struct crashlog_status status;
|
|
const struct crashlog_control control;
|
|
const struct attribute_group *attr_grp;
|
|
};
|
|
|
|
struct crashlog_entry {
|
|
/* entry must be first member of struct */
|
|
struct intel_pmt_entry entry;
|
|
struct mutex control_mutex;
|
|
const struct crashlog_info *info;
|
|
};
|
|
|
|
struct pmt_crashlog_priv {
|
|
int num_entries;
|
|
struct crashlog_entry entry[];
|
|
};
|
|
|
|
/*
|
|
* I/O
|
|
*/
|
|
|
|
/* Read, modify, write the control register, setting or clearing @bit based on @set */
|
|
static void pmt_crashlog_rmw(struct crashlog_entry *crashlog, u32 bit, bool set)
|
|
{
|
|
const struct crashlog_control *control = &crashlog->info->control;
|
|
struct intel_pmt_entry *entry = &crashlog->entry;
|
|
u32 reg = readl(entry->disc_table + control->offset);
|
|
|
|
reg &= ~control->trigger_mask;
|
|
|
|
if (set)
|
|
reg |= bit;
|
|
else
|
|
reg &= ~bit;
|
|
|
|
writel(reg, entry->disc_table + control->offset);
|
|
}
|
|
|
|
/* Read the status register and see if the specified @bit is set */
|
|
static bool pmt_crashlog_rc(struct crashlog_entry *crashlog, u32 bit)
|
|
{
|
|
const struct crashlog_status *status = &crashlog->info->status;
|
|
u32 reg = readl(crashlog->entry.disc_table + status->offset);
|
|
|
|
return !!(reg & bit);
|
|
}
|
|
|
|
static bool pmt_crashlog_complete(struct crashlog_entry *crashlog)
|
|
{
|
|
/* return current value of the crashlog complete flag */
|
|
return pmt_crashlog_rc(crashlog, crashlog->info->status.complete);
|
|
}
|
|
|
|
static bool pmt_crashlog_disabled(struct crashlog_entry *crashlog)
|
|
{
|
|
/* return current value of the crashlog disabled flag */
|
|
return pmt_crashlog_rc(crashlog, crashlog->info->status.disabled);
|
|
}
|
|
|
|
static bool pmt_crashlog_supported(struct intel_pmt_entry *entry, u32 *crash_type, u32 *version)
|
|
{
|
|
u32 discovery_header = readl(entry->disc_table + CONTROL_OFFSET);
|
|
|
|
*crash_type = GET_TYPE(discovery_header);
|
|
*version = GET_VERSION(discovery_header);
|
|
|
|
/*
|
|
* Currently we only recognize OOBMSM (type 1) and version 0 or 2
|
|
* devices.
|
|
*
|
|
* Ignore all other crashlog devices in the system.
|
|
*/
|
|
if (*crash_type == CRASH_TYPE_OOBMSM && (*version == 0 || *version == 2))
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
static void pmt_crashlog_set_disable(struct crashlog_entry *crashlog,
|
|
bool disable)
|
|
{
|
|
pmt_crashlog_rmw(crashlog, crashlog->info->control.disable, disable);
|
|
}
|
|
|
|
static void pmt_crashlog_set_clear(struct crashlog_entry *crashlog)
|
|
{
|
|
pmt_crashlog_rmw(crashlog, crashlog->info->control.clear, true);
|
|
}
|
|
|
|
static void pmt_crashlog_set_execute(struct crashlog_entry *crashlog)
|
|
{
|
|
pmt_crashlog_rmw(crashlog, crashlog->info->control.manual, true);
|
|
}
|
|
|
|
static bool pmt_crashlog_cleared(struct crashlog_entry *crashlog)
|
|
{
|
|
return pmt_crashlog_rc(crashlog, crashlog->info->status.cleared);
|
|
}
|
|
|
|
static bool pmt_crashlog_consumed(struct crashlog_entry *crashlog)
|
|
{
|
|
return pmt_crashlog_rc(crashlog, crashlog->info->status.consumed);
|
|
}
|
|
|
|
static void pmt_crashlog_set_consumed(struct crashlog_entry *crashlog)
|
|
{
|
|
pmt_crashlog_rmw(crashlog, crashlog->info->control.consume, true);
|
|
}
|
|
|
|
static bool pmt_crashlog_error(struct crashlog_entry *crashlog)
|
|
{
|
|
return pmt_crashlog_rc(crashlog, crashlog->info->status.error);
|
|
}
|
|
|
|
static bool pmt_crashlog_rearm(struct crashlog_entry *crashlog)
|
|
{
|
|
return pmt_crashlog_rc(crashlog, crashlog->info->status.rearmed);
|
|
}
|
|
|
|
static void pmt_crashlog_set_rearm(struct crashlog_entry *crashlog)
|
|
{
|
|
pmt_crashlog_rmw(crashlog, crashlog->info->control.rearm, true);
|
|
}
|
|
|
|
/*
|
|
* sysfs
|
|
*/
|
|
static ssize_t
|
|
clear_show(struct device *dev, struct device_attribute *attr, char *buf)
|
|
{
|
|
struct crashlog_entry *crashlog = dev_get_drvdata(dev);
|
|
bool cleared = pmt_crashlog_cleared(crashlog);
|
|
|
|
return sysfs_emit(buf, "%d\n", cleared);
|
|
}
|
|
|
|
static ssize_t
|
|
clear_store(struct device *dev, struct device_attribute *attr,
|
|
const char *buf, size_t count)
|
|
{
|
|
struct crashlog_entry *crashlog;
|
|
bool clear;
|
|
int result;
|
|
|
|
crashlog = dev_get_drvdata(dev);
|
|
|
|
result = kstrtobool(buf, &clear);
|
|
if (result)
|
|
return result;
|
|
|
|
/* set bit only */
|
|
if (!clear)
|
|
return -EINVAL;
|
|
|
|
guard(mutex)(&crashlog->control_mutex);
|
|
|
|
pmt_crashlog_set_clear(crashlog);
|
|
|
|
return count;
|
|
}
|
|
static DEVICE_ATTR_RW(clear);
|
|
|
|
static ssize_t
|
|
consumed_show(struct device *dev, struct device_attribute *attr, char *buf)
|
|
{
|
|
struct crashlog_entry *crashlog = dev_get_drvdata(dev);
|
|
bool consumed = pmt_crashlog_consumed(crashlog);
|
|
|
|
return sysfs_emit(buf, "%d\n", consumed);
|
|
}
|
|
|
|
static ssize_t
|
|
consumed_store(struct device *dev, struct device_attribute *attr, const char *buf,
|
|
size_t count)
|
|
{
|
|
struct crashlog_entry *crashlog;
|
|
bool consumed;
|
|
int result;
|
|
|
|
crashlog = dev_get_drvdata(dev);
|
|
|
|
result = kstrtobool(buf, &consumed);
|
|
if (result)
|
|
return result;
|
|
|
|
/* set bit only */
|
|
if (!consumed)
|
|
return -EINVAL;
|
|
|
|
guard(mutex)(&crashlog->control_mutex);
|
|
|
|
if (pmt_crashlog_disabled(crashlog))
|
|
return -EBUSY;
|
|
|
|
if (!pmt_crashlog_complete(crashlog))
|
|
return -EEXIST;
|
|
|
|
pmt_crashlog_set_consumed(crashlog);
|
|
|
|
return count;
|
|
}
|
|
static DEVICE_ATTR_RW(consumed);
|
|
|
|
static ssize_t
|
|
enable_show(struct device *dev, struct device_attribute *attr, char *buf)
|
|
{
|
|
struct crashlog_entry *crashlog = dev_get_drvdata(dev);
|
|
bool enabled = !pmt_crashlog_disabled(crashlog);
|
|
|
|
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 *crashlog;
|
|
bool enabled;
|
|
int result;
|
|
|
|
crashlog = dev_get_drvdata(dev);
|
|
|
|
result = kstrtobool(buf, &enabled);
|
|
if (result)
|
|
return result;
|
|
|
|
guard(mutex)(&crashlog->control_mutex);
|
|
|
|
pmt_crashlog_set_disable(crashlog, !enabled);
|
|
|
|
return count;
|
|
}
|
|
static DEVICE_ATTR_RW(enable);
|
|
|
|
static ssize_t
|
|
error_show(struct device *dev, struct device_attribute *attr, char *buf)
|
|
{
|
|
struct crashlog_entry *crashlog = dev_get_drvdata(dev);
|
|
bool error = pmt_crashlog_error(crashlog);
|
|
|
|
return sysfs_emit(buf, "%d\n", error);
|
|
}
|
|
static DEVICE_ATTR_RO(error);
|
|
|
|
static ssize_t
|
|
rearm_show(struct device *dev, struct device_attribute *attr, char *buf)
|
|
{
|
|
struct crashlog_entry *crashlog = dev_get_drvdata(dev);
|
|
int rearmed = pmt_crashlog_rearm(crashlog);
|
|
|
|
return sysfs_emit(buf, "%d\n", rearmed);
|
|
}
|
|
|
|
static ssize_t
|
|
rearm_store(struct device *dev, struct device_attribute *attr, const char *buf,
|
|
size_t count)
|
|
{
|
|
struct crashlog_entry *crashlog;
|
|
bool rearm;
|
|
int result;
|
|
|
|
crashlog = dev_get_drvdata(dev);
|
|
|
|
result = kstrtobool(buf, &rearm);
|
|
if (result)
|
|
return result;
|
|
|
|
/* set only */
|
|
if (!rearm)
|
|
return -EINVAL;
|
|
|
|
guard(mutex)(&crashlog->control_mutex);
|
|
|
|
pmt_crashlog_set_rearm(crashlog);
|
|
|
|
return count;
|
|
}
|
|
static DEVICE_ATTR_RW(rearm);
|
|
|
|
static ssize_t
|
|
trigger_show(struct device *dev, struct device_attribute *attr, char *buf)
|
|
{
|
|
struct crashlog_entry *crashlog;
|
|
bool trigger;
|
|
|
|
crashlog = dev_get_drvdata(dev);
|
|
trigger = pmt_crashlog_complete(crashlog);
|
|
|
|
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 *crashlog;
|
|
bool trigger;
|
|
int result;
|
|
|
|
crashlog = dev_get_drvdata(dev);
|
|
|
|
result = kstrtobool(buf, &trigger);
|
|
if (result)
|
|
return result;
|
|
|
|
guard(mutex)(&crashlog->control_mutex);
|
|
|
|
/* if device is currently disabled, return busy */
|
|
if (pmt_crashlog_disabled(crashlog))
|
|
return -EBUSY;
|
|
|
|
if (!trigger) {
|
|
pmt_crashlog_set_clear(crashlog);
|
|
return count;
|
|
}
|
|
|
|
/* we cannot trigger a new crash if one is still pending */
|
|
if (pmt_crashlog_complete(crashlog))
|
|
return -EEXIST;
|
|
|
|
pmt_crashlog_set_execute(crashlog);
|
|
|
|
return count;
|
|
}
|
|
static DEVICE_ATTR_RW(trigger);
|
|
|
|
static struct attribute *pmt_crashlog_type1_ver0_attrs[] = {
|
|
&dev_attr_enable.attr,
|
|
&dev_attr_trigger.attr,
|
|
NULL
|
|
};
|
|
|
|
static struct attribute *pmt_crashlog_type1_ver2_attrs[] = {
|
|
&dev_attr_clear.attr,
|
|
&dev_attr_consumed.attr,
|
|
&dev_attr_enable.attr,
|
|
&dev_attr_error.attr,
|
|
&dev_attr_rearm.attr,
|
|
&dev_attr_trigger.attr,
|
|
NULL
|
|
};
|
|
|
|
static const struct attribute_group pmt_crashlog_type1_ver0_group = {
|
|
.attrs = pmt_crashlog_type1_ver0_attrs,
|
|
};
|
|
|
|
static const struct attribute_group pmt_crashlog_type1_ver2_group = {
|
|
.attrs = pmt_crashlog_type1_ver2_attrs,
|
|
};
|
|
|
|
static const struct crashlog_info crashlog_type1_ver0 = {
|
|
.status.offset = TYPE1_VER0_STATUS_OFFSET,
|
|
.status.cleared = TYPE1_VER0_CLEAR,
|
|
.status.complete = TYPE1_VER0_COMPLETE,
|
|
.status.disabled = TYPE1_VER0_DISABLE,
|
|
|
|
.control.offset = TYPE1_VER0_CONTROL_OFFSET,
|
|
.control.trigger_mask = TYPE1_VER0_TRIGGER_MASK,
|
|
.control.clear = TYPE1_VER0_CLEAR,
|
|
.control.disable = TYPE1_VER0_DISABLE,
|
|
.control.manual = TYPE1_VER0_EXECUTE,
|
|
.attr_grp = &pmt_crashlog_type1_ver0_group,
|
|
};
|
|
|
|
static const struct crashlog_info crashlog_type1_ver2 = {
|
|
.status.offset = TYPE1_VER2_STATUS_OFFSET,
|
|
.status.clear_supported = TYPE1_VER2_CLEAR_SUPPORT,
|
|
.status.cleared = TYPE1_VER2_CLEARED,
|
|
.status.complete = TYPE1_VER2_COMPLETE,
|
|
.status.consumed = TYPE1_VER2_CONSUMED,
|
|
.status.disabled = TYPE1_VER2_DISABLED,
|
|
.status.error = TYPE1_VER2_ERROR,
|
|
.status.in_progress = TYPE1_VER2_IN_PROGRESS,
|
|
.status.rearmed = TYPE1_VER2_REARMED,
|
|
|
|
.control.offset = TYPE1_VER2_CONTROL_OFFSET,
|
|
.control.trigger_mask = TYPE1_VER2_TRIGGER_MASK,
|
|
.control.clear = TYPE1_VER2_CLEAR,
|
|
.control.consume = TYPE1_VER2_CONSUME,
|
|
.control.disable = TYPE1_VER2_DISABLE,
|
|
.control.manual = TYPE1_VER2_EXECUTE,
|
|
.control.rearm = TYPE1_VER2_REARM,
|
|
.attr_grp = &pmt_crashlog_type1_ver2_group,
|
|
};
|
|
|
|
static const struct crashlog_info *select_crashlog_info(u32 type, u32 version)
|
|
{
|
|
if (version == 0)
|
|
return &crashlog_type1_ver0;
|
|
|
|
return &crashlog_type1_ver2;
|
|
}
|
|
|
|
static int pmt_crashlog_header_decode(struct intel_pmt_entry *entry,
|
|
struct device *dev)
|
|
{
|
|
void __iomem *disc_table = entry->disc_table;
|
|
struct intel_pmt_header *header = &entry->header;
|
|
struct crashlog_entry *crashlog;
|
|
u32 version;
|
|
u32 type;
|
|
|
|
if (!pmt_crashlog_supported(entry, &type, &version))
|
|
return 1;
|
|
|
|
/* initialize the crashlog struct */
|
|
crashlog = container_of(entry, struct crashlog_entry, entry);
|
|
mutex_init(&crashlog->control_mutex);
|
|
|
|
crashlog->info = select_crashlog_info(type, version);
|
|
|
|
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));
|
|
|
|
entry->attr_grp = crashlog->info->attr_grp;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static DEFINE_XARRAY_ALLOC(crashlog_array);
|
|
static struct intel_pmt_namespace pmt_crashlog_ns = {
|
|
.name = "crashlog",
|
|
.xa = &crashlog_array,
|
|
.pmt_header_decode = pmt_crashlog_header_decode,
|
|
};
|
|
|
|
/*
|
|
* initialization
|
|
*/
|
|
static void pmt_crashlog_remove(struct auxiliary_device *auxdev)
|
|
{
|
|
struct pmt_crashlog_priv *priv = auxiliary_get_drvdata(auxdev);
|
|
int i;
|
|
|
|
for (i = 0; i < priv->num_entries; i++) {
|
|
struct crashlog_entry *crashlog = &priv->entry[i];
|
|
|
|
intel_pmt_dev_destroy(&crashlog->entry, &pmt_crashlog_ns);
|
|
mutex_destroy(&crashlog->control_mutex);
|
|
}
|
|
}
|
|
|
|
static int pmt_crashlog_probe(struct auxiliary_device *auxdev,
|
|
const struct auxiliary_device_id *id)
|
|
{
|
|
struct intel_vsec_device *intel_vsec_dev = auxdev_to_ivdev(auxdev);
|
|
struct pmt_crashlog_priv *priv;
|
|
size_t size;
|
|
int i, ret;
|
|
|
|
size = struct_size(priv, entry, intel_vsec_dev->num_resources);
|
|
priv = devm_kzalloc(&auxdev->dev, size, GFP_KERNEL);
|
|
if (!priv)
|
|
return -ENOMEM;
|
|
|
|
auxiliary_set_drvdata(auxdev, priv);
|
|
|
|
for (i = 0; i < intel_vsec_dev->num_resources; i++) {
|
|
struct intel_pmt_entry *entry = &priv->entry[priv->num_entries].entry;
|
|
|
|
ret = intel_pmt_dev_create(entry, &pmt_crashlog_ns, intel_vsec_dev, i);
|
|
if (ret < 0)
|
|
goto abort_probe;
|
|
if (ret)
|
|
continue;
|
|
|
|
priv->num_entries++;
|
|
}
|
|
|
|
return 0;
|
|
abort_probe:
|
|
pmt_crashlog_remove(auxdev);
|
|
return ret;
|
|
}
|
|
|
|
static const struct auxiliary_device_id pmt_crashlog_id_table[] = {
|
|
{ .name = "intel_vsec.crashlog" },
|
|
{}
|
|
};
|
|
MODULE_DEVICE_TABLE(auxiliary, pmt_crashlog_id_table);
|
|
|
|
static struct auxiliary_driver pmt_crashlog_aux_driver = {
|
|
.id_table = pmt_crashlog_id_table,
|
|
.remove = pmt_crashlog_remove,
|
|
.probe = pmt_crashlog_probe,
|
|
};
|
|
|
|
static int __init pmt_crashlog_init(void)
|
|
{
|
|
return auxiliary_driver_register(&pmt_crashlog_aux_driver);
|
|
}
|
|
|
|
static void __exit pmt_crashlog_exit(void)
|
|
{
|
|
auxiliary_driver_unregister(&pmt_crashlog_aux_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_LICENSE("GPL v2");
|
|
MODULE_IMPORT_NS("INTEL_PMT");
|