brintos

brintos / linux-shallow public Read only

0
0
Text · 12.9 KiB · 7b5cc99 Raw
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