516 lines · c
1// SPDX-License-Identifier: GPL-2.02/*3 * Intel Vendor Specific Extended Capabilities auxiliary bus driver4 *5 * Copyright (c) 2021, Intel Corporation.6 * All Rights Reserved.7 *8 * Author: David E. Box <david.e.box@linux.intel.com>9 *10 * This driver discovers and creates auxiliary devices for Intel defined PCIe11 * "Vendor Specific" and "Designated Vendor Specific" Extended Capabilities,12 * VSEC and DVSEC respectively. The driver supports features on specific PCIe13 * endpoints that exist primarily to expose them.14 */15 16#include <linux/auxiliary_bus.h>17#include <linux/bits.h>18#include <linux/cleanup.h>19#include <linux/delay.h>20#include <linux/idr.h>21#include <linux/intel_vsec.h>22#include <linux/kernel.h>23#include <linux/module.h>24#include <linux/pci.h>25#include <linux/types.h>26 27#define PMT_XA_START 028#define PMT_XA_MAX INT_MAX29#define PMT_XA_LIMIT XA_LIMIT(PMT_XA_START, PMT_XA_MAX)30 31static DEFINE_IDA(intel_vsec_ida);32static DEFINE_IDA(intel_vsec_sdsi_ida);33static DEFINE_XARRAY_ALLOC(auxdev_array);34 35static const char *intel_vsec_name(enum intel_vsec_id id)36{37 switch (id) {38 case VSEC_ID_TELEMETRY:39 return "telemetry";40 41 case VSEC_ID_WATCHER:42 return "watcher";43 44 case VSEC_ID_CRASHLOG:45 return "crashlog";46 47 case VSEC_ID_SDSI:48 return "sdsi";49 50 case VSEC_ID_TPMI:51 return "tpmi";52 53 default:54 return NULL;55 }56}57 58static bool intel_vsec_supported(u16 id, unsigned long caps)59{60 switch (id) {61 case VSEC_ID_TELEMETRY:62 return !!(caps & VSEC_CAP_TELEMETRY);63 case VSEC_ID_WATCHER:64 return !!(caps & VSEC_CAP_WATCHER);65 case VSEC_ID_CRASHLOG:66 return !!(caps & VSEC_CAP_CRASHLOG);67 case VSEC_ID_SDSI:68 return !!(caps & VSEC_CAP_SDSI);69 case VSEC_ID_TPMI:70 return !!(caps & VSEC_CAP_TPMI);71 default:72 return false;73 }74}75 76static void intel_vsec_remove_aux(void *data)77{78 auxiliary_device_delete(data);79 auxiliary_device_uninit(data);80}81 82static DEFINE_MUTEX(vsec_ida_lock);83 84static void intel_vsec_dev_release(struct device *dev)85{86 struct intel_vsec_device *intel_vsec_dev = dev_to_ivdev(dev);87 88 xa_erase(&auxdev_array, intel_vsec_dev->id);89 90 mutex_lock(&vsec_ida_lock);91 ida_free(intel_vsec_dev->ida, intel_vsec_dev->auxdev.id);92 mutex_unlock(&vsec_ida_lock);93 94 kfree(intel_vsec_dev->resource);95 kfree(intel_vsec_dev);96}97 98int intel_vsec_add_aux(struct pci_dev *pdev, struct device *parent,99 struct intel_vsec_device *intel_vsec_dev,100 const char *name)101{102 struct auxiliary_device *auxdev = &intel_vsec_dev->auxdev;103 int ret, id;104 105 if (!parent)106 return -EINVAL;107 108 ret = xa_alloc(&auxdev_array, &intel_vsec_dev->id, intel_vsec_dev,109 PMT_XA_LIMIT, GFP_KERNEL);110 if (ret < 0) {111 kfree(intel_vsec_dev->resource);112 kfree(intel_vsec_dev);113 return ret;114 }115 116 mutex_lock(&vsec_ida_lock);117 id = ida_alloc(intel_vsec_dev->ida, GFP_KERNEL);118 mutex_unlock(&vsec_ida_lock);119 if (id < 0) {120 xa_erase(&auxdev_array, intel_vsec_dev->id);121 kfree(intel_vsec_dev->resource);122 kfree(intel_vsec_dev);123 return id;124 }125 126 auxdev->id = id;127 auxdev->name = name;128 auxdev->dev.parent = parent;129 auxdev->dev.release = intel_vsec_dev_release;130 131 ret = auxiliary_device_init(auxdev);132 if (ret < 0) {133 intel_vsec_dev_release(&auxdev->dev);134 return ret;135 }136 137 ret = auxiliary_device_add(auxdev);138 if (ret < 0) {139 auxiliary_device_uninit(auxdev);140 return ret;141 }142 143 return devm_add_action_or_reset(parent, intel_vsec_remove_aux,144 auxdev);145}146EXPORT_SYMBOL_NS_GPL(intel_vsec_add_aux, INTEL_VSEC);147 148static int intel_vsec_add_dev(struct pci_dev *pdev, struct intel_vsec_header *header,149 struct intel_vsec_platform_info *info)150{151 struct intel_vsec_device __free(kfree) *intel_vsec_dev = NULL;152 struct resource __free(kfree) *res = NULL;153 struct resource *tmp;154 struct device *parent;155 unsigned long quirks = info->quirks;156 u64 base_addr;157 int i;158 159 if (info->parent)160 parent = info->parent;161 else162 parent = &pdev->dev;163 164 if (!intel_vsec_supported(header->id, info->caps))165 return -EINVAL;166 167 if (!header->num_entries) {168 dev_dbg(&pdev->dev, "Invalid 0 entry count for header id %d\n", header->id);169 return -EINVAL;170 }171 172 if (!header->entry_size) {173 dev_dbg(&pdev->dev, "Invalid 0 entry size for header id %d\n", header->id);174 return -EINVAL;175 }176 177 intel_vsec_dev = kzalloc(sizeof(*intel_vsec_dev), GFP_KERNEL);178 if (!intel_vsec_dev)179 return -ENOMEM;180 181 res = kcalloc(header->num_entries, sizeof(*res), GFP_KERNEL);182 if (!res)183 return -ENOMEM;184 185 if (quirks & VSEC_QUIRK_TABLE_SHIFT)186 header->offset >>= TABLE_OFFSET_SHIFT;187 188 if (info->base_addr)189 base_addr = info->base_addr;190 else191 base_addr = pdev->resource[header->tbir].start;192 193 /*194 * The DVSEC/VSEC contains the starting offset and count for a block of195 * discovery tables. Create a resource array of these tables to the196 * auxiliary device driver.197 */198 for (i = 0, tmp = res; i < header->num_entries; i++, tmp++) {199 tmp->start = base_addr + header->offset + i * (header->entry_size * sizeof(u32));200 tmp->end = tmp->start + (header->entry_size * sizeof(u32)) - 1;201 tmp->flags = IORESOURCE_MEM;202 203 /* Check resource is not in use */204 if (!request_mem_region(tmp->start, resource_size(tmp), ""))205 return -EBUSY;206 207 release_mem_region(tmp->start, resource_size(tmp));208 }209 210 intel_vsec_dev->pcidev = pdev;211 intel_vsec_dev->resource = no_free_ptr(res);212 intel_vsec_dev->num_resources = header->num_entries;213 intel_vsec_dev->quirks = info->quirks;214 intel_vsec_dev->base_addr = info->base_addr;215 intel_vsec_dev->priv_data = info->priv_data;216 217 if (header->id == VSEC_ID_SDSI)218 intel_vsec_dev->ida = &intel_vsec_sdsi_ida;219 else220 intel_vsec_dev->ida = &intel_vsec_ida;221 222 /*223 * Pass the ownership of intel_vsec_dev and resource within it to224 * intel_vsec_add_aux()225 */226 return intel_vsec_add_aux(pdev, parent, no_free_ptr(intel_vsec_dev),227 intel_vsec_name(header->id));228}229 230static bool intel_vsec_walk_header(struct pci_dev *pdev,231 struct intel_vsec_platform_info *info)232{233 struct intel_vsec_header **header = info->headers;234 bool have_devices = false;235 int ret;236 237 for ( ; *header; header++) {238 ret = intel_vsec_add_dev(pdev, *header, info);239 if (!ret)240 have_devices = true;241 }242 243 return have_devices;244}245 246static bool intel_vsec_walk_dvsec(struct pci_dev *pdev,247 struct intel_vsec_platform_info *info)248{249 bool have_devices = false;250 int pos = 0;251 252 do {253 struct intel_vsec_header header;254 u32 table, hdr;255 u16 vid;256 int ret;257 258 pos = pci_find_next_ext_capability(pdev, pos, PCI_EXT_CAP_ID_DVSEC);259 if (!pos)260 break;261 262 pci_read_config_dword(pdev, pos + PCI_DVSEC_HEADER1, &hdr);263 vid = PCI_DVSEC_HEADER1_VID(hdr);264 if (vid != PCI_VENDOR_ID_INTEL)265 continue;266 267 /* Support only revision 1 */268 header.rev = PCI_DVSEC_HEADER1_REV(hdr);269 if (header.rev != 1) {270 dev_info(&pdev->dev, "Unsupported DVSEC revision %d\n", header.rev);271 continue;272 }273 274 header.length = PCI_DVSEC_HEADER1_LEN(hdr);275 276 pci_read_config_byte(pdev, pos + INTEL_DVSEC_ENTRIES, &header.num_entries);277 pci_read_config_byte(pdev, pos + INTEL_DVSEC_SIZE, &header.entry_size);278 pci_read_config_dword(pdev, pos + INTEL_DVSEC_TABLE, &table);279 280 header.tbir = INTEL_DVSEC_TABLE_BAR(table);281 header.offset = INTEL_DVSEC_TABLE_OFFSET(table);282 283 pci_read_config_dword(pdev, pos + PCI_DVSEC_HEADER2, &hdr);284 header.id = PCI_DVSEC_HEADER2_ID(hdr);285 286 ret = intel_vsec_add_dev(pdev, &header, info);287 if (ret)288 continue;289 290 have_devices = true;291 } while (true);292 293 return have_devices;294}295 296static bool intel_vsec_walk_vsec(struct pci_dev *pdev,297 struct intel_vsec_platform_info *info)298{299 bool have_devices = false;300 int pos = 0;301 302 do {303 struct intel_vsec_header header;304 u32 table, hdr;305 int ret;306 307 pos = pci_find_next_ext_capability(pdev, pos, PCI_EXT_CAP_ID_VNDR);308 if (!pos)309 break;310 311 pci_read_config_dword(pdev, pos + PCI_VNDR_HEADER, &hdr);312 313 /* Support only revision 1 */314 header.rev = PCI_VNDR_HEADER_REV(hdr);315 if (header.rev != 1) {316 dev_info(&pdev->dev, "Unsupported VSEC revision %d\n", header.rev);317 continue;318 }319 320 header.id = PCI_VNDR_HEADER_ID(hdr);321 header.length = PCI_VNDR_HEADER_LEN(hdr);322 323 /* entry, size, and table offset are the same as DVSEC */324 pci_read_config_byte(pdev, pos + INTEL_DVSEC_ENTRIES, &header.num_entries);325 pci_read_config_byte(pdev, pos + INTEL_DVSEC_SIZE, &header.entry_size);326 pci_read_config_dword(pdev, pos + INTEL_DVSEC_TABLE, &table);327 328 header.tbir = INTEL_DVSEC_TABLE_BAR(table);329 header.offset = INTEL_DVSEC_TABLE_OFFSET(table);330 331 ret = intel_vsec_add_dev(pdev, &header, info);332 if (ret)333 continue;334 335 have_devices = true;336 } while (true);337 338 return have_devices;339}340 341void intel_vsec_register(struct pci_dev *pdev,342 struct intel_vsec_platform_info *info)343{344 if (!pdev || !info || !info->headers)345 return;346 347 intel_vsec_walk_header(pdev, info);348}349EXPORT_SYMBOL_NS_GPL(intel_vsec_register, INTEL_VSEC);350 351static int intel_vsec_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id)352{353 struct intel_vsec_platform_info *info;354 bool have_devices = false;355 int ret;356 357 ret = pcim_enable_device(pdev);358 if (ret)359 return ret;360 361 pci_save_state(pdev);362 info = (struct intel_vsec_platform_info *)id->driver_data;363 if (!info)364 return -EINVAL;365 366 if (intel_vsec_walk_dvsec(pdev, info))367 have_devices = true;368 369 if (intel_vsec_walk_vsec(pdev, info))370 have_devices = true;371 372 if (info && (info->quirks & VSEC_QUIRK_NO_DVSEC) &&373 intel_vsec_walk_header(pdev, info))374 have_devices = true;375 376 if (!have_devices)377 return -ENODEV;378 379 return 0;380}381 382/* DG1 info */383static struct intel_vsec_header dg1_header = {384 .length = 0x10,385 .id = 2,386 .num_entries = 1,387 .entry_size = 3,388 .tbir = 0,389 .offset = 0x466000,390};391 392static struct intel_vsec_header *dg1_headers[] = {393 &dg1_header,394 NULL395};396 397static const struct intel_vsec_platform_info dg1_info = {398 .caps = VSEC_CAP_TELEMETRY,399 .headers = dg1_headers,400 .quirks = VSEC_QUIRK_NO_DVSEC | VSEC_QUIRK_EARLY_HW,401};402 403/* MTL info */404static const struct intel_vsec_platform_info mtl_info = {405 .caps = VSEC_CAP_TELEMETRY,406};407 408/* OOBMSM info */409static const struct intel_vsec_platform_info oobmsm_info = {410 .caps = VSEC_CAP_TELEMETRY | VSEC_CAP_SDSI | VSEC_CAP_TPMI,411};412 413/* TGL info */414static const struct intel_vsec_platform_info tgl_info = {415 .caps = VSEC_CAP_TELEMETRY,416 .quirks = VSEC_QUIRK_TABLE_SHIFT | VSEC_QUIRK_EARLY_HW,417};418 419/* LNL info */420static const struct intel_vsec_platform_info lnl_info = {421 .caps = VSEC_CAP_TELEMETRY | VSEC_CAP_WATCHER,422};423 424#define PCI_DEVICE_ID_INTEL_VSEC_ADL 0x467d425#define PCI_DEVICE_ID_INTEL_VSEC_DG1 0x490e426#define PCI_DEVICE_ID_INTEL_VSEC_MTL_M 0x7d0d427#define PCI_DEVICE_ID_INTEL_VSEC_MTL_S 0xad0d428#define PCI_DEVICE_ID_INTEL_VSEC_OOBMSM 0x09a7429#define PCI_DEVICE_ID_INTEL_VSEC_RPL 0xa77d430#define PCI_DEVICE_ID_INTEL_VSEC_TGL 0x9a0d431#define PCI_DEVICE_ID_INTEL_VSEC_LNL_M 0x647d432static const struct pci_device_id intel_vsec_pci_ids[] = {433 { PCI_DEVICE_DATA(INTEL, VSEC_ADL, &tgl_info) },434 { PCI_DEVICE_DATA(INTEL, VSEC_DG1, &dg1_info) },435 { PCI_DEVICE_DATA(INTEL, VSEC_MTL_M, &mtl_info) },436 { PCI_DEVICE_DATA(INTEL, VSEC_MTL_S, &mtl_info) },437 { PCI_DEVICE_DATA(INTEL, VSEC_OOBMSM, &oobmsm_info) },438 { PCI_DEVICE_DATA(INTEL, VSEC_RPL, &tgl_info) },439 { PCI_DEVICE_DATA(INTEL, VSEC_TGL, &tgl_info) },440 { PCI_DEVICE_DATA(INTEL, VSEC_LNL_M, &lnl_info) },441 { }442};443MODULE_DEVICE_TABLE(pci, intel_vsec_pci_ids);444 445static pci_ers_result_t intel_vsec_pci_error_detected(struct pci_dev *pdev,446 pci_channel_state_t state)447{448 pci_ers_result_t status = PCI_ERS_RESULT_NEED_RESET;449 450 dev_info(&pdev->dev, "PCI error detected, state %d", state);451 452 if (state == pci_channel_io_perm_failure)453 status = PCI_ERS_RESULT_DISCONNECT;454 else455 pci_disable_device(pdev);456 457 return status;458}459 460static pci_ers_result_t intel_vsec_pci_slot_reset(struct pci_dev *pdev)461{462 struct intel_vsec_device *intel_vsec_dev;463 pci_ers_result_t status = PCI_ERS_RESULT_DISCONNECT;464 const struct pci_device_id *pci_dev_id;465 unsigned long index;466 467 dev_info(&pdev->dev, "Resetting PCI slot\n");468 469 msleep(2000);470 if (pci_enable_device(pdev)) {471 dev_info(&pdev->dev,472 "Failed to re-enable PCI device after reset.\n");473 goto out;474 }475 476 status = PCI_ERS_RESULT_RECOVERED;477 478 xa_for_each(&auxdev_array, index, intel_vsec_dev) {479 /* check if pdev doesn't match */480 if (pdev != intel_vsec_dev->pcidev)481 continue;482 devm_release_action(&pdev->dev, intel_vsec_remove_aux,483 &intel_vsec_dev->auxdev);484 }485 pci_disable_device(pdev);486 pci_restore_state(pdev);487 pci_dev_id = pci_match_id(intel_vsec_pci_ids, pdev);488 intel_vsec_pci_probe(pdev, pci_dev_id);489 490out:491 return status;492}493 494static void intel_vsec_pci_resume(struct pci_dev *pdev)495{496 dev_info(&pdev->dev, "Done resuming PCI device\n");497}498 499static const struct pci_error_handlers intel_vsec_pci_err_handlers = {500 .error_detected = intel_vsec_pci_error_detected,501 .slot_reset = intel_vsec_pci_slot_reset,502 .resume = intel_vsec_pci_resume,503};504 505static struct pci_driver intel_vsec_pci_driver = {506 .name = "intel_vsec",507 .id_table = intel_vsec_pci_ids,508 .probe = intel_vsec_pci_probe,509 .err_handler = &intel_vsec_pci_err_handlers,510};511module_pci_driver(intel_vsec_pci_driver);512 513MODULE_AUTHOR("David E. Box <david.e.box@linux.intel.com>");514MODULE_DESCRIPTION("Intel Extended Capabilities auxiliary bus driver");515MODULE_LICENSE("GPL v2");516