halt_mod_LDFLAGS = $(COMMON_LDFLAGS)
# For serial.mod.
-serial_mod_SOURCES = term/i386/pc/serial.c
+serial_mod_SOURCES = term/serial.c
serial_mod_CFLAGS = $(COMMON_CFLAGS)
serial_mod_LDFLAGS = $(COMMON_LDFLAGS)
halt_mod_LDFLAGS = $(COMMON_LDFLAGS)
# For serial.mod.
-serial_mod_SOURCES = term/i386/pc/serial.c
+serial_mod_SOURCES = term/serial.c
serial_mod_CFLAGS = $(COMMON_CFLAGS)
serial_mod_LDFLAGS = $(COMMON_LDFLAGS)
halt_mod_LDFLAGS = $(COMMON_LDFLAGS)
# For serial.mod.
-serial_mod_SOURCES = term/i386/pc/serial.c
+serial_mod_SOURCES = term/serial.c
serial_mod_CFLAGS = $(COMMON_CFLAGS)
serial_mod_LDFLAGS = $(COMMON_LDFLAGS)
#ifndef GRUB_IO_H
#define GRUB_IO_H 1
+typedef unsigned short int grub_port_t;
+
static __inline unsigned char
grub_inb (unsigned short int port)
{
+++ /dev/null
-/* serial.h - serial device interface */
-/*
- * GRUB -- GRand Unified Bootloader
- * Copyright (C) 2000,2001,2002,2005,2007 Free Software Foundation, Inc.
- *
- * GRUB 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 3 of the License, or
- * (at your option) any later version.
- *
- * GRUB 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 GRUB. If not, see <http://www.gnu.org/licenses/>.
- */
-
-#ifndef GRUB_SERIAL_MACHINE_HEADER
-#define GRUB_SERIAL_MACHINE_HEADER 1
-
-/* Macros. */
-
-/* The offsets of UART registers. */
-#define UART_TX 0
-#define UART_RX 0
-#define UART_DLL 0
-#define UART_IER 1
-#define UART_DLH 1
-#define UART_IIR 2
-#define UART_FCR 2
-#define UART_LCR 3
-#define UART_MCR 4
-#define UART_LSR 5
-#define UART_MSR 6
-#define UART_SR 7
-
-/* For LSR bits. */
-#define UART_DATA_READY 0x01
-#define UART_EMPTY_TRANSMITTER 0x20
-
-/* The type of parity. */
-#define UART_NO_PARITY 0x00
-#define UART_ODD_PARITY 0x08
-#define UART_EVEN_PARITY 0x18
-
-/* The type of word length. */
-#define UART_5BITS_WORD 0x00
-#define UART_6BITS_WORD 0x01
-#define UART_7BITS_WORD 0x02
-#define UART_8BITS_WORD 0x03
-
-/* The type of the length of stop bit. */
-#define UART_1_STOP_BIT 0x00
-#define UART_2_STOP_BITS 0x04
-
-/* the switch of DLAB. */
-#define UART_DLAB 0x80
-
-/* Enable the FIFO. */
-#define UART_ENABLE_FIFO 0xC7
-
-/* Turn on DTR, RTS, and OUT2. */
-#define UART_ENABLE_MODEM 0x0B
-
-#endif /* ! GRUB_SERIAL_MACHINE_HEADER */
--- /dev/null
+/* serial.h - serial device interface */
+/*
+ * GRUB -- GRand Unified Bootloader
+ * Copyright (C) 2000,2001,2002,2005,2007 Free Software Foundation, Inc.
+ *
+ * GRUB 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 3 of the License, or
+ * (at your option) any later version.
+ *
+ * GRUB 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 GRUB. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef GRUB_SERIAL_HEADER
+#define GRUB_SERIAL_HEADER 1
+
+/* Macros. */
+
+/* The offsets of UART registers. */
+#define UART_TX 0
+#define UART_RX 0
+#define UART_DLL 0
+#define UART_IER 1
+#define UART_DLH 1
+#define UART_IIR 2
+#define UART_FCR 2
+#define UART_LCR 3
+#define UART_MCR 4
+#define UART_LSR 5
+#define UART_MSR 6
+#define UART_SR 7
+
+/* For LSR bits. */
+#define UART_DATA_READY 0x01
+#define UART_EMPTY_TRANSMITTER 0x20
+
+/* The type of parity. */
+#define UART_NO_PARITY 0x00
+#define UART_ODD_PARITY 0x08
+#define UART_EVEN_PARITY 0x18
+
+/* The type of word length. */
+#define UART_5BITS_WORD 0x00
+#define UART_6BITS_WORD 0x01
+#define UART_7BITS_WORD 0x02
+#define UART_8BITS_WORD 0x03
+
+/* The type of the length of stop bit. */
+#define UART_1_STOP_BIT 0x00
+#define UART_2_STOP_BITS 0x04
+
+/* the switch of DLAB. */
+#define UART_DLAB 0x80
+
+/* Enable the FIFO. */
+#define UART_ENABLE_FIFO 0xC7
+
+/* Turn on DTR, RTS, and OUT2. */
+#define UART_ENABLE_MODEM 0x0B
+
+#endif /* ! GRUB_SERIAL_MACHINE_HEADER */
+++ /dev/null
-/*
- * GRUB -- GRand Unified Bootloader
- * Copyright (C) 2000,2001,2002,2003,2004,2005,2007,2008,2009 Free Software Foundation, Inc.
- *
- * GRUB 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 3 of the License, or
- * (at your option) any later version.
- *
- * GRUB 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 GRUB. If not, see <http://www.gnu.org/licenses/>.
- */
-
-#include <grub/machine/machine.h>
-#include <grub/machine/memory.h>
-#include <grub/machine/serial.h>
-#include <grub/machine/console.h>
-#include <grub/term.h>
-#include <grub/types.h>
-#include <grub/dl.h>
-#include <grub/misc.h>
-#include <grub/terminfo.h>
-#include <grub/cpu/io.h>
-#include <grub/extcmd.h>
-
-#define TEXT_WIDTH 80
-#define TEXT_HEIGHT 25
-
-static unsigned int xpos, ypos;
-static unsigned int keep_track = 1;
-static unsigned int registered = 0;
-
-/* An input buffer. */
-static char input_buf[8];
-static unsigned int npending = 0;
-
-/* Argument options. */
-static const struct grub_arg_option options[] =
-{
- {"unit", 'u', 0, "Set the serial unit", 0, ARG_TYPE_INT},
- {"port", 'p', 0, "Set the serial port address", 0, ARG_TYPE_STRING},
- {"speed", 's', 0, "Set the serial port speed", 0, ARG_TYPE_INT},
- {"word", 'w', 0, "Set the serial port word length", 0, ARG_TYPE_INT},
- {"parity", 'r', 0, "Set the serial port parity", 0, ARG_TYPE_STRING},
- {"stop", 't', 0, "Set the serial port stop bits", 0, ARG_TYPE_INT},
- {0, 0, 0, 0, 0, 0}
-};
-
-/* Serial port settings. */
-struct serial_port
-{
- unsigned short port;
- unsigned short divisor;
- unsigned short word_len;
- unsigned int parity;
- unsigned short stop_bits;
-};
-
-/* Serial port settings. */
-static struct serial_port serial_settings;
-
-#ifdef GRUB_MACHINE_PCBIOS
-static const unsigned short *serial_hw_io_addr = (const unsigned short *) GRUB_MEMORY_MACHINE_BIOS_DATA_AREA_ADDR;
-#define GRUB_SERIAL_PORT_NUM 4
-#else
-static const unsigned short serial_hw_io_addr[] = { 0x3f8, 0x2f8, 0x3e8, 0x2e8 };
-#define GRUB_SERIAL_PORT_NUM (ARRAY_SIZE(serial_hw_io_addr))
-#endif
-
-/* Return the port number for the UNITth serial device. */
-static inline unsigned short
-serial_hw_get_port (const unsigned int unit)
-{
- if (unit < GRUB_SERIAL_PORT_NUM)
- return serial_hw_io_addr[unit];
- else
- return 0;
-}
-
-/* Fetch a key. */
-static int
-serial_hw_fetch (void)
-{
- if (grub_inb (serial_settings.port + UART_LSR) & UART_DATA_READY)
- return grub_inb (serial_settings.port + UART_RX);
-
- return -1;
-}
-
-/* Put a character. */
-static void
-serial_hw_put (const int c)
-{
- unsigned int timeout = 100000;
-
- /* Wait until the transmitter holding register is empty. */
- while ((grub_inb (serial_settings.port + UART_LSR) & UART_EMPTY_TRANSMITTER) == 0)
- {
- if (--timeout == 0)
- /* There is something wrong. But what can I do? */
- return;
- }
-
- grub_outb (c, serial_settings.port + UART_TX);
-}
-
-static void
-serial_translate_key_sequence (void)
-{
- unsigned int i;
- static struct
- {
- char key;
- char ascii;
- }
- three_code_table[] =
- {
- {'A', 16},
- {'B', 14},
- {'C', 6},
- {'D', 2},
- {'F', 5},
- {'H', 1},
- {'4', 4}
- };
-
- static struct
- {
- short key;
- char ascii;
- }
- four_code_table[] =
- {
- {('1' | ('~' << 8)), 1},
- {('3' | ('~' << 8)), 4},
- {('5' | ('~' << 8)), 7},
- {('6' | ('~' << 8)), 3}
- };
-
- if (npending < 3)
- return;
-
- /* The buffer must start with "ESC [". */
- if (input_buf[0] != '\e' || input_buf[1] != '[')
- return;
-
- for (i = 0; ARRAY_SIZE (three_code_table); 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)
- {
- short key = input_buf[3] | (input_buf[4] << 8);
-
- for (i = 0; i < ARRAY_SIZE (four_code_table); 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 (const 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;
-}
-
-/* Convert speed to divisor. */
-static unsigned short
-serial_get_divisor (unsigned int speed)
-{
- unsigned int i;
-
- /* The structure for speed vs. divisor. */
- struct divisor
- {
- unsigned int speed;
- unsigned short div;
- };
-
- /* The table which lists common configurations. */
- /* 1843200 / (speed * 16) */
- static struct divisor divisor_tab[] =
- {
- { 2400, 0x0030 },
- { 4800, 0x0018 },
- { 9600, 0x000C },
- { 19200, 0x0006 },
- { 38400, 0x0003 },
- { 57600, 0x0002 },
- { 115200, 0x0001 }
- };
-
- /* Set the baud rate. */
- for (i = 0; i < sizeof (divisor_tab) / sizeof (divisor_tab[0]); i++)
- if (divisor_tab[i].speed == speed)
- return divisor_tab[i].div;
- return 0;
-}
-
-/* The serial version of checkkey. */
-static int
-grub_serial_checkkey (void)
-{
- if (fill_input_buf (1))
- return input_buf[0];
- else
- return -1;
-}
-
-/* The serial version of getkey. */
-static int
-grub_serial_getkey (void)
-{
- int c;
-
- while (! fill_input_buf (0))
- ;
-
- c = input_buf[0];
- grub_memmove (input_buf, input_buf + 1, --npending);
-
- return c;
-}
-
-/* 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. */
-static grub_err_t
-serial_hw_init (void)
-{
- unsigned char status = 0;
-
- /* Turn off the interrupt. */
- grub_outb (0, serial_settings.port + UART_IER);
-
- /* Set DLAB. */
- grub_outb (UART_DLAB, serial_settings.port + UART_LCR);
-
- /* Set the baud rate. */
- grub_outb (serial_settings.divisor & 0xFF, serial_settings.port + UART_DLL);
- grub_outb (serial_settings.divisor >> 8, serial_settings.port + UART_DLH);
-
- /* Set the line status. */
- status |= (serial_settings.parity
- | serial_settings.word_len
- | serial_settings.stop_bits);
- grub_outb (status, serial_settings.port + UART_LCR);
-
- /* Enable the FIFO. */
- grub_outb (UART_ENABLE_FIFO, serial_settings.port + UART_FCR);
-
- /* Turn on DTR, RTS, and OUT2. */
- grub_outb (UART_ENABLE_MODEM, serial_settings.port + UART_MCR);
-
- /* Drain the input buffer. */
- while (grub_serial_checkkey () != -1)
- (void) grub_serial_getkey ();
-
- /* FIXME: should check if the serial terminal was found. */
-
- return GRUB_ERR_NONE;
-}
-
-/* The serial version of putchar. */
-static void
-grub_serial_putchar (grub_uint32_t c)
-{
- /* Keep track of the cursor. */
- if (keep_track)
- {
- /* The serial terminal does not have VGA fonts. */
- if (c > 0x7F)
- {
- /* Better than nothing. */
- switch (c)
- {
- case GRUB_TERM_DISP_LEFT:
- c = '<';
- break;
-
- case GRUB_TERM_DISP_UP:
- c = '^';
- break;
-
- case GRUB_TERM_DISP_RIGHT:
- c = '>';
- break;
-
- case GRUB_TERM_DISP_DOWN:
- c = 'v';
- break;
-
- case GRUB_TERM_DISP_HLINE:
- c = '-';
- break;
-
- case GRUB_TERM_DISP_VLINE:
- c = '|';
- break;
-
- case GRUB_TERM_DISP_UL:
- case GRUB_TERM_DISP_UR:
- case GRUB_TERM_DISP_LL:
- case GRUB_TERM_DISP_LR:
- c = '+';
- break;
-
- default:
- c = '?';
- break;
- }
- }
-
- switch (c)
- {
- case '\a':
- break;
-
- case '\b':
- case 127:
- if (xpos > 0)
- xpos--;
- break;
-
- case '\n':
- if (ypos < TEXT_HEIGHT)
- ypos++;
- break;
-
- case '\r':
- xpos = 0;
- break;
-
- default:
- if (xpos >= TEXT_WIDTH)
- {
- grub_putchar ('\r');
- grub_putchar ('\n');
- }
- xpos++;
- break;
- }
- }
-
- serial_hw_put (c);
-}
-
-static grub_ssize_t
-grub_serial_getcharwidth (grub_uint32_t c __attribute__ ((unused)))
-{
- return 1;
-}
-
-static grub_uint16_t
-grub_serial_getwh (void)
-{
- return (TEXT_WIDTH << 8) | TEXT_HEIGHT;
-}
-
-static grub_uint16_t
-grub_serial_getxy (void)
-{
- return ((xpos << 8) | ypos);
-}
-
-static void
-grub_serial_gotoxy (grub_uint8_t x, grub_uint8_t y)
-{
- if (x > TEXT_WIDTH || y > TEXT_HEIGHT)
- {
- grub_error (GRUB_ERR_OUT_OF_RANGE, "invalid point (%u,%u)", x, y);
- }
- else
- {
- keep_track = 0;
- grub_terminfo_gotoxy (x, y);
- keep_track = 1;
-
- xpos = x;
- ypos = y;
- }
-}
-
-static void
-grub_serial_cls (void)
-{
- keep_track = 0;
- grub_terminfo_cls ();
- keep_track = 1;
-
- xpos = ypos = 0;
-}
-
-static void
-grub_serial_setcolorstate (const grub_term_color_state state)
-{
- keep_track = 0;
- switch (state)
- {
- case GRUB_TERM_COLOR_STANDARD:
- case GRUB_TERM_COLOR_NORMAL:
- grub_terminfo_reverse_video_off ();
- break;
- case GRUB_TERM_COLOR_HIGHLIGHT:
- grub_terminfo_reverse_video_on ();
- break;
- default:
- break;
- }
- keep_track = 1;
-}
-
-static void
-grub_serial_setcursor (const int on)
-{
- if (on)
- grub_terminfo_cursor_on ();
- else
- grub_terminfo_cursor_off ();
-}
-
-static struct grub_term_input grub_serial_term_input =
-{
- .name = "serial",
- .checkkey = grub_serial_checkkey,
- .getkey = grub_serial_getkey,
-};
-
-static struct grub_term_output grub_serial_term_output =
-{
- .name = "serial",
- .putchar = grub_serial_putchar,
- .getcharwidth = grub_serial_getcharwidth,
- .getwh = grub_serial_getwh,
- .getxy = grub_serial_getxy,
- .gotoxy = grub_serial_gotoxy,
- .cls = grub_serial_cls,
- .setcolorstate = grub_serial_setcolorstate,
- .setcursor = grub_serial_setcursor,
- .flags = 0,
-};
-
-\f
-
-static grub_err_t
-grub_cmd_serial (grub_extcmd_t cmd,
- int argc __attribute__ ((unused)),
- char **args __attribute__ ((unused)))
-{
- struct grub_arg_list *state = cmd->state;
- struct serial_port backup_settings = serial_settings;
- grub_err_t hwiniterr;
-
- if (state[0].set)
- {
- unsigned int unit;
-
- unit = grub_strtoul (state[0].arg, 0, 0);
- serial_settings.port = serial_hw_get_port (unit);
- if (!serial_settings.port)
- return grub_error (GRUB_ERR_BAD_ARGUMENT, "bad unit number.");
- }
-
- if (state[1].set)
- serial_settings.port = (unsigned short) grub_strtoul (state[1].arg, 0, 0);
-
- if (state[2].set)
- {
- unsigned long speed;
-
- speed = grub_strtoul (state[2].arg, 0, 0);
- serial_settings.divisor = serial_get_divisor ((unsigned int) speed);
- if (serial_settings.divisor == 0)
- {
- serial_settings = backup_settings;
- return grub_error (GRUB_ERR_BAD_ARGUMENT, "bad speed");
- }
- }
-
- if (state[3].set)
- {
- if (! grub_strcmp (state[3].arg, "5"))
- serial_settings.word_len = UART_5BITS_WORD;
- else if (! grub_strcmp (state[3].arg, "6"))
- serial_settings.word_len = UART_6BITS_WORD;
- else if (! grub_strcmp (state[3].arg, "7"))
- serial_settings.word_len = UART_7BITS_WORD;
- else if (! grub_strcmp (state[3].arg, "8"))
- serial_settings.word_len = UART_8BITS_WORD;
- else
- {
- serial_settings = backup_settings;
- return grub_error (GRUB_ERR_BAD_ARGUMENT, "bad word length");
- }
- }
-
- if (state[4].set)
- {
- if (! grub_strcmp (state[4].arg, "no"))
- serial_settings.parity = UART_NO_PARITY;
- else if (! grub_strcmp (state[4].arg, "odd"))
- serial_settings.parity = UART_ODD_PARITY;
- else if (! grub_strcmp (state[4].arg, "even"))
- serial_settings.parity = UART_EVEN_PARITY;
- else
- {
- serial_settings = backup_settings;
- return grub_error (GRUB_ERR_BAD_ARGUMENT, "bad parity");
- }
- }
-
- if (state[5].set)
- {
- if (! grub_strcmp (state[5].arg, "1"))
- serial_settings.stop_bits = UART_1_STOP_BIT;
- else if (! grub_strcmp (state[5].arg, "2"))
- serial_settings.stop_bits = UART_2_STOP_BITS;
- else
- {
- serial_settings = backup_settings;
- return grub_error (GRUB_ERR_BAD_ARGUMENT, "bad number of stop bits");
- }
- }
-
- /* Initialize with new settings. */
- hwiniterr = serial_hw_init ();
-
- if (hwiniterr == GRUB_ERR_NONE)
- {
- /* Register terminal if not yet registered. */
- if (registered == 0)
- {
- grub_term_register_input ("serial", &grub_serial_term_input);
- grub_term_register_output ("serial", &grub_serial_term_output);
- registered = 1;
- }
- }
- else
- {
- /* Initialization with new settings failed. */
- if (registered == 1)
- {
- /* If the terminal is registered, attempt to restore previous
- settings. */
- serial_settings = backup_settings;
- if (serial_hw_init () != GRUB_ERR_NONE)
- {
- /* If unable to restore settings, unregister terminal. */
- grub_term_unregister_input (&grub_serial_term_input);
- grub_term_unregister_output (&grub_serial_term_output);
- registered = 0;
- }
- }
- }
-
- return hwiniterr;
-}
-
-static grub_extcmd_t cmd;
-
-GRUB_MOD_INIT(serial)
-{
- cmd = grub_register_extcmd ("serial", grub_cmd_serial,
- GRUB_COMMAND_FLAG_BOTH,
- "serial [OPTIONS...]",
- "Configure serial port.", options);
-
- /* Set default settings. */
- serial_settings.port = serial_hw_get_port (0);
- serial_settings.divisor = serial_get_divisor (9600);
- serial_settings.word_len = UART_8BITS_WORD;
- serial_settings.parity = UART_NO_PARITY;
- serial_settings.stop_bits = UART_1_STOP_BIT;
-}
-
-GRUB_MOD_FINI(serial)
-{
- grub_unregister_extcmd (cmd);
- if (registered == 1) /* Unregister terminal only if registered. */
- {
- grub_term_unregister_input (&grub_serial_term_input);
- grub_term_unregister_output (&grub_serial_term_output);
- }
-}
--- /dev/null
+/*
+ * GRUB -- GRand Unified Bootloader
+ * Copyright (C) 2000,2001,2002,2003,2004,2005,2007,2008,2009 Free Software Foundation, Inc.
+ *
+ * GRUB 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 3 of the License, or
+ * (at your option) any later version.
+ *
+ * GRUB 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 GRUB. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <grub/machine/machine.h>
+#include <grub/machine/memory.h>
+#include <grub/machine/serial.h>
+#include <grub/machine/console.h>
+#include <grub/term.h>
+#include <grub/types.h>
+#include <grub/dl.h>
+#include <grub/misc.h>
+#include <grub/terminfo.h>
+#include <grub/cpu/io.h>
+#include <grub/extcmd.h>
+
+#define TEXT_WIDTH 80
+#define TEXT_HEIGHT 25
+
+static unsigned int xpos, ypos;
+static unsigned int keep_track = 1;
+static unsigned int registered = 0;
+
+/* An input buffer. */
+static char input_buf[8];
+static unsigned int npending = 0;
+
+/* Argument options. */
+static const struct grub_arg_option options[] =
+{
+ {"unit", 'u', 0, "Set the serial unit", 0, ARG_TYPE_INT},
+ {"port", 'p', 0, "Set the serial port address", 0, ARG_TYPE_STRING},
+ {"speed", 's', 0, "Set the serial port speed", 0, ARG_TYPE_INT},
+ {"word", 'w', 0, "Set the serial port word length", 0, ARG_TYPE_INT},
+ {"parity", 'r', 0, "Set the serial port parity", 0, ARG_TYPE_STRING},
+ {"stop", 't', 0, "Set the serial port stop bits", 0, ARG_TYPE_INT},
+ {0, 0, 0, 0, 0, 0}
+};
+
+/* Serial port settings. */
+struct serial_port
+{
+ grub_port_t port;
+ unsigned short divisor;
+ unsigned short word_len;
+ unsigned int parity;
+ unsigned short stop_bits;
+};
+
+/* Serial port settings. */
+static struct serial_port serial_settings;
+
+#ifdef GRUB_MACHINE_PCBIOS
+static const unsigned short *serial_hw_io_addr = (const unsigned short *) GRUB_MEMORY_MACHINE_BIOS_DATA_AREA_ADDR;
+#define GRUB_SERIAL_PORT_NUM 4
+#else
+static const grub_port_t serial_hw_io_addr[] = { 0x3f8, 0x2f8, 0x3e8, 0x2e8 };
+#define GRUB_SERIAL_PORT_NUM (ARRAY_SIZE(serial_hw_io_addr))
+#endif
+
+/* Return the port number for the UNITth serial device. */
+static inline grub_port_t
+serial_hw_get_port (const unsigned int unit)
+{
+ if (unit < GRUB_SERIAL_PORT_NUM)
+ return serial_hw_io_addr[unit];
+ else
+ return 0;
+}
+
+/* Fetch a key. */
+static int
+serial_hw_fetch (void)
+{
+ if (grub_inb (serial_settings.port + UART_LSR) & UART_DATA_READY)
+ return grub_inb (serial_settings.port + UART_RX);
+
+ return -1;
+}
+
+/* Put a character. */
+static void
+serial_hw_put (const int c)
+{
+ unsigned int timeout = 100000;
+
+ /* Wait until the transmitter holding register is empty. */
+ while ((grub_inb (serial_settings.port + UART_LSR) & UART_EMPTY_TRANSMITTER) == 0)
+ {
+ if (--timeout == 0)
+ /* There is something wrong. But what can I do? */
+ return;
+ }
+
+ grub_outb (c, serial_settings.port + UART_TX);
+}
+
+static void
+serial_translate_key_sequence (void)
+{
+ unsigned int i;
+ static struct
+ {
+ char key;
+ char ascii;
+ }
+ three_code_table[] =
+ {
+ {'A', 16},
+ {'B', 14},
+ {'C', 6},
+ {'D', 2},
+ {'F', 5},
+ {'H', 1},
+ {'4', 4}
+ };
+
+ static struct
+ {
+ short key;
+ char ascii;
+ }
+ four_code_table[] =
+ {
+ {('1' | ('~' << 8)), 1},
+ {('3' | ('~' << 8)), 4},
+ {('5' | ('~' << 8)), 7},
+ {('6' | ('~' << 8)), 3}
+ };
+
+ if (npending < 3)
+ return;
+
+ /* The buffer must start with "ESC [". */
+ if (input_buf[0] != '\e' || input_buf[1] != '[')
+ return;
+
+ for (i = 0; ARRAY_SIZE (three_code_table); 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)
+ {
+ short key = input_buf[3] | (input_buf[4] << 8);
+
+ for (i = 0; i < ARRAY_SIZE (four_code_table); 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 (const 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;
+}
+
+/* Convert speed to divisor. */
+static unsigned short
+serial_get_divisor (unsigned int speed)
+{
+ unsigned int i;
+
+ /* The structure for speed vs. divisor. */
+ struct divisor
+ {
+ unsigned int speed;
+ unsigned short div;
+ };
+
+ /* The table which lists common configurations. */
+ /* 1843200 / (speed * 16) */
+ static struct divisor divisor_tab[] =
+ {
+ { 2400, 0x0030 },
+ { 4800, 0x0018 },
+ { 9600, 0x000C },
+ { 19200, 0x0006 },
+ { 38400, 0x0003 },
+ { 57600, 0x0002 },
+ { 115200, 0x0001 }
+ };
+
+ /* Set the baud rate. */
+ for (i = 0; i < sizeof (divisor_tab) / sizeof (divisor_tab[0]); i++)
+ if (divisor_tab[i].speed == speed)
+ return divisor_tab[i].div;
+ return 0;
+}
+
+/* The serial version of checkkey. */
+static int
+grub_serial_checkkey (void)
+{
+ if (fill_input_buf (1))
+ return input_buf[0];
+ else
+ return -1;
+}
+
+/* The serial version of getkey. */
+static int
+grub_serial_getkey (void)
+{
+ int c;
+
+ while (! fill_input_buf (0))
+ ;
+
+ c = input_buf[0];
+ grub_memmove (input_buf, input_buf + 1, --npending);
+
+ return c;
+}
+
+/* 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. */
+static grub_err_t
+serial_hw_init (void)
+{
+ unsigned char status = 0;
+
+ /* Turn off the interrupt. */
+ grub_outb (0, serial_settings.port + UART_IER);
+
+ /* Set DLAB. */
+ grub_outb (UART_DLAB, serial_settings.port + UART_LCR);
+
+ /* Set the baud rate. */
+ grub_outb (serial_settings.divisor & 0xFF, serial_settings.port + UART_DLL);
+ grub_outb (serial_settings.divisor >> 8, serial_settings.port + UART_DLH);
+
+ /* Set the line status. */
+ status |= (serial_settings.parity
+ | serial_settings.word_len
+ | serial_settings.stop_bits);
+ grub_outb (status, serial_settings.port + UART_LCR);
+
+ /* Enable the FIFO. */
+ grub_outb (UART_ENABLE_FIFO, serial_settings.port + UART_FCR);
+
+ /* Turn on DTR, RTS, and OUT2. */
+ grub_outb (UART_ENABLE_MODEM, serial_settings.port + UART_MCR);
+
+ /* Drain the input buffer. */
+ while (grub_serial_checkkey () != -1)
+ (void) grub_serial_getkey ();
+
+ /* FIXME: should check if the serial terminal was found. */
+
+ return GRUB_ERR_NONE;
+}
+
+/* The serial version of putchar. */
+static void
+grub_serial_putchar (grub_uint32_t c)
+{
+ /* Keep track of the cursor. */
+ if (keep_track)
+ {
+ /* The serial terminal does not have VGA fonts. */
+ if (c > 0x7F)
+ {
+ /* Better than nothing. */
+ switch (c)
+ {
+ case GRUB_TERM_DISP_LEFT:
+ c = '<';
+ break;
+
+ case GRUB_TERM_DISP_UP:
+ c = '^';
+ break;
+
+ case GRUB_TERM_DISP_RIGHT:
+ c = '>';
+ break;
+
+ case GRUB_TERM_DISP_DOWN:
+ c = 'v';
+ break;
+
+ case GRUB_TERM_DISP_HLINE:
+ c = '-';
+ break;
+
+ case GRUB_TERM_DISP_VLINE:
+ c = '|';
+ break;
+
+ case GRUB_TERM_DISP_UL:
+ case GRUB_TERM_DISP_UR:
+ case GRUB_TERM_DISP_LL:
+ case GRUB_TERM_DISP_LR:
+ c = '+';
+ break;
+
+ default:
+ c = '?';
+ break;
+ }
+ }
+
+ switch (c)
+ {
+ case '\a':
+ break;
+
+ case '\b':
+ case 127:
+ if (xpos > 0)
+ xpos--;
+ break;
+
+ case '\n':
+ if (ypos < TEXT_HEIGHT)
+ ypos++;
+ break;
+
+ case '\r':
+ xpos = 0;
+ break;
+
+ default:
+ if (xpos >= TEXT_WIDTH)
+ {
+ grub_putchar ('\r');
+ grub_putchar ('\n');
+ }
+ xpos++;
+ break;
+ }
+ }
+
+ serial_hw_put (c);
+}
+
+static grub_ssize_t
+grub_serial_getcharwidth (grub_uint32_t c __attribute__ ((unused)))
+{
+ return 1;
+}
+
+static grub_uint16_t
+grub_serial_getwh (void)
+{
+ return (TEXT_WIDTH << 8) | TEXT_HEIGHT;
+}
+
+static grub_uint16_t
+grub_serial_getxy (void)
+{
+ return ((xpos << 8) | ypos);
+}
+
+static void
+grub_serial_gotoxy (grub_uint8_t x, grub_uint8_t y)
+{
+ if (x > TEXT_WIDTH || y > TEXT_HEIGHT)
+ {
+ grub_error (GRUB_ERR_OUT_OF_RANGE, "invalid point (%u,%u)", x, y);
+ }
+ else
+ {
+ keep_track = 0;
+ grub_terminfo_gotoxy (x, y);
+ keep_track = 1;
+
+ xpos = x;
+ ypos = y;
+ }
+}
+
+static void
+grub_serial_cls (void)
+{
+ keep_track = 0;
+ grub_terminfo_cls ();
+ keep_track = 1;
+
+ xpos = ypos = 0;
+}
+
+static void
+grub_serial_setcolorstate (const grub_term_color_state state)
+{
+ keep_track = 0;
+ switch (state)
+ {
+ case GRUB_TERM_COLOR_STANDARD:
+ case GRUB_TERM_COLOR_NORMAL:
+ grub_terminfo_reverse_video_off ();
+ break;
+ case GRUB_TERM_COLOR_HIGHLIGHT:
+ grub_terminfo_reverse_video_on ();
+ break;
+ default:
+ break;
+ }
+ keep_track = 1;
+}
+
+static void
+grub_serial_setcursor (const int on)
+{
+ if (on)
+ grub_terminfo_cursor_on ();
+ else
+ grub_terminfo_cursor_off ();
+}
+
+static struct grub_term_input grub_serial_term_input =
+{
+ .name = "serial",
+ .checkkey = grub_serial_checkkey,
+ .getkey = grub_serial_getkey,
+};
+
+static struct grub_term_output grub_serial_term_output =
+{
+ .name = "serial",
+ .putchar = grub_serial_putchar,
+ .getcharwidth = grub_serial_getcharwidth,
+ .getwh = grub_serial_getwh,
+ .getxy = grub_serial_getxy,
+ .gotoxy = grub_serial_gotoxy,
+ .cls = grub_serial_cls,
+ .setcolorstate = grub_serial_setcolorstate,
+ .setcursor = grub_serial_setcursor,
+ .flags = 0,
+};
+
+\f
+
+static grub_err_t
+grub_cmd_serial (grub_extcmd_t cmd,
+ int argc __attribute__ ((unused)),
+ char **args __attribute__ ((unused)))
+{
+ struct grub_arg_list *state = cmd->state;
+ struct serial_port backup_settings = serial_settings;
+ grub_err_t hwiniterr;
+
+ if (state[0].set)
+ {
+ unsigned int unit;
+
+ unit = grub_strtoul (state[0].arg, 0, 0);
+ serial_settings.port = serial_hw_get_port (unit);
+ if (!serial_settings.port)
+ return grub_error (GRUB_ERR_BAD_ARGUMENT, "bad unit number.");
+ }
+
+ if (state[1].set)
+ serial_settings.port = (grub_port_t) grub_strtoul (state[1].arg, 0, 0);
+
+ if (state[2].set)
+ {
+ unsigned long speed;
+
+ speed = grub_strtoul (state[2].arg, 0, 0);
+ serial_settings.divisor = serial_get_divisor ((unsigned int) speed);
+ if (serial_settings.divisor == 0)
+ {
+ serial_settings = backup_settings;
+ return grub_error (GRUB_ERR_BAD_ARGUMENT, "bad speed");
+ }
+ }
+
+ if (state[3].set)
+ {
+ if (! grub_strcmp (state[3].arg, "5"))
+ serial_settings.word_len = UART_5BITS_WORD;
+ else if (! grub_strcmp (state[3].arg, "6"))
+ serial_settings.word_len = UART_6BITS_WORD;
+ else if (! grub_strcmp (state[3].arg, "7"))
+ serial_settings.word_len = UART_7BITS_WORD;
+ else if (! grub_strcmp (state[3].arg, "8"))
+ serial_settings.word_len = UART_8BITS_WORD;
+ else
+ {
+ serial_settings = backup_settings;
+ return grub_error (GRUB_ERR_BAD_ARGUMENT, "bad word length");
+ }
+ }
+
+ if (state[4].set)
+ {
+ if (! grub_strcmp (state[4].arg, "no"))
+ serial_settings.parity = UART_NO_PARITY;
+ else if (! grub_strcmp (state[4].arg, "odd"))
+ serial_settings.parity = UART_ODD_PARITY;
+ else if (! grub_strcmp (state[4].arg, "even"))
+ serial_settings.parity = UART_EVEN_PARITY;
+ else
+ {
+ serial_settings = backup_settings;
+ return grub_error (GRUB_ERR_BAD_ARGUMENT, "bad parity");
+ }
+ }
+
+ if (state[5].set)
+ {
+ if (! grub_strcmp (state[5].arg, "1"))
+ serial_settings.stop_bits = UART_1_STOP_BIT;
+ else if (! grub_strcmp (state[5].arg, "2"))
+ serial_settings.stop_bits = UART_2_STOP_BITS;
+ else
+ {
+ serial_settings = backup_settings;
+ return grub_error (GRUB_ERR_BAD_ARGUMENT, "bad number of stop bits");
+ }
+ }
+
+ /* Initialize with new settings. */
+ hwiniterr = serial_hw_init ();
+
+ if (hwiniterr == GRUB_ERR_NONE)
+ {
+ /* Register terminal if not yet registered. */
+ if (registered == 0)
+ {
+ grub_term_register_input ("serial", &grub_serial_term_input);
+ grub_term_register_output ("serial", &grub_serial_term_output);
+ registered = 1;
+ }
+ }
+ else
+ {
+ /* Initialization with new settings failed. */
+ if (registered == 1)
+ {
+ /* If the terminal is registered, attempt to restore previous
+ settings. */
+ serial_settings = backup_settings;
+ if (serial_hw_init () != GRUB_ERR_NONE)
+ {
+ /* If unable to restore settings, unregister terminal. */
+ grub_term_unregister_input (&grub_serial_term_input);
+ grub_term_unregister_output (&grub_serial_term_output);
+ registered = 0;
+ }
+ }
+ }
+
+ return hwiniterr;
+}
+
+static grub_extcmd_t cmd;
+
+GRUB_MOD_INIT(serial)
+{
+ cmd = grub_register_extcmd ("serial", grub_cmd_serial,
+ GRUB_COMMAND_FLAG_BOTH,
+ "serial [OPTIONS...]",
+ "Configure serial port.", options);
+
+ /* Set default settings. */
+ serial_settings.port = serial_hw_get_port (0);
+ serial_settings.divisor = serial_get_divisor (9600);
+ serial_settings.word_len = UART_8BITS_WORD;
+ serial_settings.parity = UART_NO_PARITY;
+ serial_settings.stop_bits = UART_1_STOP_BIT;
+}
+
+GRUB_MOD_FINI(serial)
+{
+ grub_unregister_extcmd (cmd);
+ if (registered == 1) /* Unregister terminal only if registered. */
+ {
+ grub_term_unregister_input (&grub_serial_term_input);
+ grub_term_unregister_output (&grub_serial_term_output);
+ }
+}