| /* 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 */ |
| |
| |
| /* Table of Serial Terminal Escape Sequences |
| *======================================================================= |
| * Key ANSI VT100 extension VT220 extension |
| * --------- ------- --------------- --------------- |
| * Up arrow <Esc>[A |
| * Down arrow <Esc>[B |
| * Right arrow <Esc>[C |
| * Left arrow <Esc>[D |
| * F1 <Esc>Op |
| * F2 <Esc>Oq |
| * F3 <Esc>Or |
| * F4 <Esc>Os |
| * F5 <Esc>Ot |
| * F6 <Esc>Ou <Esc>[17~ <Esc>[17~ |
| * F7 <Esc>Ov <Esc>[18~ <Esc>[18~ |
| * F8 <Esc>Ow <Esc>[19~ <Esc>[19~ |
| * F9 <Esc>Ox <Esc>[20~ <Esc>[20~ |
| * F10 <Esc>Oy <Esc>[21~ <Esc>[21~ |
| * F11 <Esc>Oz <Esc>[23~ <Esc>[23~ |
| * F12 <Esc>Oa <Esc>[24~ <Esc>[24~ |
| * Home <Esc>[1~ |
| * End <Esc>[4~ |
| * Insert <Esc>[2~ |
| * Delete <Esc>[3~ |
| * Page Up <Esc>[5~ |
| * Page Down <Esc>[6~ |
| * Shift-Tab <Esc>[Z <Esc>[0Z |
| */ |
| |
| /* Generic definitions. */ |
| |
| static void |
| serial_translate_key_sequence (void) |
| { |
| const struct |
| { |
| short /*char*/ key; |
| short /*char*/ ascii; |
| } |
| three_code_table[] = |
| { |
| {'A', KEY_UP/*16*/}, /* Up arrow */ |
| {'B', KEY_DOWN/*14*/}, /* Down arrow */ |
| {'C', KEY_RIGHT/*6*/}, /* Right arrow */ |
| {'D', KEY_LEFT/*2*/}, /* Left arrow */ |
| {'F', KEY_END/*5*/}, /* End */ |
| {'H', KEY_HOME/*1*/}, /* Home */ |
| {'4', KEY_DC/*4*/} /* Delete */ |
| }; |
| |
| const struct |
| { |
| short key; |
| short /*char*/ ascii; |
| } |
| four_code_table[] = |
| { |
| {('1' | ('~' << 8)), KEY_HOME/*1*/}, /* Home */ |
| {('3' | ('~' << 8)), KEY_DC/*4*/}, /* Delete */ |
| {('5' | ('~' << 8)), KEY_PPAGE/*7*/}, /* Page Up */ |
| {('6' | ('~' << 8)), KEY_NPAGE/*3*/}, /* Page Down */ |
| }; |
| |
| /* 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]) |
| { |
| *(short *)input_buf = three_code_table[i].ascii; |
| npending--;//npending -= 2; |
| grub_memmove (input_buf + 2/*1*/, input_buf + 3, npending - 2/*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) |
| { |
| *(short *)input_buf = four_code_table[i].ascii; |
| npending -= 2/*3*/; |
| grub_memmove (input_buf + 2/*1*/, input_buf + 4, npending - 2/*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)) |
| ; |
| |
| npending--; |
| if ((c = input_buf[0])) |
| grub_memmove (input_buf, input_buf + 1, npending); |
| else |
| { |
| npending--; |
| c = *(unsigned short *)input_buf; |
| grub_memmove (input_buf, input_buf + 2, npending); |
| } |
| |
| return c; |
| } |
| |
| /* The serial version of checkkey. */ |
| int |
| serial_checkkey (void) |
| { |
| if (fill_input_buf (1)) |
| return input_buf[0] ? input_buf[0] : *(unsigned short *)input_buf; |
| |
| return -1; |
| } |
| |
| /* The serial version of grub_putchar. */ |
| void |
| serial_putchar (int c) |
| { |
| /* Keep track of the cursor. */ |
| if (keep_track) |
| { |
| #if 0 |
| /* 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; |
| } |
| #endif |
| |
| 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 */ |