root/sys/dev/acpica/Osd/OsdSynch.c
/*-
 * Copyright (c) 2000 Michael Smith
 * Copyright (c) 2000 BSDi
 * Copyright (c) 2007-2009 Jung-uk Kim <jkim@FreeBSD.org>
 * 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 <sys/cdefs.h>
#include <contrib/dev/acpica/include/acpi.h>
#include <contrib/dev/acpica/include/accommon.h>

#include <sys/condvar.h>
#include <sys/kernel.h>
#include <sys/lock.h>
#include <sys/malloc.h>
#include <sys/mutex.h>

#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);
}