/*
* Эмулятор терминала
* Вызов:
* term [Порт [Скорость [Четность [Слово [Стоп]]]]]
* Порт    - номер порта. По умолчанию 1.
* Скорость  - Число от 50 до 115200. По умолчанию 9600
* Четность  - Один из символов N, O, E, M или S где
*       N - Hет контроля (NONE)
*       O - Сумма битов нечетная (ODD)
*       E - Сумма битов четная (EVEN)
*       M - Бит 7 всегда 1 (MARK)
*       S - Бит 7 всегда 0 (SPACE)
*       По умолчанию контроль по четности отсутствует.
* Слово - число информационных бит в байте. По умолчанию 8.
* Стоп  - число стоповых бит, 1 или 2. По умолчанию 1.
*/

#parsecommandline TRUE
#use80386
#pragma option texe

#include "printf.h--"
#include "sscanf.h--"
char USAGE="term [Порт [Скорость [Четность [Слово [Стоп]]]]]\n";

/*  Смещения регистров относительно базы          */
#define LCR 3 /* Регистр управления линией        */
#define MCR 4 /* Регистр управления модемом       */
#define IER 1 /* Регистр идентификации прерываний */
#define LSR 5 /* Регистр статуса линии            */

/*  Маски для установки битов в регистрах         */
#define GPO2  8 /* Сигнал разрешения аппаратных прерываний  */
#define R_INT 1 /* Прерывание по приходу байта        */

/* Константы относящиеся к контролеру прерываний 8259     */
#define IMR 0x21  /* Адрес регистра масок прерываний  */
#define ICR 0x20  /* Адрес регистра команд      */
#define IRQ3  0xF7  /* Маска для разрешения прерывания от COM2  */
#define IRQ4  0xEF  /* Маска для разрешения прерываний от COM1  */
#define EOI3  0x63  /* Сигнал об окончании обработки прерывания от COM2 */
#define EOI4  0x64  /* Сигнал об окончании обработки прерывания от COM1 */

int eoi;     /* Сигнал об окончании обработки прерывания */

/*  Маски для установки битов в регистре управления модемом */
#define DTR 1 /* Сигнал готовности терминала    */
#define RTS 2 /* Сигнал запроса на передачу   */

#define TRRDY 0x20  /* Маска для определения готовности порта к передаче
         байта */

/* Глобальные переменные    */
int p_base; /*  Базовый адрес последовательного порта   */
word oldvect[2]; /* Старый вектор прерывания 0xB/0xC */

#define BSIZE 4096 /* Размер кольцевого буфера      */
char *inptr=#buf;  /* Указатель на первое свободное место  */
char *outptr=#buf; /* Указатель на первый считываемый байт */
byte buf[BSIZE];   /* Кольцевой буфер         */
int port=1;        /* Hомер используемого порта     */
char parity='N';   /* Контроль по четности        */
word words=8;      /* Число информационных битов      */
word stop=1;       /* Число стоповых битов        */
long speeds=9600;  /* Скорость передачи        */

#define ESC 0x1B   /* ASCII код символа Esc  */

/* Прототипы функций */
int portin();
int portout(byte ch);

int main()
{
  @PARAMCOUNT();
/* Разбор аргументов командной строки */
  IF (AX > 5) {
    WRITESTR(#USAGE);
    EXIT(1);
  }
  SWITCH (AX){
    case 5:     /* Число стоповых битов     */
      sscanf(PARAMSTR(4),"%d",#stop);
    case 4:     /* Число информационных битов   */
      sscanf(PARAMSTR(3),"%d",#words);
    case 3:     /* Контроль по четности     */
      BX=PARAMSTR(2);
      parity = DSBYTE[BX]&0x5F;
    case 2:     /* Скорость передачи      */
      sscanf(PARAMSTR(1),"%ld",#speeds);
    case 1:     /* Hомер используемого порта    */
      sscanf(PARAMSTR(0),"%d",#port);
  }
/* Анализ параметров работы */
  IF((port<1)||(port>4)){
    printf("Hеправильный номер порта! '%d'\r\n%s",port,#USAGE);
    EXIT(1);
  }
  IF((speeds<50)||(speeds>115200)){
    printf("Hеправильно задана скорость! '%ld'\r\n%s",speeds,#USAGE);
    EXIT(1);
  }
  IF((words>8)||(words<5)){
    printf("Hеправильно задан размер слова! '%d'\r\n%s",words,#USAGE);
    EXIT(1);
  }
 AL=parity;
  IF((AL != 'N') && (AL != 'O') && (AL != 'M') &&
    (AL != 'S') && (AL != 'E')) {
    printf("Hеправильно задана четность! '%c'\r\n%s",parity,#USAGE);
    EXIT(1);
  }
  IF((stop > 2) || (stop < 1)) {
    printf("Hеправильно задано число стоповых битов! '%d'\r\n%s",stop,#USAGE);
    EXIT(1);
  }
/*  Определение базового адреса последовательного порта */
  IF(portbase()==-1){
    printf("Порт COM%d отсутсвует на Вашей машине!",port);
    EXIT(1);
  }
/* Установка параметров обмена  */
  setparm();
/* Подготовка порта к работе  */
  porton();
  printf("\r\nПорт %d  Скорость %ld\r\nДля выхода нажмите Esc\r\n\n\n",
     port,speeds);
/*** Основной цикл  ***/
  for(;;){
    IF(portin()!=-1)   /* Есть данные в буфере? */
      WRITE(AL);      /*  Отобразим на экране  */
    IF(KBHIT()){        /* Было нажатие клавиш? */
      BIOSREADKEY();
      IF(AL != ESC) /* Да. Конец работы? */
        portout(AL);       /* Hет. Посылаем в линию  */
      ELSE BREAK;       /* Да. Конец    */
    }
  }
/* Вернем порт в обычное состояние   */
  portoff();
  EXIT(0);
}

/*************************************************************************
* Функция portbase выбирает базовый адрес заданного порта из
*   таблицы глобальных переменных BIOS и устанавливает глобальную
*   переменную p_base.
*   Возвращает значение p_base или -1 при отсутствии порта в машине
*/
int portbase()
{
  ES=0;
  p_base=ESWORD[port-1*2+0x400];
  IF(AX==0)AX=-1;
}

/*************************************************************************
* Функция setparm устанавливает параметры обмена: скорость,
*   вид контроля по четности, число битов данных и число стоповых битов.
*/
void setparm()
{
  IF(!p_base)return;  // 0 означает то, что базовый адрес порта не установлен
  CL=inp(p_base+LCR);  /* Чтение текущих параметров линии  */
/* Установка скорости обмена    */
/* Делитель = 1.8432 МГц / (speeds * 16)  */
  EAX=115200/speeds;
  BX=AX;
  outp(CL|0x80,p_base+LCR);  /* Подготовка порта к установке скорости.
                                Бит 7 в 1*/
  outp(BH,p_base + 1); /* Старший байт делителя  */
  outp(BL,p_base);   /* Младший байт делителя    */
/* Подготовка байта параметров для регистра управления линией */
/* Четность */
  CL&=7;    /* По умолчанию NONE    */
  SWITCH (parity) {
    CASE 'S':   /* SPACE    */
      CL|=7<<3;
      BREAK;
    CASE 'M':   /* MARK     */
      CL|=5<<3;
      BREAK;
    CASE 'E':   /* EVEN     */
      CL|=3<<3;
      BREAK;
    CASE 'O':   /* ODD      */
      CL|=1<<3;
      BREAK;
    }
/* Установка числа битов данных и числа стоповых битов  */
    DL=words-5;
    AL=stop-1<<2|DL;
  CL|=AL;
/* Установка параметров линии и переключение порта в рабочий режим  */
  outp(CL,p_base + LCR);
}

/*************************************************************************
* Функция porton переводит порт в рабочий режим.
*   Подменяет вектор прерывания, разрешает прерывания от порта и
*   выставляет сигналы готовности терминала и запроса на передачу.
*/
void porton()
{
  IF((port==2)||(port==4)) /* Определение номера прерывания    */
    BX = 0xB;
  ELSE BX = 0xC;
  $push BX
  GETINTVECT(#oldvect,BX);  /* Сохраним текущий вектор прерывания    */
  $pop BX
  SETINTVECT(,BX,CS,#i_handler); /* Заменим обработчик прерывания на свой */
/* Разрешим прерывания от порта */
  AL=inp(p_base + MCR) | GPO2 | DTR | RTS;   /* Разрешение прерывания от
                 схем RS-232 и установка
                 сигналов DTR и RTS */
  outp(AL,p_base + MCR);
  outp(R_INT,p_base + IER);      /* Прерывания только по приходу байта   */
/* Програмирование контроллера 8259 */
  AL=inp(IMR);
  IF((port == 2)||(port == 4)){
    AL&=IRQ3;      /* Разрешение прерывания от COM2        */
    eoi=EOI3;     /* Сигнал об окончании обработки прерывания от COM2 */
  }
  ELSE {
    AL&=IRQ4;      /* Разрешение прерывания от COM1        */
    eoi=EOI4;     /* Сигнал об окончании обработки прерывания от COM1 */
  }
  outp(AL,IMR);
}

/*************************************************************************
* Функция portoff переводит порт в из рабочего режима в обычное
*   состояние для MS-DOS (обратная к porton).
*   Запрещает прерывания от порта, востанавливает обработчик прерываний
*   и маскирует сигналы MCR.
*/
void portoff()
{
/* Запрещаем прерывания от порта    */
/* Програмирование контроллера 8259 */
  inp(IMR);
  IF((port==2)||(port==4))
    AL|=!IRQ3;     /* Запрещение прерываний от COM2 */
  ELSE AL|=!IRQ4;  /* Запрещение прерываний от COM1 */
  outp(AL,IMR);
  outp(0,p_base + IER);    /* Запрещение всех типов прерываний */
  outp(0,p_base + MCR);    /* Маскируем все сигналы        */
  IF((port == 2) || (port == 4)) /* Определение номера прерывания    */
    AX = 0xB;
  ELSE AX = 0xC;
/* Восстановим обработчик прерывания */
  SETINTVECT(AX,DSWORD[#oldvect+2],oldvect);
}

/*************************************************************************
* Функция i_handler является обработчиком прерываний от порта.
*   Функция получает управление при возникновении прерывания. Она
*   забирает байт из порта и кладет его в буфер. Буфер кольцевой - т.е.
*   при заполнении он начинает заполняться сначала.
*/
interrupt i_handler()
{
  $push AX,BX,DX,DS
  DS=CS;
  *inptr=inp(p_base);   /* Считаем байт */
  inptr++;
  IF(#buf+BSIZE<inptr)   /* Проверка выхода за границу буфера  */
    inptr=#buf;   /* Если да, то начнем сначала */
  outp(eoi,ICR);
  $pop DS,DX,BX,AX
}

/*************************************************************************
* Функция portin выбирает один байт из кольцевого буфера. Если буфер пуст,
* то возвращается -1
*/
int portin()
{
  IF (inptr!=outptr){   /* Есть в буфере данные?    */
    AH=0;
    AL=*outptr&0x7F;  /* Да. Берем ASCII символ   */
    $push AX
    outptr++;
    IF(#buf+BSIZE<outptr)    /* Проверка выхода за границу буфера */
      outptr = #buf;        /* Если да, то начнем сначала  */
    $pop AX
  }
  ELSE AX=-1;
}

/*************************************************************************
* Функция portout посылает байт ch в линию. Возвращает 0 при нормальном
* завершении. При ошибке возвращается -1
*/
int portout(byte ch)
{
  CX=0x8000;
  WHILE(byte inp(p_base + LSR) & TRRDY == 0){
    CX--;  /* Пока порт не готов к передаче */
    IF (ZEROFLAG)RETURN -1;
  }           /* Ошибка. Выход по таймауту  */
  outp(ch,p_base);  /* Вывод символа */
  return 0;
}
