X-Git-Url: http://git.ipfire.org/?a=blobdiff_plain;f=cpus-common.c;h=eaf590cb38708590ac6f1895aa64d8a1c7aefb58;hb=808cf3cb6af8171b4e24d24f2a2d461434dc6572;hp=429652c7fd28c016ecafca9f2a1714428705a33f;hpb=758e1b2b622d7c177dc2d95e887a11aa069b7e68;p=thirdparty%2Fqemu.git diff --git a/cpus-common.c b/cpus-common.c index 429652c7fd2..eaf590cb387 100644 --- a/cpus-common.c +++ b/cpus-common.c @@ -18,8 +18,9 @@ */ #include "qemu/osdep.h" +#include "qemu/main-loop.h" #include "exec/cpu-common.h" -#include "qom/cpu.h" +#include "hw/core/cpu.h" #include "sysemu/cpus.h" static QemuMutex qemu_cpu_list_lock; @@ -27,6 +28,9 @@ static QemuCond exclusive_cond; static QemuCond exclusive_resume; static QemuCond qemu_work_cond; +/* >= 1 if a thread is inside start_exclusive/end_exclusive. Written + * under qemu_cpu_list_lock, read with atomic operations. + */ static int pending_cpus; void qemu_init_cpu_list(void) @@ -65,12 +69,6 @@ static int cpu_get_free_index(void) return cpu_index; } -static void finish_safe_work(CPUState *cpu) -{ - cpu_exec_start(cpu); - cpu_exec_end(cpu); -} - void cpu_list_add(CPUState *cpu) { qemu_mutex_lock(&qemu_cpu_list_lock); @@ -80,10 +78,8 @@ void cpu_list_add(CPUState *cpu) } else { assert(!cpu_index_auto_assigned); } - QTAILQ_INSERT_TAIL(&cpus, cpu, node); + QTAILQ_INSERT_TAIL_RCU(&cpus, cpu, node); qemu_mutex_unlock(&qemu_cpu_list_lock); - - finish_safe_work(cpu); } void cpu_list_remove(CPUState *cpu) @@ -95,9 +91,9 @@ void cpu_list_remove(CPUState *cpu) return; } - assert(!(cpu_index_auto_assigned && cpu != QTAILQ_LAST(&cpus, CPUTailQ))); + assert(!(cpu_index_auto_assigned && cpu != QTAILQ_LAST(&cpus))); - QTAILQ_REMOVE(&cpus, cpu, node); + QTAILQ_REMOVE_RCU(&cpus, cpu, node); cpu->cpu_index = UNASSIGNED_CPU_INDEX; qemu_mutex_unlock(&qemu_cpu_list_lock); } @@ -105,8 +101,8 @@ void cpu_list_remove(CPUState *cpu) struct qemu_work_item { struct qemu_work_item *next; run_on_cpu_func func; - void *data; - bool free, done; + run_on_cpu_data data; + bool free, exclusive, done; }; static void queue_work_on_cpu(CPUState *cpu, struct qemu_work_item *wi) @@ -125,7 +121,7 @@ static void queue_work_on_cpu(CPUState *cpu, struct qemu_work_item *wi) qemu_cpu_kick(cpu); } -void do_run_on_cpu(CPUState *cpu, run_on_cpu_func func, void *data, +void do_run_on_cpu(CPUState *cpu, run_on_cpu_func func, run_on_cpu_data data, QemuMutex *mutex) { struct qemu_work_item wi; @@ -139,6 +135,7 @@ void do_run_on_cpu(CPUState *cpu, run_on_cpu_func func, void *data, wi.data = data; wi.done = false; wi.free = false; + wi.exclusive = false; queue_work_on_cpu(cpu, &wi); while (!atomic_mb_read(&wi.done)) { @@ -149,7 +146,7 @@ void do_run_on_cpu(CPUState *cpu, run_on_cpu_func func, void *data, } } -void async_run_on_cpu(CPUState *cpu, run_on_cpu_func func, void *data) +void async_run_on_cpu(CPUState *cpu, run_on_cpu_func func, run_on_cpu_data data) { struct qemu_work_item *wi; @@ -175,18 +172,26 @@ static inline void exclusive_idle(void) void start_exclusive(void) { CPUState *other_cpu; + int running_cpus; qemu_mutex_lock(&qemu_cpu_list_lock); exclusive_idle(); /* Make all other cpus stop executing. */ - pending_cpus = 1; + atomic_set(&pending_cpus, 1); + + /* Write pending_cpus before reading other_cpu->running. */ + smp_mb(); + running_cpus = 0; CPU_FOREACH(other_cpu) { - if (other_cpu->running) { - pending_cpus++; + if (atomic_read(&other_cpu->running)) { + other_cpu->has_waiter = true; + running_cpus++; qemu_cpu_kick(other_cpu); } } + + atomic_set(&pending_cpus, running_cpus + 1); while (pending_cpus > 1) { qemu_cond_wait(&exclusive_cond, &qemu_cpu_list_lock); } @@ -195,13 +200,17 @@ void start_exclusive(void) * section until end_exclusive resets pending_cpus to 0. */ qemu_mutex_unlock(&qemu_cpu_list_lock); + + current_cpu->in_exclusive_context = true; } /* Finish an exclusive operation. */ void end_exclusive(void) { + current_cpu->in_exclusive_context = false; + qemu_mutex_lock(&qemu_cpu_list_lock); - pending_cpus = 0; + atomic_set(&pending_cpus, 0); qemu_cond_broadcast(&exclusive_resume); qemu_mutex_unlock(&qemu_cpu_list_lock); } @@ -209,24 +218,92 @@ void end_exclusive(void) /* Wait for exclusive ops to finish, and begin cpu execution. */ void cpu_exec_start(CPUState *cpu) { - qemu_mutex_lock(&qemu_cpu_list_lock); - exclusive_idle(); - cpu->running = true; - qemu_mutex_unlock(&qemu_cpu_list_lock); + atomic_set(&cpu->running, true); + + /* Write cpu->running before reading pending_cpus. */ + smp_mb(); + + /* 1. start_exclusive saw cpu->running == true and pending_cpus >= 1. + * After taking the lock we'll see cpu->has_waiter == true and run---not + * for long because start_exclusive kicked us. cpu_exec_end will + * decrement pending_cpus and signal the waiter. + * + * 2. start_exclusive saw cpu->running == false but pending_cpus >= 1. + * This includes the case when an exclusive item is running now. + * Then we'll see cpu->has_waiter == false and wait for the item to + * complete. + * + * 3. pending_cpus == 0. Then start_exclusive is definitely going to + * see cpu->running == true, and it will kick the CPU. + */ + if (unlikely(atomic_read(&pending_cpus))) { + qemu_mutex_lock(&qemu_cpu_list_lock); + if (!cpu->has_waiter) { + /* Not counted in pending_cpus, let the exclusive item + * run. Since we have the lock, just set cpu->running to true + * while holding it; no need to check pending_cpus again. + */ + atomic_set(&cpu->running, false); + exclusive_idle(); + /* Now pending_cpus is zero. */ + atomic_set(&cpu->running, true); + } else { + /* Counted in pending_cpus, go ahead and release the + * waiter at cpu_exec_end. + */ + } + qemu_mutex_unlock(&qemu_cpu_list_lock); + } } /* Mark cpu as not executing, and release pending exclusive ops. */ void cpu_exec_end(CPUState *cpu) { - qemu_mutex_lock(&qemu_cpu_list_lock); - cpu->running = false; - if (pending_cpus > 1) { - pending_cpus--; - if (pending_cpus == 1) { - qemu_cond_signal(&exclusive_cond); + atomic_set(&cpu->running, false); + + /* Write cpu->running before reading pending_cpus. */ + smp_mb(); + + /* 1. start_exclusive saw cpu->running == true. Then it will increment + * pending_cpus and wait for exclusive_cond. After taking the lock + * we'll see cpu->has_waiter == true. + * + * 2. start_exclusive saw cpu->running == false but here pending_cpus >= 1. + * This includes the case when an exclusive item started after setting + * cpu->running to false and before we read pending_cpus. Then we'll see + * cpu->has_waiter == false and not touch pending_cpus. The next call to + * cpu_exec_start will run exclusive_idle if still necessary, thus waiting + * for the item to complete. + * + * 3. pending_cpus == 0. Then start_exclusive is definitely going to + * see cpu->running == false, and it can ignore this CPU until the + * next cpu_exec_start. + */ + if (unlikely(atomic_read(&pending_cpus))) { + qemu_mutex_lock(&qemu_cpu_list_lock); + if (cpu->has_waiter) { + cpu->has_waiter = false; + atomic_set(&pending_cpus, pending_cpus - 1); + if (pending_cpus == 1) { + qemu_cond_signal(&exclusive_cond); + } } + qemu_mutex_unlock(&qemu_cpu_list_lock); } - qemu_mutex_unlock(&qemu_cpu_list_lock); +} + +void async_safe_run_on_cpu(CPUState *cpu, run_on_cpu_func func, + run_on_cpu_data data) +{ + struct qemu_work_item *wi; + + wi = g_malloc0(sizeof(struct qemu_work_item)); + wi->func = func; + wi->data = data; + wi->free = true; + wi->exclusive = true; + + queue_work_on_cpu(cpu, wi); } void process_queued_cpu_work(CPUState *cpu) @@ -245,7 +322,21 @@ void process_queued_cpu_work(CPUState *cpu) cpu->queued_work_last = NULL; } qemu_mutex_unlock(&cpu->work_mutex); - wi->func(cpu, wi->data); + if (wi->exclusive) { + /* Running work items outside the BQL avoids the following deadlock: + * 1) start_exclusive() is called with the BQL taken while another + * CPU is running; 2) cpu_exec in the other CPU tries to takes the + * BQL, so it goes to sleep; start_exclusive() is sleeping too, so + * neither CPU can proceed. + */ + qemu_mutex_unlock_iothread(); + start_exclusive(); + wi->func(cpu, wi->data); + end_exclusive(); + qemu_mutex_lock_iothread(); + } else { + wi->func(cpu, wi->data); + } qemu_mutex_lock(&cpu->work_mutex); if (wi->free) { g_free(wi);