diff options
Diffstat (limited to 'drivers/acpi/resource.c')
-rw-r--r-- | drivers/acpi/resource.c | 160 |
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 | } |
623 | EXPORT_SYMBOL_GPL(acpi_dev_filter_resource_type); | 624 | EXPORT_SYMBOL_GPL(acpi_dev_filter_resource_type); |
625 | |||
626 | struct reserved_region { | ||
627 | struct list_head node; | ||
628 | u64 start; | ||
629 | u64 end; | ||
630 | }; | ||
631 | |||
632 | static LIST_HEAD(reserved_io_regions); | ||
633 | static LIST_HEAD(reserved_mem_regions); | ||
634 | |||
635 | static 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 | |||
651 | static 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(®->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 | */ | ||
692 | int 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, ®->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(®->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 | } | ||
783 | EXPORT_SYMBOL_GPL(acpi_reserve_region); | ||