ttyld.cc
//*************************************************************************
// MODULE : TTYLD.CPP - Line Dicipline driver for Terminal devices *
// AUTHOR : Ron Chernich *
// PURPOSE: This class used by RCOS to perform text line input for a *
// Terminal. It performs simple editing (destructive backspace) *
// input echo and "break" condition checks. One is instantiated *
// the Kernel when a terminal is OPENED in buffered mode. *
// Concept and design based on Chapter 10 of Bach, M.J. (1986): *
// "The Design of the Unix Operating System", Prentice-Hall Inc *
// HISTORY: *
// 20-APR-93 Material split from CONX module. *
// 14-MAY-93 Bug! not advancing nStart after posting message *
// 20-APR-94 Memory leak through CbNew fixed (Ron, use the Stack!) *
//*************************************************************************
#include "ttyld.hh"
#include "kernel.hh"
/////////////
// globals define ansi sequences to destructively backspace, etc
//
#define LEN_BKSP 4 // length of array declared in LnEdit()
#define CLR_SCRN AFF // char to clear screen and home cursor
///////////////////////////////////////////////////////////////////////////
// Line Protocol Character Block Methods
//-------------------------------------------------------------------------
// We can initialsze new CBLOCKS on the stack, 'cause the Double Link List
// class copies the passed data to memory allocated from the heap.
//
PCBLOCK Cblock::CbNew (void)
{
CBLOCK NewCb;
NewCb.nStart = NewCb.nEnd = 0;
return (PCBLOCK)DblAppend((void*)&NewCb, sizeof(CBLOCK));
}
///////////////////////////////////////////////////////////////////////////
// TTY Line Driver Methods
//-------------------------------------------------------------------------
// Initialize our Tx/Rx ports, register with the kernel and ask the her
// for a terminal. If we get a terminal (and we bloody well better, since
// the kernel has made sure one is available before calling us into being),
// we clear its screen, then request a character. This effectively puts us
// to sleep, waiting on some input.
//
LnDrv::LnDrv (UINT16 n, UINT16 id, Knl *pK)
: port(id, CLS_SysRes, pK), uPid(n)
{
bBreakOn = TRUE;
cDelim = (char)ACR;
nChReq = nBlkReq = 0;
MSG msg(uID, KM_Open, CLS_VDU, (void*)(TTY_UseANSI));
pTx->SendMsg(ID_Kernel, &msg);
if ((uTerm = msg.wParam) != ID_NULL) {
MSG msg(uID, KM_Read);
pTx->PostMsg(uTerm, &msg);
}
}
////////////////
// Destructor closes the terminal (which flushes its buffers) and closes
// our Registration with the Kernel. The DblList destructor will cleanup
// any unused CBLOCKS.
//
LnDrv::~LnDrv (void)
{
MSG msg(uID, KM_Close, uTerm);
pTx->SendMsg(ID_Kernel, &msg);
msg = message(uID, KM_CheckOut);
pTx->SendMsg(ID_Kernel, &msg);
}
///////////////////
// Line Driver receive port Write requests may only come from our "owner"
// PID, or the Kernel, so just process 'em. Read messages can be requests
// from the PID (normally block requests), or raw data from terminal. Raw
// data goes through the edit routine (and thence, back to the terminal).
// NOTE NASTY TRICK - if an IoCtrl message is not a Break On/Off, it FALLS
// THROUGH and is sent/posted to the TTY driver.
//
void LnDrv::RxPort (PMSG pM)
{
switch (pM->wMsgType & ~MM_Sync) {
case KM_IoCtrl:
if ((pM->wParam == DM_BreakOn) || (pM->wParam == DM_BreakOff)) {
bBreakOn = (BOOL)(pM->wParam == DM_BreakOn);
return;
}
case KM_Write:
case KM_WriteBlk:
pM->wSender = uID;
if (IS_SYNCH(pM->wMsgType))
pTx->SendMsg(uTerm, pM);
else
pTx->PostMsg(uTerm, pM);
break;
case KM_ReadBlk:
LnReq();
break;
case KM_Read:
if (pM->wSender == uTerm)
LnEdit((char)pM->wParam);
else {
PCBLOCK pCB = Cblk.CbHead();
if (pCB == NULL)
++nChReq;
else {
pM->wSender = uID;
pM->wParam = (UINT16)(pCB->cBuf[pCB->nStart++]);
if (pCB->nStart >= CBLOCK_LEN)
Cblk.CbDrop();
pTx->PostMsg(uPid, pM);
}
}
break;
default :
DevError(ET_NoSupport, FALSE);
}
}
//////////////
// Input and line editing routine. If the char is a backspace (and we
// have something to backspace into), backup the buffered data and sned
// an appropriate sequence to the terminal. Otherwise put the char in the
// buffer(s) and if it was a block delimiter and we have any outstanding
// requests for a data block, process the buffer.
//
// NOTE: At this time, all delimiters are CR's, but in the future, our
// PLL/2 compiler may want to READ space delimited numeric variables..
// hence the two tests for and .
//
void LnDrv::LnEdit (char ch)
{
MSG msg;
PCBLOCK pCB = Cblk.CbTail();
static char arrBksp[] = { BKSP, ESC, '[', 'K' };
if ((ch == BREAK_CHAR) && bBreakOn) {
msg = message(uID, KM_Break, uTerm);
pTx->PostMsg(uPid, &msg);
return;
}
if ((ch == BKSP) && pCB) {
char *cp = new char[LEN_BKSP];
memcpy(cp, arrBksp, LEN_BKSP);
msg = message(uID, KM_WriteBlk, LEN_BKSP, (void*)cp);
pTx->PostMsg(uTerm, &msg);
if (--(pCB->nEnd) < 0)
Cblk.CbDrop();
}
if ((ch == ACR) || ((ch >= ' ') && (ch < DEL))) {
if (pCB == NULL)
pCB = Cblk.CbNew();
pCB->cBuf[pCB->nEnd++] = ch;
if (pCB->nEnd >= CBLOCK_LEN)
Cblk.CbNew();
msg = message(uID, KM_Write, (UINT16)ch);
pTx->SendMsg(uTerm, &msg);
if (nBlkReq && ((ch == cDelim) || (ch == ACR)))
LnReq();
}
msg = message(uID, KM_Read);
pTx->PostMsg(uTerm, &msg);
}
////////////////////
// This routine is called when we have an outstanding request for data,
// AND we have just received a block terminator char, OR WHEN we have no
// idea if we have a full block or not, but a request has been received.
// In the latter case, if it turns out the data is yet to be terminated,
// we simply up the count on the number of blocks we are waiting for.
// The process is not as wastefull as it looks. A request may cause a
// single, fruitless char scan through the buffer(s) for a terminator,
// but the request itself has put the sender to sleep - we will wake it
// up when the terminator finally arrives.
//
// NOTE: The actual terminator is removed from the buffer, but not
// included in the posted data block count.
//
void LnDrv::LnReq (void)
{
INT16 n = 0;
PCBLOCK pCB = Cblk.CbHead();
while (pCB) {
for (INT16 i = pCB->nStart; i < pCB->nEnd; i++, n++) {
if ((pCB->cBuf[i] == cDelim) || (pCB->cBuf[i] == ACR)) {
pCB = Cblk.CbHead();
char *pBlk = new char[n];
for (i = 0; i <= n; i++) {
if (i == n)
pCB->nStart++;
else
pBlk[i] = pCB->cBuf[pCB->nStart++];
if (pCB->nStart >= CBLOCK_LEN) {
Cblk.CbDrop();
pCB = Cblk.CbHead();
}
}
MSG msg(uID, KM_ReadBlk, n, (void*)pBlk);
pTx->PostMsg(uPid, &msg);
if (nBlkReq)
--nBlkReq;
return;
}
}
pCB = Cblk.CbNext();
}
++nBlkReq;
}
/********************************** eof **********************************/