/*- * Copyright (c) 2000 Michael Smith * Copyright (c) 2000 BSDi * Copyright (c) 2007-2009 Jung-uk Kim * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ /* * 6.1 : Mutual Exclusion and Synchronisation */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #define _COMPONENT ACPI_OS_SERVICES ACPI_MODULE_NAME("SYNCH") static MALLOC_DEFINE(M_ACPISEM, "acpisem", "ACPI semaphore"); /* * Convert milliseconds to ticks. */ static int timeout2hz(UINT16 Timeout) { struct timeval tv; tv.tv_sec = (time_t)(Timeout / 1000); tv.tv_usec = (suseconds_t)(Timeout % 1000) * 1000; return (tvtohz(&tv)); } /* * ACPI_SEMAPHORE */ struct acpi_sema { struct mtx as_lock; char as_name[32]; struct cv as_cv; UINT32 as_maxunits; UINT32 as_units; int as_waiters; int as_reset; }; ACPI_STATUS AcpiOsCreateSemaphore(UINT32 MaxUnits, UINT32 InitialUnits, ACPI_SEMAPHORE *OutHandle) { struct acpi_sema *as; ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); if (OutHandle == NULL || MaxUnits == 0 || InitialUnits > MaxUnits) return_ACPI_STATUS (AE_BAD_PARAMETER); if ((as = malloc(sizeof(*as), M_ACPISEM, M_NOWAIT | M_ZERO)) == NULL) return_ACPI_STATUS (AE_NO_MEMORY); snprintf(as->as_name, sizeof(as->as_name), "ACPI sema (%p)", as); mtx_init(&as->as_lock, as->as_name, NULL, MTX_DEF); cv_init(&as->as_cv, as->as_name); as->as_maxunits = MaxUnits; as->as_units = InitialUnits; *OutHandle = (ACPI_SEMAPHORE)as; ACPI_DEBUG_PRINT((ACPI_DB_MUTEX, "created %s, max %u, initial %u\n", as->as_name, MaxUnits, InitialUnits)); return_ACPI_STATUS (AE_OK); } ACPI_STATUS AcpiOsDeleteSemaphore(ACPI_SEMAPHORE Handle) { struct acpi_sema *as = (struct acpi_sema *)Handle; ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); if (as == NULL) return_ACPI_STATUS (AE_BAD_PARAMETER); mtx_lock(&as->as_lock); ACPI_DEBUG_PRINT((ACPI_DB_MUTEX, "delete %s\n", as->as_name)); if (as->as_waiters > 0) { ACPI_DEBUG_PRINT((ACPI_DB_MUTEX, "reset %s, units %u, waiters %d\n", as->as_name, as->as_units, as->as_waiters)); as->as_reset = 1; cv_broadcast(&as->as_cv); while (as->as_waiters > 0) { if (mtx_sleep(&as->as_reset, &as->as_lock, PCATCH, "acsrst", hz) == EINTR) { ACPI_DEBUG_PRINT((ACPI_DB_MUTEX, "failed to reset %s, waiters %d\n", as->as_name, as->as_waiters)); mtx_unlock(&as->as_lock); return_ACPI_STATUS (AE_ERROR); } ACPI_DEBUG_PRINT((ACPI_DB_MUTEX, "wait %s, units %u, waiters %d\n", as->as_name, as->as_units, as->as_waiters)); } } mtx_unlock(&as->as_lock); mtx_destroy(&as->as_lock); cv_destroy(&as->as_cv); free(as, M_ACPISEM); return_ACPI_STATUS (AE_OK); } #define ACPISEM_AVAIL(s, u) ((s)->as_units >= (u)) ACPI_STATUS AcpiOsWaitSemaphore(ACPI_SEMAPHORE Handle, UINT32 Units, UINT16 Timeout) { struct acpi_sema *as = (struct acpi_sema *)Handle; int error, prevtick, slptick, tmo; ACPI_STATUS status = AE_OK; ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); if (as == NULL || Units == 0) return_ACPI_STATUS (AE_BAD_PARAMETER); mtx_lock(&as->as_lock); ACPI_DEBUG_PRINT((ACPI_DB_MUTEX, "get %u unit(s) from %s, units %u, waiters %d, timeout %u\n", Units, as->as_name, as->as_units, as->as_waiters, Timeout)); if (as->as_maxunits != ACPI_NO_UNIT_LIMIT && as->as_maxunits < Units) { mtx_unlock(&as->as_lock); return_ACPI_STATUS (AE_LIMIT); } switch (Timeout) { case ACPI_DO_NOT_WAIT: if (!ACPISEM_AVAIL(as, Units)) status = AE_TIME; break; case ACPI_WAIT_FOREVER: while (!ACPISEM_AVAIL(as, Units)) { as->as_waiters++; error = cv_wait_sig(&as->as_cv, &as->as_lock); as->as_waiters--; if (error == EINTR || as->as_reset) { status = AE_ERROR; break; } } break; default: if (cold) { /* * Just spin polling the semaphore once a * millisecond. */ while (!ACPISEM_AVAIL(as, Units)) { if (Timeout == 0) { status = AE_TIME; break; } Timeout--; mtx_unlock(&as->as_lock); DELAY(1000); mtx_lock(&as->as_lock); } break; } tmo = timeout2hz(Timeout); while (!ACPISEM_AVAIL(as, Units)) { prevtick = ticks; as->as_waiters++; error = cv_timedwait_sig(&as->as_cv, &as->as_lock, tmo); as->as_waiters--; if (error == EINTR || as->as_reset) { status = AE_ERROR; break; } if (ACPISEM_AVAIL(as, Units)) break; slptick = ticks - prevtick; if (slptick >= tmo || slptick < 0) { status = AE_TIME; break; } tmo -= slptick; } } if (ACPI_SUCCESS(status)) as->as_units -= Units; mtx_unlock(&as->as_lock); return_ACPI_STATUS (status); } ACPI_STATUS AcpiOsSignalSemaphore(ACPI_SEMAPHORE Handle, UINT32 Units) { struct acpi_sema *as = (struct acpi_sema *)Handle; UINT32 i; ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); if (as == NULL || Units == 0) return_ACPI_STATUS (AE_BAD_PARAMETER); mtx_lock(&as->as_lock); ACPI_DEBUG_PRINT((ACPI_DB_MUTEX, "return %u units to %s, units %u, waiters %d\n", Units, as->as_name, as->as_units, as->as_waiters)); if (as->as_maxunits != ACPI_NO_UNIT_LIMIT && (as->as_maxunits < Units || as->as_maxunits - Units < as->as_units)) { ACPI_DEBUG_PRINT((ACPI_DB_MUTEX, "exceeded max units %u\n", as->as_maxunits)); mtx_unlock(&as->as_lock); return_ACPI_STATUS (AE_LIMIT); } as->as_units += Units; if (as->as_waiters > 0 && ACPISEM_AVAIL(as, Units)) for (i = 0; i < Units; i++) cv_signal(&as->as_cv); mtx_unlock(&as->as_lock); return_ACPI_STATUS (AE_OK); } #undef ACPISEM_AVAIL /* * ACPI_MUTEX */ struct acpi_mutex { struct mtx am_lock; char am_name[32]; struct thread *am_owner; int am_nested; int am_waiters; int am_reset; }; ACPI_STATUS AcpiOsCreateMutex(ACPI_MUTEX *OutHandle) { struct acpi_mutex *am; ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); if (OutHandle == NULL) return_ACPI_STATUS (AE_BAD_PARAMETER); if ((am = malloc(sizeof(*am), M_ACPISEM, M_NOWAIT | M_ZERO)) == NULL) return_ACPI_STATUS (AE_NO_MEMORY); snprintf(am->am_name, sizeof(am->am_name), "ACPI mutex (%p)", am); mtx_init(&am->am_lock, am->am_name, NULL, MTX_DEF); ACPI_DEBUG_PRINT((ACPI_DB_MUTEX, "created %s\n", am->am_name)); *OutHandle = (ACPI_MUTEX)am; return_ACPI_STATUS (AE_OK); } #define ACPIMTX_AVAIL(m) ((m)->am_owner == NULL) #define ACPIMTX_OWNED(m) ((m)->am_owner == curthread) void AcpiOsDeleteMutex(ACPI_MUTEX Handle) { struct acpi_mutex *am = (struct acpi_mutex *)Handle; ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); if (am == NULL) { ACPI_DEBUG_PRINT((ACPI_DB_MUTEX, "cannot delete null mutex\n")); return_VOID; } mtx_lock(&am->am_lock); ACPI_DEBUG_PRINT((ACPI_DB_MUTEX, "delete %s\n", am->am_name)); if (am->am_waiters > 0) { ACPI_DEBUG_PRINT((ACPI_DB_MUTEX, "reset %s, owner %p\n", am->am_name, am->am_owner)); am->am_reset = 1; wakeup(am); while (am->am_waiters > 0) { if (mtx_sleep(&am->am_reset, &am->am_lock, PCATCH, "acmrst", hz) == EINTR) { ACPI_DEBUG_PRINT((ACPI_DB_MUTEX, "failed to reset %s, waiters %d\n", am->am_name, am->am_waiters)); mtx_unlock(&am->am_lock); return_VOID; } if (ACPIMTX_AVAIL(am)) ACPI_DEBUG_PRINT((ACPI_DB_MUTEX, "wait %s, waiters %d\n", am->am_name, am->am_waiters)); else ACPI_DEBUG_PRINT((ACPI_DB_MUTEX, "wait %s, owner %p, waiters %d\n", am->am_name, am->am_owner, am->am_waiters)); } } mtx_unlock(&am->am_lock); mtx_destroy(&am->am_lock); free(am, M_ACPISEM); } ACPI_STATUS AcpiOsAcquireMutex(ACPI_MUTEX Handle, UINT16 Timeout) { struct acpi_mutex *am = (struct acpi_mutex *)Handle; int error, prevtick, slptick, tmo; ACPI_STATUS status = AE_OK; ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); if (am == NULL) return_ACPI_STATUS (AE_BAD_PARAMETER); mtx_lock(&am->am_lock); ACPI_DEBUG_PRINT((ACPI_DB_MUTEX, "acquire %s\n", am->am_name)); if (ACPIMTX_OWNED(am)) { am->am_nested++; ACPI_DEBUG_PRINT((ACPI_DB_MUTEX, "acquire nested %s, depth %d\n", am->am_name, am->am_nested)); mtx_unlock(&am->am_lock); return_ACPI_STATUS (AE_OK); } switch (Timeout) { case ACPI_DO_NOT_WAIT: if (!ACPIMTX_AVAIL(am)) status = AE_TIME; break; case ACPI_WAIT_FOREVER: while (!ACPIMTX_AVAIL(am)) { am->am_waiters++; error = mtx_sleep(am, &am->am_lock, PCATCH, "acmtx", 0); am->am_waiters--; if (error == EINTR || am->am_reset) { status = AE_ERROR; break; } } break; default: if (cold) { /* * Just spin polling the mutex once a * millisecond. */ while (!ACPIMTX_AVAIL(am)) { if (Timeout == 0) { status = AE_TIME; break; } Timeout--; mtx_unlock(&am->am_lock); DELAY(1000); mtx_lock(&am->am_lock); } break; } tmo = timeout2hz(Timeout); while (!ACPIMTX_AVAIL(am)) { prevtick = ticks; am->am_waiters++; error = mtx_sleep(am, &am->am_lock, PCATCH, "acmtx", tmo); am->am_waiters--; if (error == EINTR || am->am_reset) { status = AE_ERROR; break; } if (ACPIMTX_AVAIL(am)) break; slptick = ticks - prevtick; if (slptick >= tmo || slptick < 0) { status = AE_TIME; break; } tmo -= slptick; } } if (ACPI_SUCCESS(status)) am->am_owner = curthread; mtx_unlock(&am->am_lock); return_ACPI_STATUS (status); } void AcpiOsReleaseMutex(ACPI_MUTEX Handle) { struct acpi_mutex *am = (struct acpi_mutex *)Handle; ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); if (am == NULL) { ACPI_DEBUG_PRINT((ACPI_DB_MUTEX, "cannot release null mutex\n")); return_VOID; } mtx_lock(&am->am_lock); ACPI_DEBUG_PRINT((ACPI_DB_MUTEX, "release %s\n", am->am_name)); if (ACPIMTX_OWNED(am)) { if (am->am_nested > 0) { ACPI_DEBUG_PRINT((ACPI_DB_MUTEX, "release nested %s, depth %d\n", am->am_name, am->am_nested)); am->am_nested--; } else am->am_owner = NULL; } else { if (ACPIMTX_AVAIL(am)) ACPI_DEBUG_PRINT((ACPI_DB_MUTEX, "release already available %s\n", am->am_name)); else ACPI_DEBUG_PRINT((ACPI_DB_MUTEX, "release unowned %s from %p, depth %d\n", am->am_name, am->am_owner, am->am_nested)); } if (am->am_waiters > 0 && ACPIMTX_AVAIL(am)) wakeup_one(am); mtx_unlock(&am->am_lock); } #undef ACPIMTX_AVAIL #undef ACPIMTX_OWNED /* * ACPI_SPINLOCK */ struct acpi_spinlock { struct mtx al_lock; char al_name[32]; int al_nested; }; ACPI_STATUS AcpiOsCreateLock(ACPI_SPINLOCK *OutHandle) { struct acpi_spinlock *al; ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); if (OutHandle == NULL) return_ACPI_STATUS (AE_BAD_PARAMETER); if ((al = malloc(sizeof(*al), M_ACPISEM, M_NOWAIT | M_ZERO)) == NULL) return_ACPI_STATUS (AE_NO_MEMORY); #ifdef ACPI_DEBUG if (OutHandle == &AcpiGbl_GpeLock) snprintf(al->al_name, sizeof(al->al_name), "ACPI lock (GPE)"); else if (OutHandle == &AcpiGbl_HardwareLock) snprintf(al->al_name, sizeof(al->al_name), "ACPI lock (HW)"); else #endif snprintf(al->al_name, sizeof(al->al_name), "ACPI lock (%p)", al); mtx_init(&al->al_lock, al->al_name, NULL, MTX_SPIN); ACPI_DEBUG_PRINT((ACPI_DB_MUTEX, "created %s\n", al->al_name)); *OutHandle = (ACPI_SPINLOCK)al; return_ACPI_STATUS (AE_OK); } void AcpiOsDeleteLock(ACPI_SPINLOCK Handle) { struct acpi_spinlock *al = (struct acpi_spinlock *)Handle; ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); if (al == NULL) { ACPI_DEBUG_PRINT((ACPI_DB_MUTEX, "cannot delete null spinlock\n")); return_VOID; } ACPI_DEBUG_PRINT((ACPI_DB_MUTEX, "delete %s\n", al->al_name)); mtx_destroy(&al->al_lock); free(al, M_ACPISEM); } ACPI_CPU_FLAGS AcpiOsAcquireLock(ACPI_SPINLOCK Handle) { struct acpi_spinlock *al = (struct acpi_spinlock *)Handle; ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); if (al == NULL) { ACPI_DEBUG_PRINT((ACPI_DB_MUTEX, "cannot acquire null spinlock\n")); return (0); } ACPI_DEBUG_PRINT((ACPI_DB_MUTEX, "acquire %s\n", al->al_name)); if (mtx_owned(&al->al_lock)) { al->al_nested++; ACPI_DEBUG_PRINT((ACPI_DB_MUTEX, "acquire nested %s, depth %d\n", al->al_name, al->al_nested)); } else mtx_lock_spin(&al->al_lock); return (0); } void AcpiOsReleaseLock(ACPI_SPINLOCK Handle, ACPI_CPU_FLAGS Flags) { struct acpi_spinlock *al = (struct acpi_spinlock *)Handle; ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); if (al == NULL) { ACPI_DEBUG_PRINT((ACPI_DB_MUTEX, "cannot release null spinlock\n")); return_VOID; } ACPI_DEBUG_PRINT((ACPI_DB_MUTEX, "release %s\n", al->al_name)); if (mtx_owned(&al->al_lock)) { if (al->al_nested > 0) { ACPI_DEBUG_PRINT((ACPI_DB_MUTEX, "release nested %s, depth %d\n", al->al_name, al->al_nested)); al->al_nested--; } else mtx_unlock_spin(&al->al_lock); } else ACPI_DEBUG_PRINT((ACPI_DB_MUTEX, "cannot release unowned %s\n", al->al_name)); } /* Section 5.2.10.1: global lock acquire/release functions */ /* * Acquire the global lock. If busy, set the pending bit. The caller * will wait for notification from the BIOS that the lock is available * and then attempt to acquire it again. */ int acpi_acquire_global_lock(volatile uint32_t *lock) { uint32_t new, old; do { old = *lock; new = (old & ~ACPI_GLOCK_PENDING) | ACPI_GLOCK_OWNED; if ((old & ACPI_GLOCK_OWNED) != 0) new |= ACPI_GLOCK_PENDING; } while (atomic_cmpset_32(lock, old, new) == 0); return ((new & ACPI_GLOCK_PENDING) == 0); } /* * Release the global lock, returning whether there is a waiter pending. * If the BIOS set the pending bit, OSPM must notify the BIOS when it * releases the lock. */ int acpi_release_global_lock(volatile uint32_t *lock) { uint32_t new, old; do { old = *lock; new = old & ~(ACPI_GLOCK_PENDING | ACPI_GLOCK_OWNED); } while (atomic_cmpset_32(lock, old, new) == 0); return ((old & ACPI_GLOCK_PENDING) != 0); }