| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522 |
- // SPDX-License-Identifier: GPL-2.0
- /*
- * Intel Vendor Specific Extended Capabilities auxiliary bus driver
- *
- * Copyright (c) 2021, Intel Corporation.
- * All Rights Reserved.
- *
- * Author: David E. Box <david.e.box@linux.intel.com>
- *
- * This driver discovers and creates auxiliary devices for Intel defined PCIe
- * "Vendor Specific" and "Designated Vendor Specific" Extended Capabilities,
- * VSEC and DVSEC respectively. The driver supports features on specific PCIe
- * endpoints that exist primarily to expose them.
- */
- #include <linux/auxiliary_bus.h>
- #include <linux/bits.h>
- #include <linux/cleanup.h>
- #include <linux/delay.h>
- #include <linux/idr.h>
- #include <linux/intel_vsec.h>
- #include <linux/kernel.h>
- #include <linux/module.h>
- #include <linux/pci.h>
- #include <linux/types.h>
- #define PMT_XA_START 0
- #define PMT_XA_MAX INT_MAX
- #define PMT_XA_LIMIT XA_LIMIT(PMT_XA_START, PMT_XA_MAX)
- static DEFINE_IDA(intel_vsec_ida);
- static DEFINE_IDA(intel_vsec_sdsi_ida);
- static DEFINE_XARRAY_ALLOC(auxdev_array);
- static const char *intel_vsec_name(enum intel_vsec_id id)
- {
- switch (id) {
- case VSEC_ID_TELEMETRY:
- return "telemetry";
- case VSEC_ID_WATCHER:
- return "watcher";
- case VSEC_ID_CRASHLOG:
- return "crashlog";
- case VSEC_ID_SDSI:
- return "sdsi";
- case VSEC_ID_TPMI:
- return "tpmi";
- default:
- return NULL;
- }
- }
- static bool intel_vsec_supported(u16 id, unsigned long caps)
- {
- switch (id) {
- case VSEC_ID_TELEMETRY:
- return !!(caps & VSEC_CAP_TELEMETRY);
- case VSEC_ID_WATCHER:
- return !!(caps & VSEC_CAP_WATCHER);
- case VSEC_ID_CRASHLOG:
- return !!(caps & VSEC_CAP_CRASHLOG);
- case VSEC_ID_SDSI:
- return !!(caps & VSEC_CAP_SDSI);
- case VSEC_ID_TPMI:
- return !!(caps & VSEC_CAP_TPMI);
- default:
- return false;
- }
- }
- static void intel_vsec_remove_aux(void *data)
- {
- auxiliary_device_delete(data);
- auxiliary_device_uninit(data);
- }
- static DEFINE_MUTEX(vsec_ida_lock);
- static void intel_vsec_dev_release(struct device *dev)
- {
- struct intel_vsec_device *intel_vsec_dev = dev_to_ivdev(dev);
- xa_erase(&auxdev_array, intel_vsec_dev->id);
- mutex_lock(&vsec_ida_lock);
- ida_free(intel_vsec_dev->ida, intel_vsec_dev->auxdev.id);
- mutex_unlock(&vsec_ida_lock);
- kfree(intel_vsec_dev->resource);
- kfree(intel_vsec_dev);
- }
- int intel_vsec_add_aux(struct pci_dev *pdev, struct device *parent,
- struct intel_vsec_device *intel_vsec_dev,
- const char *name)
- {
- struct auxiliary_device *auxdev = &intel_vsec_dev->auxdev;
- int ret, id;
- if (!parent)
- return -EINVAL;
- ret = xa_alloc(&auxdev_array, &intel_vsec_dev->id, intel_vsec_dev,
- PMT_XA_LIMIT, GFP_KERNEL);
- if (ret < 0) {
- kfree(intel_vsec_dev->resource);
- kfree(intel_vsec_dev);
- return ret;
- }
- mutex_lock(&vsec_ida_lock);
- id = ida_alloc(intel_vsec_dev->ida, GFP_KERNEL);
- mutex_unlock(&vsec_ida_lock);
- if (id < 0) {
- xa_erase(&auxdev_array, intel_vsec_dev->id);
- kfree(intel_vsec_dev->resource);
- kfree(intel_vsec_dev);
- return id;
- }
- auxdev->id = id;
- auxdev->name = name;
- auxdev->dev.parent = parent;
- auxdev->dev.release = intel_vsec_dev_release;
- ret = auxiliary_device_init(auxdev);
- if (ret < 0) {
- intel_vsec_dev_release(&auxdev->dev);
- return ret;
- }
- ret = auxiliary_device_add(auxdev);
- if (ret < 0) {
- auxiliary_device_uninit(auxdev);
- return ret;
- }
- return devm_add_action_or_reset(parent, intel_vsec_remove_aux,
- auxdev);
- }
- EXPORT_SYMBOL_NS_GPL(intel_vsec_add_aux, INTEL_VSEC);
- static int intel_vsec_add_dev(struct pci_dev *pdev, struct intel_vsec_header *header,
- struct intel_vsec_platform_info *info)
- {
- struct intel_vsec_device __free(kfree) *intel_vsec_dev = NULL;
- struct resource __free(kfree) *res = NULL;
- struct resource *tmp;
- struct device *parent;
- unsigned long quirks = info->quirks;
- u64 base_addr;
- int i;
- if (info->parent)
- parent = info->parent;
- else
- parent = &pdev->dev;
- if (!intel_vsec_supported(header->id, info->caps))
- return -EINVAL;
- if (!header->num_entries) {
- dev_dbg(&pdev->dev, "Invalid 0 entry count for header id %d\n", header->id);
- return -EINVAL;
- }
- if (!header->entry_size) {
- dev_dbg(&pdev->dev, "Invalid 0 entry size for header id %d\n", header->id);
- return -EINVAL;
- }
- intel_vsec_dev = kzalloc(sizeof(*intel_vsec_dev), GFP_KERNEL);
- if (!intel_vsec_dev)
- return -ENOMEM;
- res = kcalloc(header->num_entries, sizeof(*res), GFP_KERNEL);
- if (!res)
- return -ENOMEM;
- if (quirks & VSEC_QUIRK_TABLE_SHIFT)
- header->offset >>= TABLE_OFFSET_SHIFT;
- if (info->base_addr)
- base_addr = info->base_addr;
- else
- base_addr = pdev->resource[header->tbir].start;
- /*
- * The DVSEC/VSEC contains the starting offset and count for a block of
- * discovery tables. Create a resource array of these tables to the
- * auxiliary device driver.
- */
- for (i = 0, tmp = res; i < header->num_entries; i++, tmp++) {
- tmp->start = base_addr + header->offset + i * (header->entry_size * sizeof(u32));
- tmp->end = tmp->start + (header->entry_size * sizeof(u32)) - 1;
- tmp->flags = IORESOURCE_MEM;
- /* Check resource is not in use */
- if (!request_mem_region(tmp->start, resource_size(tmp), ""))
- return -EBUSY;
- release_mem_region(tmp->start, resource_size(tmp));
- }
- intel_vsec_dev->pcidev = pdev;
- intel_vsec_dev->resource = no_free_ptr(res);
- intel_vsec_dev->num_resources = header->num_entries;
- intel_vsec_dev->quirks = info->quirks;
- intel_vsec_dev->base_addr = info->base_addr;
- intel_vsec_dev->priv_data = info->priv_data;
- if (header->id == VSEC_ID_SDSI)
- intel_vsec_dev->ida = &intel_vsec_sdsi_ida;
- else
- intel_vsec_dev->ida = &intel_vsec_ida;
- /*
- * Pass the ownership of intel_vsec_dev and resource within it to
- * intel_vsec_add_aux()
- */
- return intel_vsec_add_aux(pdev, parent, no_free_ptr(intel_vsec_dev),
- intel_vsec_name(header->id));
- }
- static bool intel_vsec_walk_header(struct pci_dev *pdev,
- struct intel_vsec_platform_info *info)
- {
- struct intel_vsec_header **header = info->headers;
- bool have_devices = false;
- int ret;
- for ( ; *header; header++) {
- ret = intel_vsec_add_dev(pdev, *header, info);
- if (!ret)
- have_devices = true;
- }
- return have_devices;
- }
- static bool intel_vsec_walk_dvsec(struct pci_dev *pdev,
- struct intel_vsec_platform_info *info)
- {
- bool have_devices = false;
- int pos = 0;
- do {
- struct intel_vsec_header header;
- u32 table, hdr;
- u16 vid;
- int ret;
- pos = pci_find_next_ext_capability(pdev, pos, PCI_EXT_CAP_ID_DVSEC);
- if (!pos)
- break;
- pci_read_config_dword(pdev, pos + PCI_DVSEC_HEADER1, &hdr);
- vid = PCI_DVSEC_HEADER1_VID(hdr);
- if (vid != PCI_VENDOR_ID_INTEL)
- continue;
- /* Support only revision 1 */
- header.rev = PCI_DVSEC_HEADER1_REV(hdr);
- if (header.rev != 1) {
- dev_info(&pdev->dev, "Unsupported DVSEC revision %d\n", header.rev);
- continue;
- }
- header.length = PCI_DVSEC_HEADER1_LEN(hdr);
- pci_read_config_byte(pdev, pos + INTEL_DVSEC_ENTRIES, &header.num_entries);
- pci_read_config_byte(pdev, pos + INTEL_DVSEC_SIZE, &header.entry_size);
- pci_read_config_dword(pdev, pos + INTEL_DVSEC_TABLE, &table);
- header.tbir = INTEL_DVSEC_TABLE_BAR(table);
- header.offset = INTEL_DVSEC_TABLE_OFFSET(table);
- pci_read_config_dword(pdev, pos + PCI_DVSEC_HEADER2, &hdr);
- header.id = PCI_DVSEC_HEADER2_ID(hdr);
- ret = intel_vsec_add_dev(pdev, &header, info);
- if (ret)
- continue;
- have_devices = true;
- } while (true);
- return have_devices;
- }
- static bool intel_vsec_walk_vsec(struct pci_dev *pdev,
- struct intel_vsec_platform_info *info)
- {
- bool have_devices = false;
- int pos = 0;
- do {
- struct intel_vsec_header header;
- u32 table, hdr;
- int ret;
- pos = pci_find_next_ext_capability(pdev, pos, PCI_EXT_CAP_ID_VNDR);
- if (!pos)
- break;
- pci_read_config_dword(pdev, pos + PCI_VNDR_HEADER, &hdr);
- /* Support only revision 1 */
- header.rev = PCI_VNDR_HEADER_REV(hdr);
- if (header.rev != 1) {
- dev_info(&pdev->dev, "Unsupported VSEC revision %d\n", header.rev);
- continue;
- }
- header.id = PCI_VNDR_HEADER_ID(hdr);
- header.length = PCI_VNDR_HEADER_LEN(hdr);
- /* entry, size, and table offset are the same as DVSEC */
- pci_read_config_byte(pdev, pos + INTEL_DVSEC_ENTRIES, &header.num_entries);
- pci_read_config_byte(pdev, pos + INTEL_DVSEC_SIZE, &header.entry_size);
- pci_read_config_dword(pdev, pos + INTEL_DVSEC_TABLE, &table);
- header.tbir = INTEL_DVSEC_TABLE_BAR(table);
- header.offset = INTEL_DVSEC_TABLE_OFFSET(table);
- ret = intel_vsec_add_dev(pdev, &header, info);
- if (ret)
- continue;
- have_devices = true;
- } while (true);
- return have_devices;
- }
- void intel_vsec_register(struct pci_dev *pdev,
- struct intel_vsec_platform_info *info)
- {
- if (!pdev || !info || !info->headers)
- return;
- intel_vsec_walk_header(pdev, info);
- }
- EXPORT_SYMBOL_NS_GPL(intel_vsec_register, INTEL_VSEC);
- static int intel_vsec_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id)
- {
- struct intel_vsec_platform_info *info;
- bool have_devices = false;
- int ret;
- ret = pcim_enable_device(pdev);
- if (ret)
- return ret;
- pci_save_state(pdev);
- info = (struct intel_vsec_platform_info *)id->driver_data;
- if (!info)
- return -EINVAL;
- if (intel_vsec_walk_dvsec(pdev, info))
- have_devices = true;
- if (intel_vsec_walk_vsec(pdev, info))
- have_devices = true;
- if (info && (info->quirks & VSEC_QUIRK_NO_DVSEC) &&
- intel_vsec_walk_header(pdev, info))
- have_devices = true;
- if (!have_devices)
- return -ENODEV;
- return 0;
- }
- /* DG1 info */
- static struct intel_vsec_header dg1_header = {
- .length = 0x10,
- .id = 2,
- .num_entries = 1,
- .entry_size = 3,
- .tbir = 0,
- .offset = 0x466000,
- };
- static struct intel_vsec_header *dg1_headers[] = {
- &dg1_header,
- NULL
- };
- static const struct intel_vsec_platform_info dg1_info = {
- .caps = VSEC_CAP_TELEMETRY,
- .headers = dg1_headers,
- .quirks = VSEC_QUIRK_NO_DVSEC | VSEC_QUIRK_EARLY_HW,
- };
- /* MTL info */
- static const struct intel_vsec_platform_info mtl_info = {
- .caps = VSEC_CAP_TELEMETRY,
- };
- /* OOBMSM info */
- static const struct intel_vsec_platform_info oobmsm_info = {
- .caps = VSEC_CAP_TELEMETRY | VSEC_CAP_SDSI | VSEC_CAP_TPMI,
- };
- /* DMR OOBMSM info */
- static const struct intel_vsec_platform_info dmr_oobmsm_info = {
- .caps = VSEC_CAP_TELEMETRY | VSEC_CAP_TPMI,
- };
- /* TGL info */
- static const struct intel_vsec_platform_info tgl_info = {
- .caps = VSEC_CAP_TELEMETRY,
- .quirks = VSEC_QUIRK_TABLE_SHIFT | VSEC_QUIRK_EARLY_HW,
- };
- /* LNL info */
- static const struct intel_vsec_platform_info lnl_info = {
- .caps = VSEC_CAP_TELEMETRY | VSEC_CAP_WATCHER,
- };
- #define PCI_DEVICE_ID_INTEL_VSEC_ADL 0x467d
- #define PCI_DEVICE_ID_INTEL_VSEC_DG1 0x490e
- #define PCI_DEVICE_ID_INTEL_VSEC_MTL_M 0x7d0d
- #define PCI_DEVICE_ID_INTEL_VSEC_MTL_S 0xad0d
- #define PCI_DEVICE_ID_INTEL_VSEC_OOBMSM 0x09a7
- #define PCI_DEVICE_ID_INTEL_VSEC_OOBMSM_DMR 0x09a1
- #define PCI_DEVICE_ID_INTEL_VSEC_RPL 0xa77d
- #define PCI_DEVICE_ID_INTEL_VSEC_TGL 0x9a0d
- #define PCI_DEVICE_ID_INTEL_VSEC_LNL_M 0x647d
- static const struct pci_device_id intel_vsec_pci_ids[] = {
- { PCI_DEVICE_DATA(INTEL, VSEC_ADL, &tgl_info) },
- { PCI_DEVICE_DATA(INTEL, VSEC_DG1, &dg1_info) },
- { PCI_DEVICE_DATA(INTEL, VSEC_MTL_M, &mtl_info) },
- { PCI_DEVICE_DATA(INTEL, VSEC_MTL_S, &mtl_info) },
- { PCI_DEVICE_DATA(INTEL, VSEC_OOBMSM, &oobmsm_info) },
- { PCI_DEVICE_DATA(INTEL, VSEC_OOBMSM_DMR, &dmr_oobmsm_info) },
- { PCI_DEVICE_DATA(INTEL, VSEC_RPL, &tgl_info) },
- { PCI_DEVICE_DATA(INTEL, VSEC_TGL, &tgl_info) },
- { PCI_DEVICE_DATA(INTEL, VSEC_LNL_M, &lnl_info) },
- { }
- };
- MODULE_DEVICE_TABLE(pci, intel_vsec_pci_ids);
- static pci_ers_result_t intel_vsec_pci_error_detected(struct pci_dev *pdev,
- pci_channel_state_t state)
- {
- pci_ers_result_t status = PCI_ERS_RESULT_NEED_RESET;
- dev_info(&pdev->dev, "PCI error detected, state %d", state);
- if (state == pci_channel_io_perm_failure)
- status = PCI_ERS_RESULT_DISCONNECT;
- else
- pci_disable_device(pdev);
- return status;
- }
- static pci_ers_result_t intel_vsec_pci_slot_reset(struct pci_dev *pdev)
- {
- struct intel_vsec_device *intel_vsec_dev;
- pci_ers_result_t status = PCI_ERS_RESULT_DISCONNECT;
- const struct pci_device_id *pci_dev_id;
- unsigned long index;
- dev_info(&pdev->dev, "Resetting PCI slot\n");
- msleep(2000);
- if (pci_enable_device(pdev)) {
- dev_info(&pdev->dev,
- "Failed to re-enable PCI device after reset.\n");
- goto out;
- }
- status = PCI_ERS_RESULT_RECOVERED;
- xa_for_each(&auxdev_array, index, intel_vsec_dev) {
- /* check if pdev doesn't match */
- if (pdev != intel_vsec_dev->pcidev)
- continue;
- devm_release_action(&pdev->dev, intel_vsec_remove_aux,
- &intel_vsec_dev->auxdev);
- }
- pci_disable_device(pdev);
- pci_restore_state(pdev);
- pci_dev_id = pci_match_id(intel_vsec_pci_ids, pdev);
- intel_vsec_pci_probe(pdev, pci_dev_id);
- out:
- return status;
- }
- static void intel_vsec_pci_resume(struct pci_dev *pdev)
- {
- dev_info(&pdev->dev, "Done resuming PCI device\n");
- }
- static const struct pci_error_handlers intel_vsec_pci_err_handlers = {
- .error_detected = intel_vsec_pci_error_detected,
- .slot_reset = intel_vsec_pci_slot_reset,
- .resume = intel_vsec_pci_resume,
- };
- static struct pci_driver intel_vsec_pci_driver = {
- .name = "intel_vsec",
- .id_table = intel_vsec_pci_ids,
- .probe = intel_vsec_pci_probe,
- .err_handler = &intel_vsec_pci_err_handlers,
- };
- module_pci_driver(intel_vsec_pci_driver);
- MODULE_AUTHOR("David E. Box <david.e.box@linux.intel.com>");
- MODULE_DESCRIPTION("Intel Extended Capabilities auxiliary bus driver");
- MODULE_LICENSE("GPL v2");
|