1 // SPDX-License-Identifier: GPL-2.0 2 /* 3 * Intel Platform Monitoring Technology Crashlog driver 4 * 5 * Copyright (c) 2020, Intel Corporation. 6 * All Rights Reserved. 7 * 8 * Author: "Alexander Duyck" <alexander.h.duyck@linux.intel.com> 9 */ 10 11 #include <linux/auxiliary_bus.h> 12 #include <linux/intel_vsec.h> 13 #include <linux/kernel.h> 14 #include <linux/module.h> 15 #include <linux/mutex.h> 16 #include <linux/pci.h> 17 #include <linux/slab.h> 18 #include <linux/uaccess.h> 19 #include <linux/overflow.h> 20 21 #include "class.h" 22 23 /* Crashlog discovery header types */ 24 #define CRASH_TYPE_OOBMSM 1 25 26 /* Control Flags */ 27 #define CRASHLOG_FLAG_DISABLE BIT(28) 28 29 /* 30 * Bits 29 and 30 control the state of bit 31. 31 * 32 * Bit 29 will clear bit 31, if set, allowing a new crashlog to be captured. 33 * Bit 30 will immediately trigger a crashlog to be generated, setting bit 31. 34 * Bit 31 is the read-only status with a 1 indicating log is complete. 35 */ 36 #define CRASHLOG_FLAG_TRIGGER_CLEAR BIT(29) 37 #define CRASHLOG_FLAG_TRIGGER_EXECUTE BIT(30) 38 #define CRASHLOG_FLAG_TRIGGER_COMPLETE BIT(31) 39 #define CRASHLOG_FLAG_TRIGGER_MASK GENMASK(31, 28) 40 41 /* Crashlog Discovery Header */ 42 #define CONTROL_OFFSET 0x0 43 #define GUID_OFFSET 0x4 44 #define BASE_OFFSET 0x8 45 #define SIZE_OFFSET 0xC 46 #define GET_ACCESS(v) ((v) & GENMASK(3, 0)) 47 #define GET_TYPE(v) (((v) & GENMASK(7, 4)) >> 4) 48 #define GET_VERSION(v) (((v) & GENMASK(19, 16)) >> 16) 49 /* size is in bytes */ 50 #define GET_SIZE(v) ((v) * sizeof(u32)) 51 52 struct crashlog_entry { 53 /* entry must be first member of struct */ 54 struct intel_pmt_entry entry; 55 struct mutex control_mutex; 56 }; 57 58 struct pmt_crashlog_priv { 59 int num_entries; 60 struct crashlog_entry entry[]; 61 }; 62 63 /* 64 * I/O 65 */ 66 static bool pmt_crashlog_complete(struct intel_pmt_entry *entry) 67 { 68 u32 control = readl(entry->disc_table + CONTROL_OFFSET); 69 70 /* return current value of the crashlog complete flag */ 71 return !!(control & CRASHLOG_FLAG_TRIGGER_COMPLETE); 72 } 73 74 static bool pmt_crashlog_disabled(struct intel_pmt_entry *entry) 75 { 76 u32 control = readl(entry->disc_table + CONTROL_OFFSET); 77 78 /* return current value of the crashlog disabled flag */ 79 return !!(control & CRASHLOG_FLAG_DISABLE); 80 } 81 82 static bool pmt_crashlog_supported(struct intel_pmt_entry *entry) 83 { 84 u32 discovery_header = readl(entry->disc_table + CONTROL_OFFSET); 85 u32 crash_type, version; 86 87 crash_type = GET_TYPE(discovery_header); 88 version = GET_VERSION(discovery_header); 89 90 /* 91 * Currently we only recognize OOBMSM version 0 devices. 92 * We can ignore all other crashlog devices in the system. 93 */ 94 return crash_type == CRASH_TYPE_OOBMSM && version == 0; 95 } 96 97 static void pmt_crashlog_set_disable(struct intel_pmt_entry *entry, 98 bool disable) 99 { 100 u32 control = readl(entry->disc_table + CONTROL_OFFSET); 101 102 /* clear trigger bits so we are only modifying disable flag */ 103 control &= ~CRASHLOG_FLAG_TRIGGER_MASK; 104 105 if (disable) 106 control |= CRASHLOG_FLAG_DISABLE; 107 else 108 control &= ~CRASHLOG_FLAG_DISABLE; 109 110 writel(control, entry->disc_table + CONTROL_OFFSET); 111 } 112 113 static void pmt_crashlog_set_clear(struct intel_pmt_entry *entry) 114 { 115 u32 control = readl(entry->disc_table + CONTROL_OFFSET); 116 117 control &= ~CRASHLOG_FLAG_TRIGGER_MASK; 118 control |= CRASHLOG_FLAG_TRIGGER_CLEAR; 119 120 writel(control, entry->disc_table + CONTROL_OFFSET); 121 } 122 123 static void pmt_crashlog_set_execute(struct intel_pmt_entry *entry) 124 { 125 u32 control = readl(entry->disc_table + CONTROL_OFFSET); 126 127 control &= ~CRASHLOG_FLAG_TRIGGER_MASK; 128 control |= CRASHLOG_FLAG_TRIGGER_EXECUTE; 129 130 writel(control, entry->disc_table + CONTROL_OFFSET); 131 } 132 133 /* 134 * sysfs 135 */ 136 static ssize_t 137 enable_show(struct device *dev, struct device_attribute *attr, char *buf) 138 { 139 struct intel_pmt_entry *entry = dev_get_drvdata(dev); 140 int enabled = !pmt_crashlog_disabled(entry); 141 142 return sprintf(buf, "%d\n", enabled); 143 } 144 145 static ssize_t 146 enable_store(struct device *dev, struct device_attribute *attr, 147 const char *buf, size_t count) 148 { 149 struct crashlog_entry *entry; 150 bool enabled; 151 int result; 152 153 entry = dev_get_drvdata(dev); 154 155 result = kstrtobool(buf, &enabled); 156 if (result) 157 return result; 158 159 mutex_lock(&entry->control_mutex); 160 pmt_crashlog_set_disable(&entry->entry, !enabled); 161 mutex_unlock(&entry->control_mutex); 162 163 return count; 164 } 165 static DEVICE_ATTR_RW(enable); 166 167 static ssize_t 168 trigger_show(struct device *dev, struct device_attribute *attr, char *buf) 169 { 170 struct intel_pmt_entry *entry; 171 int trigger; 172 173 entry = dev_get_drvdata(dev); 174 trigger = pmt_crashlog_complete(entry); 175 176 return sprintf(buf, "%d\n", trigger); 177 } 178 179 static ssize_t 180 trigger_store(struct device *dev, struct device_attribute *attr, 181 const char *buf, size_t count) 182 { 183 struct crashlog_entry *entry; 184 bool trigger; 185 int result; 186 187 entry = dev_get_drvdata(dev); 188 189 result = kstrtobool(buf, &trigger); 190 if (result) 191 return result; 192 193 mutex_lock(&entry->control_mutex); 194 195 if (!trigger) { 196 pmt_crashlog_set_clear(&entry->entry); 197 } else if (pmt_crashlog_complete(&entry->entry)) { 198 /* we cannot trigger a new crash if one is still pending */ 199 result = -EEXIST; 200 goto err; 201 } else if (pmt_crashlog_disabled(&entry->entry)) { 202 /* if device is currently disabled, return busy */ 203 result = -EBUSY; 204 goto err; 205 } else { 206 pmt_crashlog_set_execute(&entry->entry); 207 } 208 209 result = count; 210 err: 211 mutex_unlock(&entry->control_mutex); 212 return result; 213 } 214 static DEVICE_ATTR_RW(trigger); 215 216 static struct attribute *pmt_crashlog_attrs[] = { 217 &dev_attr_enable.attr, 218 &dev_attr_trigger.attr, 219 NULL 220 }; 221 222 static const struct attribute_group pmt_crashlog_group = { 223 .attrs = pmt_crashlog_attrs, 224 }; 225 226 static int pmt_crashlog_header_decode(struct intel_pmt_entry *entry, 227 struct device *dev) 228 { 229 void __iomem *disc_table = entry->disc_table; 230 struct intel_pmt_header *header = &entry->header; 231 struct crashlog_entry *crashlog; 232 233 if (!pmt_crashlog_supported(entry)) 234 return 1; 235 236 /* initialize control mutex */ 237 crashlog = container_of(entry, struct crashlog_entry, entry); 238 mutex_init(&crashlog->control_mutex); 239 240 header->access_type = GET_ACCESS(readl(disc_table)); 241 header->guid = readl(disc_table + GUID_OFFSET); 242 header->base_offset = readl(disc_table + BASE_OFFSET); 243 244 /* Size is measured in DWORDS, but accessor returns bytes */ 245 header->size = GET_SIZE(readl(disc_table + SIZE_OFFSET)); 246 247 return 0; 248 } 249 250 static DEFINE_XARRAY_ALLOC(crashlog_array); 251 static struct intel_pmt_namespace pmt_crashlog_ns = { 252 .name = "crashlog", 253 .xa = &crashlog_array, 254 .attr_grp = &pmt_crashlog_group, 255 .pmt_header_decode = pmt_crashlog_header_decode, 256 }; 257 258 /* 259 * initialization 260 */ 261 static void pmt_crashlog_remove(struct auxiliary_device *auxdev) 262 { 263 struct pmt_crashlog_priv *priv = auxiliary_get_drvdata(auxdev); 264 int i; 265 266 for (i = 0; i < priv->num_entries; i++) { 267 struct crashlog_entry *crashlog = &priv->entry[i]; 268 269 intel_pmt_dev_destroy(&crashlog->entry, &pmt_crashlog_ns); 270 mutex_destroy(&crashlog->control_mutex); 271 } 272 } 273 274 static int pmt_crashlog_probe(struct auxiliary_device *auxdev, 275 const struct auxiliary_device_id *id) 276 { 277 struct intel_vsec_device *intel_vsec_dev = auxdev_to_ivdev(auxdev); 278 struct pmt_crashlog_priv *priv; 279 size_t size; 280 int i, ret; 281 282 size = struct_size(priv, entry, intel_vsec_dev->num_resources); 283 priv = devm_kzalloc(&auxdev->dev, size, GFP_KERNEL); 284 if (!priv) 285 return -ENOMEM; 286 287 auxiliary_set_drvdata(auxdev, priv); 288 289 for (i = 0; i < intel_vsec_dev->num_resources; i++) { 290 struct intel_pmt_entry *entry = &priv->entry[priv->num_entries].entry; 291 292 ret = intel_pmt_dev_create(entry, &pmt_crashlog_ns, intel_vsec_dev, i); 293 if (ret < 0) 294 goto abort_probe; 295 if (ret) 296 continue; 297 298 priv->num_entries++; 299 } 300 301 return 0; 302 abort_probe: 303 pmt_crashlog_remove(auxdev); 304 return ret; 305 } 306 307 static const struct auxiliary_device_id pmt_crashlog_id_table[] = { 308 { .name = "intel_vsec.crashlog" }, 309 {} 310 }; 311 MODULE_DEVICE_TABLE(auxiliary, pmt_crashlog_id_table); 312 313 static struct auxiliary_driver pmt_crashlog_aux_driver = { 314 .id_table = pmt_crashlog_id_table, 315 .remove = pmt_crashlog_remove, 316 .probe = pmt_crashlog_probe, 317 }; 318 319 static int __init pmt_crashlog_init(void) 320 { 321 return auxiliary_driver_register(&pmt_crashlog_aux_driver); 322 } 323 324 static void __exit pmt_crashlog_exit(void) 325 { 326 auxiliary_driver_unregister(&pmt_crashlog_aux_driver); 327 xa_destroy(&crashlog_array); 328 } 329 330 module_init(pmt_crashlog_init); 331 module_exit(pmt_crashlog_exit); 332 333 MODULE_AUTHOR("Alexander Duyck <alexander.h.duyck@linux.intel.com>"); 334 MODULE_DESCRIPTION("Intel PMT Crashlog driver"); 335 MODULE_LICENSE("GPL v2"); 336 MODULE_IMPORT_NS("INTEL_PMT"); 337