0f1b414d19
Commitb9a5e5e18f
"ACPI / init: Fix the ordering of acpi_reserve_resources()" overlooked the fact that the memory and/or I/O regions reserved by acpi_reserve_resources() may conflict with those reserved by the PNP "system" driver. If that conflict actually takes place, it causes the reservations made by the "system" driver to fail while before commitb9a5e5e18f
all reservations made by it and by acpi_reserve_resources() would be successful. In turn, that allows the resources that haven't been reserved by the "system" driver to be used by others (e.g. PCI) which sometimes leads to functional problems (up to and including boot failures). To fix that issue, introduce a common resource reservation routine, acpi_reserve_region(), to be used by both acpi_reserve_resources() and the "system" driver, that will track all resources reserved by it and avoid making conflicting requests. Link: https://bugzilla.kernel.org/show_bug.cgi?id=99831 Link: http://marc.info/?t=143389402600001&r=1&w=2 Fixes:b9a5e5e18f
"ACPI / init: Fix the ordering of acpi_reserve_resources()" Reported-by: Roland Dreier <roland@purestorage.com> Cc: All applicable <stable@vger.kernel.org> Signed-off-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com>
130 lines
3.1 KiB
C
130 lines
3.1 KiB
C
/*
|
|
* system.c - a driver for reserving pnp system resources
|
|
*
|
|
* Some code is based on pnpbios_core.c
|
|
* Copyright 2002 Adam Belay <ambx1@neo.rr.com>
|
|
* (c) Copyright 2007 Hewlett-Packard Development Company, L.P.
|
|
* Bjorn Helgaas <bjorn.helgaas@hp.com>
|
|
*/
|
|
|
|
#include <linux/acpi.h>
|
|
#include <linux/pnp.h>
|
|
#include <linux/device.h>
|
|
#include <linux/init.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/ioport.h>
|
|
|
|
static const struct pnp_device_id pnp_dev_table[] = {
|
|
/* General ID for reserving resources */
|
|
{"PNP0c02", 0},
|
|
/* memory controller */
|
|
{"PNP0c01", 0},
|
|
{"", 0}
|
|
};
|
|
|
|
#ifdef CONFIG_ACPI
|
|
static bool __reserve_range(u64 start, unsigned int length, bool io, char *desc)
|
|
{
|
|
u8 space_id = io ? ACPI_ADR_SPACE_SYSTEM_IO : ACPI_ADR_SPACE_SYSTEM_MEMORY;
|
|
return !acpi_reserve_region(start, length, space_id, IORESOURCE_BUSY, desc);
|
|
}
|
|
#else
|
|
static bool __reserve_range(u64 start, unsigned int length, bool io, char *desc)
|
|
{
|
|
struct resource *res;
|
|
|
|
res = io ? request_region(start, length, desc) :
|
|
request_mem_region(start, length, desc);
|
|
if (res) {
|
|
res->flags &= ~IORESOURCE_BUSY;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
#endif
|
|
|
|
static void reserve_range(struct pnp_dev *dev, struct resource *r, int port)
|
|
{
|
|
char *regionid;
|
|
const char *pnpid = dev_name(&dev->dev);
|
|
resource_size_t start = r->start, end = r->end;
|
|
bool reserved;
|
|
|
|
regionid = kmalloc(16, GFP_KERNEL);
|
|
if (!regionid)
|
|
return;
|
|
|
|
snprintf(regionid, 16, "pnp %s", pnpid);
|
|
reserved = __reserve_range(start, end - start + 1, !!port, regionid);
|
|
if (!reserved)
|
|
kfree(regionid);
|
|
|
|
/*
|
|
* Failures at this point are usually harmless. pci quirks for
|
|
* example do reserve stuff they know about too, so we may well
|
|
* have double reservations.
|
|
*/
|
|
dev_info(&dev->dev, "%pR %s reserved\n", r,
|
|
reserved ? "has been" : "could not be");
|
|
}
|
|
|
|
static void reserve_resources_of_dev(struct pnp_dev *dev)
|
|
{
|
|
struct resource *res;
|
|
int i;
|
|
|
|
for (i = 0; (res = pnp_get_resource(dev, IORESOURCE_IO, i)); i++) {
|
|
if (res->flags & IORESOURCE_DISABLED)
|
|
continue;
|
|
if (res->start == 0)
|
|
continue; /* disabled */
|
|
if (res->start < 0x100)
|
|
/*
|
|
* Below 0x100 is only standard PC hardware
|
|
* (pics, kbd, timer, dma, ...)
|
|
* We should not get resource conflicts there,
|
|
* and the kernel reserves these anyway
|
|
* (see arch/i386/kernel/setup.c).
|
|
* So, do nothing
|
|
*/
|
|
continue;
|
|
if (res->end < res->start)
|
|
continue; /* invalid */
|
|
|
|
reserve_range(dev, res, 1);
|
|
}
|
|
|
|
for (i = 0; (res = pnp_get_resource(dev, IORESOURCE_MEM, i)); i++) {
|
|
if (res->flags & IORESOURCE_DISABLED)
|
|
continue;
|
|
|
|
reserve_range(dev, res, 0);
|
|
}
|
|
}
|
|
|
|
static int system_pnp_probe(struct pnp_dev *dev,
|
|
const struct pnp_device_id *dev_id)
|
|
{
|
|
reserve_resources_of_dev(dev);
|
|
return 0;
|
|
}
|
|
|
|
static struct pnp_driver system_pnp_driver = {
|
|
.name = "system",
|
|
.id_table = pnp_dev_table,
|
|
.flags = PNP_DRIVER_RES_DO_NOT_CHANGE,
|
|
.probe = system_pnp_probe,
|
|
};
|
|
|
|
static int __init pnp_system_init(void)
|
|
{
|
|
return pnp_register_driver(&system_pnp_driver);
|
|
}
|
|
|
|
/**
|
|
* Reserve motherboard resources after PCI claim BARs,
|
|
* but before PCI assign resources for uninitialized PCI devices
|
|
*/
|
|
fs_initcall(pnp_system_init);
|