aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/acpi
diff options
context:
space:
mode:
authorRafael J. Wysocki <rafael.j.wysocki@intel.com>2015-06-18 12:32:02 -0400
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>2015-07-21 13:10:02 -0400
commitc75c95bb4bb656d1d2914caf17d71317941100f2 (patch)
tree019b68d8462e562ae6cc2d4016d8c88ca757090c /drivers/acpi
parent5ab4a6010600c24c37f5178d6da981e18cbb090a (diff)
ACPI / PNP: Avoid conflicting resource reservations
commit 0f1b414d190724617eb1cdd615592fa8cd9d0b50 upstream. Commit b9a5e5e18fbf "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 commit b9a5e5e18fbf 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: b9a5e5e18fbf "ACPI / init: Fix the ordering of acpi_reserve_resources()" Reported-by: Roland Dreier <roland@purestorage.com> Signed-off-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Diffstat (limited to 'drivers/acpi')
-rw-r--r--drivers/acpi/osl.c6
-rw-r--r--drivers/acpi/resource.c160
2 files changed, 161 insertions, 5 deletions
diff --git a/drivers/acpi/osl.c b/drivers/acpi/osl.c
index 7ccba395c9dd..5226a8b921ae 100644
--- a/drivers/acpi/osl.c
+++ b/drivers/acpi/osl.c
@@ -175,11 +175,7 @@ static void __init acpi_request_region (struct acpi_generic_address *gas,
175 if (!addr || !length) 175 if (!addr || !length)
176 return; 176 return;
177 177
178 /* Resources are never freed */ 178 acpi_reserve_region(addr, length, gas->space_id, 0, desc);
179 if (gas->space_id == ACPI_ADR_SPACE_SYSTEM_IO)
180 request_region(addr, length, desc);
181 else if (gas->space_id == ACPI_ADR_SPACE_SYSTEM_MEMORY)
182 request_mem_region(addr, length, desc);
183} 179}
184 180
185static void __init acpi_reserve_resources(void) 181static void __init acpi_reserve_resources(void)
diff --git a/drivers/acpi/resource.c b/drivers/acpi/resource.c
index 8244f013f210..fcb7807ea8b7 100644
--- a/drivers/acpi/resource.c
+++ b/drivers/acpi/resource.c
@@ -26,6 +26,7 @@
26#include <linux/device.h> 26#include <linux/device.h>
27#include <linux/export.h> 27#include <linux/export.h>
28#include <linux/ioport.h> 28#include <linux/ioport.h>
29#include <linux/list.h>
29#include <linux/slab.h> 30#include <linux/slab.h>
30 31
31#ifdef CONFIG_X86 32#ifdef CONFIG_X86
@@ -621,3 +622,162 @@ int acpi_dev_filter_resource_type(struct acpi_resource *ares,
621 return (type & types) ? 0 : 1; 622 return (type & types) ? 0 : 1;
622} 623}
623EXPORT_SYMBOL_GPL(acpi_dev_filter_resource_type); 624EXPORT_SYMBOL_GPL(acpi_dev_filter_resource_type);
625
626struct reserved_region {
627 struct list_head node;
628 u64 start;
629 u64 end;
630};
631
632static LIST_HEAD(reserved_io_regions);
633static LIST_HEAD(reserved_mem_regions);
634
635static int request_range(u64 start, u64 end, u8 space_id, unsigned long flags,
636 char *desc)
637{
638 unsigned int length = end - start + 1;
639 struct resource *res;
640
641 res = space_id == ACPI_ADR_SPACE_SYSTEM_IO ?
642 request_region(start, length, desc) :
643 request_mem_region(start, length, desc);
644 if (!res)
645 return -EIO;
646
647 res->flags &= ~flags;
648 return 0;
649}
650
651static int add_region_before(u64 start, u64 end, u8 space_id,
652 unsigned long flags, char *desc,
653 struct list_head *head)
654{
655 struct reserved_region *reg;
656 int error;
657
658 reg = kmalloc(sizeof(*reg), GFP_KERNEL);
659 if (!reg)
660 return -ENOMEM;
661
662 error = request_range(start, end, space_id, flags, desc);
663 if (error)
664 return error;
665
666 reg->start = start;
667 reg->end = end;
668 list_add_tail(&reg->node, head);
669 return 0;
670}
671
672/**
673 * acpi_reserve_region - Reserve an I/O or memory region as a system resource.
674 * @start: Starting address of the region.
675 * @length: Length of the region.
676 * @space_id: Identifier of address space to reserve the region from.
677 * @flags: Resource flags to clear for the region after requesting it.
678 * @desc: Region description (for messages).
679 *
680 * Reserve an I/O or memory region as a system resource to prevent others from
681 * using it. If the new region overlaps with one of the regions (in the given
682 * address space) already reserved by this routine, only the non-overlapping
683 * parts of it will be reserved.
684 *
685 * Returned is either 0 (success) or a negative error code indicating a resource
686 * reservation problem. It is the code of the first encountered error, but the
687 * routine doesn't abort until it has attempted to request all of the parts of
688 * the new region that don't overlap with other regions reserved previously.
689 *
690 * The resources requested by this routine are never released.
691 */
692int acpi_reserve_region(u64 start, unsigned int length, u8 space_id,
693 unsigned long flags, char *desc)
694{
695 struct list_head *regions;
696 struct reserved_region *reg;
697 u64 end = start + length - 1;
698 int ret = 0, error = 0;
699
700 if (space_id == ACPI_ADR_SPACE_SYSTEM_IO)
701 regions = &reserved_io_regions;
702 else if (space_id == ACPI_ADR_SPACE_SYSTEM_MEMORY)
703 regions = &reserved_mem_regions;
704 else
705 return -EINVAL;
706
707 if (list_empty(regions))
708 return add_region_before(start, end, space_id, flags, desc, regions);
709
710 list_for_each_entry(reg, regions, node)
711 if (reg->start == end + 1) {
712 /* The new region can be prepended to this one. */
713 ret = request_range(start, end, space_id, flags, desc);
714 if (!ret)
715 reg->start = start;
716
717 return ret;
718 } else if (reg->start > end) {
719 /* No overlap. Add the new region here and get out. */
720 return add_region_before(start, end, space_id, flags,
721 desc, &reg->node);
722 } else if (reg->end == start - 1) {
723 goto combine;
724 } else if (reg->end >= start) {
725 goto overlap;
726 }
727
728 /* The new region goes after the last existing one. */
729 return add_region_before(start, end, space_id, flags, desc, regions);
730
731 overlap:
732 /*
733 * The new region overlaps an existing one.
734 *
735 * The head part of the new region immediately preceding the existing
736 * overlapping one can be combined with it right away.
737 */
738 if (reg->start > start) {
739 error = request_range(start, reg->start - 1, space_id, flags, desc);
740 if (error)
741 ret = error;
742 else
743 reg->start = start;
744 }
745
746 combine:
747 /*
748 * The new region is adjacent to an existing one. If it extends beyond
749 * that region all the way to the next one, it is possible to combine
750 * all three of them.
751 */
752 while (reg->end < end) {
753 struct reserved_region *next = NULL;
754 u64 a = reg->end + 1, b = end;
755
756 if (!list_is_last(&reg->node, regions)) {
757 next = list_next_entry(reg, node);
758 if (next->start <= end)
759 b = next->start - 1;
760 }
761 error = request_range(a, b, space_id, flags, desc);
762 if (!error) {
763 if (next && next->start == b + 1) {
764 reg->end = next->end;
765 list_del(&next->node);
766 kfree(next);
767 } else {
768 reg->end = end;
769 break;
770 }
771 } else if (next) {
772 if (!ret)
773 ret = error;
774
775 reg = next;
776 } else {
777 break;
778 }
779 }
780
781 return ret ? ret : error;
782}
783EXPORT_SYMBOL_GPL(acpi_reserve_region);