/* * GRUB -- GRand Unified Bootloader * Copyright (C) 2000,2001,2002,2003,2004,2005,2006,2007,2008,2009,2010,2012 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 . */ #include #include #include #include #include #include #include GRUB_MOD_LICENSE ("GPLv3+"); struct grub_escc_descriptor { volatile grub_uint8_t *escc_ctrl; volatile grub_uint8_t *escc_data; }; static void do_real_config (struct grub_serial_port *port) { grub_uint8_t bitsspec; grub_uint8_t parity_stop_spec; if (port->configured) return; /* Make sure the port is waiting for address now. */ (void) *port->escc_desc->escc_ctrl; switch (port->config.speed) { case 57600: *port->escc_desc->escc_ctrl = 13; *port->escc_desc->escc_ctrl = 0; *port->escc_desc->escc_ctrl = 12; *port->escc_desc->escc_ctrl = 0; *port->escc_desc->escc_ctrl = 14; *port->escc_desc->escc_ctrl = 1; *port->escc_desc->escc_ctrl = 11; *port->escc_desc->escc_ctrl = 0x50; break; case 38400: *port->escc_desc->escc_ctrl = 13; *port->escc_desc->escc_ctrl = 0; *port->escc_desc->escc_ctrl = 12; *port->escc_desc->escc_ctrl = 1; *port->escc_desc->escc_ctrl = 14; *port->escc_desc->escc_ctrl = 1; *port->escc_desc->escc_ctrl = 11; *port->escc_desc->escc_ctrl = 0x50; break; } parity_stop_spec = 0; switch (port->config.parity) { case GRUB_SERIAL_PARITY_NONE: parity_stop_spec |= 0; break; case GRUB_SERIAL_PARITY_ODD: parity_stop_spec |= 1; break; case GRUB_SERIAL_PARITY_EVEN: parity_stop_spec |= 3; break; } switch (port->config.stop_bits) { case GRUB_SERIAL_STOP_BITS_1: parity_stop_spec |= 0x4; break; case GRUB_SERIAL_STOP_BITS_1_5: parity_stop_spec |= 0x8; break; case GRUB_SERIAL_STOP_BITS_2: parity_stop_spec |= 0xc; break; } *port->escc_desc->escc_ctrl = 4; *port->escc_desc->escc_ctrl = 0x40 | parity_stop_spec; bitsspec = port->config.word_len - 5; bitsspec = ((bitsspec >> 1) | (bitsspec << 1)) & 3; *port->escc_desc->escc_ctrl = 3; *port->escc_desc->escc_ctrl = (bitsspec << 6) | 0x1; port->configured = 1; return; } /* Fetch a key. */ static int serial_hw_fetch (struct grub_serial_port *port) { do_real_config (port); *port->escc_desc->escc_ctrl = 0; if (*port->escc_desc->escc_ctrl & 1) return *port->escc_desc->escc_data; return -1; } /* Put a character. */ static void serial_hw_put (struct grub_serial_port *port, const int c) { grub_uint64_t endtime; do_real_config (port); if (port->broken > 5) endtime = grub_get_time_ms (); else if (port->broken > 1) endtime = grub_get_time_ms () + 50; else endtime = grub_get_time_ms () + 200; /* Wait until the transmitter holding register is empty. */ while (1) { *port->escc_desc->escc_ctrl = 0; if (*port->escc_desc->escc_ctrl & 4) break; if (grub_get_time_ms () > endtime) { port->broken++; /* There is something wrong. But what can I do? */ return; } } if (port->broken) port->broken--; *port->escc_desc->escc_data = 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_configure (struct grub_serial_port *port __attribute__ ((unused)), struct grub_serial_config *config __attribute__ ((unused))) { if (config->speed != 38400 && config->speed != 57600) return grub_error (GRUB_ERR_BAD_ARGUMENT, N_("unsupported serial port speed")); if (config->parity != GRUB_SERIAL_PARITY_NONE && config->parity != GRUB_SERIAL_PARITY_ODD && config->parity != GRUB_SERIAL_PARITY_EVEN) return grub_error (GRUB_ERR_BAD_ARGUMENT, N_("unsupported serial port parity")); if (config->stop_bits != GRUB_SERIAL_STOP_BITS_1 && config->stop_bits != GRUB_SERIAL_STOP_BITS_1_5 && config->stop_bits != GRUB_SERIAL_STOP_BITS_2) return grub_error (GRUB_ERR_BAD_ARGUMENT, N_("unsupported serial port stop bits number")); if (config->word_len < 5 || config->word_len > 8) return grub_error (GRUB_ERR_BAD_ARGUMENT, N_("unsupported serial port word length")); port->config = *config; port->configured = 0; /* FIXME: should check if the serial terminal was found. */ return GRUB_ERR_NONE; } struct grub_serial_driver grub_escc_driver = { .configure = serial_hw_configure, .fetch = serial_hw_fetch, .put = serial_hw_put }; static struct grub_escc_descriptor escc_descs[2]; static char *macio = 0; static void add_device (grub_addr_t addr, int channel) { struct grub_serial_port *port; grub_err_t err; struct grub_serial_config config = { .speed = 38400, .word_len = 8, .parity = GRUB_SERIAL_PARITY_NONE, .stop_bits = GRUB_SERIAL_STOP_BITS_1 }; escc_descs[channel].escc_ctrl = (volatile grub_uint8_t *) (grub_addr_t) addr; escc_descs[channel].escc_data = escc_descs[channel].escc_ctrl + 16; port = grub_zalloc (sizeof (*port)); if (!port) { grub_errno = 0; return; } port->name = grub_xasprintf ("escc-ch-%c", channel + 'a'); if (!port->name) { grub_errno = 0; return; } port->escc_desc = &escc_descs[channel]; port->driver = &grub_escc_driver; err = port->driver->configure (port, &config); if (err) grub_print_error (); grub_serial_register (port); } static int find_macio (struct grub_ieee1275_devalias *alias) { if (grub_strcmp (alias->type, "mac-io") != 0) return 0; macio = grub_strdup (alias->path); return 1; } GRUB_MOD_INIT (escc) { grub_uint32_t macio_addr[4]; grub_uint32_t escc_addr[2]; grub_ieee1275_phandle_t dev; struct grub_ieee1275_devalias alias; char *escc = 0; grub_ieee1275_devices_iterate (find_macio); if (!macio) return; FOR_IEEE1275_DEVCHILDREN(macio, alias) if (grub_strcmp (alias.type, "escc") == 0) { escc = grub_strdup (alias.path); break; } grub_ieee1275_devalias_free (&alias); if (!escc) { grub_free (macio); return; } if (grub_ieee1275_finddevice (macio, &dev)) { grub_free (macio); grub_free (escc); return; } if (grub_ieee1275_get_integer_property (dev, "assigned-addresses", macio_addr, sizeof (macio_addr), 0)) { grub_free (macio); grub_free (escc); return; } if (grub_ieee1275_finddevice (escc, &dev)) { grub_free (macio); grub_free (escc); return; } if (grub_ieee1275_get_integer_property (dev, "reg", escc_addr, sizeof (escc_addr), 0)) { grub_free (macio); grub_free (escc); return; } add_device (macio_addr[2] + escc_addr[0] + 32, 0); add_device (macio_addr[2] + escc_addr[0], 1); grub_free (macio); grub_free (escc); } GRUB_MOD_FINI (escc) { }