| /* |
| * 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) |
| { |
| 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); |
| } |