// 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 #include #include #include #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 "); MODULE_DESCRIPTION("Intel PMT Crashlog driver"); MODULE_LICENSE("GPL v2"); MODULE_IMPORT_NS("INTEL_PMT");