Srinivas Pandruvada c00493dc46 platform/x86/intel/vsec: Add TPMI ID
Add TPMI (Topology Aware Register and PM Capsule Interface) VSEC ID to
create an aux device. This will allow TPMI driver to enumerate on this
aux device.

Signed-off-by: Srinivas Pandruvada <srinivas.pandruvada@linux.intel.com>
Acked-by: David E. Box <david.e.box@linux.intel.com>
Link: https://lore.kernel.org/r/20230202010738.2186174-2-srinivas.pandruvada@linux.intel.com
Reviewed-by: Hans de Goede <hdegoede@redhat.com>
Signed-off-by: Hans de Goede <hdegoede@redhat.com>
2023-02-06 13:47:10 +01:00

502 lines
12 KiB
C

// 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/delay.h>
#include <linux/kernel.h>
#include <linux/idr.h>
#include <linux/module.h>
#include <linux/pci.h>
#include <linux/types.h>
#include "vsec.h"
/* Intel DVSEC offsets */
#define INTEL_DVSEC_ENTRIES 0xA
#define INTEL_DVSEC_SIZE 0xB
#define INTEL_DVSEC_TABLE 0xC
#define INTEL_DVSEC_TABLE_BAR(x) ((x) & GENMASK(2, 0))
#define INTEL_DVSEC_TABLE_OFFSET(x) ((x) & GENMASK(31, 3))
#define TABLE_OFFSET_SHIFT 3
#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);
/**
* struct intel_vsec_header - Common fields of Intel VSEC and DVSEC registers.
* @rev: Revision ID of the VSEC/DVSEC register space
* @length: Length of the VSEC/DVSEC register space
* @id: ID of the feature
* @num_entries: Number of instances of the feature
* @entry_size: Size of the discovery table for each feature
* @tbir: BAR containing the discovery tables
* @offset: BAR offset of start of the first discovery table
*/
struct intel_vsec_header {
u8 rev;
u16 length;
u16 id;
u8 num_entries;
u8 entry_size;
u8 tbir;
u32 offset;
};
enum intel_vsec_id {
VSEC_ID_TELEMETRY = 2,
VSEC_ID_WATCHER = 3,
VSEC_ID_CRASHLOG = 4,
VSEC_ID_SDSI = 65,
VSEC_ID_TPMI = 66,
};
static enum intel_vsec_id intel_vsec_allow_list[] = {
VSEC_ID_TELEMETRY,
VSEC_ID_WATCHER,
VSEC_ID_CRASHLOG,
VSEC_ID_SDSI,
VSEC_ID_TPMI,
};
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_allowed(u16 id)
{
int i;
for (i = 0; i < ARRAY_SIZE(intel_vsec_allow_list); i++)
if (intel_vsec_allow_list[i] == id)
return true;
return false;
}
static bool intel_vsec_disabled(u16 id, unsigned long quirks)
{
switch (id) {
case VSEC_ID_WATCHER:
return !!(quirks & VSEC_QUIRK_NO_WATCHER);
case VSEC_ID_CRASHLOG:
return !!(quirks & VSEC_QUIRK_NO_CRASHLOG);
default:
return false;
}
}
static void intel_vsec_remove_aux(void *data)
{
auxiliary_device_delete(data);
auxiliary_device_uninit(data);
}
static void intel_vsec_dev_release(struct device *dev)
{
struct intel_vsec_device *intel_vsec_dev = dev_to_ivdev(dev);
ida_free(intel_vsec_dev->ida, intel_vsec_dev->auxdev.id);
kfree(intel_vsec_dev->resource);
kfree(intel_vsec_dev);
}
static int intel_vsec_add_aux(struct pci_dev *pdev, struct intel_vsec_device *intel_vsec_dev,
const char *name)
{
struct auxiliary_device *auxdev = &intel_vsec_dev->auxdev;
int ret, id;
ret = ida_alloc(intel_vsec_dev->ida, GFP_KERNEL);
if (ret < 0) {
kfree(intel_vsec_dev);
return ret;
}
auxdev->id = ret;
auxdev->name = name;
auxdev->dev.parent = &pdev->dev;
auxdev->dev.release = intel_vsec_dev_release;
ret = auxiliary_device_init(auxdev);
if (ret < 0) {
ida_free(intel_vsec_dev->ida, auxdev->id);
kfree(intel_vsec_dev->resource);
kfree(intel_vsec_dev);
return ret;
}
ret = auxiliary_device_add(auxdev);
if (ret < 0) {
auxiliary_device_uninit(auxdev);
return ret;
}
ret = devm_add_action_or_reset(&pdev->dev, intel_vsec_remove_aux,
auxdev);
if (ret < 0)
return ret;
/* Add auxdev to list */
ret = xa_alloc(&auxdev_array, &id, intel_vsec_dev, PMT_XA_LIMIT,
GFP_KERNEL);
if (ret)
return ret;
return 0;
}
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 *intel_vsec_dev;
struct resource *res, *tmp;
unsigned long quirks = info->quirks;
int i;
if (!intel_vsec_allowed(header->id) || intel_vsec_disabled(header->id, quirks))
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) {
kfree(intel_vsec_dev);
return -ENOMEM;
}
if (quirks & VSEC_QUIRK_TABLE_SHIFT)
header->offset >>= TABLE_OFFSET_SHIFT;
/*
* 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 = pdev->resource[header->tbir].start +
header->offset + i * (header->entry_size * sizeof(u32));
tmp->end = tmp->start + (header->entry_size * sizeof(u32)) - 1;
tmp->flags = IORESOURCE_MEM;
}
intel_vsec_dev->pcidev = pdev;
intel_vsec_dev->resource = res;
intel_vsec_dev->num_resources = header->num_entries;
intel_vsec_dev->info = info;
if (header->id == VSEC_ID_SDSI)
intel_vsec_dev->ida = &intel_vsec_sdsi_ida;
else
intel_vsec_dev->ida = &intel_vsec_ida;
return intel_vsec_add_aux(pdev, 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->capabilities;
bool have_devices = false;
int ret;
for ( ; *header; header++) {
ret = intel_vsec_add_dev(pdev, *header, info);
if (ret)
dev_info(&pdev->dev, "Could not add device for DVSEC id %d\n",
(*header)->id);
else
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;
}
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;
}
/* TGL info */
static const struct intel_vsec_platform_info tgl_info = {
.quirks = VSEC_QUIRK_NO_WATCHER | VSEC_QUIRK_NO_CRASHLOG |
VSEC_QUIRK_TABLE_SHIFT | VSEC_QUIRK_EARLY_HW,
};
/* DG1 info */
static struct intel_vsec_header dg1_telemetry = {
.length = 0x10,
.id = 2,
.num_entries = 1,
.entry_size = 3,
.tbir = 0,
.offset = 0x466000,
};
static struct intel_vsec_header *dg1_capabilities[] = {
&dg1_telemetry,
NULL
};
static const struct intel_vsec_platform_info dg1_info = {
.capabilities = dg1_capabilities,
.quirks = VSEC_QUIRK_NO_DVSEC | VSEC_QUIRK_EARLY_HW,
};
#define PCI_DEVICE_ID_INTEL_VSEC_ADL 0x467d
#define PCI_DEVICE_ID_INTEL_VSEC_DG1 0x490e
#define PCI_DEVICE_ID_INTEL_VSEC_OOBMSM 0x09a7
#define PCI_DEVICE_ID_INTEL_VSEC_RPL 0xa77d
#define PCI_DEVICE_ID_INTEL_VSEC_TGL 0x9a0d
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_OOBMSM, &(struct intel_vsec_platform_info) {}) },
{ PCI_DEVICE_DATA(INTEL, VSEC_RPL, &tgl_info) },
{ PCI_DEVICE_DATA(INTEL, VSEC_TGL, &tgl_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");