diff options
Diffstat (limited to 'accel/tcg/user-exec.c')
| -rw-r--r-- | accel/tcg/user-exec.c | 59 |
1 files changed, 44 insertions, 15 deletions
diff --git a/accel/tcg/user-exec.c b/accel/tcg/user-exec.c index a3cecda405..a8eb63ab96 100644 --- a/accel/tcg/user-exec.c +++ b/accel/tcg/user-exec.c @@ -22,6 +22,7 @@ #include "exec/exec-all.h" #include "tcg/tcg.h" #include "qemu/bitops.h" +#include "qemu/rcu.h" #include "exec/cpu_ldst.h" #include "exec/translate-all.h" #include "exec/helper-proto.h" @@ -136,6 +137,7 @@ bool handle_sigsegv_accerr_write(CPUState *cpu, sigset_t *old_set, } typedef struct PageFlagsNode { + struct rcu_head rcu; IntervalTreeNode itree; int flags; } PageFlagsNode; @@ -266,7 +268,7 @@ static bool pageflags_unset(target_ulong start, target_ulong last) } } else if (p_last <= last) { /* Range completely covers node -- remove it. */ - g_free(p); + g_free_rcu(p, rcu); } else { /* Truncate the node from the start. */ p->itree.start = last + 1; @@ -311,7 +313,7 @@ static void pageflags_create_merge(target_ulong start, target_ulong last, if (prev) { if (next) { prev->itree.last = next->itree.last; - g_free(next); + g_free_rcu(next, rcu); } else { prev->itree.last = last; } @@ -376,7 +378,7 @@ static bool pageflags_set_clear(target_ulong start, target_ulong last, p->flags = merge_flags; } else { interval_tree_remove(&p->itree, &pageflags_root); - g_free(p); + g_free_rcu(p, rcu); } goto done; } @@ -421,7 +423,7 @@ static bool pageflags_set_clear(target_ulong start, target_ulong last, p->flags = merge_flags; } else { interval_tree_remove(&p->itree, &pageflags_root); - g_free(p); + g_free_rcu(p, rcu); } if (p_last < last) { start = p_last + 1; @@ -462,7 +464,7 @@ static bool pageflags_set_clear(target_ulong start, target_ulong last, p->itree.start = last + 1; interval_tree_insert(&p->itree, &pageflags_root); } else { - g_free(p); + g_free_rcu(p, rcu); goto restart; } if (set_flags) { @@ -523,6 +525,8 @@ void page_set_flags(target_ulong start, target_ulong end, int flags) int page_check_range(target_ulong start, target_ulong len, int flags) { target_ulong last; + int locked; /* tri-state: =0: unlocked, +1: global, -1: local */ + int ret; if (len == 0) { return 0; /* trivial length */ @@ -533,42 +537,67 @@ int page_check_range(target_ulong start, target_ulong len, int flags) return -1; /* wrap around */ } + locked = have_mmap_lock(); while (true) { PageFlagsNode *p = pageflags_find(start, last); int missing; if (!p) { - return -1; /* entire region invalid */ + if (!locked) { + /* + * Lockless lookups have false negatives. + * Retry with the lock held. + */ + mmap_lock(); + locked = -1; + p = pageflags_find(start, last); + } + if (!p) { + ret = -1; /* entire region invalid */ + break; + } } if (start < p->itree.start) { - return -1; /* initial bytes invalid */ + ret = -1; /* initial bytes invalid */ + break; } missing = flags & ~p->flags; if (missing & PAGE_READ) { - return -1; /* page not readable */ + ret = -1; /* page not readable */ + break; } if (missing & PAGE_WRITE) { if (!(p->flags & PAGE_WRITE_ORG)) { - return -1; /* page not writable */ + ret = -1; /* page not writable */ + break; } /* Asking about writable, but has been protected: undo. */ if (!page_unprotect(start, 0)) { - return -1; + ret = -1; + break; } /* TODO: page_unprotect should take a range, not a single page. */ if (last - start < TARGET_PAGE_SIZE) { - return 0; /* ok */ + ret = 0; /* ok */ + break; } start += TARGET_PAGE_SIZE; continue; } if (last <= p->itree.last) { - return 0; /* ok */ + ret = 0; /* ok */ + break; } start = p->itree.last + 1; } + + /* Release the lock if acquired locally. */ + if (locked < 0) { + mmap_unlock(); + } + return ret; } void page_protect(tb_page_addr_t address) @@ -779,6 +808,7 @@ tb_page_addr_t get_page_addr_code_hostp(CPUArchState *env, target_ulong addr, #define TBD_MASK (TARGET_PAGE_MASK * TPD_PAGES) typedef struct TargetPageDataNode { + struct rcu_head rcu; IntervalTreeNode itree; char data[TPD_PAGES][TARGET_PAGE_DATA_SIZE] __attribute__((aligned)); } TargetPageDataNode; @@ -801,11 +831,11 @@ void page_reset_target_data(target_ulong start, target_ulong end) n = next, next = next ? interval_tree_iter_next(n, start, last) : NULL) { target_ulong n_start, n_last, p_ofs, p_len; - TargetPageDataNode *t; + TargetPageDataNode *t = container_of(n, TargetPageDataNode, itree); if (n->start >= start && n->last <= last) { interval_tree_remove(n, &targetdata_root); - g_free(n); + g_free_rcu(t, rcu); continue; } @@ -819,7 +849,6 @@ void page_reset_target_data(target_ulong start, target_ulong end) n_last = MIN(last, n->last); p_len = (n_last + 1 - n_start) >> TARGET_PAGE_BITS; - t = container_of(n, TargetPageDataNode, itree); memset(t->data[p_ofs], 0, p_len * TARGET_PAGE_DATA_SIZE); } } |