/* serial.c - serial device interface */
/*
* GRUB -- GRand Unified Bootloader
* Copyright (C) 2000,2001,2002 Free Software Foundation, Inc.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#ifdef SUPPORT_SERIAL
#include <shared.h>
#include <serial.h>
#include <term.h>
#include <terminfo.h>
/* An input buffer. */
static char input_buf[8];
static int npending = 0;
static int serial_x;
static int serial_y;
static int keep_track = 1;
/* Hardware-dependent definitions. */
#ifndef GRUB_UTIL
/* The structure for speed vs. divisor. */
struct divisor
{
int speed;
unsigned short div;
};
/* Store the port number of a serial unit. */
static unsigned short serial_hw_port = 0;
/* The table which lists common configurations. */
static struct divisor divisor_tab[] =
{
{ 2400, 0x0030 },
{ 4800, 0x0018 },
{ 9600, 0x000C },
{ 19200, 0x0006 },
{ 38400, 0x0003 },
{ 57600, 0x0002 },
{ 115200, 0x0001 }
};
/* Read a byte from a port. */
static inline unsigned char
inb (unsigned short port)
{
unsigned char value;
asm volatile ("inb %w1, %0" : "=a" (value) : "Nd" (port));
asm volatile ("outb %%al, $0x80" : : );
return value;
}
/* Write a byte to a port. */
static inline void
outb (unsigned short port, unsigned char value)
{
asm volatile ("outb %b0, %w1" : : "a" (value), "Nd" (port));
asm volatile ("outb %%al, $0x80" : : );
}
/* Fetch a key. */
int
serial_hw_fetch (void)
{
if (inb (serial_hw_port + UART_LSR) & UART_DATA_READY)
return inb (serial_hw_port + UART_RX);
return -1;
}
/* Put a chararacter. */
void
serial_hw_put (int c)
{
int timeout = 100000;
/* Wait until the transmitter holding register is empty. */
while ((inb (serial_hw_port + UART_LSR) & UART_EMPTY_TRANSMITTER) == 0)
{
if (--timeout == 0)
/* There is something wrong. But what can I do? */
return;
}
outb (serial_hw_port + UART_TX, c);
}
void
serial_hw_delay (void)
{
outb (0x80, 0);
}
/* Return the port number for the UNITth serial device. */
unsigned short
serial_hw_get_port (int unit)
{
/* The BIOS data area. */
const unsigned short *addr = (const unsigned short *) 0x0400;
return addr[unit];
}
/* Initialize a serial device. PORT is the port number for a serial device.
SPEED is a DTE-DTE speed which must be one of these: 2400, 4800, 9600,
19200, 38400, 57600 and 115200. WORD_LEN is the word length to be used
for the device. Likewise, PARITY is the type of the parity and
STOP_BIT_LEN is the length of the stop bit. The possible values for
WORD_LEN, PARITY and STOP_BIT_LEN are defined in the header file as
macros. */
int
serial_hw_init (unsigned short port, unsigned int speed,
int word_len, int parity, int stop_bit_len)
{
int i;
unsigned short div = 0;
unsigned char status = 0;
/* Turn off the interrupt. */
outb (port + UART_IER, 0);
/* Set DLAB. */
outb (port + UART_LCR, UART_DLAB);
/* Set the baud rate. */
for (i = 0; i < sizeof (divisor_tab) / sizeof (divisor_tab[0]); i++)
if (divisor_tab[i].speed == speed)
{
div = divisor_tab[i].div;
break;
}
if (div == 0)
return 0;
outb (port + UART_DLL, div & 0xFF);
outb (port + UART_DLH, div >> 8);
/* Set the line status. */
status |= parity | word_len | stop_bit_len;
outb (port + UART_LCR, status);
/* Enable the FIFO. */
outb (port + UART_FCR, UART_ENABLE_FIFO);
/* Turn on DTR, RTS, and OUT2. */
outb (port + UART_MCR, UART_ENABLE_MODEM);
/* Store the port number. */
serial_hw_port = port;
/* Drain the input buffer. */
while (serial_checkkey () != -1)
(void) serial_getkey ();
/* Get rid of TERM_NEED_INIT from the serial terminal. */
for (i = 0; term_table[i].name; i++)
if (grub_strcmp (term_table[i].name, "serial") == 0)
{
term_table[i].flags &= ~TERM_NEED_INIT;
break;
}
/* FIXME: should check if the serial terminal was found. */
return 1;
}
#endif /* ! GRUB_UTIL */
/* Generic definitions. */
static void
serial_translate_key_sequence (void)
{
const struct
{
char key;
char ascii;
}
three_code_table[] =
{
{'A', 16},
{'B', 14},
{'C', 6},
{'D', 2},
{'F', 5},
{'H', 1},
{'4', 4}
};
const struct
{
short key;
char ascii;
}
four_code_table[] =
{
{('1' | ('~' << 8)), 1},
{('3' | ('~' << 8)), 4},
{('5' | ('~' << 8)), 7},
{('6' | ('~' << 8)), 3},
};
/* The buffer must start with ``ESC [''. */
if (*((unsigned short *) input_buf) != ('\e' | ('[' << 8)))
return;
if (npending >= 3)
{
int i;
for (i = 0;
i < sizeof (three_code_table) / sizeof (three_code_table[0]);
i++)
if (three_code_table[i].key == input_buf[2])
{
input_buf[0] = three_code_table[i].ascii;
npending -= 2;
grub_memmove (input_buf + 1, input_buf + 3, npending - 1);
return;
}
}
if (npending >= 4)
{
int i;
short key = *((short *) (input_buf + 2));
for (i = 0;
i < sizeof (four_code_table) / sizeof (four_code_table[0]);
i++)
if (four_code_table[i].key == key)
{
input_buf[0] = four_code_table[i].ascii;
npending -= 3;
grub_memmove (input_buf + 1, input_buf + 4, npending - 1);
return;
}
}
}
static
int fill_input_buf (int nowait)
{
int i;
for (i = 0; i < 10000 && npending < sizeof (input_buf); i++)
{
int c;
c = serial_hw_fetch ();
if (c >= 0)
{
input_buf[npending++] = c;
/* Reset the counter to zero, to wait for the same interval. */
i = 0;
}
if (nowait)
break;
}
/* Translate some key sequences. */
serial_translate_key_sequence ();
return npending;
}
/* The serial version of getkey. */
int
serial_getkey (void)
{
int c;
while (! fill_input_buf (0))
;
c = input_buf[0];
npending--;
grub_memmove (input_buf, input_buf + 1, npending);
return c;
}
/* The serial version of checkkey. */
int
serial_checkkey (void)
{
if (fill_input_buf (1))
return input_buf[0];
return -1;
}
/* The serial version of grub_putchar. */
void
serial_putchar (int c)
{
/* Keep track of the cursor. */
if (keep_track)
{
/* The serial terminal doesn't have VGA fonts. */
switch (c)
{
case DISP_UL:
c = ACS_ULCORNER;
break;
case DISP_UR:
c = ACS_URCORNER;
break;
case DISP_LL:
c = ACS_LLCORNER;
break;
case DISP_LR:
c = ACS_LRCORNER;
break;
case DISP_HORIZ:
c = ACS_HLINE;
break;
case DISP_VERT:
c = ACS_VLINE;
break;
case DISP_LEFT:
c = ACS_LARROW;
break;
case DISP_RIGHT:
c = ACS_RARROW;
break;
case DISP_UP:
c = ACS_UARROW;
break;
case DISP_DOWN:
c = ACS_DARROW;
break;
default:
break;
}
switch (c)
{
case '\r':
serial_x = 0;
break;
case '\n':
serial_y++;
break;
case '\b':
case 127:
if (serial_x > 0)
serial_x--;
break;
case '\a':
break;
default:
if (serial_x >= 79)
{
serial_putchar ('\r');
serial_putchar ('\n');
}
serial_x++;
break;
}
}
serial_hw_put (c);
}
int
serial_getxy (void)
{
return (serial_x << 8) | serial_y;
}
void
serial_gotoxy (int x, int y)
{
keep_track = 0;
ti_cursor_address (x, y);
keep_track = 1;
serial_x = x;
serial_y = y;
}
void
serial_cls (void)
{
keep_track = 0;
ti_clear_screen ();
keep_track = 1;
serial_x = serial_y = 0;
}
void
serial_setcolorstate (color_state state)
{
keep_track = 0;
if (state == COLOR_STATE_HIGHLIGHT)
ti_enter_standout_mode ();
else
ti_exit_standout_mode ();
keep_track = 1;
}
#endif /* SUPPORT_SERIAL */