| /* |
| * Recursive FIFO lock |
| * |
| * Copyright Red Hat, Inc. 2013 |
| * |
| * Authors: |
| * Stefan Hajnoczi <stefanha@redhat.com> |
| * |
| * This work is licensed under the terms of the GNU LGPL, version 2 or later. |
| * See the COPYING.LIB file in the top-level directory. |
| * |
| */ |
| |
| #include <assert.h> |
| #include "qemu/rfifolock.h" |
| |
| void rfifolock_init(RFifoLock *r, void (*cb)(void *), void *opaque) |
| { |
| qemu_mutex_init(&r->lock); |
| r->head = 0; |
| r->tail = 0; |
| qemu_cond_init(&r->cond); |
| r->nesting = 0; |
| r->cb = cb; |
| r->cb_opaque = opaque; |
| } |
| |
| void rfifolock_destroy(RFifoLock *r) |
| { |
| qemu_cond_destroy(&r->cond); |
| qemu_mutex_destroy(&r->lock); |
| } |
| |
| /* |
| * Theory of operation: |
| * |
| * In order to ensure FIFO ordering, implement a ticketlock. Threads acquiring |
| * the lock enqueue themselves by incrementing the tail index. When the lock |
| * is unlocked, the head is incremented and waiting threads are notified. |
| * |
| * Recursive locking does not take a ticket since the head is only incremented |
| * when the outermost recursive caller unlocks. |
| */ |
| void rfifolock_lock(RFifoLock *r) |
| { |
| qemu_mutex_lock(&r->lock); |
| |
| /* Take a ticket */ |
| unsigned int ticket = r->tail++; |
| |
| if (r->nesting > 0 && qemu_thread_is_self(&r->owner_thread)) { |
| r->tail--; /* put ticket back, we're nesting */ |
| } else { |
| while (ticket != r->head) { |
| /* Invoke optional contention callback */ |
| if (r->cb) { |
| r->cb(r->cb_opaque); |
| } |
| qemu_cond_wait(&r->cond, &r->lock); |
| } |
| } |
| |
| qemu_thread_get_self(&r->owner_thread); |
| r->nesting++; |
| qemu_mutex_unlock(&r->lock); |
| } |
| |
| void rfifolock_unlock(RFifoLock *r) |
| { |
| qemu_mutex_lock(&r->lock); |
| assert(r->nesting > 0); |
| assert(qemu_thread_is_self(&r->owner_thread)); |
| if (--r->nesting == 0) { |
| r->head++; |
| qemu_cond_broadcast(&r->cond); |
| } |
| qemu_mutex_unlock(&r->lock); |
| } |