diff --git a/ChangeLog b/ChangeLog index fa2178875..e840254ec 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,82 @@ +2010-08-20 Vladimir Serbinenko + + USB hotunplugging and USB serial support. + + * bus/usb/ohci.c (grub_ohci_transfer): Fill *actual and respect timeout. + * bus/usb/uhci.c (grub_free_queue): Compute *actual. + (grub_uhci_transfer): Respect timeout and set *actual. + * bus/usb/usb.c (grub_usb_device_initialize): Correctly skip fields of + non-standard length. + (grub_usb_device_attach): Autoload modules. + (GRUB_MOD_INIT): Set grub_term_poll_usb. + (GRUB_MOD_FINI): Unset grub_term_poll_usb. + * bus/usb/usbhub.c (grub_usb_hub): Replace speed with devices. All + users updated. + (grub_usb_add_hub): Fill nports and children. + (attach_root_port): Receive hub instead of controller. + All users updated. Fill hub->devices. + (grub_usb_root_hub): Allocate hub->devices. + (detach_device): New function. + (poll_nonroot_hub): Fill children and detach devices. + * bus/usb/usbtrans.c (grub_usb_bulk_readwrite): Accept timeout and + actual arguments. All users updated. + (grub_usb_bulk_read_extended): New function. + * bus/usb/serial/common.c: New file. + * bus/usb/serial/ftdi.c: Likewise. + * bus/usb/serial/pl2303.c: Likewise. + * commands/terminal.c (handle_command): Support wildcard. + * commands/usbtest.c: Output "Unknown" instead of empty string. + * conf/any-emu.rmk (pkglib_MODULES): Add usbserial_common.mod. + (usbserial_common_mod_SOURCES): New variable. + (usbserial_common_mod_CFLAGS): Likewise. + (usbserial_common_mod_LDFLAGS): Likewise. + (pkglib_MODULES): Add usbserial_pl2303.mod. + (usbserial_pl2303_mod_SOURCES): New variable. + (usbserial_pl2303_mod_CFLAGS): Likewise. + (usbserial_pl2303_mod_LDFLAGS): Likewise. + (pkglib_MODULES): Add usbserial_ftdi.mod. + (usbserial_ftdi_mod_SOURCES): New variable. + (usbserial_ftdi_mod_CFLAGS): Likewise. + (usbserial_ftdi_mod_LDFLAGS): Likewise. + (pkglib_MODULES): Add serial.mod. + (serial_mod_SOURCES): New variable. + (serial_mod_CFLAGS): Likewise. + (serial_mod_LDFLAGS): Likewise. + * conf/i386-pc.rmk: Likewise. + * conf/mips-yeeloong.rmk: Likewise. + * conf/i386.rmk (serial_mod_SOURCES): Add term/ns8250.c. + * conf/mips-yeeloong.rmk (kernel_img_SOURCES): Likewise. + * disk/usbms.c (first_available_slot): New variable. + (grub_usbms_attach): Don't reuse free slots due to potential cache + problems. + * include/grub/serial.h: Moved to .. + * include/grub/ns8250.h: ...this. + * include/grub/serial.h: New file. + * include/grub/term.h (grub_term_poll_usb): New variable. + * include/grub/terminfo.h (grub_terminfo_input_state): Pass term to + readkey. All users updated. + (grub_terminfo_output_state): Pass term to put. + * include/grub/usb.h (GRUB_USB_REQTYPE): New enum. + (grub_usb_controller_dev): Add timeout and actual arguments to + transfer. All users updated. + (grub_usb_interface): New field detach_data. + (grub_usb_device): New fields children and nports. + (grub_usb_ep_type_t): New type. + (grub_usb_get_ep_type): New function. + (grub_usb_bulk_read_extended): Likewise. + * include/grub/usbdesc.h (grub_usb_desc): New type. + * include/grub/usbserial.h: New file. + * include/grub/usbtrans.h (grub_usb_transaction): New field preceding. + * kern/term.c (grub_term_poll_usb): New variable. + (grub_getkey): Call grub_term_poll_usb if set. + (grub_checkkey): Likewise. + (grub_getkeystatus): Likewise. + * term/serial.c: Moved controller-specific parts to ... + * term/ns8250.c: ... here. + * term/serial.c: Mostly rewritten. + * term/usb_keyboard.c: Reorganised to use GET_REPORT only on attaching + according to spec. + 2010-08-20 Robert Millan Make kFreeBSD code more generic to support ext2fs as root, ufs as diff --git a/boot/mips/yeeloong/fwstart.S b/boot/mips/yeeloong/fwstart.S index 7fe5d7ac2..425458401 100644 --- a/boot/mips/yeeloong/fwstart.S +++ b/boot/mips/yeeloong/fwstart.S @@ -20,7 +20,8 @@ #include #include #include -#include +#include +#include #include #include diff --git a/bus/usb/ohci.c b/bus/usb/ohci.c index 42af11466..7f757485c 100644 --- a/bus/usb/ohci.c +++ b/bus/usb/ohci.c @@ -654,7 +654,8 @@ grub_ohci_transaction (grub_ohci_td_t td, static grub_usb_err_t grub_ohci_transfer (grub_usb_controller_t dev, - grub_usb_transfer_t transfer) + grub_usb_transfer_t transfer, int timeout, + grub_size_t *actual) { struct grub_ohci *o = (struct grub_ohci *) dev->data; grub_ohci_ed_t ed_virt; @@ -680,6 +681,8 @@ grub_ohci_transfer (grub_usb_controller_t dev, int err_unrec = 0; grub_uint32_t intstatus; + *actual = 0; + /* Pre-set target for ED - we need it to find proper ED */ /* Set the device address. */ target = transfer->devaddr; @@ -832,7 +835,7 @@ grub_ohci_transfer (grub_usb_controller_t dev, } /* Safety measure to avoid a hang. */ - maxtime = grub_get_time_ms () + 1000; + maxtime = grub_get_time_ms () + timeout; /* Wait until the transfer is completed or STALLs. */ do @@ -986,6 +989,7 @@ grub_ohci_transfer (grub_usb_controller_t dev, transfer->last_trans = tderr_virt->tr_index; else transfer->last_trans = -1; + *actual = transfer->size + 1; } else if (err_halt) /* error, ED is halted by OHCI, i.e. can be modified */ @@ -1032,7 +1036,7 @@ grub_ohci_transfer (grub_usb_controller_t dev, { case 0: /* XXX: Should not happen! */ - grub_error (GRUB_ERR_IO, "OHCI without reporting the reason"); + grub_error (GRUB_ERR_IO, "OHCI failed without reporting the reason"); err = GRUB_USB_ERR_INTERNAL; break; @@ -1078,12 +1082,14 @@ grub_ohci_transfer (grub_usb_controller_t dev, case 9: /* XXX: Data underrun error. */ - err = GRUB_USB_ERR_DATA; grub_dprintf ("ohci", "Underrun, failed TD address: %p, index: %d\n", tderr_virt, tderr_virt->tr_index); - grub_dprintf ("ohci", "Underrun, number of not transferred bytes: %d\n", - 1 + grub_le_to_cpu32 (tderr_virt->buffer_end) - - grub_le_to_cpu32 (tderr_virt->buffer)); + if (transfer->last_trans == -1) + break; + *actual = transfer->transactions[transfer->last_trans].size + - (grub_le_to_cpu32 (tderr_virt->buffer_end) + - grub_le_to_cpu32 (tderr_virt->buffer)) + + transfer->transactions[transfer->last_trans].preceding; break; case 10: @@ -1172,12 +1178,10 @@ grub_ohci_transfer (grub_usb_controller_t dev, transfer->last_trans = tderr_virt->tr_index; else transfer->last_trans = -1; - } - /* Set empty ED - set HEAD = TAIL = last (not processed) TD */ - ed_virt->td_head = grub_cpu_to_le32 ( grub_le_to_cpu32 ( - ed_virt->td_tail) & ~0xf); + /* Set empty ED - set HEAD = TAIL = last (not processed) TD */ + ed_virt->td_head = grub_cpu_to_le32 (grub_le_to_cpu32 (ed_virt->td_tail) & ~0xf); /* At this point always should be: * ED has skip bit set and halted or empty or after next SOF, diff --git a/bus/usb/serial/common.c b/bus/usb/serial/common.c new file mode 100644 index 000000000..6b5b90059 --- /dev/null +++ b/bus/usb/serial/common.c @@ -0,0 +1,125 @@ +/* + * 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 . + */ + +#include +#include + +void +grub_usbserial_fini (struct grub_serial_port *port) +{ + port->usbdev->config[port->configno].interf[port->interfno].detach_hook = 0; + port->usbdev->config[port->configno].interf[port->interfno].attached = 0; +} + +void +grub_usbserial_detach (grub_usb_device_t usbdev, int configno, int interfno) +{ + static struct grub_serial_port *port; + port = usbdev->config[configno].interf[interfno].detach_data; + + grub_serial_unregister (port); +} + +static int usbnum = 0; + +int +grub_usbserial_attach (grub_usb_device_t usbdev, int configno, int interfno, + struct grub_serial_driver *driver) +{ + struct grub_serial_port *port; + int j; + struct grub_usb_desc_if *interf; + + interf = usbdev->config[configno].interf[interfno].descif; + + port = grub_malloc (sizeof (*port)); + if (!port) + { + grub_print_error (); + return 0; + } + + port->name = grub_xasprintf ("usb%d", usbnum++); + if (!port->name) + { + grub_free (port); + grub_print_error (); + return 0; + } + + port->usbdev = usbdev; + port->driver = driver; + for (j = 0; j < interf->endpointcnt; j++) + { + struct grub_usb_desc_endp *endp; + endp = &usbdev->config[0].interf[interfno].descendp[j]; + + if ((endp->endp_addr & 128) && (endp->attrib & 3) == 2) + { + /* Bulk IN endpoint. */ + port->in_endp = endp; + } + else if (!(endp->endp_addr & 128) && (endp->attrib & 3) == 2) + { + /* Bulk OUT endpoint. */ + port->out_endp = endp; + } + } + if (!port->out_endp || !port->in_endp) + { + grub_free (port->name); + grub_free (port); + return 0; + } + + port->configno = configno; + port->interfno = interfno; + + grub_serial_config_defaults (port); + grub_serial_register (port); + + port->usbdev->config[port->configno].interf[port->interfno].detach_hook + = grub_usbserial_detach; + port->usbdev->config[port->configno].interf[port->interfno].detach_data + = port; + + return 1; +} + +int +grub_usbserial_fetch (struct grub_serial_port *port, grub_size_t header_size) +{ + grub_usb_err_t err; + grub_size_t actual; + + if (port->bufstart < port->bufend) + return port->buf[port->bufstart++]; + + err = grub_usb_bulk_read_extended (port->usbdev, port->in_endp->endp_addr, + sizeof (port->buf), port->buf, 10, + &actual); + if (err != GRUB_USB_ERR_NONE) + return -1; + + port->bufstart = header_size; + port->bufend = actual; + if (port->bufstart >= port->bufend) + return -1; + + return port->buf[port->bufstart++]; +} diff --git a/bus/usb/serial/ftdi.c b/bus/usb/serial/ftdi.c new file mode 100644 index 000000000..bd1713b27 --- /dev/null +++ b/bus/usb/serial/ftdi.c @@ -0,0 +1,205 @@ +/* + * 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 . + */ + +#include +#include +#include +#include +#include +#include +#include + +enum + { + GRUB_FTDI_MODEM_CTRL = 0x01, + GRUB_FTDI_FLOW_CTRL = 0x02, + GRUB_FTDI_SPEED_CTRL = 0x03, + GRUB_FTDI_DATA_CTRL = 0x04 + }; + +#define GRUB_FTDI_MODEM_CTRL_DTRRTS 3 +#define GRUB_FTDI_FLOW_CTRL_DTRRTS 3 + +/* Convert speed to divisor. */ +static grub_uint32_t +get_divisor (unsigned int speed) +{ + unsigned int i; + + /* The structure for speed vs. divisor. */ + struct divisor + { + unsigned int speed; + grub_uint32_t div; + }; + + /* The table which lists common configurations. */ + /* Computed with a division formula with 3MHz as base frequency. */ + static struct divisor divisor_tab[] = + { + { 2400, 0x04e2 }, + { 4800, 0x0271 }, + { 9600, 0x4138 }, + { 19200, 0x809c }, + { 38400, 0xc04e }, + { 57600, 0xc034 }, + { 115200, 0x001a } + }; + + /* Set the baud rate. */ + for (i = 0; i < ARRAY_SIZE (divisor_tab); i++) + if (divisor_tab[i].speed == speed) + return divisor_tab[i].div; + return 0; +} + +static void +real_config (struct grub_serial_port *port) +{ + grub_uint32_t divisor; + const grub_uint16_t parities[] = { + [GRUB_SERIAL_PARITY_NONE] = 0x0000, + [GRUB_SERIAL_PARITY_ODD] = 0x0100, + [GRUB_SERIAL_PARITY_EVEN] = 0x0200 + }; + const grub_uint16_t stop_bits[] = { + [GRUB_SERIAL_STOP_BITS_1] = 0x0000, + [GRUB_SERIAL_STOP_BITS_2] = 0x1000, + }; + + if (port->configured) + return; + + grub_usb_control_msg (port->usbdev, GRUB_USB_REQTYPE_VENDOR_OUT, + GRUB_FTDI_MODEM_CTRL, + GRUB_FTDI_MODEM_CTRL_DTRRTS, 0, 0, 0); + + grub_usb_control_msg (port->usbdev, GRUB_USB_REQTYPE_VENDOR_OUT, + GRUB_FTDI_FLOW_CTRL, + GRUB_FTDI_FLOW_CTRL_DTRRTS, 0, 0, 0); + + divisor = get_divisor (port->config.speed); + grub_usb_control_msg (port->usbdev, GRUB_USB_REQTYPE_VENDOR_OUT, + GRUB_FTDI_SPEED_CTRL, + divisor & 0xffff, divisor >> 16, 0, 0); + + grub_usb_control_msg (port->usbdev, GRUB_USB_REQTYPE_VENDOR_OUT, + GRUB_FTDI_DATA_CTRL, + parities[port->config.parity] + | stop_bits[port->config.stop_bits] + | port->config.word_len, 0, 0, 0); + + port->configured = 1; +} + +/* Fetch a key. */ +static int +ftdi_hw_fetch (struct grub_serial_port *port) +{ + real_config (port); + + return grub_usbserial_fetch (port, 2); +} + +/* Put a character. */ +static void +ftdi_hw_put (struct grub_serial_port *port, const int c) +{ + char cc = c; + + real_config (port); + + grub_usb_bulk_write (port->usbdev, port->out_endp->endp_addr, 1, &cc); +} + +static grub_err_t +ftdi_hw_configure (struct grub_serial_port *port, + struct grub_serial_config *config) +{ + grub_uint16_t divisor; + + divisor = get_divisor (config->speed); + if (divisor == 0) + return grub_error (GRUB_ERR_BAD_ARGUMENT, "bad 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, "unsupported parity"); + + if (config->stop_bits != GRUB_SERIAL_STOP_BITS_1 + && config->stop_bits != GRUB_SERIAL_STOP_BITS_2) + return grub_error (GRUB_ERR_BAD_ARGUMENT, "unsupported stop bits"); + + if (config->word_len < 5 || config->word_len > 8) + return grub_error (GRUB_ERR_BAD_ARGUMENT, "unsupported word length"); + + port->config = *config; + port->configured = 0; + + return GRUB_ERR_NONE; +} + +static struct grub_serial_driver grub_ftdi_driver = + { + .configure = ftdi_hw_configure, + .fetch = ftdi_hw_fetch, + .put = ftdi_hw_put, + .fini = grub_usbserial_fini + }; + +static const struct +{ + grub_uint16_t vendor, product; +} products[] = + { + {0x0403, 0x6001} /* QEMU virtual USBserial. */ + }; + +static int +grub_ftdi_attach (grub_usb_device_t usbdev, int configno, int interfno) +{ + unsigned j; + + for (j = 0; j < ARRAY_SIZE (products); j++) + if (usbdev->descdev.vendorid == products[j].vendor + && usbdev->descdev.prodid == products[j].product) + break; + if (j == ARRAY_SIZE (products)) + return 0; + + return grub_usbserial_attach (usbdev, configno, interfno, + &grub_ftdi_driver); +} + +static struct grub_usb_attach_desc attach_hook = +{ + .class = 0xff, + .hook = grub_ftdi_attach +}; + +GRUB_MOD_INIT(usbserial_ftdi) +{ + grub_usb_register_attach_hook_class (&attach_hook); +} + +GRUB_MOD_FINI(usbserial_ftdi) +{ + grub_serial_unregister_driver (&grub_ftdi_driver); + grub_usb_unregister_attach_hook_class (&attach_hook); +} diff --git a/bus/usb/serial/pl2303.c b/bus/usb/serial/pl2303.c new file mode 100644 index 000000000..9e3b9ae7e --- /dev/null +++ b/bus/usb/serial/pl2303.c @@ -0,0 +1,218 @@ +/* + * 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 . + */ + +#include +#include +#include +#include +#include +#include +#include + +/* Convert speed to divisor. */ +static grub_uint32_t +is_speed_supported (unsigned int speed) +{ + unsigned int i; + unsigned int supported[] = { 2400, 4800, 9600, 19200, 38400, 57600, 115200}; + + for (i = 0; i < ARRAY_SIZE (supported); i++) + if (supported[i] == speed) + return 1; + return 0; +} + +#define GRUB_PL2303_REQUEST_SET_CONFIG 0x20 +#define GRUB_PL2303_STOP_BITS_1 0x0 +#define GRUB_PL2303_STOP_BITS_2 0x2 + +#define GRUB_PL2303_PARITY_NONE 0 +#define GRUB_PL2303_PARITY_ODD 1 +#define GRUB_PL2303_PARITY_EVEN 2 + +struct grub_pl2303_config +{ + grub_uint32_t speed; + grub_uint8_t stop_bits; + grub_uint8_t parity; + grub_uint8_t word_len; +} __attribute__ ((packed)); + +static void +real_config (struct grub_serial_port *port) +{ + struct grub_pl2303_config config_pl2303; + char xx; + + if (port->configured) + return; + + grub_usb_control_msg (port->usbdev, GRUB_USB_REQTYPE_VENDOR_IN, + 1, 0x8484, 0, 1, &xx); + grub_usb_control_msg (port->usbdev, GRUB_USB_REQTYPE_VENDOR_OUT, + 1, 0x0404, 0, 0, 0); + + grub_usb_control_msg (port->usbdev, GRUB_USB_REQTYPE_VENDOR_IN, + 1, 0x8484, 0, 1, &xx); + grub_usb_control_msg (port->usbdev, GRUB_USB_REQTYPE_VENDOR_IN, + 1, 0x8383, 0, 1, &xx); + grub_usb_control_msg (port->usbdev, GRUB_USB_REQTYPE_VENDOR_IN, + 1, 0x8484, 0, 1, &xx); + grub_usb_control_msg (port->usbdev, GRUB_USB_REQTYPE_VENDOR_OUT, + 1, 0x0404, 1, 0, 0); + + grub_usb_control_msg (port->usbdev, GRUB_USB_REQTYPE_VENDOR_IN, + 1, 0x8484, 0, 1, &xx); + grub_usb_control_msg (port->usbdev, GRUB_USB_REQTYPE_VENDOR_IN, + 1, 0x8383, 0, 1, &xx); + + grub_usb_control_msg (port->usbdev, GRUB_USB_REQTYPE_VENDOR_OUT, + 1, 0, 1, 0, 0); + grub_usb_control_msg (port->usbdev, GRUB_USB_REQTYPE_VENDOR_OUT, + 1, 1, 0, 0, 0); + grub_usb_control_msg (port->usbdev, GRUB_USB_REQTYPE_VENDOR_OUT, + 1, 2, 0x44, 0, 0); + grub_usb_control_msg (port->usbdev, GRUB_USB_REQTYPE_VENDOR_OUT, + 1, 8, 0, 0, 0); + grub_usb_control_msg (port->usbdev, GRUB_USB_REQTYPE_VENDOR_OUT, + 1, 9, 0, 0, 0); + + if (port->config.stop_bits == GRUB_SERIAL_STOP_BITS_2) + config_pl2303.stop_bits = GRUB_PL2303_STOP_BITS_2; + else + config_pl2303.stop_bits = GRUB_PL2303_STOP_BITS_1; + + switch (port->config.parity) + { + case GRUB_SERIAL_PARITY_NONE: + config_pl2303.parity = GRUB_PL2303_PARITY_NONE; + break; + case GRUB_SERIAL_PARITY_ODD: + config_pl2303.parity = GRUB_PL2303_PARITY_ODD; + break; + case GRUB_SERIAL_PARITY_EVEN: + config_pl2303.parity = GRUB_PL2303_PARITY_EVEN; + break; + } + + config_pl2303.word_len = port->config.word_len; + config_pl2303.speed = port->config.speed; + grub_usb_control_msg (port->usbdev, GRUB_USB_REQTYPE_CLASS_INTERFACE_OUT, + GRUB_PL2303_REQUEST_SET_CONFIG, 0, 0, + sizeof (config_pl2303), (char *) &config_pl2303); + grub_usb_control_msg (port->usbdev, GRUB_USB_REQTYPE_CLASS_INTERFACE_OUT, + 0x22, 3, 0, 0, 0); + + grub_usb_control_msg (port->usbdev, GRUB_USB_REQTYPE_VENDOR_OUT, + 1, 0, 0x61, 0, 0); + port->configured = 1; +} + +/* Fetch a key. */ +static int +pl2303_hw_fetch (struct grub_serial_port *port) +{ + real_config (port); + + return grub_usbserial_fetch (port, 0); +} + +/* Put a character. */ +static void +pl2303_hw_put (struct grub_serial_port *port, const int c) +{ + char cc = c; + + real_config (port); + + grub_usb_bulk_write (port->usbdev, port->out_endp->endp_addr, 1, &cc); +} + +static grub_err_t +pl2303_hw_configure (struct grub_serial_port *port, + struct grub_serial_config *config) +{ + if (!is_speed_supported (config->speed)) + return grub_error (GRUB_ERR_BAD_ARGUMENT, "bad 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, "unsupported parity"); + + if (config->stop_bits != GRUB_SERIAL_STOP_BITS_1 + && config->stop_bits != GRUB_SERIAL_STOP_BITS_2) + return grub_error (GRUB_ERR_BAD_ARGUMENT, "unsupported stop bits"); + + if (config->word_len < 5 || config->word_len > 8) + return grub_error (GRUB_ERR_BAD_ARGUMENT, "unsupported word length"); + + port->config = *config; + port->configured = 0; + + return GRUB_ERR_NONE; +} + +static struct grub_serial_driver grub_pl2303_driver = + { + .configure = pl2303_hw_configure, + .fetch = pl2303_hw_fetch, + .put = pl2303_hw_put, + .fini = grub_usbserial_fini + }; + +static const struct +{ + grub_uint16_t vendor, product; +} products[] = + { + {0x067b, 0x2303} + }; + +static int +grub_pl2303_attach (grub_usb_device_t usbdev, int configno, int interfno) +{ + unsigned j; + + for (j = 0; j < ARRAY_SIZE (products); j++) + if (usbdev->descdev.vendorid == products[j].vendor + && usbdev->descdev.prodid == products[j].product) + break; + if (j == ARRAY_SIZE (products)) + return 0; + + return grub_usbserial_attach (usbdev, configno, interfno, + &grub_pl2303_driver); +} + +static struct grub_usb_attach_desc attach_hook = +{ + .class = 0xff, + .hook = grub_pl2303_attach +}; + +GRUB_MOD_INIT(usbserial_pl2303) +{ + grub_usb_register_attach_hook_class (&attach_hook); +} + +GRUB_MOD_FINI(usbserial_pl2303) +{ + grub_serial_unregister_driver (&grub_pl2303_driver); + grub_usb_unregister_attach_hook_class (&attach_hook); +} diff --git a/bus/usb/uhci.c b/bus/usb/uhci.c index efdf3aceb..0bba24b54 100644 --- a/bus/usb/uhci.c +++ b/bus/usb/uhci.c @@ -333,9 +333,11 @@ grub_free_td (struct grub_uhci *u, grub_uhci_td_t td) static void grub_free_queue (struct grub_uhci *u, grub_uhci_td_t td, - grub_usb_transfer_t transfer) + grub_usb_transfer_t transfer, grub_size_t *actual) { int i; /* Index of TD in transfer */ + + *actual = 0; /* Free the TDs in this queue and set last_trans. */ for (i=0; td; i++) @@ -345,6 +347,8 @@ grub_free_queue (struct grub_uhci *u, grub_uhci_td_t td, /* Check state of TD and possibly set last_trans */ if (transfer && (td->linkptr & 1)) transfer->last_trans = i; + + *actual += (td->ctrl_status + 1) & 0x7ff; /* Unlink the queue. */ tdprev = td; @@ -436,7 +440,8 @@ grub_uhci_transaction (struct grub_uhci *u, unsigned int endp, static grub_usb_err_t grub_uhci_transfer (grub_usb_controller_t dev, - grub_usb_transfer_t transfer) + grub_usb_transfer_t transfer, + int timeout, grub_size_t *actual) { struct grub_uhci *u = (struct grub_uhci *) dev->data; grub_uhci_qh_t qh; @@ -447,10 +452,12 @@ grub_uhci_transfer (grub_usb_controller_t dev, int i; grub_uint64_t endtime; + *actual = 0; + /* Allocate a queue head for the transfer queue. */ qh = grub_alloc_qh (u, GRUB_USB_TRANSACTION_TYPE_CONTROL); if (! qh) - return grub_errno; + return GRUB_USB_ERR_INTERNAL; grub_dprintf ("uhci", "transfer, iobase:%08x\n", u->iobase); @@ -468,7 +475,7 @@ grub_uhci_transfer (grub_usb_controller_t dev, td_prev->linkptr = 1; if (td_first) - grub_free_queue (u, td_first, NULL); + grub_free_queue (u, td_first, NULL, actual); return GRUB_USB_ERR_INTERNAL; } @@ -496,7 +503,7 @@ grub_uhci_transfer (grub_usb_controller_t dev, /* Wait until either the transaction completed or an error occurred. */ - endtime = grub_get_time_ms () + 1000; + endtime = grub_get_time_ms () + timeout; for (;;) { grub_uhci_td_t errtd; @@ -512,42 +519,37 @@ grub_uhci_transfer (grub_usb_controller_t dev, grub_dprintf ("uhci", "t status=0x%02x\n", errtd->ctrl_status); - /* Check if the TD is not longer active. */ - if (! (errtd->ctrl_status & (1 << 23))) - { - grub_dprintf ("uhci", ">>t status=0x%02x\n", errtd->ctrl_status); + /* Check if the endpoint is stalled. */ + if (errtd->ctrl_status & (1 << 22)) + err = GRUB_USB_ERR_STALL; - /* Check if the endpoint is stalled. */ - if (errtd->ctrl_status & (1 << 22)) - err = GRUB_USB_ERR_STALL; + /* Check if an error related to the data buffer occurred. */ + if (errtd->ctrl_status & (1 << 21)) + err = GRUB_USB_ERR_DATA; - /* Check if an error related to the data buffer occurred. */ - if (errtd->ctrl_status & (1 << 21)) - err = GRUB_USB_ERR_DATA; + /* Check if a babble error occurred. */ + if (errtd->ctrl_status & (1 << 20)) + err = GRUB_USB_ERR_BABBLE; - /* Check if a babble error occurred. */ - if (errtd->ctrl_status & (1 << 20)) - err = GRUB_USB_ERR_BABBLE; + /* Check if a NAK occurred. */ + if (errtd->ctrl_status & (1 << 19)) + err = GRUB_USB_ERR_NAK; - /* Check if a NAK occurred. */ - if (errtd->ctrl_status & (1 << 19)) - err = GRUB_USB_ERR_NAK; + /* Check if a timeout occurred. */ + if (errtd->ctrl_status & (1 << 18)) + err = GRUB_USB_ERR_TIMEOUT; - /* Check if a timeout occurred. */ - if (errtd->ctrl_status & (1 << 18)) - err = GRUB_USB_ERR_TIMEOUT; + /* Check if a bitstuff error occurred. */ + if (errtd->ctrl_status & (1 << 17)) + err = GRUB_USB_ERR_BITSTUFF; - /* Check if a bitstuff error occurred. */ - if (errtd->ctrl_status & (1 << 17)) - err = GRUB_USB_ERR_BITSTUFF; + if (err) + goto fail; - if (err) - goto fail; + /* Fall through, no errors occurred, so the QH might be + updated. */ + grub_dprintf ("uhci", "transaction fallthrough\n"); - /* Fall through, no errors occurred, so the QH might be - updated. */ - grub_dprintf ("uhci", "transaction fallthrough\n"); - } if (grub_get_time_ms () > endtime) { err = GRUB_USB_ERR_STALL; @@ -567,7 +569,7 @@ grub_uhci_transfer (grub_usb_controller_t dev, /* Place the QH back in the free list and deallocate the associated TDs. */ qh->elinkptr = 1; - grub_free_queue (u, td_first, transfer); + grub_free_queue (u, td_first, transfer, actual); return err; } diff --git a/bus/usb/usb.c b/bus/usb/usb.c index f6a0a8b56..2bd805ef2 100644 --- a/bus/usb/usb.c +++ b/bus/usb/usb.c @@ -22,6 +22,7 @@ #include #include #include +#include static grub_usb_controller_dev_t grub_usb_list; struct grub_usb_attach_desc *attach_hooks; @@ -208,14 +209,23 @@ grub_usb_device_initialize (grub_usb_device_t dev) goto fail; /* Skip the configuration descriptor. */ - pos = sizeof (struct grub_usb_desc_config); + pos = dev->config[i].descconf->length; /* Read all interfaces. */ for (currif = 0; currif < dev->config[i].descconf->numif; currif++) { + while (pos < config.totallen + && ((struct grub_usb_desc *)&data[pos])->type + != GRUB_USB_DESCRIPTOR_INTERFACE) + pos += ((struct grub_usb_desc *)&data[pos])->length; dev->config[i].interf[currif].descif = (struct grub_usb_desc_if *) &data[pos]; - pos += sizeof (struct grub_usb_desc_if); + pos += dev->config[i].interf[currif].descif->length; + + while (pos < config.totallen + && ((struct grub_usb_desc *)&data[pos])->type + != GRUB_USB_DESCRIPTOR_ENDPOINT) + pos += ((struct grub_usb_desc *)&data[pos])->length; /* Point to the first endpoint. */ dev->config[i].interf[currif].descendp @@ -256,6 +266,24 @@ void grub_usb_device_attach (grub_usb_device_t dev) for (desc = attach_hooks; desc; desc = desc->next) if (interf->class == desc->class && desc->hook (dev, 0, i)) dev->config[0].interf[i].attached = 1; + + if (dev->config[0].interf[i].attached) + continue; + + switch (interf->class) + { + case GRUB_USB_CLASS_MASS_STORAGE: + grub_dl_load ("usbms"); + break; + case GRUB_USB_CLASS_HID: + grub_dl_load ("usb_keyboard"); + break; + case 0xff: + /* FIXME: don't load useless modules. */ + grub_dl_load ("usbserial_ftdi"); + grub_dl_load ("usbserial_pl2303"); + break; + } } } @@ -306,3 +334,14 @@ grub_usb_unregister_attach_hook_class (struct grub_usb_attach_desc *desc) { grub_list_remove (GRUB_AS_LIST_P (&attach_hooks), GRUB_AS_LIST (desc)); } + + +GRUB_MOD_INIT(usb) +{ + grub_term_poll_usb = grub_usb_poll_devices; +} + +GRUB_MOD_FINI(usb) +{ + grub_term_poll_usb = NULL; +} diff --git a/bus/usb/usbhub.c b/bus/usb/usbhub.c index 6c14f4b8d..7f2c8d24b 100644 --- a/bus/usb/usbhub.c +++ b/bus/usb/usbhub.c @@ -33,7 +33,7 @@ struct grub_usb_hub struct grub_usb_hub *next; grub_usb_controller_t controller; int nports; - grub_usb_speed_t *speed; + struct grub_usb_device **devices; grub_usb_device_t dev; }; @@ -104,7 +104,7 @@ grub_usb_hub_add_dev (grub_usb_controller_t controller, grub_usb_speed_t speed) } -static grub_err_t +static grub_usb_err_t grub_usb_add_hub (grub_usb_device_t dev) { struct grub_usb_usb_hubdesc hubdesc; @@ -112,6 +112,7 @@ grub_usb_add_hub (grub_usb_device_t dev) int i; grub_uint64_t timeout; grub_usb_device_t next_dev; + grub_usb_device_t *attached_devices; err = grub_usb_control_msg (dev, (GRUB_USB_REQTYPE_IN | GRUB_USB_REQTYPE_CLASS @@ -130,6 +131,13 @@ grub_usb_add_hub (grub_usb_device_t dev) grub_dprintf ("usb", "Hub set configuration\n"); grub_usb_set_configuration (dev, 1); + attached_devices = grub_zalloc (hubdesc.portcnt + * sizeof (attached_devices[0])); + if (!attached_devices) + return GRUB_USB_ERR_INTERNAL; + dev->children = attached_devices; + dev->nports = hubdesc.portcnt; + /* Power on all Hub ports. */ for (i = 1; i <= hubdesc.portcnt; i++) { @@ -236,6 +244,8 @@ grub_usb_add_hub (grub_usb_device_t dev) if (! next_dev) continue; + attached_devices[i - 1] = next_dev; + /* If the device is a Hub, scan it for more devices. */ if (next_dev->descdev.class == 0x09) grub_usb_add_hub (next_dev); @@ -246,27 +256,29 @@ grub_usb_add_hub (grub_usb_device_t dev) } static void -attach_root_port (grub_usb_controller_t controller, int portno, +attach_root_port (struct grub_usb_hub *hub, int portno, grub_usb_speed_t speed) { grub_usb_device_t dev; grub_err_t err; /* Disable the port. XXX: Why? */ - err = controller->dev->portstatus (controller, portno, 0); + err = hub->controller->dev->portstatus (hub->controller, portno, 0); if (err) return; /* Enable the port. */ - err = controller->dev->portstatus (controller, portno, 1); + err = hub->controller->dev->portstatus (hub->controller, portno, 1); if (err) return; /* Enable the port and create a device. */ - dev = grub_usb_hub_add_dev (controller, speed); + dev = grub_usb_hub_add_dev (hub->controller, speed); if (! dev) return; + hub->devices[portno] = dev; + /* If the device is a Hub, scan it for more devices. */ if (dev->descdev.class == 0x09) grub_usb_add_hub (dev); @@ -297,8 +309,8 @@ grub_usb_root_hub (grub_usb_controller_t controller) /* Query the number of ports the root Hub has. */ hub->nports = controller->dev->hubports (controller); - hub->speed = grub_malloc (sizeof (hub->speed[0]) * hub->nports); - if (!hub->speed) + hub->devices = grub_zalloc (sizeof (hub->devices[0]) * hub->nports); + if (!hub->devices) { grub_free (hub->controller); grub_free (hub); @@ -307,36 +319,54 @@ grub_usb_root_hub (grub_usb_controller_t controller) for (i = 0; i < hub->nports; i++) { - hub->speed[i] = controller->dev->detect_dev (hub->controller, i, - &changed); + grub_usb_speed_t speed; + speed = controller->dev->detect_dev (hub->controller, i, + &changed); - if (hub->speed[i] != GRUB_USB_SPEED_NONE) - attach_root_port (hub->controller, i, hub->speed[i]); + if (speed != GRUB_USB_SPEED_NONE) + attach_root_port (hub, i, speed); } return GRUB_USB_ERR_NONE; } +static void detach_device (grub_usb_device_t dev); + +static void +detach_device (grub_usb_device_t dev) +{ + unsigned i; + int k; + if (!dev) + return; + if (dev->descdev.class == GRUB_USB_CLASS_HUB) + { + for (i = 0; i < dev->nports; i++) + detach_device (dev->children[i]); + grub_free (dev->children); + } + for (i = 0; i < ARRAY_SIZE (dev->config); i++) + if (dev->config[i].descconf) + for (k = 0; k < dev->config[i].descconf->numif; k++) + { + struct grub_usb_interface *inter = &dev->config[i].interf[k]; + if (inter && inter->detach_hook) + inter->detach_hook (dev, i, k); + } + grub_usb_devs[dev->addr] = 0; +} + static void poll_nonroot_hub (grub_usb_device_t dev) { - struct grub_usb_usb_hubdesc hubdesc; grub_err_t err; - int i; + unsigned i; grub_uint64_t timeout; grub_usb_device_t next_dev; - - err = grub_usb_control_msg (dev, (GRUB_USB_REQTYPE_IN - | GRUB_USB_REQTYPE_CLASS - | GRUB_USB_REQTYPE_TARGET_DEV), - GRUB_USB_REQ_GET_DESCRIPTOR, - (GRUB_USB_DESCRIPTOR_HUB << 8) | 0, - 0, sizeof (hubdesc), (char *) &hubdesc); - if (err) - return; - + grub_usb_device_t *attached_devices = dev->children; + /* Iterate over the Hub ports. */ - for (i = 1; i <= hubdesc.portcnt; i++) + for (i = 1; i <= dev->nports; i++) { grub_uint32_t status; @@ -350,6 +380,12 @@ poll_nonroot_hub (grub_usb_device_t dev) status. */ if (err) continue; + + if (status & GRUB_USB_HUB_STATUS_C_CONNECTED) + { + detach_device (attached_devices[i-1]); + attached_devices[i - 1] = NULL; + } /* Connected and status of connection changed ? */ if ((status & GRUB_USB_HUB_STATUS_CONNECTED) @@ -417,6 +453,8 @@ poll_nonroot_hub (grub_usb_device_t dev) if (! next_dev) continue; + attached_devices[i - 1] = next_dev; + /* If the device is a Hub, scan it for more devices. */ if (next_dev->descdev.class == 0x09) grub_usb_add_hub (next_dev); @@ -434,26 +472,23 @@ grub_usb_poll_devices (void) for (hub = hubs; hub; hub = hub->next) { - int changed=0; /* Do we have to recheck number of ports? */ /* No, it should be never changed, it should be constant. */ for (i = 0; i < hub->nports; i++) { grub_usb_speed_t speed; + int changed = 0; speed = hub->controller->dev->detect_dev (hub->controller, i, &changed); - if (speed != GRUB_USB_SPEED_NONE) + if (changed) { - if (changed) - attach_root_port (hub->controller, i, speed); + detach_device (hub->devices[i]); + hub->devices[i] = NULL; + if (speed != GRUB_USB_SPEED_NONE) + attach_root_port (hub, i, speed); } - - /* XXX: There should be also handling - * of disconnected devices. */ - - hub->speed[i] = speed; } } @@ -463,9 +498,7 @@ grub_usb_poll_devices (void) grub_usb_device_t dev = grub_usb_devs[i]; if (dev && dev->descdev.class == 0x09) - { - poll_nonroot_hub (dev); - } + poll_nonroot_hub (dev); } } diff --git a/bus/usb/usbtrans.c b/bus/usb/usbtrans.c index 4a55cab11..db2ec097a 100644 --- a/bus/usb/usbtrans.c +++ b/bus/usb/usbtrans.c @@ -43,6 +43,7 @@ grub_usb_control_msg (grub_usb_device_t dev, volatile char *data; grub_uint32_t data_addr; grub_size_t size = size0; + grub_size_t actual; /* FIXME: avoid allocation any kind of buffer in a first place. */ data_chunk = grub_memalign_dma32 (128, size ? : 16); @@ -132,6 +133,7 @@ grub_usb_control_msg (grub_usb_device_t dev, else tr->pid = GRUB_USB_TRANSFER_TYPE_OUT; tr->data = data_addr + i * max; + tr->preceding = i * max; size -= max; } @@ -145,7 +147,8 @@ grub_usb_control_msg (grub_usb_device_t dev, transfer->transactions[datablocks + 1].toggle = 1; - err = dev->controller.dev->transfer (&dev->controller, transfer); + err = dev->controller.dev->transfer (&dev->controller, transfer, + 1000, &actual); grub_dprintf ("usb", "control: err=%d\n", err); grub_free (transfer->transactions); @@ -162,7 +165,8 @@ grub_usb_control_msg (grub_usb_device_t dev, static grub_usb_err_t grub_usb_bulk_readwrite (grub_usb_device_t dev, int endpoint, grub_size_t size0, char *data_in, - grub_transfer_type_t type) + grub_transfer_type_t type, int timeout, + grub_size_t *actual) { int i; grub_usb_transfer_t transfer; @@ -240,10 +244,12 @@ grub_usb_bulk_readwrite (grub_usb_device_t dev, toggle = toggle ? 0 : 1; tr->pid = type; tr->data = data_addr + i * max; + tr->preceding = i * max; size -= tr->size; } - err = dev->controller.dev->transfer (&dev->controller, transfer); + err = dev->controller.dev->transfer (&dev->controller, transfer, timeout, + actual); /* We must remember proper toggle value even if some transactions * were not processed - correct value should be inversion of last * processed transaction (TD). */ @@ -268,14 +274,34 @@ grub_usb_err_t grub_usb_bulk_write (grub_usb_device_t dev, int endpoint, grub_size_t size, char *data) { - return grub_usb_bulk_readwrite (dev, endpoint, size, data, - GRUB_USB_TRANSFER_TYPE_OUT); + grub_size_t actual; + grub_usb_err_t err; + + err = grub_usb_bulk_readwrite (dev, endpoint, size, data, + GRUB_USB_TRANSFER_TYPE_OUT, 1000, &actual); + if (!err && actual != size) + err = GRUB_USB_ERR_DATA; + return err; } grub_usb_err_t grub_usb_bulk_read (grub_usb_device_t dev, int endpoint, grub_size_t size, char *data) { - return grub_usb_bulk_readwrite (dev, endpoint, size, data, - GRUB_USB_TRANSFER_TYPE_IN); + grub_size_t actual; + grub_usb_err_t err; + err = grub_usb_bulk_readwrite (dev, endpoint, size, data, + GRUB_USB_TRANSFER_TYPE_IN, 1000, &actual); + if (!err && actual != size) + err = GRUB_USB_ERR_DATA; + return err; +} + +grub_usb_err_t +grub_usb_bulk_read_extended (grub_usb_device_t dev, + int endpoint, grub_size_t size, char *data, + int timeout, grub_size_t *actual) +{ + return grub_usb_bulk_readwrite (dev, endpoint, size, data, + GRUB_USB_TRANSFER_TYPE_IN, timeout, actual); } diff --git a/commands/terminal.c b/commands/terminal.c index d34602a1b..c8b1b6315 100644 --- a/commands/terminal.c +++ b/commands/terminal.c @@ -59,11 +59,17 @@ handle_command (int argc, char **args, struct abstract_terminal **enabled, for (aut = autoloads; aut; aut = aut->next) { for (term = *disabled; term; term = term->next) - if (grub_strcmp (term->name, aut->name) == 0) + if (grub_strcmp (term->name, aut->name) == 0 + || (aut->name[0] && aut->name[grub_strlen (aut->name) - 1] == '*' + && grub_memcmp (term->name, aut->name, + grub_strlen (aut->name) - 1) == 0)) break; if (!term) for (term = *enabled; term; term = term->next) - if (grub_strcmp (term->name, aut->name) == 0) + if (grub_strcmp (term->name, aut->name) == 0 + || (aut->name[0] && aut->name[grub_strlen (aut->name) - 1] == '*' + && grub_memcmp (term->name, aut->name, + grub_strlen (aut->name) - 1) == 0)) break; if (!term) grub_printf ("%s ", aut->name); @@ -98,7 +104,10 @@ handle_command (int argc, char **args, struct abstract_terminal **enabled, return grub_error (GRUB_ERR_BAD_ARGUMENT, "unknown terminal '%s'\n", args[i]); for (aut = autoloads; aut; aut = aut->next) - if (grub_strcmp (args[i], aut->name) == 0) + if (grub_strcmp (args[i], aut->name) == 0 + || (aut->name[0] && aut->name[grub_strlen (aut->name) - 1] == '*' + && grub_memcmp (args[i], aut->name, + grub_strlen (aut->name) - 1) == 0)) { grub_dl_t mod; mod = grub_dl_load (aut->modname); diff --git a/commands/usbtest.c b/commands/usbtest.c index 213288b52..7f00c8856 100644 --- a/commands/usbtest.c +++ b/commands/usbtest.c @@ -29,11 +29,11 @@ static const char *usb_classes[] = { - "", + "Unknown", "Audio", "Communication Interface", "HID", - "", + "Unknown", "Physical", "Image", "Printer", @@ -138,10 +138,10 @@ usb_iterate (grub_usb_device_t dev) usb_print_str ("Vendor", dev, descdev->strvendor); usb_print_str ("Serial", dev, descdev->strserial); - if (descdev->class > 0 && descdev->class <= 0x0E) - grub_printf ("Class: (0x%02x) %s, Subclass: 0x%02x, Protocol: 0x%02x\n", - descdev->class, usb_classes[descdev->class], - descdev->subclass, descdev->protocol); + grub_printf ("Class: (0x%02x) %s, Subclass: 0x%02x, Protocol: 0x%02x\n", + descdev->class, descdev->class < ARRAY_SIZE (usb_classes) + ? usb_classes[descdev->class] : "Unknown", + descdev->subclass, descdev->protocol); grub_printf ("USB version %d.%d, VendorID: 0x%02x, ProductID: 0x%02x, #conf: %d\n", descdev->usbrel >> 8, (descdev->usbrel >> 4) & 0x0F, descdev->vendorid, descdev->prodid, descdev->configcnt); @@ -164,10 +164,10 @@ usb_iterate (grub_usb_device_t dev) grub_printf ("Interface #%d: #Endpoints: %d ", i, interf->endpointcnt); - if (interf->class > 0 && interf->class <= 0x0E) - grub_printf ("Class: (0x%02x) %s, Subclass: 0x%02x, Protocol: 0x%02x\n", - interf->class, usb_classes[interf->class], - interf->subclass, interf->protocol); + grub_printf ("Class: (0x%02x) %s, Subclass: 0x%02x, Protocol: 0x%02x\n", + interf->class, interf->class < ARRAY_SIZE (usb_classes) + ? usb_classes[interf->class] : "Unknown", + interf->subclass, interf->protocol); usb_print_str ("Interface", dev, interf->strif); diff --git a/conf/any-emu.rmk b/conf/any-emu.rmk index 958da2bee..8272bf25e 100644 --- a/conf/any-emu.rmk +++ b/conf/any-emu.rmk @@ -70,6 +70,30 @@ usbms_mod_SOURCES = disk/usbms.c usbms_mod_CFLAGS = $(COMMON_CFLAGS) usbms_mod_LDFLAGS = $(COMMON_LDFLAGS) +# For serial.mod. +pkglib_MODULES += usbserial_common.mod +usbserial_common_mod_SOURCES = bus/usb/serial/common.c +usbserial_common_mod_CFLAGS = $(COMMON_CFLAGS) +usbserial_common_mod_LDFLAGS = $(COMMON_LDFLAGS) + +# For serial.mod. +pkglib_MODULES += usbserial_pl2303.mod +usbserial_pl2303_mod_SOURCES = bus/usb/serial/pl2303.c +usbserial_pl2303_mod_CFLAGS = $(COMMON_CFLAGS) +usbserial_pl2303_mod_LDFLAGS = $(COMMON_LDFLAGS) + +# For serial.mod. +pkglib_MODULES += usbserial_ftdi.mod +usbserial_ftdi_mod_SOURCES = bus/usb/serial/ftdi.c +usbserial_ftdi_mod_CFLAGS = $(COMMON_CFLAGS) +usbserial_ftdi_mod_LDFLAGS = $(COMMON_LDFLAGS) + +# For serial.mod. +pkglib_MODULES += serial.mod +serial_mod_SOURCES = term/serial.c +serial_mod_CFLAGS = $(COMMON_CFLAGS) +serial_mod_LDFLAGS = $(COMMON_LDFLAGS) + grub_emu_LDFLAGS += $(LIBUSB) endif diff --git a/conf/i386-pc.rmk b/conf/i386-pc.rmk index 5e9ec882f..e9c0b4202 100644 --- a/conf/i386-pc.rmk +++ b/conf/i386-pc.rmk @@ -195,6 +195,24 @@ usb_mod_SOURCES = bus/usb/usb.c bus/usb/usbtrans.c bus/usb/usbhub.c usb_mod_CFLAGS = $(COMMON_CFLAGS) usb_mod_LDFLAGS = $(COMMON_LDFLAGS) +# For serial.mod. +pkglib_MODULES += usbserial_common.mod +usbserial_common_mod_SOURCES = bus/usb/serial/common.c +usbserial_common_mod_CFLAGS = $(COMMON_CFLAGS) +usbserial_common_mod_LDFLAGS = $(COMMON_LDFLAGS) + +# For serial.mod. +pkglib_MODULES += usbserial_pl2303.mod +usbserial_pl2303_mod_SOURCES = bus/usb/serial/pl2303.c +usbserial_pl2303_mod_CFLAGS = $(COMMON_CFLAGS) +usbserial_pl2303_mod_LDFLAGS = $(COMMON_LDFLAGS) + +# For serial.mod. +pkglib_MODULES += usbserial_ftdi.mod +usbserial_ftdi_mod_SOURCES = bus/usb/serial/ftdi.c +usbserial_ftdi_mod_CFLAGS = $(COMMON_CFLAGS) +usbserial_ftdi_mod_LDFLAGS = $(COMMON_LDFLAGS) + # For usbtest.mod usbtest_mod_SOURCES = commands/usbtest.c usbtest_mod_CFLAGS = $(COMMON_CFLAGS) diff --git a/conf/i386.rmk b/conf/i386.rmk index b1df584a6..6bf1b3410 100644 --- a/conf/i386.rmk +++ b/conf/i386.rmk @@ -56,7 +56,7 @@ multiboot2_mod_ASFLAGS = $(COMMON_ASFLAGS) # For serial.mod. pkglib_MODULES += serial.mod -serial_mod_SOURCES = term/serial.c +serial_mod_SOURCES = term/serial.c term/ns8250.c serial_mod_CFLAGS = $(COMMON_CFLAGS) serial_mod_LDFLAGS = $(COMMON_LDFLAGS) diff --git a/conf/mips-yeeloong.rmk b/conf/mips-yeeloong.rmk index deb6dd33f..9cbbdf472 100644 --- a/conf/mips-yeeloong.rmk +++ b/conf/mips-yeeloong.rmk @@ -5,7 +5,7 @@ COMMON_CFLAGS += -march=mips3 COMMON_ASFLAGS += -march=mips3 kernel_img_HEADERS += pci.h bitmap.h video.h gfxterm.h font.h \ - bitmap_scale.h bufio.h cs5536.h machine/pci.h + bitmap_scale.h bufio.h cs5536.h machine/pci.h serial.h include $(srcdir)/conf/mips.mk @@ -27,7 +27,7 @@ kernel_img_SOURCES = kern/$(target_cpu)/startup.S \ video/fb/fbfill.c video/fb/fbutil.c video/bitmap.c \ video/bitmap_scale.c video/sm712.c bus/pci.c bus/bonito.c \ term/gfxterm.c commands/extcmd.c lib/arg.c \ - bus/cs5536.c term/serial.c term/terminfo.c term/tparm.c \ + bus/cs5536.c term/serial.c term/ns8250.c term/terminfo.c term/tparm.c \ symlist.c kernel_img_CFLAGS = $(COMMON_CFLAGS) -DUSE_ASCII_FAILBACK kernel_img_ASFLAGS = $(COMMON_ASFLAGS) @@ -97,6 +97,24 @@ usb_mod_SOURCES = bus/usb/usb.c bus/usb/usbtrans.c bus/usb/usbhub.c usb_mod_CFLAGS = $(COMMON_CFLAGS) usb_mod_LDFLAGS = $(COMMON_LDFLAGS) +# For serial.mod. +pkglib_MODULES += usbserial_common.mod +usbserial_common_mod_SOURCES = bus/usb/serial/common.c +usbserial_common_mod_CFLAGS = $(COMMON_CFLAGS) +usbserial_common_mod_LDFLAGS = $(COMMON_LDFLAGS) + +# For serial.mod. +pkglib_MODULES += usbserial_pl2303.mod +usbserial_pl2303_mod_SOURCES = bus/usb/serial/pl2303.c +usbserial_pl2303_mod_CFLAGS = $(COMMON_CFLAGS) +usbserial_pl2303_mod_LDFLAGS = $(COMMON_LDFLAGS) + +# For serial.mod. +pkglib_MODULES += usbserial_ftdi.mod +usbserial_ftdi_mod_SOURCES = bus/usb/serial/ftdi.c +usbserial_ftdi_mod_CFLAGS = $(COMMON_CFLAGS) +usbserial_ftdi_mod_LDFLAGS = $(COMMON_LDFLAGS) + # For usbtest.mod pkglib_MODULES += usbtest.mod usbtest_mod_SOURCES = commands/usbtest.c diff --git a/disk/usbms.c b/disk/usbms.c index 225761e0f..e63105ccc 100644 --- a/disk/usbms.c +++ b/disk/usbms.c @@ -65,6 +65,7 @@ typedef struct grub_usbms_dev *grub_usbms_dev_t; /* FIXME: remove limit. */ #define MAX_USBMS_DEVICES 128 static grub_usbms_dev_t grub_usbms_devices[MAX_USBMS_DEVICES]; +static int first_available_slot = 0; static grub_err_t grub_usbms_reset (grub_usb_device_t dev, int interface) @@ -96,13 +97,12 @@ grub_usbms_attach (grub_usb_device_t usbdev, int configno, int interfno) unsigned curnum; grub_usb_err_t err; - for (curnum = 0; curnum < ARRAY_SIZE (grub_usbms_devices); curnum++) - if (!grub_usbms_devices[curnum]) - break; - - if (curnum == ARRAY_SIZE (grub_usbms_devices)) + if (first_available_slot == ARRAY_SIZE (grub_usbms_devices)) return 0; + curnum = first_available_slot; + first_available_slot++; + interf = usbdev->config[configno].interf[interfno].descif; if ((interf->subclass != GRUB_USBMS_SUBCLASS_BULK diff --git a/include/grub/ns8250.h b/include/grub/ns8250.h new file mode 100644 index 000000000..f8b9c3a8c --- /dev/null +++ b/include/grub/ns8250.h @@ -0,0 +1,73 @@ +/* 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 . + */ + +#ifndef GRUB_NS8250_HEADER +#define GRUB_NS8250_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_TRIGGER14 0xC7 + +/* Enable the FIFO. */ +#define UART_ENABLE_FIFO_TRIGGER1 0x07 + +/* Turn on DTR, RTS, and OUT2. */ +#define UART_ENABLE_DTRRTS 0x03 + +/* Turn on DTR, RTS, and OUT2. */ +#define UART_ENABLE_OUT2 0x08 + +#endif /* ! GRUB_SERIAL_MACHINE_HEADER */ diff --git a/include/grub/serial.h b/include/grub/serial.h index 758b6fb3e..652268b2e 100644 --- a/include/grub/serial.h +++ b/include/grub/serial.h @@ -1,7 +1,7 @@ /* serial.h - serial device interface */ /* * GRUB -- GRand Unified Bootloader - * Copyright (C) 2000,2001,2002,2005,2007 Free Software Foundation, Inc. + * Copyright (C) 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 @@ -20,54 +20,100 @@ #ifndef GRUB_SERIAL_HEADER #define GRUB_SERIAL_HEADER 1 -/* Macros. */ +#include +#include +#include +#include +#include -/* 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 +struct grub_serial_port; +struct grub_serial_config; -/* For LSR bits. */ -#define UART_DATA_READY 0x01 -#define UART_EMPTY_TRANSMITTER 0x20 +struct grub_serial_driver +{ + grub_err_t (*configure) (struct grub_serial_port *port, + struct grub_serial_config *config); + int (*fetch) (struct grub_serial_port *port); + void (*put) (struct grub_serial_port *port, const int c); + void (*fini) (struct grub_serial_port *port); +}; /* The type of parity. */ -#define UART_NO_PARITY 0x00 -#define UART_ODD_PARITY 0x08 -#define UART_EVEN_PARITY 0x18 +typedef enum + { + GRUB_SERIAL_PARITY_NONE, + GRUB_SERIAL_PARITY_ODD, + GRUB_SERIAL_PARITY_EVEN, + } grub_serial_parity_t; -/* 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 +typedef enum + { + GRUB_SERIAL_STOP_BITS_1, + GRUB_SERIAL_STOP_BITS_2, + } grub_serial_stop_bits_t; -/* The type of the length of stop bit. */ -#define UART_1_STOP_BIT 0x00 -#define UART_2_STOP_BITS 0x04 +struct grub_serial_config +{ + unsigned speed; + int word_len; + grub_serial_parity_t parity; + grub_serial_stop_bits_t stop_bits; +}; -/* the switch of DLAB. */ -#define UART_DLAB 0x80 +struct grub_serial_port +{ + struct grub_serial_port *next; + char *name; + struct grub_serial_driver *driver; + struct grub_serial_config config; + int configured; + /* This should be void *data but since serial is useful as an early console + when malloc isn't available it's a union. + */ + union + { + grub_port_t port; + struct + { + grub_usb_device_t usbdev; + int configno; + int interfno; + char buf[64]; + int bufstart, bufend; + struct grub_usb_desc_endp *in_endp; + struct grub_usb_desc_endp *out_endp; + }; + }; + grub_term_output_t term_out; + grub_term_input_t term_in; +}; -/* Enable the FIFO. */ -#define UART_ENABLE_FIFO_TRIGGER14 0xC7 +grub_err_t EXPORT_FUNC(grub_serial_register) (struct grub_serial_port *port); -/* Enable the FIFO. */ -#define UART_ENABLE_FIFO_TRIGGER1 0x07 +void EXPORT_FUNC(grub_serial_unregister) (struct grub_serial_port *port); -/* Turn on DTR, RTS, and OUT2. */ -#define UART_ENABLE_DTRRTS 0x03 + /* Set default settings. */ +static inline grub_err_t +grub_serial_config_defaults (struct grub_serial_port *port) +{ + struct grub_serial_config config = + { +#ifdef GRUB_MACHINE_MIPS_YEELOONG + .speed = 115200, +#else + .speed = 9600, +#endif + .word_len = 8, + .parity = GRUB_SERIAL_PARITY_NONE, + .stop_bits = GRUB_SERIAL_STOP_BITS_1 + }; -/* Turn on DTR, RTS, and OUT2. */ -#define UART_ENABLE_OUT2 0x08 + return port->driver->configure (port, &config); +} -#endif /* ! GRUB_SERIAL_MACHINE_HEADER */ +void grub_ns8250_init (void); +char *grub_serial_ns8250_add_port (grub_port_t port); +extern struct grub_serial_driver grub_ns8250_driver; +void EXPORT_FUNC(grub_serial_unregister_driver) (struct grub_serial_driver *driver); + +#endif diff --git a/include/grub/term.h b/include/grub/term.h index f40d935e4..734e4ab17 100644 --- a/include/grub/term.h +++ b/include/grub/term.h @@ -459,6 +459,7 @@ grub_print_spaces (struct grub_term_output *term, int number_spaces) grub_putcode (' ', term); } +extern void (*EXPORT_VAR (grub_term_poll_usb)) (void); /* For convenience. */ #define GRUB_TERM_ASCII_CHAR(c) ((c) & 0xff) diff --git a/include/grub/terminfo.h b/include/grub/terminfo.h index d6907d7f0..85ddb5779 100644 --- a/include/grub/terminfo.h +++ b/include/grub/terminfo.h @@ -32,7 +32,7 @@ struct grub_terminfo_input_state { int input_buf[GRUB_TERMINFO_READKEY_MAX_LEN]; int npending; - int (*readkey) (void); + int (*readkey) (struct grub_term_input *term); }; struct grub_terminfo_output_state @@ -51,7 +51,7 @@ struct grub_terminfo_output_state unsigned int xpos, ypos; - void (*put) (const int c); + void (*put) (struct grub_term_output *term, const int c); }; void EXPORT_FUNC(grub_terminfo_gotoxy) (grub_term_output_t term, diff --git a/include/grub/usb.h b/include/grub/usb.h index 3c17318fc..674f7ec46 100644 --- a/include/grub/usb.h +++ b/include/grub/usb.h @@ -19,6 +19,7 @@ #ifndef GRUB_USB_H #define GRUB_USB_H 1 +#include #include #include @@ -47,6 +48,14 @@ typedef enum GRUB_USB_SPEED_HIGH } grub_usb_speed_t; +enum + { + GRUB_USB_REQTYPE_CLASS_INTERFACE_OUT = 0x21, + GRUB_USB_REQTYPE_VENDOR_OUT = 0x40, + GRUB_USB_REQTYPE_CLASS_INTERFACE_IN = 0xa1, + GRUB_USB_REQTYPE_VENDOR_IN = 0xc0 + }; + /* Call HOOK with each device, until HOOK returns non-zero. */ int grub_usb_iterate (int (*hook) (grub_usb_device_t dev)); @@ -97,7 +106,8 @@ struct grub_usb_controller_dev int (*iterate) (int (*hook) (grub_usb_controller_t dev)); grub_usb_err_t (*transfer) (grub_usb_controller_t dev, - grub_usb_transfer_t transfer); + grub_usb_transfer_t transfer, + int timeout, grub_size_t *actual); int (*hubports) (grub_usb_controller_t dev); @@ -132,6 +142,8 @@ struct grub_usb_interface int attached; void (*detach_hook) (struct grub_usb_device *dev, int config, int interface); + + void *detach_data; }; struct grub_usb_configuration @@ -166,12 +178,29 @@ struct grub_usb_device /* Data toggle values (used for bulk transfers only). */ int toggle[256]; - /* Device-specific data. */ - void *data; + /* Array of children for a hub. */ + grub_usb_device_t *children; + + /* Number of hub ports. */ + unsigned nports; }; +typedef enum grub_usb_ep_type + { + GRUB_USB_EP_CONTROL, + GRUB_USB_EP_ISOCHRONOUS, + GRUB_USB_EP_BULK, + GRUB_USB_EP_INTERRUPT + } grub_usb_ep_type_t; + +static inline enum grub_usb_ep_type +grub_usb_get_ep_type (struct grub_usb_desc_endp *ep) +{ + return ep->attrib & 3; +} + typedef enum { GRUB_USB_CLASS_NOTHERE, @@ -230,5 +259,9 @@ void grub_usb_unregister_attach_hook_class (struct grub_usb_attach_desc *desc); void grub_usb_poll_devices (void); void grub_usb_device_attach (grub_usb_device_t dev); +grub_usb_err_t +grub_usb_bulk_read_extended (grub_usb_device_t dev, + int endpoint, grub_size_t size, char *data, + int timeout, grub_size_t *actual); #endif /* GRUB_USB_H */ diff --git a/include/grub/usbdesc.h b/include/grub/usbdesc.h index 2f711d755..84b723a62 100644 --- a/include/grub/usbdesc.h +++ b/include/grub/usbdesc.h @@ -31,6 +31,12 @@ typedef enum { GRUB_USB_DESCRIPTOR_HUB = 0x29 } grub_usb_descriptor_t; +struct grub_usb_desc +{ + grub_uint8_t length; + grub_uint8_t type; +} __attribute__ ((packed)); + struct grub_usb_desc_device { grub_uint8_t length; diff --git a/include/grub/usbserial.h b/include/grub/usbserial.h new file mode 100644 index 000000000..74201256e --- /dev/null +++ b/include/grub/usbserial.h @@ -0,0 +1,34 @@ +/* serial.h - serial device interface */ +/* + * GRUB -- GRand Unified Bootloader + * Copyright (C) 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 . + */ + +#ifndef GRUB_USBSERIAL_HEADER +#define GRUB_USBSERIAL_HEADER 1 + +void grub_usbserial_fini (struct grub_serial_port *port); + +void grub_usbserial_detach (grub_usb_device_t usbdev, int configno, + int interfno); + +int +grub_usbserial_attach (grub_usb_device_t usbdev, int configno, int interfno, + struct grub_serial_driver *driver); +int +grub_usbserial_fetch (struct grub_serial_port *port, grub_size_t header_size); + +#endif diff --git a/include/grub/usbtrans.h b/include/grub/usbtrans.h index e68698c1d..a5bb2e8b2 100644 --- a/include/grub/usbtrans.h +++ b/include/grub/usbtrans.h @@ -38,6 +38,7 @@ struct grub_usb_transaction int toggle; grub_transfer_type_t pid; grub_uint32_t data; + grub_size_t preceding; }; typedef struct grub_usb_transaction *grub_usb_transaction_t; diff --git a/kern/term.c b/kern/term.c index 47bd426fd..360539e50 100644 --- a/kern/term.c +++ b/kern/term.c @@ -28,6 +28,8 @@ struct grub_term_input *grub_term_inputs_disabled; struct grub_term_output *grub_term_outputs; struct grub_term_input *grub_term_inputs; +void (*grub_term_poll_usb) (void) = NULL; + /* Put a Unicode character. */ static void grub_putcode_dumb (grub_uint32_t code, @@ -85,6 +87,9 @@ grub_getkey (void) while (1) { + if (grub_term_poll_usb) + grub_term_poll_usb (); + FOR_ACTIVE_TERM_INPUTS(term) { int key = term->checkkey (term); @@ -101,6 +106,9 @@ grub_checkkey (void) { grub_term_input_t term; + if (grub_term_poll_usb) + grub_term_poll_usb (); + FOR_ACTIVE_TERM_INPUTS(term) { int key = term->checkkey (term); @@ -117,6 +125,9 @@ grub_getkeystatus (void) int status = 0; grub_term_input_t term; + if (grub_term_poll_usb) + grub_term_poll_usb (); + FOR_ACTIVE_TERM_INPUTS(term) { if (term->getkeystatus) diff --git a/term/ns8250.c b/term/ns8250.c new file mode 100644 index 000000000..315d3527f --- /dev/null +++ b/term/ns8250.c @@ -0,0 +1,253 @@ +/* + * 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 . + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#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 +#include +static const grub_port_t serial_hw_io_addr[] = GRUB_MACHINE_SERIAL_PORTS; +#define GRUB_SERIAL_PORT_NUM (ARRAY_SIZE(serial_hw_io_addr)) +#endif + +/* 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 < ARRAY_SIZE (divisor_tab); i++) + if (divisor_tab[i].speed == speed) + /* UART in Yeeloong runs twice the usual rate. */ +#ifdef GRUB_MACHINE_MIPS_YEELOONG + return 2 * divisor_tab[i].div; +#else + return divisor_tab[i].div; +#endif + return 0; +} + +static void +do_real_config (struct grub_serial_port *port) +{ + int divisor; + unsigned char status = 0; + const unsigned char parities[] = { + [GRUB_SERIAL_PARITY_NONE] = UART_NO_PARITY, + [GRUB_SERIAL_PARITY_ODD] = UART_ODD_PARITY, + [GRUB_SERIAL_PARITY_EVEN] = UART_EVEN_PARITY + }; + const unsigned char stop_bits[] = { + [GRUB_SERIAL_STOP_BITS_1] = UART_1_STOP_BIT, + [GRUB_SERIAL_STOP_BITS_2] = UART_2_STOP_BITS, + }; + + if (port->configured) + return; + + divisor = serial_get_divisor (port->config.speed); + + /* Turn off the interrupt. */ + grub_outb (0, port->port + UART_IER); + + /* Set DLAB. */ + grub_outb (UART_DLAB, port->port + UART_LCR); + + /* Set the baud rate. */ + grub_outb (divisor & 0xFF, port->port + UART_DLL); + grub_outb (divisor >> 8, port->port + UART_DLH); + + /* Set the line status. */ + status |= (parities[port->config.parity] + | (port->config.word_len - 5) + | stop_bits[port->config.stop_bits]); + grub_outb (status, port->port + UART_LCR); + + /* In Yeeloong serial port has only 3 wires. */ +#ifndef GRUB_MACHINE_MIPS_YEELOONG + /* Enable the FIFO. */ + grub_outb (UART_ENABLE_FIFO_TRIGGER1, port->port + UART_FCR); + + /* Turn on DTR and RTS. */ + grub_outb (UART_ENABLE_DTRRTS, port->port + UART_MCR); +#else + /* Enable the FIFO. */ + grub_outb (UART_ENABLE_FIFO_TRIGGER14, port->port + UART_FCR); + + /* Turn on DTR, RTS, and OUT2. */ + grub_outb (UART_ENABLE_DTRRTS | UART_ENABLE_OUT2, port->port + UART_MCR); +#endif + + /* Drain the input buffer. */ + while (grub_inb (port->port + UART_LSR) & UART_DATA_READY) + grub_inb (port->port + UART_RX); + + port->configured = 1; +} + +/* Fetch a key. */ +static int +serial_hw_fetch (struct grub_serial_port *port) +{ + do_real_config (port); + if (grub_inb (port->port + UART_LSR) & UART_DATA_READY) + return grub_inb (port->port + UART_RX); + + return -1; +} + +/* Put a character. */ +static void +serial_hw_put (struct grub_serial_port *port, const int c) +{ + unsigned int timeout = 100000; + + do_real_config (port); + + /* Wait until the transmitter holding register is empty. */ + while ((grub_inb (port->port + UART_LSR) & UART_EMPTY_TRANSMITTER) == 0) + { + if (--timeout == 0) + /* There is something wrong. But what can I do? */ + return; + } + + grub_outb (c, port->port + UART_TX); +} + +/* 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, + struct grub_serial_config *config) +{ + unsigned short divisor; + + divisor = serial_get_divisor (config->speed); + if (divisor == 0) + return grub_error (GRUB_ERR_BAD_ARGUMENT, "bad 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, "unsupported parity"); + + if (config->stop_bits != GRUB_SERIAL_STOP_BITS_1 + && config->stop_bits != GRUB_SERIAL_STOP_BITS_2) + return grub_error (GRUB_ERR_BAD_ARGUMENT, "unsupported stop bits"); + + if (config->word_len < 5 || config->word_len > 8) + return grub_error (GRUB_ERR_BAD_ARGUMENT, "unsupported 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_ns8250_driver = + { + .configure = serial_hw_configure, + .fetch = serial_hw_fetch, + .put = serial_hw_put + }; + +static char com_names[GRUB_SERIAL_PORT_NUM][20]; +static struct grub_serial_port com_ports[GRUB_SERIAL_PORT_NUM]; + +void +grub_ns8250_init (void) +{ + unsigned i; + for (i = 0; i < GRUB_SERIAL_PORT_NUM; i++) + if (serial_hw_io_addr[i]) + { + grub_err_t err; + grub_snprintf (com_names[i], sizeof (com_names[i]), "com%d", i); + com_ports[i].name = com_names[i]; + com_ports[i].driver = &grub_ns8250_driver; + com_ports[i].port = serial_hw_io_addr[i]; + err = grub_serial_config_defaults (&com_ports[i]); + if (err) + grub_print_error (); + + grub_serial_register (&com_ports[i]); + } +} + +char * +grub_serial_ns8250_add_port (grub_port_t port) +{ + struct grub_serial_port *p; + unsigned i; + for (i = 0; i < GRUB_SERIAL_PORT_NUM; i++) + if (com_ports[i].port == port) + return com_names[i]; + p = grub_malloc (sizeof (*p)); + if (!p) + return NULL; + p->name = grub_xasprintf ("port%lx", (unsigned long) port); + if (!p->name) + { + grub_free (p); + return NULL; + } + p->driver = &grub_ns8250_driver; + grub_serial_config_defaults (p); + p->port = port; + grub_serial_register (p); + + return p->name; +} diff --git a/term/serial.c b/term/serial.c index cf7759ef2..2268788af 100644 --- a/term/serial.c +++ b/term/serial.c @@ -1,6 +1,6 @@ /* * GRUB -- GRand Unified Bootloader - * Copyright (C) 2000,2001,2002,2003,2004,2005,2007,2008,2009 Free Software Foundation, Inc. + * 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 @@ -16,7 +16,6 @@ * along with GRUB. If not, see . */ -#include #include #include #include @@ -26,8 +25,9 @@ #include #include #include +#include -static unsigned int registered = 0; +#define FOR_SERIAL_PORTS(var) FOR_LIST_ELEMENTS((var), (grub_serial_ports)) /* Argument options. */ static const struct grub_arg_option options[] = @@ -41,154 +41,19 @@ static const struct grub_arg_option options[] = {0, 0, 0, 0, 0, 0} }; -/* Serial port settings. */ -struct serial_port +struct grub_serial_port *grub_serial_ports; + +struct grub_serial_output_state { - grub_port_t port; - unsigned short divisor; - unsigned short word_len; - unsigned int parity; - unsigned short stop_bits; + struct grub_terminfo_output_state tinfo; + struct grub_serial_port *port; }; -/* 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 -#include -static const grub_port_t serial_hw_io_addr[] = GRUB_MACHINE_SERIAL_PORTS; -#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) +struct grub_serial_input_state { - 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); -} - -/* 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) - /* UART in Yeeloong runs twice the usual rate. */ -#ifdef GRUB_MACHINE_MIPS_YEELOONG - return 2 * divisor_tab[i].div; -#else - return divisor_tab[i].div; -#endif - return 0; -} - -/* 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); - - /* In Yeeloong serial port has only 3 wires. */ -#ifndef GRUB_MACHINE_MIPS_YEELOONG - /* Enable the FIFO. */ - grub_outb (UART_ENABLE_FIFO_TRIGGER1, serial_settings.port + UART_FCR); - - /* Turn on DTR and RTS. */ - grub_outb (UART_ENABLE_DTRRTS, serial_settings.port + UART_MCR); -#else - /* Enable the FIFO. */ - grub_outb (UART_ENABLE_FIFO_TRIGGER14, serial_settings.port + UART_FCR); - - /* Turn on DTR, RTS, and OUT2. */ - grub_outb (UART_ENABLE_DTRRTS | UART_ENABLE_OUT2, - serial_settings.port + UART_MCR); -#endif - - /* Drain the input buffer. */ - while (serial_hw_fetch () != -1); - - /* FIXME: should check if the serial terminal was found. */ - - return GRUB_ERR_NONE; -} + struct grub_terminfo_input_state tinfo; + struct grub_serial_port *port; +}; static grub_uint16_t grub_serial_getwh (struct grub_term_output *term __attribute__ ((unused))) @@ -198,16 +63,38 @@ grub_serial_getwh (struct grub_term_output *term __attribute__ ((unused))) return (TEXT_WIDTH << 8) | TEXT_HEIGHT; } -struct grub_terminfo_input_state grub_serial_terminfo_input = +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); +} + +struct grub_serial_input_state grub_serial_terminfo_input = { - .readkey = serial_hw_fetch + .tinfo = + { + .readkey = serial_fetch + } }; -struct grub_terminfo_output_state grub_serial_terminfo_output = +struct grub_serial_output_state grub_serial_terminfo_output = { - .put = serial_hw_put + .tinfo = + { + .put = serial_put + } }; +int registered = 0; + static struct grub_term_input grub_serial_term_input = { .name = "serial", @@ -235,120 +122,219 @@ static struct grub_term_output grub_serial_term_output = +static struct grub_serial_port * +grub_serial_find (char *name) +{ + struct grub_serial_port *port; + + FOR_SERIAL_PORTS (port) + if (grub_strcmp (port->name, name) == 0) + break; + +#ifndef GRUB_MACHINE_EMU + if (!port && grub_memcmp (name, "port", sizeof ("port") - 1) == 0 + && grub_isdigit (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 + + return port; +} + static grub_err_t -grub_cmd_serial (grub_extcmd_t cmd, - int argc __attribute__ ((unused)), - char **args __attribute__ ((unused))) +grub_cmd_serial (grub_extcmd_t cmd, int argc, char **args) { struct grub_arg_list *state = cmd->state; - struct serial_port backup_settings = serial_settings; - grub_err_t hwiniterr; + char pname[40]; + char *name = NULL; + struct grub_serial_port *port; + struct grub_serial_config config; + grub_err_t err; 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"); + grub_snprintf (pname, sizeof (pname), "com%ld", + grub_strtoul (state[0].arg, 0, 0)); + name = pname; } if (state[1].set) - serial_settings.port = (grub_port_t) grub_strtoul (state[1].arg, 0, 0); + { + 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, "unknown serial port"); + + config = port->config; 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"); - } - } + config.speed = grub_strtoul (state[2].arg, 0, 0); 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"); - } - } + config.word_len = grub_strtoul (state[3].arg, 0, 0); if (state[4].set) { if (! grub_strcmp (state[4].arg, "no")) - serial_settings.parity = UART_NO_PARITY; + config.parity = GRUB_SERIAL_PARITY_NONE; else if (! grub_strcmp (state[4].arg, "odd")) - serial_settings.parity = UART_ODD_PARITY; + config.parity = GRUB_SERIAL_PARITY_ODD; else if (! grub_strcmp (state[4].arg, "even")) - serial_settings.parity = UART_EVEN_PARITY; + config.parity = GRUB_SERIAL_PARITY_EVEN; else - { - serial_settings = backup_settings; - return grub_error (GRUB_ERR_BAD_ARGUMENT, "bad parity"); - } + 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; + config.stop_bits = GRUB_SERIAL_STOP_BITS_1; else if (! grub_strcmp (state[5].arg, "2")) - serial_settings.stop_bits = UART_2_STOP_BITS; + config.stop_bits = GRUB_SERIAL_STOP_BITS_2; else - { - serial_settings = backup_settings; - return grub_error (GRUB_ERR_BAD_ARGUMENT, "bad number of stop bits"); - } + 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) + err = port->driver->configure (port, &config); + if (err) + return err; +#ifndef GRUB_MACHINE_EMU + /* Compatibility kludge. */ + if (port->driver == &grub_ns8250_driver) { - /* Register terminal if not yet registered. */ - if (registered == 0) + if (!registered) { grub_term_register_input ("serial", &grub_serial_term_input); grub_term_register_output ("serial", &grub_serial_term_output); - grub_terminfo_output_register (&grub_serial_term_output, "vt100"); - registered = 1; } + grub_serial_terminfo_output.port = port; + grub_serial_terminfo_input.port = port; + registered = 1; } - else +#endif + return GRUB_ERR_NONE; +} + +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) { - /* 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); - grub_terminfo_output_unregister (&grub_serial_term_output); - registered = 0; - } - } + grub_free (in); + return grub_errno; } - return hwiniterr; + 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_malloc (sizeof (*out)); + if (!out) + { + grub_free (in); + grub_free (indata); + grub_free ((char *) in->name); + return grub_errno; + } + + outdata = grub_malloc (sizeof (*outdata)); + if (!outdata) + { + grub_free (in); + grub_free (indata); + grub_free ((char *) in->name); + grub_free (out); + 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_YEELOONG + if (grub_strcmp (port->name, "com0") == 0) + { + grub_term_register_input_active ("serial_*", in); + grub_term_register_output_active ("serial_*", out); + } + else +#endif + { + grub_term_register_input ("serial_*", in); + grub_term_register_output ("serial_*", out); + } + + 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_P (&grub_serial_ports), 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; @@ -359,41 +345,19 @@ GRUB_MOD_INIT(serial) GRUB_COMMAND_FLAG_BOTH, N_("[OPTIONS...]"), N_("Configure serial port."), options); - - /* Set default settings. */ - serial_settings.port = serial_hw_get_port (0); -#ifdef GRUB_MACHINE_MIPS_YEELOONG - serial_settings.divisor = serial_get_divisor (115200); -#else - serial_settings.divisor = serial_get_divisor (9600); -#endif - serial_settings.word_len = UART_8BITS_WORD; - serial_settings.parity = UART_NO_PARITY; - serial_settings.stop_bits = UART_1_STOP_BIT; - -#ifdef GRUB_MACHINE_MIPS_YEELOONG - { - grub_err_t hwiniterr; - hwiniterr = serial_hw_init (); - - if (hwiniterr == GRUB_ERR_NONE) - { - grub_term_register_input_active ("serial", &grub_serial_term_input); - grub_term_register_output_active ("serial", &grub_serial_term_output); - - registered = 1; - } - } +#ifndef GRUB_MACHINE_EMU + grub_ns8250_init (); #endif } GRUB_MOD_FINI(serial) { - grub_unregister_extcmd (cmd); - if (registered == 1) /* Unregister terminal only if registered. */ + 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_terminfo_output_unregister (&grub_serial_term_output); } + grub_unregister_extcmd (cmd); } diff --git a/term/terminfo.c b/term/terminfo.c index ff54e5dba..5691b9cc6 100644 --- a/term/terminfo.c +++ b/term/terminfo.c @@ -195,7 +195,7 @@ putstr (struct grub_term_output *term, const char *str) struct grub_terminfo_output_state *data = (struct grub_terminfo_output_state *) term->data; while (*str) - data->put (*str++); + data->put (term, *str++); } grub_uint16_t @@ -225,7 +225,7 @@ grub_terminfo_gotoxy (struct grub_term_output *term, else { if ((y == data->ypos) && (x == data->xpos - 1)) - data->put ('\b'); + data->put (term, '\b'); } data->xpos = x; @@ -348,20 +348,21 @@ grub_terminfo_putchar (struct grub_term_output *term, data->xpos = 0; if (data->ypos < grub_term_height (term) - 1) data->ypos++; - data->put ('\r'); - data->put ('\n'); + data->put (term, '\r'); + data->put (term, '\n'); } data->xpos += c->estimated_width; break; } - data->put (c->base); + data->put (term, c->base); } #define ANSI_C0 0x9b static void -grub_terminfo_readkey (int *keys, int *len, int (*readkey) (void)) +grub_terminfo_readkey (struct grub_term_input *term, int *keys, int *len, + int (*readkey) (struct grub_term_input *term)) { int c; @@ -371,7 +372,7 @@ grub_terminfo_readkey (int *keys, int *len, int (*readkey) (void)) /* On 9600 we have to wait up to 12 milliseconds. */ \ start = grub_get_time_ms (); \ do \ - c = readkey (); \ + c = readkey (term); \ while (c == -1 && grub_get_time_ms () - start < 12); \ if (c == -1) \ return; \ @@ -380,7 +381,7 @@ grub_terminfo_readkey (int *keys, int *len, int (*readkey) (void)) (*len)++; \ } - c = readkey (); + c = readkey (term); if (c < 0) { *len = 0; @@ -475,7 +476,8 @@ grub_terminfo_checkkey (struct grub_term_input *termi) if (data->npending) return data->input_buf[0]; - grub_terminfo_readkey (data->input_buf, &data->npending, data->readkey); + grub_terminfo_readkey (termi, data->input_buf, + &data->npending, data->readkey); if (data->npending) return data->input_buf[0]; @@ -491,7 +493,8 @@ grub_terminfo_getkey (struct grub_term_input *termi) = (struct grub_terminfo_input_state *) (termi->data); int ret; while (! data->npending) - grub_terminfo_readkey (data->input_buf, &data->npending, data->readkey); + grub_terminfo_readkey (termi, data->input_buf, &data->npending, + data->readkey); ret = data->input_buf[0]; data->npending--; diff --git a/term/usb_keyboard.c b/term/usb_keyboard.c index ae9c41035..d875ac00a 100644 --- a/term/usb_keyboard.c +++ b/term/usb_keyboard.c @@ -54,12 +54,6 @@ static char keyboard_map_shift[128] = '?' }; -static grub_usb_device_t usbdev; - -/* Valid values for bmRequestType. See HID definition version 1.11 section - 7.2. */ -#define USB_HID_HOST_TO_DEVICE 0x21 -#define USB_HID_DEVICE_TO_HOST 0xA1 /* Valid values for bRequest. See HID definition version 1.11 section 7.2. */ #define USB_HID_GET_REPORT 0x01 @@ -69,78 +63,176 @@ static grub_usb_device_t usbdev; #define USB_HID_SET_IDLE 0x0A #define USB_HID_SET_PROTOCOL 0x0B -static void -grub_usb_hid (void) +#define USB_HID_BOOT_SUBCLASS 0x01 +#define USB_HID_KBD_PROTOCOL 0x01 + +static int grub_usb_keyboard_checkkey (struct grub_term_input *term); +static int grub_usb_keyboard_getkey (struct grub_term_input *term); +static int grub_usb_keyboard_getkeystatus (struct grub_term_input *term); + +static struct grub_term_input grub_usb_keyboard_term = + { + .checkkey = grub_usb_keyboard_checkkey, + .getkey = grub_usb_keyboard_getkey, + .getkeystatus = grub_usb_keyboard_getkeystatus, + .next = 0 + }; + +struct grub_usb_keyboard_data { - struct grub_usb_desc_device *descdev; + grub_usb_device_t usbdev; + grub_uint8_t status; + int key; + struct grub_usb_desc_endp *endp; +}; - auto int usb_iterate (grub_usb_device_t dev); - int usb_iterate (grub_usb_device_t dev) +static struct grub_term_input grub_usb_keyboards[16]; + +static void +grub_usb_keyboard_detach (grub_usb_device_t usbdev, + int config __attribute__ ((unused)), + int interface __attribute__ ((unused))) +{ + unsigned i; + for (i = 0; i < ARRAY_SIZE (grub_usb_keyboards); i++) { - descdev = &dev->descdev; + struct grub_usb_keyboard_data *data = grub_usb_keyboards[i].data; - grub_dprintf ("usb_keyboard", "%x %x %x\n", - descdev->class, descdev->subclass, descdev->protocol); + if (!data) + continue; -#if 0 - if (descdev->class != 0x09 - || descdev->subclass == 0x01 - || descdev->protocol != 0x02) - return 0; -#endif + if (data->usbdev != usbdev) + continue; - if (descdev->class != 0 || descdev->subclass != 0 || descdev->protocol != 0) - return 0; - - grub_printf ("HID found!\n"); - - usbdev = dev; - - return 1; + grub_term_unregister_input (&grub_usb_keyboards[i]); + grub_free ((char *) grub_usb_keyboards[i].name); + grub_usb_keyboards[i].name = NULL; + grub_free (grub_usb_keyboards[i].data); + grub_usb_keyboards[i].data = 0; } - grub_usb_iterate (usb_iterate); - - /* Place the device in boot mode. */ - grub_usb_control_msg (usbdev, USB_HID_HOST_TO_DEVICE, USB_HID_SET_PROTOCOL, - 0, 0, 0, 0); - - /* Reports every time an event occurs and not more often than that. */ - grub_usb_control_msg (usbdev, USB_HID_HOST_TO_DEVICE, USB_HID_SET_IDLE, - 0<<8, 0, 0, 0); } -static grub_err_t -grub_usb_keyboard_getreport (grub_usb_device_t dev, grub_uint8_t *report) +static int +grub_usb_keyboard_attach (grub_usb_device_t usbdev, int configno, int interfno) { - return grub_usb_control_msg (dev, USB_HID_DEVICE_TO_HOST, USB_HID_GET_REPORT, - 0, 0, 8, (char *) report); + unsigned curnum; + struct grub_usb_keyboard_data *data; + struct grub_usb_desc_endp *endp = NULL; + int j; + + grub_dprintf ("usb_keyboard", "%x %x %x %d %d\n", + usbdev->descdev.class, usbdev->descdev.subclass, + usbdev->descdev.protocol, configno, interfno); + + for (curnum = 0; curnum < ARRAY_SIZE (grub_usb_keyboards); curnum++) + if (!grub_usb_keyboards[curnum].data) + break; + + if (curnum == ARRAY_SIZE (grub_usb_keyboards)) + return 0; + + if (usbdev->descdev.class != 0 + || usbdev->descdev.subclass != 0 || usbdev->descdev.protocol != 0) + return 0; + + if (usbdev->config[configno].interf[interfno].descif->subclass + != USB_HID_BOOT_SUBCLASS + || usbdev->config[configno].interf[interfno].descif->protocol + != USB_HID_KBD_PROTOCOL) + return 0; + + for (j = 0; j < usbdev->config[configno].interf[interfno].descif->endpointcnt; + j++) + { + endp = &usbdev->config[configno].interf[interfno].descendp[j]; + + if ((endp->endp_addr & 128) && grub_usb_get_ep_type(endp) + == GRUB_USB_EP_INTERRUPT) + break; + } + if (j == usbdev->config[configno].interf[interfno].descif->endpointcnt) + return 0; + + grub_dprintf ("usb_keyboard", "HID found!\n"); + + data = grub_malloc (sizeof (*data)); + if (!data) + { + grub_print_error (); + return 0; + } + + data->usbdev = usbdev; + data->endp = endp; + + /* Place the device in boot mode. */ + grub_usb_control_msg (usbdev, GRUB_USB_REQTYPE_CLASS_INTERFACE_OUT, + USB_HID_SET_PROTOCOL, 0, 0, 0, 0); + + /* Reports every time an event occurs and not more often than that. */ + grub_usb_control_msg (usbdev, GRUB_USB_REQTYPE_CLASS_INTERFACE_OUT, + USB_HID_SET_IDLE, 0<<8, 0, 0, 0); + + grub_memcpy (&grub_usb_keyboards[curnum], &grub_usb_keyboard_term, + sizeof (grub_usb_keyboards[curnum])); + grub_usb_keyboards[curnum].data = data; + usbdev->config[configno].interf[interfno].detach_hook + = grub_usb_keyboard_detach; + grub_usb_keyboards[curnum].name = grub_xasprintf ("usb_keyboard%d", curnum); + if (!grub_usb_keyboards[curnum].name) + { + grub_print_error (); + return 0; + } + + { + grub_uint8_t report[8]; + grub_usb_err_t err; + grub_memset (report, 0, sizeof (report)); + err = grub_usb_control_msg (usbdev, GRUB_USB_REQTYPE_CLASS_INTERFACE_IN, + USB_HID_GET_REPORT, 0x0000, interfno, + sizeof (report), (char *) report); + if (err) + { + data->status = 0; + data->key = -1; + } + else + { + data->status = report[0]; + data->key = report[2] ? : -1; + } + } + + grub_term_register_input_active ("usb_keyboard", &grub_usb_keyboards[curnum]); + + return 1; } static int -grub_usb_keyboard_checkkey (struct grub_term_input *term __attribute__ ((unused))) +grub_usb_keyboard_checkkey (struct grub_term_input *term) { grub_uint8_t data[8]; - int key; - grub_err_t err; - grub_uint64_t currtime; - int timeout = 50; + grub_usb_err_t err; + struct grub_usb_keyboard_data *termdata = term->data; + grub_size_t actual; + + if (termdata->key != -1) + return termdata->key; data[2] = 0; - currtime = grub_get_time_ms (); - do - { - /* Get_Report. */ - err = grub_usb_keyboard_getreport (usbdev, data); + /* Poll interrupt pipe. */ + err = grub_usb_bulk_read_extended (termdata->usbdev, + termdata->endp->endp_addr, sizeof (data), + (char *) data, 10, &actual); + if (err || actual < 1) + return -1; - /* Implement a timeout. */ - if (grub_get_time_ms () > currtime + timeout) - break; - } - while (err || !data[2]); + termdata->status = data[0]; - if (err || !data[2]) + if (actual < 3 || !data[2]) return -1; grub_dprintf ("usb_keyboard", @@ -151,178 +243,74 @@ grub_usb_keyboard_checkkey (struct grub_term_input *term __attribute__ ((unused) /* Check if the Control or Shift key was pressed. */ if (data[0] & 0x01 || data[0] & 0x10) - key = keyboard_map[data[2]] - 'a' + 1; + termdata->key = keyboard_map[data[2]] - 'a' + 1; else if (data[0] & 0x02 || data[0] & 0x20) - key = keyboard_map_shift[data[2]]; + termdata->key = keyboard_map_shift[data[2]]; else - key = keyboard_map[data[2]]; + termdata->key = keyboard_map[data[2]]; - if (key == 0) + if (termdata->key == 0) grub_printf ("Unknown key 0x%x detected\n", data[2]); -#if 0 - /* Wait until the key is released. */ - while (!err && data[2]) - { - err = grub_usb_control_msg (usbdev, USB_HID_DEVICE_TO_HOST, - USB_HID_GET_REPORT, 0, 0, - sizeof (data), (char *) data); - grub_dprintf ("usb_keyboard", - "report2: 0x%02x 0x%02x 0x%02x 0x%02x" - " 0x%02x 0x%02x 0x%02x 0x%02x\n", - data[0], data[1], data[2], data[3], - data[4], data[5], data[6], data[7]); - } -#endif - grub_errno = GRUB_ERR_NONE; - return key; + return termdata->key; } -typedef enum -{ - GRUB_HIDBOOT_REPEAT_NONE, - GRUB_HIDBOOT_REPEAT_FIRST, - GRUB_HIDBOOT_REPEAT -} grub_usb_keyboard_repeat_t; - static int grub_usb_keyboard_getkey (struct grub_term_input *term) { - int key; - grub_err_t err; - grub_uint8_t data[8]; - grub_uint64_t currtime; - int timeout; - static grub_usb_keyboard_repeat_t repeat = GRUB_HIDBOOT_REPEAT_NONE; + int ret; + struct grub_usb_keyboard_data *termdata = term->data; - again: + while (termdata->key == -1) + grub_usb_keyboard_checkkey (term); - do - { - key = grub_usb_keyboard_checkkey (term); - } while (key == -1); + ret = termdata->key; - data[2] = !0; /* Or whatever. */ - err = 0; + termdata->key = -1; - switch (repeat) - { - case GRUB_HIDBOOT_REPEAT_FIRST: - timeout = 500; - break; - case GRUB_HIDBOOT_REPEAT: - timeout = 50; - break; - default: - timeout = 100; - break; - } - - /* Wait until the key is released. */ - currtime = grub_get_time_ms (); - while (!err && data[2]) - { - /* Implement a timeout. */ - if (grub_get_time_ms () > currtime + timeout) - { - if (repeat == 0) - repeat = 1; - else - repeat = 2; - - grub_errno = GRUB_ERR_NONE; - return key; - } - - err = grub_usb_keyboard_getreport (usbdev, data); - } - - if (repeat) - { - repeat = 0; - goto again; - } - - repeat = 0; - - grub_errno = GRUB_ERR_NONE; - - return key; + return ret; } static int -grub_usb_keyboard_getkeystatus (struct grub_term_input *term __attribute__ ((unused))) +grub_usb_keyboard_getkeystatus (struct grub_term_input *term) { - grub_uint8_t data[8]; + struct grub_usb_keyboard_data *termdata = term->data; int mods = 0; - grub_err_t err; - grub_uint64_t currtime; - int timeout = 50; - - /* Set idle time to the minimum offered by the spec (4 milliseconds) so - that we can find out the current state. */ - grub_usb_control_msg (usbdev, USB_HID_HOST_TO_DEVICE, USB_HID_SET_IDLE, - 0<<8, 0, 0, 0); - - currtime = grub_get_time_ms (); - do - { - /* Get_Report. */ - err = grub_usb_keyboard_getreport (usbdev, data); - - /* Implement a timeout. */ - if (grub_get_time_ms () > currtime + timeout) - break; - } - while (err || !data[0]); - - /* Go back to reporting every time an event occurs and not more often than - that. */ - grub_usb_control_msg (usbdev, USB_HID_HOST_TO_DEVICE, USB_HID_SET_IDLE, - 0<<8, 0, 0, 0); - - /* We allowed a while for modifiers to show up in the report, but it is - not an error if they never did. */ - if (err) - return -1; - - grub_dprintf ("usb_keyboard", - "report: 0x%02x 0x%02x 0x%02x 0x%02x" - " 0x%02x 0x%02x 0x%02x 0x%02x\n", - data[0], data[1], data[2], data[3], - data[4], data[5], data[6], data[7]); /* Check Shift, Control, and Alt status. */ - if (data[0] & 0x02 || data[0] & 0x20) + if (termdata->status & 0x02 || termdata->status & 0x20) mods |= GRUB_TERM_STATUS_SHIFT; - if (data[0] & 0x01 || data[0] & 0x10) + if (termdata->status & 0x01 || termdata->status & 0x10) mods |= GRUB_TERM_STATUS_CTRL; - if (data[0] & 0x04 || data[0] & 0x40) + if (termdata->status & 0x04 || termdata->status & 0x40) mods |= GRUB_TERM_STATUS_ALT; - grub_errno = GRUB_ERR_NONE; - return mods; } -static struct grub_term_input grub_usb_keyboard_term = - { - .name = "usb_keyboard", - .checkkey = grub_usb_keyboard_checkkey, - .getkey = grub_usb_keyboard_getkey, - .getkeystatus = grub_usb_keyboard_getkeystatus, - .next = 0 - }; +struct grub_usb_attach_desc attach_hook = +{ + .class = GRUB_USB_CLASS_HID, + .hook = grub_usb_keyboard_attach +}; GRUB_MOD_INIT(usb_keyboard) { - grub_usb_hid (); - grub_term_register_input ("usb_keyboard", &grub_usb_keyboard_term); + grub_usb_register_attach_hook_class (&attach_hook); } GRUB_MOD_FINI(usb_keyboard) { - grub_term_unregister_input (&grub_usb_keyboard_term); + unsigned i; + for (i = 0; i < ARRAY_SIZE (grub_usb_keyboards); i++) + if (grub_usb_keyboards[i].data) + { + grub_term_unregister_input (&grub_usb_keyboards[i]); + grub_free ((char *) grub_usb_keyboards[i].name); + grub_usb_keyboards[i].name = NULL; + grub_usb_keyboards[i].data = 0; + } + grub_usb_unregister_attach_hook_class (&attach_hook); }