/*static char *SCCSID = "@(#)kbeep.c    6.6 91/07/31" */

/***    Kernel Beep Services
 *
 *      IBM/Microsoft Confidential
 *
 *      Copyright (c) IBM Corporation  1987, 1990
 *      Copyright (c) Microsoft Corp.  1987, 1990
 *      All Rights Reserved
 *
 *      DESCRIPTION
 *      This module contains all the kernel beep services.
 *
 *      MODIFICATION HISTORY
 *      04/17/90 MTS    Created (port to 32-bit C from beep.asm)
 */


/***    include files
 */

#define INCL_TYPES              //for basedef.h typedefs
#define INCL_ERRORS             //for bseerr.h defines

#include <basedef.h>
#include <basemac.h>
#include <ostypes.h>
#include <bseerr.h>             //for ERROR_*
#include <infoseg.h>            //for tkptda.h, SIS_*, LIS_*
#include <dossymc.h>            //for tkptda.h
#include <sigdef.h>             //for tkptda.h
#include <except.h>             //for tkptda.h
#include <pgexport.h>           //for tkxport.h
#include <vmexport.h>           //for tkptda.h
#include <tkexport.h>           //for tkptda.h
#include <curdir.h>             //for tkptda.h, find.h
#include <dirent.h>             //for tkptda.h, find.h
#include <find.h>               //for tkptda.h
#include <screen.h>             //for tkptda.h
#include <sem.h>                //for tkptda.h
#include <tkptda.h>             //for ptda_infoseg
#include <kutil.h>              //for _enable() and _disable()
#include <tom.h>
#include <beep.h>
#include <ptimer.h>


/***    definitions and macros
 */

#define NO_TIMEOUT              -1
#define INTERRUPTIBLE           0
#define ENABLE()                _enable()
#define DISABLE()               _disable()

#ifdef  MISCSTRICT
#  define Assert(t)             _assert(t)
#  define AssertTRUE(t)         _assert(t)
#  define AssertFALSE(t)        _assert(!(t))
#  define AssertZERO(t)         _assert(!(t))
#  define AssertNONZERO(t)      _assert(t)
#  define AssertRC(t)           _assert(t)
#else
#  define Assert(t)
#  define AssertTRUE(t)
#  define AssertFALSE(t)
#  define AssertZERO(t)
#  define AssertNONZERO(t)
#  define AssertRC(t)           (t)
#endif


/***    type definitions
 */

typedef (PASCAL FAR32 *PFNBEEP)(ULONG, ULONG);


/***    data declarations
 */

toe_t   toeBeep = {&toeBeep, &toeBeep, -1};
BOOL    fPreemptBeep = FALSE;
BOOL    fSerialBeep = FALSE;
ULONG   ulBeepTaskID = 0;
ULONG   ulSerialBeepFreq = 0;
ULONG   ulSerialBeepDoneTime = 0;
PFNBEEP f32pfnPTDBeep = 0;
BOOL    fBeepOwned = FALSE;     //boolean semaphore for speaker ownership

extern struct InfoSegGDT SISData;

/***    Local function prototypes
 */

BOOL BeepOpen(ULONG);
ULONG BeepGetTaskID(VOID);
VOID BeepTom(toe_t *);
VOID BeepClose(ULONG);


// #pragma code_seg("HIGH32SWAP_TEXT", "CODE")
#pragma code_seg("LOAD_BEEP_SWP", "CODE")

/***EP  w_Beep - worker of the DOSBEEP Service
 *
 *      This is the worker routine for DOSBEEP.  It generates
 *      a tone from the system speaker with the given frequency
 *      and duration.
 *
 *      ENTRY
 *          ulFreq     = beep frequency in Hz
 *          ulDuration = beep duration in ms
 *
 *      EXIT-SUCCESS
 *          returns 0
 *
 *      EXIT-FAILURE
 *          returns error code - ERROR_INVALID_FREQUENCY
 *
 *      CONTEXT
 *          Task
 *
 *      PSEUDOCODE
 */

ULONG w_Beep(ULONG ulFreq, ULONG ulDuration)
{
    if (ulDuration == 0L)   /* if user gives a 0 duration then just return */
                            /* with a No_Error return code                 */
        return 0;

    if ((ulFreq < DOSBEEP_FREQLO) || (ulFreq > DOSBEEP_FREQHI))
        return ERROR_INVALID_FREQUENCY;

    AssertNONZERO(f32pfnPTDBeep);

    if (BeepOpen(BeepGetTaskID())) {    //BeepOpen has interrupt disabled
        ulSerialBeepFreq = ulFreq;
        ulSerialBeepDoneTime = SISData.SIS_MsCount + ulDuration;
        (*f32pfnPTDBeep)(PTBEEP_FREQ, ulFreq);
        (*f32pfnPTDBeep)(PTBEEP_ON, 0);
        ENABLE();
        if (toeBeep.toe_ptoeNext != &toeBeep)   //if a timer exist, cancel it
            TOMCancel(&toeBeep);
        TOMSet(&toeBeep, ulDuration, TOM_W_BEEPDONE);
        //BeepTom will turn the speaker off and do the BeepClose if necessary

        //wait until beep is done or else it is interrupted
        if (TKSleep(&fSerialBeep, ulDuration, INTERRUPTIBLE, WAKEUP_SINGLE)) {
            TOMCancel(&toeBeep);
            (*f32pfnPTDBeep)(PTBEEP_OFF, 0);
            BeepClose(ulBeepTaskID);
        }
    }           //BUGBUG MTS: Should we return an error if BeepOpen failed?

    return 0;
}       /* w_Beep */


/***LP  BeepOpen - Declare ownership of the system speaker
 *
 *      This subroutine is called to declare ownership of
 *      the system speaker in order to achieve speaker
 *      usage serialization by various tasks.
 *
 *      ENTRY
 *          ulID = uniq owner ID
 *
 *      EXIT-SUCCESS
 *          returns TRUE with interrupt disabled;
 *      EXIT-FAILURE
 *          returns FALSE
 *
 *      CONTEXT
 *          Task
 *
 *      PSEUDOCODE
 *
 *      NOTE
 *          It is possible for a thread to call this routine
 *          again while it already owns the speaker.  In such
 *          case, this routine simply ignores the call.
 *
 *          It is necessary to enter critical section (disabling
 *          interrupt) after obtaining the beep semaphore because
 *          we don't want to be preempted by a preempt beep until
 *          the beep has started.  We would like to avoid the
 *          follow scenario:
 *          - successfully obtaining the beep semaphore
 *          - preempted by a preempt beep
 *          - the preempt beep has started
 *          - regain control
 *          - start the serial beep which will override the started
 *            preempt beep
 *
 *          Since this routine will exit with interrupt disabled, the
 *          caller IS RESPONSIBLE to re-enable the interrupt after
 *          starting the beep.
 */

BOOL BeepOpen(ULONG ulID)
{
    DISABLE();
    if (ulID != ulBeepTaskID) { //Task has not already owned the speaker
        if (fBeepOwned) {
            if (TKSleep(&fBeepOwned, NO_TIMEOUT, INTERRUPTIBLE, WAKEUP_SINGLE))
                return FALSE;
            DISABLE();          //TKSleep has enabled interrupt
        }
        (*f32pfnPTDBeep)(PTBEEP_OPEN, 0);//get speaker ownership
        fBeepOwned = TRUE;      //SemRequest(&fBeepOwned)
        ulBeepTaskID = ulID;
        fSerialBeep = TRUE;
    }
    return TRUE;
}       /* BeepOpen */


/***LP  BeepGetTaskID - Get a uniq ID to identify a thread
 *
 *      This subroutine returns a uniq ID identifying the calling
 *      task.
 *
 *      ENTRY
 *          None
 *
 *      EXIT
 *          returns a uniq ID of the calling task
 *
 *      CONTEXT
 *          Task
 *
 *      PSEUDOCODE
 *          return (pid << 16 + tid);
 */

ULONG BeepGetTaskID(VOID)
{
    return ((pPTDACur->ptda_infoseg.LIS_CurProcID << 16) |
            pPTDACur->ptda_infoseg.LIS_CurThrdID);
}       /* BeepGetTaskID */

#pragma code_seg()

/***EP  BeepPreempt - Preempt Beep common worker
 *
 *      This is the worker routine for all preempt beeps including
 *      VDHBeep and dh_Beep.  It performs the beep request on behalf
 *      of the requesting VDD and PDD.
 *
 *      ENTRY
 *          ulFunc  = preempt beep services:
 *                      BEEP_OFF, BEEP_TIMED
 *          ulParm1 = first parameter for the beep service
 *                      BEEP_OFF:   ulParm1 = 0 (don't care)
 *                      BEEP_TIMED: ulParm1 = ulFreq (freq. of beep)
 *          ulParm2 = second parameter for the beep service
 *                      BEEP_OFF:   ulParm2 = 0 (don't care)
 *                      BEEP_TIMED: ulParm2 = ulDuration (beep duration)
 *
 *      EXIT-SUCCESS
 *          returns 0
 *      EXIT-FAILURE
 *          returns ERROR_INVALID_FUNCTION
 *
 *      CONTEXT
 *          Task
 *          Interrupt
 *
 *      PSEUDOCODE
 */

ULONG BeepPreempt(ULONG ulFunc, ULONG ulParm1, ULONG ulParm2)
{
    ULONG   rc = 0;
    register ULONG ulTime;

    DISABLE();

    if (toeBeep.toe_ptoeNext != &toeBeep)       //if a timer exist, cancel it
        TOMCancel(&toeBeep);

    if (ulFunc != BEEP_OFF) {
        fPreemptBeep = TRUE;
        fBeepOwned = TRUE;      //SemSet(&fBeepOwned)
    }

    AssertNONZERO(f32pfnPTDBeep);

    switch (ulFunc) {
        case BEEP_OFF:          //turn off previous preempt beep
            (*f32pfnPTDBeep)(PTBEEP_OFF, 0);
            fPreemptBeep = FALSE;
            if (fSerialBeep) {  //restart serial beep
                if ((ulTime=SISData.SIS_MsCount) < ulSerialBeepDoneTime) {
                    (*f32pfnPTDBeep)(PTBEEP_FREQ, ulSerialBeepFreq);
                    (*f32pfnPTDBeep)(PTBEEP_ON, 0);
                    TOMSet(&toeBeep,
                           ulSerialBeepDoneTime - ulTime,
                           TOM_W_BEEPDONE);
                } else {        //must be expired DosBeep
                    BeepClose(ulBeepTaskID);
                    TKWakeup(&fSerialBeep, WAKEUP_SINGLE, NULL);
                }
            } else if (fBeepOwned) {    //somebody was owning the speaker
                fBeepOwned = FALSE;     //SemClear(&fBeepOwned)
                TKWakeup(&fBeepOwned, WAKEUP_SINGLE, NULL);
                (*f32pfnPTDBeep)(PTBEEP_CLOSE, 0);
            } else
                (*f32pfnPTDBeep)(PTBEEP_CLOSE, 0);
            break;
        case BEEP_TIMED:
            (*f32pfnPTDBeep)(PTBEEP_OPEN, 0);
            (*f32pfnPTDBeep)(PTBEEP_FREQ, ulParm1);
            (*f32pfnPTDBeep)(PTBEEP_ON, 0);
            TOMSet(&toeBeep, ulParm2, TOM_W_BEEPDONE);
            break;
        default:
            rc = ERROR_INVALID_FUNCTION;
    }

    ENABLE();

    return rc;
}       /* BeepPreempt */


/***LP  BeepTom - TOM worker for beep done
 *
 *      This worker routine is called by TOM when the beep timer has
 *      expired.
 *
 *      ENTRY
 *          ptoe -> toe structure
 *
 *      EXIT
 *          None
 *
 *      CONTEXT
 *          Interrupt
 *
 *      PSEUDOCODE
 */

VOID BeepTom(toe_t *ptoe)
{
    if (fPreemptBeep)
        BeepPreempt(BEEP_OFF, 0, 0);
    else {      //must be DosBeep
        (*f32pfnPTDBeep)(PTBEEP_OFF, 0);
        BeepClose(ulBeepTaskID);
        TKWakeup(&fSerialBeep, WAKEUP_SINGLE, NULL);
    }
}       /* BeepTom */


/***LP  BeepClose - Release ownership of the system speaker
 *
 *      This subroutine is called to release ownership of
 *      the system speaker in order to achieve speaker
 *      usage serialization by various tasks.
 *
 *      ENTRY
 *          ulID = uniq owner ID
 *
 *      EXIT
 *          None
 *
 *      CONTEXT
 *          Task
 *          Interrupt
 *
 *      PSEUDOCODE
 */

VOID BeepClose(ULONG ulID)
{
    AssertTRUE(ulID == ulBeepTaskID);

    DISABLE();

    fSerialBeep = FALSE;
    ulBeepTaskID = 0;

    /*
     * if preempt beep is in progress, the semaphore is
     * owned by the preempt beep.  So we should not clear it.
     */
    if (!fPreemptBeep && fBeepOwned) {
        fBeepOwned = FALSE;
        TKWakeup(&fBeepOwned, WAKEUP_SINGLE, NULL);
        (*f32pfnPTDBeep)(PTBEEP_CLOSE, 0);
    }

    ENABLE();
}       /* BeepClose */
