// SPDX-License-Identifier: GPL-2.0 /* * Intel Platform Monitoring Technology Crashlog driver * * Copyright (c) 2020, Intel Corporation. * All Rights Reserved. * * Author: "Alexander Duyck" */ #include #include #include #include #include #include #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(28) /* * 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 CRASHLOG_FLAG_TRIGGER_CLEAR BIT(29) #define CRASHLOG_FLAG_TRIGGER_EXECUTE BIT(30) #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 const 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 "); MODULE_DESCRIPTION("Intel PMT Crashlog driver"); MODULE_ALIAS("platform:" DRV_NAME); MODULE_LICENSE("GPL v2");