Back to home page

OSCL-LXR

 
 

    


0001 // SPDX-License-Identifier: GPL-2.0
0002 /*
0003  * Intel Platform Monitoring Technology Crashlog driver
0004  *
0005  * Copyright (c) 2020, Intel Corporation.
0006  * All Rights Reserved.
0007  *
0008  * Author: "Alexander Duyck" <alexander.h.duyck@linux.intel.com>
0009  */
0010 
0011 #include <linux/auxiliary_bus.h>
0012 #include <linux/kernel.h>
0013 #include <linux/module.h>
0014 #include <linux/pci.h>
0015 #include <linux/slab.h>
0016 #include <linux/uaccess.h>
0017 #include <linux/overflow.h>
0018 
0019 #include "../vsec.h"
0020 #include "class.h"
0021 
0022 /* Crashlog discovery header types */
0023 #define CRASH_TYPE_OOBMSM   1
0024 
0025 /* Control Flags */
0026 #define CRASHLOG_FLAG_DISABLE       BIT(28)
0027 
0028 /*
0029  * Bits 29 and 30 control the state of bit 31.
0030  *
0031  * Bit 29 will clear bit 31, if set, allowing a new crashlog to be captured.
0032  * Bit 30 will immediately trigger a crashlog to be generated, setting bit 31.
0033  * Bit 31 is the read-only status with a 1 indicating log is complete.
0034  */
0035 #define CRASHLOG_FLAG_TRIGGER_CLEAR BIT(29)
0036 #define CRASHLOG_FLAG_TRIGGER_EXECUTE   BIT(30)
0037 #define CRASHLOG_FLAG_TRIGGER_COMPLETE  BIT(31)
0038 #define CRASHLOG_FLAG_TRIGGER_MASK  GENMASK(31, 28)
0039 
0040 /* Crashlog Discovery Header */
0041 #define CONTROL_OFFSET      0x0
0042 #define GUID_OFFSET     0x4
0043 #define BASE_OFFSET     0x8
0044 #define SIZE_OFFSET     0xC
0045 #define GET_ACCESS(v)       ((v) & GENMASK(3, 0))
0046 #define GET_TYPE(v)     (((v) & GENMASK(7, 4)) >> 4)
0047 #define GET_VERSION(v)      (((v) & GENMASK(19, 16)) >> 16)
0048 /* size is in bytes */
0049 #define GET_SIZE(v)     ((v) * sizeof(u32))
0050 
0051 struct crashlog_entry {
0052     /* entry must be first member of struct */
0053     struct intel_pmt_entry      entry;
0054     struct mutex            control_mutex;
0055 };
0056 
0057 struct pmt_crashlog_priv {
0058     int         num_entries;
0059     struct crashlog_entry   entry[];
0060 };
0061 
0062 /*
0063  * I/O
0064  */
0065 static bool pmt_crashlog_complete(struct intel_pmt_entry *entry)
0066 {
0067     u32 control = readl(entry->disc_table + CONTROL_OFFSET);
0068 
0069     /* return current value of the crashlog complete flag */
0070     return !!(control & CRASHLOG_FLAG_TRIGGER_COMPLETE);
0071 }
0072 
0073 static bool pmt_crashlog_disabled(struct intel_pmt_entry *entry)
0074 {
0075     u32 control = readl(entry->disc_table + CONTROL_OFFSET);
0076 
0077     /* return current value of the crashlog disabled flag */
0078     return !!(control & CRASHLOG_FLAG_DISABLE);
0079 }
0080 
0081 static bool pmt_crashlog_supported(struct intel_pmt_entry *entry)
0082 {
0083     u32 discovery_header = readl(entry->disc_table + CONTROL_OFFSET);
0084     u32 crash_type, version;
0085 
0086     crash_type = GET_TYPE(discovery_header);
0087     version = GET_VERSION(discovery_header);
0088 
0089     /*
0090      * Currently we only recognize OOBMSM version 0 devices.
0091      * We can ignore all other crashlog devices in the system.
0092      */
0093     return crash_type == CRASH_TYPE_OOBMSM && version == 0;
0094 }
0095 
0096 static void pmt_crashlog_set_disable(struct intel_pmt_entry *entry,
0097                      bool disable)
0098 {
0099     u32 control = readl(entry->disc_table + CONTROL_OFFSET);
0100 
0101     /* clear trigger bits so we are only modifying disable flag */
0102     control &= ~CRASHLOG_FLAG_TRIGGER_MASK;
0103 
0104     if (disable)
0105         control |= CRASHLOG_FLAG_DISABLE;
0106     else
0107         control &= ~CRASHLOG_FLAG_DISABLE;
0108 
0109     writel(control, entry->disc_table + CONTROL_OFFSET);
0110 }
0111 
0112 static void pmt_crashlog_set_clear(struct intel_pmt_entry *entry)
0113 {
0114     u32 control = readl(entry->disc_table + CONTROL_OFFSET);
0115 
0116     control &= ~CRASHLOG_FLAG_TRIGGER_MASK;
0117     control |= CRASHLOG_FLAG_TRIGGER_CLEAR;
0118 
0119     writel(control, entry->disc_table + CONTROL_OFFSET);
0120 }
0121 
0122 static void pmt_crashlog_set_execute(struct intel_pmt_entry *entry)
0123 {
0124     u32 control = readl(entry->disc_table + CONTROL_OFFSET);
0125 
0126     control &= ~CRASHLOG_FLAG_TRIGGER_MASK;
0127     control |= CRASHLOG_FLAG_TRIGGER_EXECUTE;
0128 
0129     writel(control, entry->disc_table + CONTROL_OFFSET);
0130 }
0131 
0132 /*
0133  * sysfs
0134  */
0135 static ssize_t
0136 enable_show(struct device *dev, struct device_attribute *attr, char *buf)
0137 {
0138     struct intel_pmt_entry *entry = dev_get_drvdata(dev);
0139     int enabled = !pmt_crashlog_disabled(entry);
0140 
0141     return sprintf(buf, "%d\n", enabled);
0142 }
0143 
0144 static ssize_t
0145 enable_store(struct device *dev, struct device_attribute *attr,
0146         const char *buf, size_t count)
0147 {
0148     struct crashlog_entry *entry;
0149     bool enabled;
0150     int result;
0151 
0152     entry = dev_get_drvdata(dev);
0153 
0154     result = kstrtobool(buf, &enabled);
0155     if (result)
0156         return result;
0157 
0158     mutex_lock(&entry->control_mutex);
0159     pmt_crashlog_set_disable(&entry->entry, !enabled);
0160     mutex_unlock(&entry->control_mutex);
0161 
0162     return count;
0163 }
0164 static DEVICE_ATTR_RW(enable);
0165 
0166 static ssize_t
0167 trigger_show(struct device *dev, struct device_attribute *attr, char *buf)
0168 {
0169     struct intel_pmt_entry *entry;
0170     int trigger;
0171 
0172     entry = dev_get_drvdata(dev);
0173     trigger = pmt_crashlog_complete(entry);
0174 
0175     return sprintf(buf, "%d\n", trigger);
0176 }
0177 
0178 static ssize_t
0179 trigger_store(struct device *dev, struct device_attribute *attr,
0180         const char *buf, size_t count)
0181 {
0182     struct crashlog_entry *entry;
0183     bool trigger;
0184     int result;
0185 
0186     entry = dev_get_drvdata(dev);
0187 
0188     result = kstrtobool(buf, &trigger);
0189     if (result)
0190         return result;
0191 
0192     mutex_lock(&entry->control_mutex);
0193 
0194     if (!trigger) {
0195         pmt_crashlog_set_clear(&entry->entry);
0196     } else if (pmt_crashlog_complete(&entry->entry)) {
0197         /* we cannot trigger a new crash if one is still pending */
0198         result = -EEXIST;
0199         goto err;
0200     } else if (pmt_crashlog_disabled(&entry->entry)) {
0201         /* if device is currently disabled, return busy */
0202         result = -EBUSY;
0203         goto err;
0204     } else {
0205         pmt_crashlog_set_execute(&entry->entry);
0206     }
0207 
0208     result = count;
0209 err:
0210     mutex_unlock(&entry->control_mutex);
0211     return result;
0212 }
0213 static DEVICE_ATTR_RW(trigger);
0214 
0215 static struct attribute *pmt_crashlog_attrs[] = {
0216     &dev_attr_enable.attr,
0217     &dev_attr_trigger.attr,
0218     NULL
0219 };
0220 
0221 static const struct attribute_group pmt_crashlog_group = {
0222     .attrs  = pmt_crashlog_attrs,
0223 };
0224 
0225 static int pmt_crashlog_header_decode(struct intel_pmt_entry *entry,
0226                       struct intel_pmt_header *header,
0227                       struct device *dev)
0228 {
0229     void __iomem *disc_table = entry->disc_table;
0230     struct crashlog_entry *crashlog;
0231 
0232     if (!pmt_crashlog_supported(entry))
0233         return 1;
0234 
0235     /* initialize control mutex */
0236     crashlog = container_of(entry, struct crashlog_entry, entry);
0237     mutex_init(&crashlog->control_mutex);
0238 
0239     header->access_type = GET_ACCESS(readl(disc_table));
0240     header->guid = readl(disc_table + GUID_OFFSET);
0241     header->base_offset = readl(disc_table + BASE_OFFSET);
0242 
0243     /* Size is measured in DWORDS, but accessor returns bytes */
0244     header->size = GET_SIZE(readl(disc_table + SIZE_OFFSET));
0245 
0246     return 0;
0247 }
0248 
0249 static DEFINE_XARRAY_ALLOC(crashlog_array);
0250 static struct intel_pmt_namespace pmt_crashlog_ns = {
0251     .name = "crashlog",
0252     .xa = &crashlog_array,
0253     .attr_grp = &pmt_crashlog_group,
0254     .pmt_header_decode = pmt_crashlog_header_decode,
0255 };
0256 
0257 /*
0258  * initialization
0259  */
0260 static void pmt_crashlog_remove(struct auxiliary_device *auxdev)
0261 {
0262     struct pmt_crashlog_priv *priv = auxiliary_get_drvdata(auxdev);
0263     int i;
0264 
0265     for (i = 0; i < priv->num_entries; i++)
0266         intel_pmt_dev_destroy(&priv->entry[i].entry, &pmt_crashlog_ns);
0267 }
0268 
0269 static int pmt_crashlog_probe(struct auxiliary_device *auxdev,
0270                   const struct auxiliary_device_id *id)
0271 {
0272     struct intel_vsec_device *intel_vsec_dev = auxdev_to_ivdev(auxdev);
0273     struct pmt_crashlog_priv *priv;
0274     size_t size;
0275     int i, ret;
0276 
0277     size = struct_size(priv, entry, intel_vsec_dev->num_resources);
0278     priv = devm_kzalloc(&auxdev->dev, size, GFP_KERNEL);
0279     if (!priv)
0280         return -ENOMEM;
0281 
0282     auxiliary_set_drvdata(auxdev, priv);
0283 
0284     for (i = 0; i < intel_vsec_dev->num_resources; i++) {
0285         struct intel_pmt_entry *entry = &priv->entry[priv->num_entries].entry;
0286 
0287         ret = intel_pmt_dev_create(entry, &pmt_crashlog_ns, intel_vsec_dev, i);
0288         if (ret < 0)
0289             goto abort_probe;
0290         if (ret)
0291             continue;
0292 
0293         priv->num_entries++;
0294     }
0295 
0296     return 0;
0297 abort_probe:
0298     pmt_crashlog_remove(auxdev);
0299     return ret;
0300 }
0301 
0302 static const struct auxiliary_device_id pmt_crashlog_id_table[] = {
0303     { .name = "intel_vsec.crashlog" },
0304     {}
0305 };
0306 MODULE_DEVICE_TABLE(auxiliary, pmt_crashlog_id_table);
0307 
0308 static struct auxiliary_driver pmt_crashlog_aux_driver = {
0309     .id_table   = pmt_crashlog_id_table,
0310     .remove     = pmt_crashlog_remove,
0311     .probe      = pmt_crashlog_probe,
0312 };
0313 
0314 static int __init pmt_crashlog_init(void)
0315 {
0316     return auxiliary_driver_register(&pmt_crashlog_aux_driver);
0317 }
0318 
0319 static void __exit pmt_crashlog_exit(void)
0320 {
0321     auxiliary_driver_unregister(&pmt_crashlog_aux_driver);
0322     xa_destroy(&crashlog_array);
0323 }
0324 
0325 module_init(pmt_crashlog_init);
0326 module_exit(pmt_crashlog_exit);
0327 
0328 MODULE_AUTHOR("Alexander Duyck <alexander.h.duyck@linux.intel.com>");
0329 MODULE_DESCRIPTION("Intel PMT Crashlog driver");
0330 MODULE_LICENSE("GPL v2");