Metropoli BBS
VIEWER: digixi1.c MODE: TEXT (ASCII)
/*
Copyright (C) Magna Carta Software, Inc. 1990, 1991.  All Rights Reserved.
Code for the DigiBoard DigiCHANNEL COM/Xi family of products (i.e. the
COM/4i & COM/8i).
This file contains functions that interface the DigiBoard, Inc. M232.DOC
source code to Magna Carta Software's C Communications Toolkit.  This file
is the DigiBoard analog of INS8250.C.  Each function begins with the prefix
"comxi_" to indicate that it is specific to the DigiBoard COM/Xi series of
FEP cards.
*/

#if (defined(__TURBOC__) || defined(__POWERC))
#include <dos.h>
#else
    #include <conio.h>
#endif
#include <digixi0.h>
#undef RTS
#undef DTR
#include <comm.h>
#include <digixi1.h>


static WORD baud_xlat_table[][2] = {{50, 9}, {75, 10}, {110, 1}, {134, 11},
    {150, 2}, {200, 12}, {300, 3}, {600, 4}, {1200, 5}, {1800, 13},
    {2000, 14}, {2400, 6}, {3600, 15}, {4800, 7}, {7200, 16}, {9600, 8},
    {19200, 17}, {0X9600, 18}};

static short comxi_xlat_parity[] = {NPARITY, OPARITY, EPARITY, (OPARITY | SPARITY), (EPARITY | SPARITY)};
static short nelem = sizeof(baud_xlat_table)/(sizeof(WORD) * 2);


/*
COMXI_GET -- General purpose function to return values for the DigiBoard
COM/Xi family.
Return values:
    Success: The value of the UART register that sets stopbits;
    EOF    : Invalid number of stopbits specified;
*/
short comxi_get(COMM_PORT *p, short cmd)
{
    short ret = EOF;

    switch (cmd) {
        /* DATA FORMAT */
        case SPEED:

            break;
        case DATABITS:
            ret = p->rxdatabits;
            break;
        case STOPBITS:
            ret = p->stopbits;
            break;
        case PARITY:
            ret = p->parity;
            break;
        /* RS-232 */
        case CTS:
            break;
        case DCD:
            break;
        case DSR:
            break;
        case DTR:
            ret = p->dtr;
            break;
        case RI:
            break;
        case RTS:
            ret = p->rts;
            break;
        case OUT1:
            break;
        case OUT2:
            break;
    }

    return (ret);
}



/*
COMXI_GET_RX_XLAT -- Query the setting of each of the input translation
variables used in c_getc().
*/
short comxi_get_rx_xlat(COMM_PORT *p, short item)
{
    short ret = 0;

    switch (item) {
        case XLAT:          ret = isrxxlat(p); break;      /* OFF or ON */

        case LOCAL_ECHO:    ret = isrxecholocal(p); break; /* OFF or ON */

        case REMOTE_ECHO:   ret = isrxechoremote(p); break;/* OFF or ON */

        case PRINTER_ECHO:  ret = isrxechoprn(p); break;   /* OFF or ON */

        case CAPTURE_BUFFER_ECHO: ret = isrxechobuf(p); break; /* OFF or ON */

        /*
        EOL is one of: FALSE, LF2CR, CR2LF, EOL2SP,
        STRIPEOL, LF2CRLF, CR2CRLF, CRLF2LF, CRLF2CR
        */
        case EOL:           ret = p->rx_eol; break;

        case CASE:          ret = p->rx_case_convert; break;

        case ASCII_ONLY:    ret = p->rx_ascii_only; break;

        case INTERBYTE_DELAY: ret = p->rx_ibdelay; break;

        case FLOWCTL:       ret = p->flowctl & RX; break;   /* OFF or ON */

        case XONCHAR:       ret = p->xonchar; break;        /* ASCII value */

        case XOFFCHAR:      ret = p->xoffchar; break;       /* ASCII value */

        case ABORT_KEY:     ret = p->abort_key; break;

        default:            ret = EOF; break;
    }
    return (ret);
}



/*
COMXI_GET_TX_XLAT -- Return the value of various transmit data translation
parameters.
*/
short comxi_get_tx_xlat(COMM_PORT *p, short item)
{
    short ret = 0;

    switch (item) {
        case LOCAL_ECHO:    ret = istxecholocal(p); break; /* OFF or ON */

        case REMOTE_ECHO:   ret = istxechoremote(p);break;  /* OFF or ON */

        case PRINTER_ECHO:  ret = istxechoprn(p);   break;  /* OFF or ON */

        case CAPTURE_BUFFER_ECHO:
                            ret = istxechobuf(p);   break;  /* OFF or ON */

        case XLAT:          ret = istxxlat(p);      break;  /* OFF or ON */

        /*
        EOL is one of: FALSE, LF2CR, CR2LF, EOL2SP,
        STRIPEOL, LF2CRLF, CR2CRLF, CRLF2LF, CRLF2CR
        */
        case EOL:   ret = p->tx_eol; break;

        case CASE:  ret = p->tx_case_convert; break;

        case ASCII_ONLY:    ret = p->tx_ascii_only; break;

        /* INTERBYTE AND INTERLINE DELAYS (in ms.). */
        case INTERBYTE_DELAY: ret = p->tx_ibdelay; break;

        case INTERLINE_DELAY: ret = p->tx_ildelay; break;

        /* CHARACTER TO TRIGGER INTERLINE DELAY */
        case INTERLINE_DELAY_CHAR: ret = p->tx_ildelay_ch; break;

        case FLOWCTL:
            ret = p->flowctl & TX;
            break;

        case TX_DELAY:
            ret = p->tx_delay;
            break;

        case TX_DELAYCHAR:
            ret = p->tx_delaych;
            break;

        case XONCHAR:   ret = p->xonchar; break; /* ASCII value */

        case XOFFCHAR:  ret = p->xoffchar; break; /* ASCII value */

        /* ASCII CODE OF KEY TO ABORT XOFF */
        case ABORT_KEY: ret = p->abort_key;  break;

        default: ret = EOF; break;
    }

    return (ret);
}



/*
COMXI_INIT -- Initialize the COM/Xi board and integrate it into the toolkit.
Parameters:
    WORD port -- board I/O port address;
    WORD seg  -- board memory address to use;
    WORD size -- board RX,TX buffer size (0-2 for M232);
Return value:
    0   -- success;
    NOTWARM (-4)    -- Module not loaded;
    BADVERSN (-8)   -- ?
    BUSYHML (-5)    -- FEP busy (0h not written to "mailbox");
    TMOERROR (-2)   -- Module did not start executing;
    or ...          -- timeout error during initialization;
*/
short comxi_init(COMXI *c, WORD port, WORD seg, WORD size)
{
    short ret;

    ret = resetcomxi(port, seg);
    if (!ret) {
        ret = excM232(port, seg, size);
        if (!ret) {
            c->base_addr = seg;
            c->port_addr = port;
        }
    }

    return (ret);
}



/*
COMXI_INIT_CHANNEL -- Initialize one port on the COM/Xi board.
Parameters:
    COMM_PORT *p -- pointer to the COMM_PORT structure for this port;
Return value:

*/
short comxi_init_channel(COMM_PORT *p, COMXI *c, WORD portnum, long speed,
    WORD databits, WORD parity, WORD stopbits)
{
    if (p->open) return (PORT_OPEN);
    if (c->base_addr == 0) return (-2);

    p->utype            = DIGIBOARD_COMXI;
    p->rxdatabits       = databits;
    p->txdatabits       = databits;
    p->parity           = parity;
    p->stopbits         = stopbits;
    p->speed            = speed;
    p->dtr              = HIGH;
    p->rts              = HIGH;
    p->abort_key        = ESCAPE;
    p->ubase_addr       = c->port_addr; /* "port" is assigned to ubase_addr */
    p->user1            = c->base_addr; /* "seg" is assigned to p->user1 */
    p->user2            = portnum;      /* channel # assigned to p->user2 */
    /* INITIALIZE DEVICE-SPECIFIC FUNCTION POINTERS */
    p->set              = comxi_set;
    p->get              = comxi_get;
    p->get_rx_xlat      = comxi_get_rx_xlat;
    p->set_rx_xlat      = comxi_set_rx_xlat;
    p->get_tx_xlat      = comxi_get_tx_xlat;
    p->set_tx_xlat      = comxi_set_tx_xlat;
    p->set_speed        = comxi_set_speed;
    p->get_speed        = (long (*)(struct comm_port *)) comxi_get;
    p->rxstat           = (short (*)(struct comm_port *)) comxi_ret;
    p->txstat           = comxi_txstat;
    p->c_read           = comxi_read;
    p->c_write          = comxi_write;
    p->enable_comm_int  = (short (*)(struct comm_port *, WORD)) comxi_ret;
    p->disable_comm_int = (short (*)(struct comm_port *, WORD))comxi_ret;
    p->install_ipr      = (short (*)(struct comm_port *, WORD, void FAR_ *, BYTE FAR_ *, WORD))comxi_ret;
    p->deinstall_ipr    = (short (*)(struct comm_port *, WORD))comxi_ret;
    p->deinit_port      = (short (*)(struct comm_port *)) comxi_ret;
    p->enable_comm_int  = (short (*)(struct comm_port *, WORD))comxi_ret;
    p->disable_comm_int = (short (*)(struct comm_port *, WORD))comxi_ret;

    /* OPEN THE PORT */
    return (c_open_(p));
}



short comxi_read(COMM_PORT *p)
{
    short ret;

    ret = rdch(p->ubase_addr, p->user1, p->user2);
    if (ret < 0) {
        global_com_error = ret;
        ret = EOF;
    }

    return (ret);
}



/*
COMXI_RET -- Dummy do-nothing function to handle COMM_PORT function pointers
not supported by the COM/Xi.
*/
short comxi_ret(void)
{
    return (EOF);
}



short comxi_rxstat(COMM_PORT *p)
{
    short ret;

    ret = rdch(p->ubase_addr, p->user1, p->user2);
    if (ret < 0) {
        global_com_error = ret;
        ret = EOF;
    }

    return (ret);
}



/*
COMXI_SEND_BREAK -- Send a BREAK signal to the designated port of the COM/Xi.
Parameters:
    COMM_PORT *p    -- ptr. to the COMM_PORT structure;
    WORD duration   -- duration in ms. (calibrated to nearest 55ms.);
Return values:
    0               -- success;
    CMDQFUL (-9)    -- Command buffer full;
*/
short comxi_send_break(COMM_PORT *p, WORD duration)
{
    duration = (duration < 55) ? 1 : duration/18;
    wrtbcmd(p->user1, SBRKCNT, p->user2, duration, 0);

    return(wrtbcmd(p->user1, SBREAK, p->user2, 0, 0));
}



/*
COMXI_SET -- General purpose command dispatcher for the DigiBoard COM/Xi
family.
Return values:
    0   -- success;
    EOF -- invalid 'cmd' specified;
*/
short comxi_set(COMM_PORT *p, short cmd, short value)
{
    short ret = EOF;

    switch (cmd) {
        /* DATA FORMAT */
        case DATABITS:
            ret = comxi_set_databits(p, value);
            break;
        case STOPBITS:
            ret = comxi_set_stopbits(p, value);
            break;
        case PARITY:
            ret = comxi_set_parity(p, value);
            break;
        /* RS-232 */
        case DTR:
            comxi_set_dtr(p, value);
            break;
        case RTS:
            comxi_set_rts(p, value);
            break;
        case BREAK:
            comxi_send_break(p, value);
            break;
        case LOOPBACK:
            break;
        case OUT1:
            break;
        case OUT2:
            break;
    }

    return (ret);
}



/*
COMXI_SET_DATABITS -- Set the number of databits for the designated serial
port to the designated value.
Return values:
    Success: The value of the UART register that sets stopbits;
    EOF    : Invalid number of stopbits specified;
*/
short comxi_set_databits(COMM_PORT *p, short value)
{
    short stops, parity;

    switch (value) {
        case DATABITS5: value = DATA5;    break;
        case DATABITS6: value = DATA6;    break;
        case DATABITS7: value = DATA7;    break;
        case DATABITS8: value = DATA8;    break;
        default: return (EOF);
    }
    parity = comxi_xlat_parity[p->parity];
    stops  = (p->stopbits == 1) ? 0 : 4;
    value  = parity | stops | value;
    wrtbcmd(p->user1, SBAUDATA, p->user2, comxi_xlat_baud(p->speed), value);

    return (0);
}



/*
COMXI_SET_DTR -- Set the value of the DTR line for the designated serial
port to the designated value.
Return values:
    Success: The value of the UART register that sets stopbits;
    EOF    : Invalid value for "value" specified;
*/
short comxi_set_dtr(COMM_PORT *p, short value)
{
    if (value == HIGH) wrtbcmd(p->user1, SMODEM, p->user2, '\x1', 0);
    else if (value == LOW) wrtbcmd(p->user1, SMODEM, p->user2, '\x0', '\x1');
    else return (EOF);

    return (0);
}



/*
COMXI_SET_FLOWCTL -- Set flow control off or on.
"flowtype" is an ORing of the values in DIGIXI0.H for function 0X1B.
"state"
*/
short comxi_set_flowctl(COMM_PORT *p, short flowtype, short state)
{
    short ret;

    if (state == OFF) ret = wrtbcmd(p->user1, SHNDSHK, p->user2, '\0', flowtype);
    else ret = wrtbcmd(p->user1, SHNDSHK, p->user2, flowtype, '\0');

    return (ret);
}



/*
COMXI_SET_PARITY -- Set the parity of the designated serial port to the
designated value.
Return values:
    Success: The value of the UART register that sets stopbits;
    EOF    : Invalid number of stopbits specified;
*/
short comxi_set_parity(COMM_PORT *p, short value)
{
    short stops;

    switch (value) {
        case PARITY_NONE:   value = NPARITY;  break;
        case PARITY_ODD:    value = OPARITY;  break;
        case PARITY_EVEN:   value = EPARITY;  break;
        case PARITY_MARK:   value = (OPARITY | SPARITY); break;
        case PARITY_SPACE:  value = (EPARITY | SPARITY); break;
        default:
            return (EOF);
    }
    stops  = (p->stopbits == 1) ? 0 : 4;
    value  = (p->rxdatabits - 5) | stops | value;
    wrtbcmd(p->user1, SBAUDATA, p->user2, comxi_xlat_baud(p->speed), value);

    return (0);
}



/*
COMXI_SET_RTS -- Set the value of the RTS line for the designated serial
port to the designated value.
Return values:
    Success: The value of the UART register that sets stopbits;
    EOF    : Invalid value for "value" specified;
*/
short comxi_set_rts(COMM_PORT *p, short value)
{
    if (value == HIGH) wrtbcmd(p->user1, SMODEM, p->user2, '\x2', 0);
    else if (value == LOW) wrtbcmd(p->user1, SMODEM, p->user2, '\x0', '\x2');
    else return (EOF);

    return (0);
}



/*
COMXI_SET_RX_XLAT -- Specify the type of input translation used by c_getc()
and c_gets().
*/
short comxi_set_rx_xlat(COMM_PORT *p, short item, short value)
{
    short parm, ret = 0;

    switch (item) {
        case LOCAL_ECHO:
        case REMOTE_ECHO:
        case PRINTER_ECHO:
        case CAPTURE_BUFFER_ECHO:
            if (value) p->echo |= item;
            else p->echo &= ~item;
            break;

        /*
            EOL is one of: FALSE, LF2CR, CR2LF, EOL2SP,
            STRIPEOL, LF2CRLF, CR2CRLF, CRLF2LF, CRLF2CR
        */

        case EOL:
            p->rx_eol = value;
            break;

        case CASE:                              /* UPPER, LOWER, FALSE */
            p->rx_case_convert = value; break;

        case ASCII_ONLY:
            p->rx_ascii_only = value; break;

        case INTERBYTE_DELAY:
            p->rx_ibdelay = value; break;

        case TRAILINGBYTE_DELAY:
            p->rx_tbdelay = value; break;

        case FLOWCTL:
            switch(value) {
                case XONXOFF:
                    parm = (SHND | OHND);
                    break;
                case RTS_CTS:
                    parm = (HHND | OHND);
                    break;
                default:
                    ret = EOF;
            }
            if (!ret) comxi_set_flowctl(p, parm, value);
            break;

        case FLOWHIGH:
            p->flowhigh = value;
            break;

        case XONCHAR:               /* value to be considered an XON */
            if (value == XON_ALL) comxi_set_flowctl(p, (SHND | OHND | XAKP), value);
            else ret = wrtbcmd(p->user1, SONOFF, p->user2, (BYTE) value, (BYTE) p->xoffchar);
            p->xonchar  = value;                   /* a specific char. */
            break;

        case XOFFCHAR:
            ret = wrtbcmd(p->user1, SONOFF, p->user2, p->xonchar, value);
            p->xoffchar = value;
            break;

        case RX_BUFFER_LWM:
            wrtbcmd(p->user1, SRXLWTR, p->user2, value & 0XFF, value >> 8);
            ret = EOF;
            break;

        case RX_BUFFER_HWM:
            /* work in progress */
            ret = EOF;
            break;

        /* SCAN CODE/CHARACTER CODE OF KEY TO ABORT */
        case ABORT_KEY: p->abort_key   = value; break;

        default: ret = EOF; break;
    }
    if (!ret) {
        if (value) p->rx_xlat = 1;
        else p->rx_xlat = isrxxlat(p);
    }

    return (ret);
}



/*
COMXI_SET_SPEED -- Set the data rate (bps) for the deignated port.
Return value:
    SUCCESS (0)     -- OK;
    EOF             -- Invalid speed value;
    CMDQFUL (-9)    -- Command buffer full;
*/
short comxi_set_speed(COMM_PORT *p, long value)
{
    short stops, parity, parms;

    parity = comxi_xlat_parity[p->parity];
    stops  = (p->stopbits == 1) ? 0 : 4;
    parms  = (p->rxdatabits - 5) | stops | parity;

    if ((value = comxi_xlat_baud(value)) != EOF)
        return (wrtbcmd(p->user1, SBAUDATA, p->user2, (WORD) value, parms));

    return (EOF);
}



/*
COMXI_SET_STOPBITS -- Set the number of stopbits for the designated serial
port to the designated value.
Return values:
    Success: The value of the UART register that sets stopbits;
    EOF    : Invalid number of stopbits specified;
*/
short comxi_set_stopbits(COMM_PORT *p, short value)
{
    short parity;

    switch (value) {
        case STOPBITS1:   value = STOP1;    break;
        case STOPBITS2:   value = STOP2;    break;
        case STOPBITS15:
        default:
            return (EOF);
    }
    parity = comxi_xlat_parity[p->parity];
    value  = (p->rxdatabits - 5) | parity | value;
    wrtbcmd(p->user1, SBAUDATA, p->user2, comxi_xlat_baud(p->speed), (BYTE) value);

    return (0);
}



/*
COMXI_SET_TX_XLAT -- Assign values to data translation parameters.
*/
short comxi_set_tx_xlat(COMM_PORT *p, short item, short value)
{
    short parm, ret = 0;

    switch (item) {
        case LOCAL_ECHO:
        case REMOTE_ECHO:
        case PRINTER_ECHO:
        case CAPTURE_BUFFER_ECHO:
            if (value) p->echo |= (item << 4);
            else p->echo &= ~(item << 4);
            break;

        /*
            EOL is one of: FALSE, LF2CR, CR2LF, EOL2SP,
            STRIPEOL, LF2CRLF, CR2CRLF, CRLF2LF, CRLF2CR
        */
        case EOL:
            p->tx_eol = value;
            break;

        case CASE:                          /* UPPER, LOWER, NORMAL */
            p->tx_case_convert = value;
            break;

        case ASCII_ONLY:
            p->tx_ascii_only = value;
            break;

        case INTERBYTE_DELAY:
            p->tx_ibdelay = value;
            break;

        case TX_DELAY:
            p->tx_delay = value;
            break;

        case TX_DELAYCHAR:
            p->tx_delaych = value;
            break;

        case INTERLINE_DELAY:
            p->tx_ildelay = value;
            break;

        case INTERLINE_DELAY_CHAR:
            p->tx_ildelay_ch = value;
            break;

        case FLOWCTL:
            switch(value) {
                case XONXOFF:
                    parm = (SHND | IHND);
                    break;
                case RTS_CTS:
                    parm = (HHND | IHND);
                    break;
                default:
                    ret = EOF;
            }
            if (!ret) comxi_set_flowctl(p, parm, value);
            break;

        case XONCHAR:               /* value to be considered an XON */
            ret = wrtbcmd(p->user1, SONOFF, p->user2, value, p->xoffchar);
            p->xonchar  = value;                   /* a specific char. */
            break;

        case XOFFCHAR:
            ret = wrtbcmd(p->user1, SONOFF, p->user2, p->xonchar, value);
            p->xoffchar = value;
            break;                                  /* ASCII value  */

        /* SCAN CODE/CHARACTER CODE OF KEY TO ABORT XOFF */
        case ABORT_KEY:
            p->abort_key   = value;
            break;

        case TX_BUFFER_LWM:
            /* work in progress */
            ret = EOF;
            break;

        default:
            ret = EOF;
            break;
    }
    if (!ret) {
        if (value) p->tx_xlat = 1;
        else p->tx_xlat = istxxlat(p);
    }

    return (ret);
}



short comxi_txstat(COMM_PORT *p)
{
    WORD port = p->ubase_addr;
    WORD seg  = p->user1;
    WORD chn  = p->user2;
    WORD a;

    a = btbl(seg)[chn].BWIND;
    if (a != (inp(port) & 0X0f)) {
        if (swindow(port, seg, a)) return (FALSE);
    }
    a = btbl(seg)[chn].TXHD;
    if (++a > btbl(seg)[chn].TXMAX) a = btbl(seg)[chn].TXSTRT;
    if (a != btbl(seg)[chn].TXTL) return (TRUE);
    else return (FALSE);
}



/*
COMXI_WRITE -- Low level function to write a byte.  This function is just a
binding function to call wrtch().
*/
void comxi_write(COMM_PORT *p, short ch)
{
    wrtch(p->ubase_addr, p->user1, p->user2, ch);
}




/*
COMXI_XLAT_BAUD -- Convert a data transfer rate expressed as a number to a
DigiBoard manifest constant.
Return value:
    >0  -- DigiBoard speed specification;
    EOF -- Invalid value for "value";
*/
short comxi_xlat_baud(long value)
{
    short i;

    for (i=0; i < nelem; i++) {
        if (baud_xlat_table[i][0] == (WORD) value) return (baud_xlat_table[i][1]);
    }

    return (EOF);
}



#if defined(CCT_TEST)
/* NOT USER-RUNABLE -- RUN CCTDIGIx.C TO TEST CCT WITH THE BOARD */
#include <conio.h>
#include <ctype.h>

#define MENU        ALT_M           /* key for command summary */


/* FUNCTION PROTOTYPES */
void    term(COMM_PORT *p);         /* the main program loop */
short   menu(void);                 /* the function that handles the menu */



/* GLOBAL VARIABLES */
short       vers = 0;           /* the version number of "CCT-COMM" */
COMM_PORT d0, d1, d2, d3, d4, d5, d6, d7;
COMM_PORT *digi[] = {&d0, &d1, &d2, &d3, &d4, &d5, &d6, &d7, NULL};

void main(void)
{
    short ret;

    clrscr();
    ret = comxi_init(digi, 0X320, 0XD800, 0);
    printf("\nResult of comxi_init() is: %d", ret);
    set_speed(digi[0], 2400L);
    set_databits(digi[0], 8);
    set_stopbits(digi[0], 1);
    set_parity(digi[0], PARITY_NONE);
    set_rts(digi[0], HIGH);
    set_dtr(digi[0], HIGH);
    printf("\nCCT-COMM Version %d: Press Alt-M for a list of commands\n", vers);
    term(digi[0]);
    printf("\nEnd of CCT-COMM%d\n", vers);
    set_rts(digi[0], LOW);
    set_dtr(digi[0], LOW);
}



/*
TERM -- The terminal emulation routine. Simply polls the COM port and the
keyboard alternately for characters.
*/
void term(COMM_PORT *p_port)
{
    short c;              /* must be int to detect a -1 return */

    for (;;) {
        /* CHECK SERIAL PORT FOR BYTE */
        if ( (c = c_inchar(p_port)) != EOF)
            putchar(c);

        /* CHECK KEYBOARD FOR A KEY PRESS */
        if ( (c = inkey()) != EOF) {
            if (c == MENU) {
                if (menu()) break;
            }
            else c_putc(p_port, c);
        }
    }
}



#define EXIT 'Q'                /* key to exit from menu */


short menu(void)
{
    int c, retval = 0;
    static char *menus[] = {
        "\tQ.  EXIT from TERM.",
        NULL                        /* null string terminates list */
    };
    char **menup;

    c = !EXIT;
    while (c != EXIT) {
        puts("\n\n");
        for (menup = menus; *menup != NULL; menup++) printf("%s\n", *menup);
        printf("\n\t\t Enter selection (CR to quit menu) :  ");
        if ( (c = getch()) == CR) break;        /* return to term */
        c = toupper(c);
        switch (c) {
            case EXIT:
                retval = 1;
                break;
            default:
                puts("Invalid choice\n\007");
                break;
        }
    }
    puts("\nExiting menu");
    return (retval);                /* will be zero except if EXIT */
}
#endif
[ RETURN TO DIRECTORY ]