aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/acpi/resource.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/acpi/resource.c')
-rw-r--r--drivers/acpi/resource.c160
1 files changed, 160 insertions, 0 deletions
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);