/* Address ranges. Copyright (C) 1998-2021 Free Software Foundation, Inc. Contributed by Cygnus Solutions. This file is part of the GNU Simulators. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef _SIM_ARANGE_C_ #define _SIM_ARANGE_C_ #include "libiberty.h" #include "sim-basics.h" #include "sim-arange.h" #ifdef HAVE_STDLIB_H #include #endif #ifdef HAVE_STRING_H #include #endif /* Insert a range. */ static void insert_range (ADDR_SUBRANGE **pos, ADDR_SUBRANGE *asr) { asr->next = *pos; *pos = asr; } /* Delete a range. */ static void delete_range (ADDR_SUBRANGE **thisasrp) { ADDR_SUBRANGE *thisasr; thisasr = *thisasrp; *thisasrp = thisasr->next; free (thisasr); } /* Add or delete an address range. This code was borrowed from linux's locks.c:posix_lock_file(). ??? Todo: Given our simpler needs this could be simplified (split into two fns). */ static void frob_range (ADDR_RANGE *ar, address_word start, address_word end, int delete_p) { ADDR_SUBRANGE *asr; ADDR_SUBRANGE *new_asr, *new_asr2; ADDR_SUBRANGE *left = NULL; ADDR_SUBRANGE *right = NULL; ADDR_SUBRANGE **before; ADDR_SUBRANGE init_caller; ADDR_SUBRANGE *caller = &init_caller; int added_p = 0; memset (caller, 0, sizeof (ADDR_SUBRANGE)); new_asr = ZALLOC (ADDR_SUBRANGE); new_asr2 = ZALLOC (ADDR_SUBRANGE); caller->start = start; caller->end = end; before = &ar->ranges; while ((asr = *before) != NULL) { if (! delete_p) { /* Try next range if current range preceeds new one and not adjacent or overlapping. */ if (asr->end < caller->start - 1) goto next_range; /* Break out if new range preceeds current one and not adjacent or overlapping. */ if (asr->start > caller->end + 1) break; /* If we come here, the new and current ranges are adjacent or overlapping. Make one range yielding from the lower start address of both ranges to the higher end address. */ if (asr->start > caller->start) asr->start = caller->start; else caller->start = asr->start; if (asr->end < caller->end) asr->end = caller->end; else caller->end = asr->end; if (added_p) { delete_range (before); continue; } caller = asr; added_p = 1; } else /* deleting a range */ { /* Try next range if current range preceeds new one. */ if (asr->end < caller->start) goto next_range; /* Break out if new range preceeds current one. */ if (asr->start > caller->end) break; added_p = 1; if (asr->start < caller->start) left = asr; /* If the next range in the list has a higher end address than the new one, insert the new one here. */ if (asr->end > caller->end) { right = asr; break; } if (asr->start >= caller->start) { /* The new range completely replaces an old one (This may happen several times). */ if (added_p) { delete_range (before); continue; } /* Replace the old range with the new one. */ asr->start = caller->start; asr->end = caller->end; caller = asr; added_p = 1; } } /* Go on to next range. */ next_range: before = &asr->next; } if (!added_p) { if (delete_p) goto out; new_asr->start = caller->start; new_asr->end = caller->end; insert_range (before, new_asr); new_asr = NULL; } if (right) { if (left == right) { /* The new range breaks the old one in two pieces, so we have to use the second new range. */ new_asr2->start = right->start; new_asr2->end = right->end; left = new_asr2; insert_range (before, left); new_asr2 = NULL; } right->start = caller->end + 1; } if (left) { left->end = caller->start - 1; } out: if (new_asr) free (new_asr); if (new_asr2) free (new_asr2); } /* Free T and all subtrees. */ static void free_search_tree (ADDR_RANGE_TREE *t) { if (t != NULL) { free_search_tree (t->lower); free_search_tree (t->higher); free (t); } } /* Subroutine of build_search_tree to recursively build a balanced tree. ??? It's not an optimum tree though. */ static ADDR_RANGE_TREE * build_tree_1 (ADDR_SUBRANGE **asrtab, unsigned int n) { unsigned int mid = n / 2; ADDR_RANGE_TREE *t; if (n == 0) return NULL; t = (ADDR_RANGE_TREE *) xmalloc (sizeof (ADDR_RANGE_TREE)); t->start = asrtab[mid]->start; t->end = asrtab[mid]->end; if (mid != 0) t->lower = build_tree_1 (asrtab, mid); else t->lower = NULL; if (n > mid + 1) t->higher = build_tree_1 (asrtab + mid + 1, n - mid - 1); else t->higher = NULL; return t; } /* Build a search tree for address range AR. */ static void build_search_tree (ADDR_RANGE *ar) { /* ??? Simple version for now. */ ADDR_SUBRANGE *asr,**asrtab; unsigned int i, n; for (n = 0, asr = ar->ranges; asr != NULL; ++n, asr = asr->next) continue; asrtab = (ADDR_SUBRANGE **) xmalloc (n * sizeof (ADDR_SUBRANGE *)); for (i = 0, asr = ar->ranges; i < n; ++i, asr = asr->next) asrtab[i] = asr; ar->range_tree = build_tree_1 (asrtab, n); free (asrtab); } INLINE_SIM_ARANGE\ (void) sim_addr_range_add (ADDR_RANGE *ar, address_word start, address_word end) { frob_range (ar, start, end, 0); /* Rebuild the search tree. */ /* ??? Instead of rebuilding it here it could be done in a module resume handler, say by first checking for a `changed' flag, assuming of course this would never be done while the simulation is running. */ free_search_tree (ar->range_tree); build_search_tree (ar); } INLINE_SIM_ARANGE\ (void) sim_addr_range_delete (ADDR_RANGE *ar, address_word start, address_word end) { frob_range (ar, start, end, 1); /* Rebuild the search tree. */ /* ??? Instead of rebuilding it here it could be done in a module resume handler, say by first checking for a `changed' flag, assuming of course this would never be done while the simulation is running. */ free_search_tree (ar->range_tree); build_search_tree (ar); } INLINE_SIM_ARANGE\ (int) sim_addr_range_hit_p (ADDR_RANGE *ar, address_word addr) { ADDR_RANGE_TREE *t = ar->range_tree; while (t != NULL) { if (addr < t->start) t = t->lower; else if (addr > t->end) t = t->higher; else return 1; } return 0; } #endif /* _SIM_ARANGE_C_ */