diff --git a/ChangeLog b/ChangeLog index 8b67a496a..0470480ae 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,8 @@ +2013-11-01 Vladimir Serbinenko + + Support --base-clock for serial command to handle weird cards with + non-standard base clock. + 2013-11-01 Vladimir Serbinenko * grub-core/fs/ext2.c (grub_ext2_read_symlink): Use memcpy rather diff --git a/grub-core/term/ns8250.c b/grub-core/term/ns8250.c index 2064e0dc9..b428235cc 100644 --- a/grub-core/term/ns8250.c +++ b/grub-core/term/ns8250.c @@ -38,46 +38,33 @@ static const grub_port_t serial_hw_io_addr[] = GRUB_MACHINE_SERIAL_PORTS; static int dead_ports = 0; +#ifdef GRUB_MACHINE_MIPS_LOONGSON +#define DEFAULT_BASE_CLOCK (2 * 115200) +#else +#define DEFAULT_BASE_CLOCK 115200 +#endif + + /* Convert speed to divisor. */ static unsigned short serial_get_divisor (const struct grub_serial_port *port __attribute__ ((unused)), const struct grub_serial_config *config) { - unsigned int i; + grub_uint32_t base_clock; + grub_uint32_t divisor; + grub_uint32_t actual_speed, error; - /* The structure for speed vs. divisor. */ - struct divisor - { - unsigned int speed; - unsigned short div; - }; + base_clock = config->base_clock ? (config->base_clock >> 4) : DEFAULT_BASE_CLOCK; - /* 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 < ARRAY_SIZE (divisor_tab); i++) - if (divisor_tab[i].speed == config->speed) - { - /* internal Loongson UART runs twice the usual rate. */ -#ifdef GRUB_MACHINE_MIPS_LOONGSON - if (port->port == 0xbff003f8) - return 2 * divisor_tab[i].div; - else -#endif - return divisor_tab[i].div; - } - return 0; + divisor = (base_clock + (config->speed / 2)) / config->speed; + if (divisor > 0xffff || divisor == 0) + return 0; + actual_speed = base_clock / divisor; + error = actual_speed > config->speed ? (actual_speed - config->speed) + : (config->speed - actual_speed); + if (error > (config->speed / 30 + 1)) + return 0; + return divisor; } static void diff --git a/grub-core/term/serial.c b/grub-core/term/serial.c index 69596404b..0298ec613 100644 --- a/grub-core/term/serial.c +++ b/grub-core/term/serial.c @@ -39,6 +39,17 @@ 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 + }; + /* Argument options. */ static const struct grub_arg_option options[] = { @@ -48,6 +59,7 @@ static const struct grub_arg_option options[] = {"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 clock."), 0, ARG_TYPE_INT}, {0, 0, 0, 0, 0, 0} }; @@ -178,14 +190,14 @@ grub_cmd_serial (grub_extcmd_context_t ctxt, int argc, char **args) struct grub_serial_config config; grub_err_t err; - if (state[0].set) + if (state[OPTION_UNIT].set) { grub_snprintf (pname, sizeof (pname), "com%ld", grub_strtoul (state[0].arg, 0, 0)); name = pname; } - if (state[1].set) + if (state[OPTION_PORT].set) { grub_snprintf (pname, sizeof (pname), "port%lx", grub_strtoul (state[1].arg, 0, 0)); @@ -206,38 +218,48 @@ grub_cmd_serial (grub_extcmd_context_t ctxt, int argc, char **args) config = port->config; - if (state[2].set) - config.speed = grub_strtoul (state[2].arg, 0, 0); + if (state[OPTION_SPEED].set) + config.speed = grub_strtoul (state[OPTION_SPEED].arg, 0, 0); - if (state[3].set) - config.word_len = grub_strtoul (state[3].arg, 0, 0); + if (state[OPTION_WORD].set) + config.word_len = grub_strtoul (state[OPTION_WORD].arg, 0, 0); - if (state[4].set) + if (state[OPTION_PARITY].set) { - if (! grub_strcmp (state[4].arg, "no")) + if (! grub_strcmp (state[OPTION_PARITY].arg, "no")) config.parity = GRUB_SERIAL_PARITY_NONE; - else if (! grub_strcmp (state[4].arg, "odd")) + else if (! grub_strcmp (state[OPTION_PARITY].arg, "odd")) config.parity = GRUB_SERIAL_PARITY_ODD; - else if (! grub_strcmp (state[4].arg, "even")) + 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[5].set) + if (state[OPTION_STOP].set) { - if (! grub_strcmp (state[5].arg, "1")) + if (! grub_strcmp (state[OPTION_STOP].arg, "1")) config.stop_bits = GRUB_SERIAL_STOP_BITS_1; - else if (! grub_strcmp (state[5].arg, "2")) + else if (! grub_strcmp (state[OPTION_STOP].arg, "2")) config.stop_bits = GRUB_SERIAL_STOP_BITS_2; - else if (! grub_strcmp (state[5].arg, "1.5")) + 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 (ptr && *ptr == 'M') + config.base_clock *= 1000000; + if (ptr && (*ptr == 'k' || *ptr == 'K')) + config.base_clock *= 1000; + } + /* Initialize with new settings. */ err = port->driver->configure (port, &config); if (err) diff --git a/include/grub/serial.h b/include/grub/serial.h index 20840d04b..7d981f927 100644 --- a/include/grub/serial.h +++ b/include/grub/serial.h @@ -67,6 +67,7 @@ struct grub_serial_config int word_len; grub_serial_parity_t parity; grub_serial_stop_bits_t stop_bits; + grub_uint64_t base_clock; }; struct grub_serial_port @@ -163,7 +164,8 @@ grub_serial_config_defaults (struct grub_serial_port *port) #endif .word_len = 8, .parity = GRUB_SERIAL_PARITY_NONE, - .stop_bits = GRUB_SERIAL_STOP_BITS_1 + .stop_bits = GRUB_SERIAL_STOP_BITS_1, + .base_clock = 0 }; return port->driver->configure (port, &config);