diff options
Diffstat (limited to 'grub-core/term/serial.c')
-rw-r--r-- | grub-core/term/serial.c | 463 |
1 files changed, 463 insertions, 0 deletions
diff --git a/grub-core/term/serial.c b/grub-core/term/serial.c new file mode 100644 index 0000000..f9271b0 --- /dev/null +++ b/grub-core/term/serial.c @@ -0,0 +1,463 @@ +/* + * GRUB -- GRand Unified Bootloader + * Copyright (C) 2000,2001,2002,2003,2004,2005,2007,2008,2009,2010 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/serial.h> +#include <grub/term.h> +#include <grub/types.h> +#include <grub/dl.h> +#include <grub/misc.h> +#include <grub/terminfo.h> +#if !defined (GRUB_MACHINE_EMU) && (defined(__mips__) || defined (__i386__) || defined (__x86_64__)) +#include <grub/cpu/io.h> +#endif +#include <grub/extcmd.h> +#include <grub/i18n.h> +#include <grub/list.h> +#ifdef GRUB_MACHINE_MIPS_LOONGSON +#include <grub/machine/kernel.h> +#endif +#ifdef GRUB_MACHINE_IEEE1275 +#include <grub/ieee1275/console.h> +#endif + +GRUB_MOD_LICENSE ("GPLv3+"); + +#define FOR_SERIAL_PORTS(var) FOR_LIST_ELEMENTS((var), (grub_serial_ports)) + +enum + { + OPTION_UNIT, + OPTION_PORT, + OPTION_SPEED, + OPTION_WORD, + OPTION_PARITY, + OPTION_STOP, + OPTION_BASE_CLOCK, + OPTION_RTSCTS + }; + +/* Argument options. */ +static const struct grub_arg_option options[] = +{ + {"unit", 'u', 0, N_("Set the serial unit."), 0, ARG_TYPE_INT}, + {"port", 'p', 0, N_("Set the serial port address."), 0, ARG_TYPE_STRING}, + {"speed", 's', 0, N_("Set the serial port speed."), 0, ARG_TYPE_INT}, + {"word", 'w', 0, N_("Set the serial port word length."), 0, ARG_TYPE_INT}, + {"parity", 'r', 0, N_("Set the serial port parity."), 0, ARG_TYPE_STRING}, + {"stop", 't', 0, N_("Set the serial port stop bits."), 0, ARG_TYPE_INT}, + {"base-clock", 'b', 0, N_("Set the base frequency."), 0, ARG_TYPE_STRING}, + {"rtscts", 'f', 0, N_("Enable/disable RTS/CTS."), "on|off", ARG_TYPE_STRING}, + {0, 0, 0, 0, 0, 0} +}; + +static struct grub_serial_port *grub_serial_ports; + +struct grub_serial_output_state +{ + struct grub_terminfo_output_state tinfo; + struct grub_serial_port *port; +}; + +struct grub_serial_input_state +{ + struct grub_terminfo_input_state tinfo; + struct grub_serial_port *port; +}; + +static void +serial_put (grub_term_output_t term, const int c) +{ + struct grub_serial_output_state *data = term->data; + data->port->driver->put (data->port, c); +} + +static int +serial_fetch (grub_term_input_t term) +{ + struct grub_serial_input_state *data = term->data; + return data->port->driver->fetch (data->port); +} + +static const struct grub_serial_input_state grub_serial_terminfo_input_template = + { + .tinfo = + { + .readkey = serial_fetch + } + }; + +static const struct grub_serial_output_state grub_serial_terminfo_output_template = + { + .tinfo = + { + .put = serial_put, + .size = { 80, 24 } + } + }; + +static struct grub_serial_input_state grub_serial_terminfo_input; + +static struct grub_serial_output_state grub_serial_terminfo_output; + +static int registered = 0; + +static struct grub_term_input grub_serial_term_input = +{ + .name = "serial", + .init = grub_terminfo_input_init, + .getkey = grub_terminfo_getkey, + .data = &grub_serial_terminfo_input +}; + +static struct grub_term_output grub_serial_term_output = +{ + .name = "serial", + .init = grub_terminfo_output_init, + .putchar = grub_terminfo_putchar, + .getwh = grub_terminfo_getwh, + .getxy = grub_terminfo_getxy, + .gotoxy = grub_terminfo_gotoxy, + .cls = grub_terminfo_cls, + .setcolorstate = grub_terminfo_setcolorstate, + .setcursor = grub_terminfo_setcursor, + .flags = GRUB_TERM_CODE_TYPE_ASCII, + .data = &grub_serial_terminfo_output, + .progress_update_divisor = GRUB_PROGRESS_SLOW +}; + + + +struct grub_serial_port * +grub_serial_find (const char *name) +{ + struct grub_serial_port *port; + + FOR_SERIAL_PORTS (port) + if (grub_strcmp (port->name, name) == 0) + break; + +#if (defined(__mips__) || defined (__i386__) || defined (__x86_64__)) && !defined(GRUB_MACHINE_EMU) && !defined(GRUB_MACHINE_ARC) + if (!port && grub_memcmp (name, "port", sizeof ("port") - 1) == 0 + && grub_isxdigit (name [sizeof ("port") - 1])) + { + name = grub_serial_ns8250_add_port (grub_strtoul (&name[sizeof ("port") - 1], + 0, 16)); + if (!name) + return NULL; + + FOR_SERIAL_PORTS (port) + if (grub_strcmp (port->name, name) == 0) + break; + } +#endif + +#ifdef GRUB_MACHINE_IEEE1275 + if (!port && grub_memcmp (name, "ieee1275/", sizeof ("ieee1275/") - 1) == 0) + { + name = grub_ofserial_add_port (&name[sizeof ("ieee1275/") - 1]); + if (!name) + return NULL; + + FOR_SERIAL_PORTS (port) + if (grub_strcmp (port->name, name) == 0) + break; + } +#endif + + return port; +} + +static grub_err_t +grub_cmd_serial (grub_extcmd_context_t ctxt, int argc, char **args) +{ + struct grub_arg_list *state = ctxt->state; + char pname[40]; + const char *name = NULL; + struct grub_serial_port *port; + struct grub_serial_config config; + grub_err_t err; + + if (state[OPTION_UNIT].set) + { + grub_snprintf (pname, sizeof (pname), "com%ld", + grub_strtoul (state[0].arg, 0, 0)); + name = pname; + } + + if (state[OPTION_PORT].set) + { + grub_snprintf (pname, sizeof (pname), "port%lx", + grub_strtoul (state[1].arg, 0, 0)); + name = pname; + } + + if (argc >= 1) + name = args[0]; + + if (!name) + name = "com0"; + + port = grub_serial_find (name); + if (!port) + return grub_error (GRUB_ERR_BAD_ARGUMENT, + N_("serial port `%s' isn't found"), + name); + + config = port->config; + + if (state[OPTION_SPEED].set) { + config.speed = grub_strtoul (state[OPTION_SPEED].arg, 0, 0); + if (config.speed == 0) + return grub_error (GRUB_ERR_BAD_ARGUMENT, + N_("unsupported serial port parity")); + } + + if (state[OPTION_WORD].set) + config.word_len = grub_strtoul (state[OPTION_WORD].arg, 0, 0); + + if (state[OPTION_PARITY].set) + { + if (! grub_strcmp (state[OPTION_PARITY].arg, "no")) + config.parity = GRUB_SERIAL_PARITY_NONE; + else if (! grub_strcmp (state[OPTION_PARITY].arg, "odd")) + config.parity = GRUB_SERIAL_PARITY_ODD; + else if (! grub_strcmp (state[OPTION_PARITY].arg, "even")) + config.parity = GRUB_SERIAL_PARITY_EVEN; + else + return grub_error (GRUB_ERR_BAD_ARGUMENT, + N_("unsupported serial port parity")); + } + + if (state[OPTION_RTSCTS].set) + { + if (grub_strcmp (state[OPTION_RTSCTS].arg, "on") == 0) + config.rtscts = 1; + else if (grub_strcmp (state[OPTION_RTSCTS].arg, "off") == 0) + config.rtscts = 0; + else + return grub_error (GRUB_ERR_BAD_ARGUMENT, + N_("unsupported serial port flow control")); + } + + if (state[OPTION_STOP].set) + { + if (! grub_strcmp (state[OPTION_STOP].arg, "1")) + config.stop_bits = GRUB_SERIAL_STOP_BITS_1; + else if (! grub_strcmp (state[OPTION_STOP].arg, "2")) + config.stop_bits = GRUB_SERIAL_STOP_BITS_2; + else if (! grub_strcmp (state[OPTION_STOP].arg, "1.5")) + config.stop_bits = GRUB_SERIAL_STOP_BITS_1_5; + else + return grub_error (GRUB_ERR_BAD_ARGUMENT, + N_("unsupported serial port stop bits number")); + } + + if (state[OPTION_BASE_CLOCK].set) + { + const char *ptr; + config.base_clock = grub_strtoull (state[OPTION_BASE_CLOCK].arg, &ptr, 0); + if (grub_errno) + return grub_errno; + if (ptr && *ptr == 'M') + config.base_clock *= 1000000; + if (ptr && (*ptr == 'k' || *ptr == 'K')) + config.base_clock *= 1000; + } + + if (config.speed == 0) + config.speed = 9600; + + /* Initialize with new settings. */ + err = port->driver->configure (port, &config); + if (err) + return err; +#if !defined (GRUB_MACHINE_EMU) && !defined(GRUB_MACHINE_ARC) && (defined(__mips__) || defined (__i386__) || defined (__x86_64__)) + + /* Compatibility kludge. */ + if (port->driver == &grub_ns8250_driver) + { + if (!registered) + { + grub_terminfo_output_register (&grub_serial_term_output, "vt100"); + + grub_term_register_input ("serial", &grub_serial_term_input); + grub_term_register_output ("serial", &grub_serial_term_output); + } + grub_serial_terminfo_output.port = port; + grub_serial_terminfo_input.port = port; + registered = 1; + } +#endif + return GRUB_ERR_NONE; +} + +#ifdef GRUB_MACHINE_MIPS_LOONGSON +const char loongson_defserial[][6] = + { + [GRUB_ARCH_MACHINE_YEELOONG] = "com0", + [GRUB_ARCH_MACHINE_FULOONG2F] = "com2", + [GRUB_ARCH_MACHINE_FULOONG2E] = "com1" + }; +#endif + +grub_err_t +grub_serial_register (struct grub_serial_port *port) +{ + struct grub_term_input *in; + struct grub_term_output *out; + struct grub_serial_input_state *indata; + struct grub_serial_output_state *outdata; + + in = grub_malloc (sizeof (*in)); + if (!in) + return grub_errno; + + indata = grub_malloc (sizeof (*indata)); + if (!indata) + { + grub_free (in); + return grub_errno; + } + + grub_memcpy (in, &grub_serial_term_input, sizeof (*in)); + in->data = indata; + in->name = grub_xasprintf ("serial_%s", port->name); + grub_memcpy (indata, &grub_serial_terminfo_input, sizeof (*indata)); + + if (!in->name) + { + grub_free (in); + grub_free (indata); + return grub_errno; + } + + out = grub_zalloc (sizeof (*out)); + if (!out) + { + grub_free (indata); + grub_free ((char *) in->name); + grub_free (in); + return grub_errno; + } + + outdata = grub_malloc (sizeof (*outdata)); + if (!outdata) + { + grub_free (indata); + grub_free ((char *) in->name); + grub_free (out); + grub_free (in); + return grub_errno; + } + + grub_memcpy (out, &grub_serial_term_output, sizeof (*out)); + out->data = outdata; + out->name = in->name; + grub_memcpy (outdata, &grub_serial_terminfo_output, sizeof (*outdata)); + + grub_list_push (GRUB_AS_LIST_P (&grub_serial_ports), GRUB_AS_LIST (port)); + ((struct grub_serial_input_state *) in->data)->port = port; + ((struct grub_serial_output_state *) out->data)->port = port; + port->term_in = in; + port->term_out = out; + grub_terminfo_output_register (out, "vt100"); +#ifdef GRUB_MACHINE_MIPS_LOONGSON + if (grub_strcmp (port->name, loongson_defserial[grub_arch_machine]) == 0) + { + grub_term_register_input_active ("serial_*", in); + grub_term_register_output_active ("serial_*", out); + } + else + { + grub_term_register_input_inactive ("serial_*", in); + grub_term_register_output_inactive ("serial_*", out); + } +#else + grub_term_register_input ("serial_*", in); + grub_term_register_output ("serial_*", out); +#endif + + return GRUB_ERR_NONE; +} + +void +grub_serial_unregister (struct grub_serial_port *port) +{ + if (port->driver->fini) + port->driver->fini (port); + + if (port->term_in) + grub_term_unregister_input (port->term_in); + if (port->term_out) + grub_term_unregister_output (port->term_out); + + grub_list_remove (GRUB_AS_LIST (port)); +} + +void +grub_serial_unregister_driver (struct grub_serial_driver *driver) +{ + struct grub_serial_port *port, *next; + for (port = grub_serial_ports; port; port = next) + { + next = port->next; + if (port->driver == driver) + grub_serial_unregister (port); + } +} + +static grub_extcmd_t cmd; + +GRUB_MOD_INIT(serial) +{ + cmd = grub_register_extcmd ("serial", grub_cmd_serial, 0, + N_("[OPTIONS...]"), + N_("Configure serial port."), options); + grub_memcpy (&grub_serial_terminfo_output, + &grub_serial_terminfo_output_template, + sizeof (grub_serial_terminfo_output)); + + grub_memcpy (&grub_serial_terminfo_input, + &grub_serial_terminfo_input_template, + sizeof (grub_serial_terminfo_input)); + +#if !defined (GRUB_MACHINE_EMU) && !defined(GRUB_MACHINE_ARC) && (defined(__mips__) || defined (__i386__) || defined (__x86_64__)) + grub_ns8250_init (); +#endif +#ifdef GRUB_MACHINE_IEEE1275 + grub_ofserial_init (); +#endif +#ifdef GRUB_MACHINE_EFI + grub_efiserial_init (); +#endif +#ifdef GRUB_MACHINE_ARC + grub_arcserial_init (); +#endif +} + +GRUB_MOD_FINI(serial) +{ + while (grub_serial_ports) + grub_serial_unregister (grub_serial_ports); + if (registered) + { + grub_term_unregister_input (&grub_serial_term_input); + grub_term_unregister_output (&grub_serial_term_output); + } + grub_unregister_extcmd (cmd); +} |