mirror of https://github.com/Qortal/Brooklyn
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
248 lines
5.9 KiB
248 lines
5.9 KiB
// SPDX-License-Identifier: GPL-2.0-only |
|
/* |
|
* IOAPIC/IOxAPIC/IOSAPIC driver |
|
* |
|
* Copyright (C) 2009 Fujitsu Limited. |
|
* (c) Copyright 2009 Hewlett-Packard Development Company, L.P. |
|
* |
|
* Copyright (C) 2014 Intel Corporation |
|
* |
|
* Based on original drivers/pci/ioapic.c |
|
* Yinghai Lu <[email protected]> |
|
* Jiang Liu <[email protected]> |
|
*/ |
|
|
|
/* |
|
* This driver manages I/O APICs added by hotplug after boot. |
|
* We try to claim all I/O APIC devices, but those present at boot were |
|
* registered when we parsed the ACPI MADT. |
|
*/ |
|
|
|
#define pr_fmt(fmt) "ACPI: IOAPIC: " fmt |
|
|
|
#include <linux/slab.h> |
|
#include <linux/acpi.h> |
|
#include <linux/pci.h> |
|
#include <acpi/acpi.h> |
|
|
|
struct acpi_pci_ioapic { |
|
acpi_handle root_handle; |
|
acpi_handle handle; |
|
u32 gsi_base; |
|
struct resource res; |
|
struct pci_dev *pdev; |
|
struct list_head list; |
|
}; |
|
|
|
static LIST_HEAD(ioapic_list); |
|
static DEFINE_MUTEX(ioapic_list_lock); |
|
|
|
static acpi_status setup_res(struct acpi_resource *acpi_res, void *data) |
|
{ |
|
struct resource *res = data; |
|
struct resource_win win; |
|
|
|
/* |
|
* We might assign this to 'res' later, make sure all pointers are |
|
* cleared before the resource is added to the global list |
|
*/ |
|
memset(&win, 0, sizeof(win)); |
|
|
|
res->flags = 0; |
|
if (acpi_dev_filter_resource_type(acpi_res, IORESOURCE_MEM)) |
|
return AE_OK; |
|
|
|
if (!acpi_dev_resource_memory(acpi_res, res)) { |
|
if (acpi_dev_resource_address_space(acpi_res, &win) || |
|
acpi_dev_resource_ext_address_space(acpi_res, &win)) |
|
*res = win.res; |
|
} |
|
if ((res->flags & IORESOURCE_PREFETCH) || |
|
(res->flags & IORESOURCE_DISABLED)) |
|
res->flags = 0; |
|
|
|
return AE_CTRL_TERMINATE; |
|
} |
|
|
|
static bool acpi_is_ioapic(acpi_handle handle, char **type) |
|
{ |
|
acpi_status status; |
|
struct acpi_device_info *info; |
|
char *hid = NULL; |
|
bool match = false; |
|
|
|
if (!acpi_has_method(handle, "_GSB")) |
|
return false; |
|
|
|
status = acpi_get_object_info(handle, &info); |
|
if (ACPI_SUCCESS(status)) { |
|
if (info->valid & ACPI_VALID_HID) |
|
hid = info->hardware_id.string; |
|
if (hid) { |
|
if (strcmp(hid, "ACPI0009") == 0) { |
|
*type = "IOxAPIC"; |
|
match = true; |
|
} else if (strcmp(hid, "ACPI000A") == 0) { |
|
*type = "IOAPIC"; |
|
match = true; |
|
} |
|
} |
|
kfree(info); |
|
} |
|
|
|
return match; |
|
} |
|
|
|
static acpi_status handle_ioapic_add(acpi_handle handle, u32 lvl, |
|
void *context, void **rv) |
|
{ |
|
acpi_status status; |
|
unsigned long long gsi_base; |
|
struct acpi_pci_ioapic *ioapic; |
|
struct pci_dev *dev = NULL; |
|
struct resource *res = NULL, *pci_res = NULL, *crs_res; |
|
char *type = NULL; |
|
|
|
if (!acpi_is_ioapic(handle, &type)) |
|
return AE_OK; |
|
|
|
mutex_lock(&ioapic_list_lock); |
|
list_for_each_entry(ioapic, &ioapic_list, list) |
|
if (ioapic->handle == handle) { |
|
mutex_unlock(&ioapic_list_lock); |
|
return AE_OK; |
|
} |
|
|
|
status = acpi_evaluate_integer(handle, "_GSB", NULL, &gsi_base); |
|
if (ACPI_FAILURE(status)) { |
|
acpi_handle_warn(handle, "failed to evaluate _GSB method\n"); |
|
goto exit; |
|
} |
|
|
|
ioapic = kzalloc(sizeof(*ioapic), GFP_KERNEL); |
|
if (!ioapic) { |
|
pr_err("cannot allocate memory for new IOAPIC\n"); |
|
goto exit; |
|
} else { |
|
ioapic->root_handle = (acpi_handle)context; |
|
ioapic->handle = handle; |
|
ioapic->gsi_base = (u32)gsi_base; |
|
INIT_LIST_HEAD(&ioapic->list); |
|
} |
|
|
|
if (acpi_ioapic_registered(handle, (u32)gsi_base)) |
|
goto done; |
|
|
|
dev = acpi_get_pci_dev(handle); |
|
if (dev && pci_resource_len(dev, 0)) { |
|
if (pci_enable_device(dev) < 0) |
|
goto exit_put; |
|
pci_set_master(dev); |
|
if (pci_request_region(dev, 0, type)) |
|
goto exit_disable; |
|
pci_res = &dev->resource[0]; |
|
ioapic->pdev = dev; |
|
} else { |
|
pci_dev_put(dev); |
|
dev = NULL; |
|
} |
|
|
|
crs_res = &ioapic->res; |
|
acpi_walk_resources(handle, METHOD_NAME__CRS, setup_res, crs_res); |
|
crs_res->name = type; |
|
crs_res->flags |= IORESOURCE_BUSY; |
|
if (crs_res->flags == 0) { |
|
acpi_handle_warn(handle, "failed to get resource\n"); |
|
goto exit_release; |
|
} else if (insert_resource(&iomem_resource, crs_res)) { |
|
acpi_handle_warn(handle, "failed to insert resource\n"); |
|
goto exit_release; |
|
} |
|
|
|
/* try pci resource first, then "_CRS" resource */ |
|
res = pci_res; |
|
if (!res || !res->flags) |
|
res = crs_res; |
|
|
|
if (acpi_register_ioapic(handle, res->start, (u32)gsi_base)) { |
|
acpi_handle_warn(handle, "failed to register IOAPIC\n"); |
|
goto exit_release; |
|
} |
|
done: |
|
list_add(&ioapic->list, &ioapic_list); |
|
mutex_unlock(&ioapic_list_lock); |
|
|
|
if (dev) |
|
dev_info(&dev->dev, "%s at %pR, GSI %u\n", |
|
type, res, (u32)gsi_base); |
|
else |
|
acpi_handle_info(handle, "%s at %pR, GSI %u\n", |
|
type, res, (u32)gsi_base); |
|
|
|
return AE_OK; |
|
|
|
exit_release: |
|
if (dev) |
|
pci_release_region(dev, 0); |
|
if (ioapic->res.flags && ioapic->res.parent) |
|
release_resource(&ioapic->res); |
|
exit_disable: |
|
if (dev) |
|
pci_disable_device(dev); |
|
exit_put: |
|
pci_dev_put(dev); |
|
kfree(ioapic); |
|
exit: |
|
mutex_unlock(&ioapic_list_lock); |
|
*(acpi_status *)rv = AE_ERROR; |
|
return AE_OK; |
|
} |
|
|
|
int acpi_ioapic_add(acpi_handle root_handle) |
|
{ |
|
acpi_status status, retval = AE_OK; |
|
|
|
status = acpi_walk_namespace(ACPI_TYPE_DEVICE, root_handle, |
|
UINT_MAX, handle_ioapic_add, NULL, |
|
root_handle, (void **)&retval); |
|
|
|
return ACPI_SUCCESS(status) && ACPI_SUCCESS(retval) ? 0 : -ENODEV; |
|
} |
|
|
|
void pci_ioapic_remove(struct acpi_pci_root *root) |
|
{ |
|
struct acpi_pci_ioapic *ioapic, *tmp; |
|
|
|
mutex_lock(&ioapic_list_lock); |
|
list_for_each_entry_safe(ioapic, tmp, &ioapic_list, list) { |
|
if (root->device->handle != ioapic->root_handle) |
|
continue; |
|
if (ioapic->pdev) { |
|
pci_release_region(ioapic->pdev, 0); |
|
pci_disable_device(ioapic->pdev); |
|
pci_dev_put(ioapic->pdev); |
|
} |
|
} |
|
mutex_unlock(&ioapic_list_lock); |
|
} |
|
|
|
int acpi_ioapic_remove(struct acpi_pci_root *root) |
|
{ |
|
int retval = 0; |
|
struct acpi_pci_ioapic *ioapic, *tmp; |
|
|
|
mutex_lock(&ioapic_list_lock); |
|
list_for_each_entry_safe(ioapic, tmp, &ioapic_list, list) { |
|
if (root->device->handle != ioapic->root_handle) |
|
continue; |
|
if (acpi_unregister_ioapic(ioapic->handle, ioapic->gsi_base)) |
|
retval = -EBUSY; |
|
if (ioapic->res.flags && ioapic->res.parent) |
|
release_resource(&ioapic->res); |
|
list_del(&ioapic->list); |
|
kfree(ioapic); |
|
} |
|
mutex_unlock(&ioapic_list_lock); |
|
|
|
return retval; |
|
}
|
|
|