summaryrefslogtreecommitdiffstats
path: root/grub-core/bus
diff options
context:
space:
mode:
Diffstat (limited to 'grub-core/bus')
-rw-r--r--grub-core/bus/bonito.c176
-rw-r--r--grub-core/bus/cs5536.c381
-rw-r--r--grub-core/bus/emu/pci.c78
-rw-r--r--grub-core/bus/fdt.c256
-rw-r--r--grub-core/bus/i386/ieee1275/pci.c42
-rw-r--r--grub-core/bus/pci.c173
-rw-r--r--grub-core/bus/spi/rk3288_spi.c103
-rw-r--r--grub-core/bus/usb/ehci-fdt.c45
-rw-r--r--grub-core/bus/usb/ehci-pci.c208
-rw-r--r--grub-core/bus/usb/ehci.c1839
-rw-r--r--grub-core/bus/usb/ohci.c1468
-rw-r--r--grub-core/bus/usb/serial/common.c139
-rw-r--r--grub-core/bus/usb/serial/ftdi.c218
-rw-r--r--grub-core/bus/usb/serial/pl2303.c231
-rw-r--r--grub-core/bus/usb/serial/usbdebug_late.c93
-rw-r--r--grub-core/bus/usb/uhci.c871
-rw-r--r--grub-core/bus/usb/usb.c346
-rw-r--r--grub-core/bus/usb/usbhub.c756
-rw-r--r--grub-core/bus/usb/usbtrans.c462
19 files changed, 7885 insertions, 0 deletions
diff --git a/grub-core/bus/bonito.c b/grub-core/bus/bonito.c
new file mode 100644
index 0000000..9a63f07
--- /dev/null
+++ b/grub-core/bus/bonito.c
@@ -0,0 +1,176 @@
+/* bonito.c - PCI bonito interface. */
+/*
+ * GRUB -- GRand Unified Bootloader
+ * Copyright (C) 2009 Free Software Foundation, Inc.
+ *
+ * GRUB is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * GRUB is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with GRUB. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <grub/pci.h>
+#include <grub/misc.h>
+
+static grub_uint32_t base_win[GRUB_MACHINE_PCI_NUM_WIN];
+static const grub_size_t sizes_win[GRUB_MACHINE_PCI_NUM_WIN] =
+ {GRUB_MACHINE_PCI_WIN1_SIZE, GRUB_MACHINE_PCI_WIN_SIZE,
+ GRUB_MACHINE_PCI_WIN_SIZE};
+/* Usage counters. */
+static int usage_win[GRUB_MACHINE_PCI_NUM_WIN];
+static grub_addr_t addr_win[GRUB_MACHINE_PCI_NUM_WIN] =
+ {GRUB_MACHINE_PCI_WIN1_ADDR, GRUB_MACHINE_PCI_WIN2_ADDR,
+ GRUB_MACHINE_PCI_WIN3_ADDR};
+
+grub_bonito_type_t grub_bonito_type;
+
+static volatile void *
+config_addr (grub_pci_address_t addr)
+{
+ if (grub_bonito_type == GRUB_BONITO_2F)
+ {
+ GRUB_MACHINE_PCI_CONF_CTRL_REG_2F = 1 << ((addr >> 11) & 0xf);
+ return (volatile void *) (GRUB_MACHINE_PCI_CONFSPACE_2F
+ | (addr & 0x07ff));
+ }
+ else
+ {
+
+ if (addr >> 16)
+ return (volatile void *) (GRUB_MACHINE_PCI_CONFSPACE_3A_EXT | addr);
+ else
+ return (volatile void *) (GRUB_MACHINE_PCI_CONFSPACE_3A | addr);
+ }
+}
+
+grub_uint32_t
+grub_pci_read (grub_pci_address_t addr)
+{
+ return *(volatile grub_uint32_t *) config_addr (addr);
+}
+
+grub_uint16_t
+grub_pci_read_word (grub_pci_address_t addr)
+{
+ return *(volatile grub_uint16_t *) config_addr (addr);
+}
+
+grub_uint8_t
+grub_pci_read_byte (grub_pci_address_t addr)
+{
+ return *(volatile grub_uint8_t *) config_addr (addr);
+}
+
+void
+grub_pci_write (grub_pci_address_t addr, grub_uint32_t data)
+{
+ *(volatile grub_uint32_t *) config_addr (addr) = data;
+}
+
+void
+grub_pci_write_word (grub_pci_address_t addr, grub_uint16_t data)
+{
+ *(volatile grub_uint16_t *) config_addr (addr) = data;
+}
+
+void
+grub_pci_write_byte (grub_pci_address_t addr, grub_uint8_t data)
+{
+ *(volatile grub_uint8_t *) config_addr (addr) = data;
+}
+
+
+static inline void
+write_bases_2f (void)
+{
+ int i;
+ grub_uint32_t reg = 0;
+ for (i = 0; i < GRUB_MACHINE_PCI_NUM_WIN; i++)
+ reg |= (((base_win[i] >> GRUB_MACHINE_PCI_WIN_SHIFT)
+ & GRUB_MACHINE_PCI_WIN_MASK)
+ << (i * GRUB_MACHINE_PCI_WIN_MASK_SIZE));
+ GRUB_MACHINE_PCI_IO_CTRL_REG_2F = reg;
+}
+
+volatile void *
+grub_pci_device_map_range (grub_pci_device_t dev __attribute__ ((unused)),
+ grub_addr_t base, grub_size_t size)
+{
+ if (grub_bonito_type == GRUB_BONITO_2F)
+ {
+ int i;
+ grub_addr_t newbase;
+
+ /* First try already used registers. */
+ for (i = 0; i < GRUB_MACHINE_PCI_NUM_WIN; i++)
+ if (usage_win[i] && base_win[i] <= base
+ && base_win[i] + sizes_win[i] > base + size)
+ {
+ usage_win[i]++;
+ return (void *)
+ (addr_win[i] | (base & GRUB_MACHINE_PCI_WIN_OFFSET_MASK));
+ }
+ /* Map new register. */
+ newbase = base & ~GRUB_MACHINE_PCI_WIN_OFFSET_MASK;
+ for (i = 0; i < GRUB_MACHINE_PCI_NUM_WIN; i++)
+ if (!usage_win[i] && newbase <= base
+ && newbase + sizes_win[i] > base + size)
+ {
+ usage_win[i]++;
+ base_win[i] = newbase;
+ write_bases_2f ();
+ return (void *)
+ (addr_win[i] | (base & GRUB_MACHINE_PCI_WIN_OFFSET_MASK));
+ }
+ grub_fatal ("Out of PCI windows.");
+ }
+ else
+ {
+ int region = 0;
+ if (base >= 0x10000000
+ && base + size <= 0x18000000)
+ region = 1;
+ if (base >= 0x1c000000
+ && base + size <= 0x1f000000)
+ region = 2;
+ if (region == 0)
+ grub_fatal ("Attempt to map out of regions");
+ return (void *) (0xa0000000 | base);
+ }
+}
+
+void *
+grub_pci_device_map_range_cached (grub_pci_device_t dev,
+ grub_addr_t base, grub_size_t size)
+{
+ return (void *) (((grub_addr_t) grub_pci_device_map_range (dev, base, size))
+ & ~0x20000000);
+}
+
+void
+grub_pci_device_unmap_range (grub_pci_device_t dev __attribute__ ((unused)),
+ volatile void *mem,
+ grub_size_t size __attribute__ ((unused)))
+{
+ if (grub_bonito_type == GRUB_BONITO_2F)
+ {
+ int i;
+ for (i = 0; i < GRUB_MACHINE_PCI_NUM_WIN; i++)
+ if (usage_win[i] && addr_win[i]
+ == (((grub_addr_t) mem | 0x20000000)
+ & ~GRUB_MACHINE_PCI_WIN_OFFSET_MASK))
+ {
+ usage_win[i]--;
+ return;
+ }
+ grub_fatal ("Tried to unmap not mapped region");
+ }
+}
diff --git a/grub-core/bus/cs5536.c b/grub-core/bus/cs5536.c
new file mode 100644
index 0000000..bb9aa27
--- /dev/null
+++ b/grub-core/bus/cs5536.c
@@ -0,0 +1,381 @@
+/*
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+#include <grub/types.h>
+#include <grub/cs5536.h>
+#include <grub/pci.h>
+#include <grub/time.h>
+#include <grub/ata.h>
+#ifdef GRUB_MACHINE_MIPS_LOONGSON
+#include <grub/machine/kernel.h>
+#endif
+
+#include <grub/dl.h>
+
+GRUB_MOD_LICENSE ("GPLv3+");
+
+/* Context for grub_cs5536_find. */
+struct grub_cs5536_find_ctx
+{
+ grub_pci_device_t *devp;
+ int found;
+};
+
+/* Helper for grub_cs5536_find. */
+static int
+grub_cs5536_find_iter (grub_pci_device_t dev, grub_pci_id_t pciid, void *data)
+{
+ struct grub_cs5536_find_ctx *ctx = data;
+
+ if (pciid == GRUB_CS5536_PCIID)
+ {
+ *ctx->devp = dev;
+ ctx->found = 1;
+ return 1;
+ }
+ return 0;
+}
+
+int
+grub_cs5536_find (grub_pci_device_t *devp)
+{
+ struct grub_cs5536_find_ctx ctx = {
+ .devp = devp,
+ .found = 0
+ };
+
+ grub_pci_iterate (grub_cs5536_find_iter, &ctx);
+
+ return ctx.found;
+}
+
+grub_uint64_t
+grub_cs5536_read_msr (grub_pci_device_t dev, grub_uint32_t addr)
+{
+ grub_uint64_t ret = 0;
+ grub_pci_write (grub_pci_make_address (dev, GRUB_CS5536_MSR_MAILBOX_ADDR),
+ addr);
+ ret = (grub_uint64_t)
+ grub_pci_read (grub_pci_make_address (dev, GRUB_CS5536_MSR_MAILBOX_DATA0));
+ ret |= (((grub_uint64_t)
+ grub_pci_read (grub_pci_make_address (dev,
+ GRUB_CS5536_MSR_MAILBOX_DATA1)))
+ << 32);
+ return ret;
+}
+
+void
+grub_cs5536_write_msr (grub_pci_device_t dev, grub_uint32_t addr,
+ grub_uint64_t val)
+{
+ grub_pci_write (grub_pci_make_address (dev, GRUB_CS5536_MSR_MAILBOX_ADDR),
+ addr);
+ grub_pci_write (grub_pci_make_address (dev, GRUB_CS5536_MSR_MAILBOX_DATA0),
+ val & 0xffffffff);
+ grub_pci_write (grub_pci_make_address (dev, GRUB_CS5536_MSR_MAILBOX_DATA1),
+ val >> 32);
+}
+
+grub_err_t
+grub_cs5536_smbus_wait (grub_port_t smbbase)
+{
+ grub_uint64_t start = grub_get_time_ms ();
+ while (1)
+ {
+ grub_uint8_t status;
+ status = grub_inb (smbbase + GRUB_CS5536_SMB_REG_STATUS);
+ if (status & GRUB_CS5536_SMB_REG_STATUS_SDAST)
+ return GRUB_ERR_NONE;
+ if (status & GRUB_CS5536_SMB_REG_STATUS_BER)
+ return grub_error (GRUB_ERR_IO, "SM bus error");
+ if (status & GRUB_CS5536_SMB_REG_STATUS_NACK)
+ return grub_error (GRUB_ERR_IO, "NACK received");
+ if (grub_get_time_ms () > start + 40)
+ return grub_error (GRUB_ERR_IO, "SM stalled");
+ }
+}
+
+grub_err_t
+grub_cs5536_read_spd_byte (grub_port_t smbbase, grub_uint8_t dev,
+ grub_uint8_t addr, grub_uint8_t *res)
+{
+ grub_err_t err;
+
+ /* Send START. */
+ grub_outb (grub_inb (smbbase + GRUB_CS5536_SMB_REG_CTRL1)
+ | GRUB_CS5536_SMB_REG_CTRL1_START,
+ smbbase + GRUB_CS5536_SMB_REG_CTRL1);
+
+ /* Send device address. */
+ err = grub_cs5536_smbus_wait (smbbase);
+ if (err)
+ return err;
+ grub_outb (dev << 1, smbbase + GRUB_CS5536_SMB_REG_DATA);
+
+ /* Send ACK. */
+ err = grub_cs5536_smbus_wait (smbbase);
+ if (err)
+ return err;
+ grub_outb (grub_inb (smbbase + GRUB_CS5536_SMB_REG_CTRL1)
+ | GRUB_CS5536_SMB_REG_CTRL1_ACK,
+ smbbase + GRUB_CS5536_SMB_REG_CTRL1);
+
+ /* Send byte address. */
+ grub_outb (addr, smbbase + GRUB_CS5536_SMB_REG_DATA);
+
+ /* Send START. */
+ err = grub_cs5536_smbus_wait (smbbase);
+ if (err)
+ return err;
+ grub_outb (grub_inb (smbbase + GRUB_CS5536_SMB_REG_CTRL1)
+ | GRUB_CS5536_SMB_REG_CTRL1_START,
+ smbbase + GRUB_CS5536_SMB_REG_CTRL1);
+
+ /* Send device address. */
+ err = grub_cs5536_smbus_wait (smbbase);
+ if (err)
+ return err;
+ grub_outb ((dev << 1) | 1, smbbase + GRUB_CS5536_SMB_REG_DATA);
+
+ /* Send STOP. */
+ err = grub_cs5536_smbus_wait (smbbase);
+ if (err)
+ return err;
+ grub_outb (grub_inb (smbbase + GRUB_CS5536_SMB_REG_CTRL1)
+ | GRUB_CS5536_SMB_REG_CTRL1_STOP,
+ smbbase + GRUB_CS5536_SMB_REG_CTRL1);
+
+ err = grub_cs5536_smbus_wait (smbbase);
+ if (err)
+ return err;
+ *res = grub_inb (smbbase + GRUB_CS5536_SMB_REG_DATA);
+
+ return GRUB_ERR_NONE;
+}
+
+grub_err_t
+grub_cs5536_init_smbus (grub_pci_device_t dev, grub_uint16_t divisor,
+ grub_port_t *smbbase)
+{
+ grub_uint64_t smbbar;
+
+ smbbar = grub_cs5536_read_msr (dev, GRUB_CS5536_MSR_SMB_BAR);
+
+ /* FIXME */
+ if (!(smbbar & GRUB_CS5536_LBAR_ENABLE))
+ return grub_error(GRUB_ERR_IO, "SMB controller not enabled\n");
+ *smbbase = (smbbar & GRUB_CS5536_LBAR_ADDR_MASK) + GRUB_MACHINE_PCI_IO_BASE;
+
+ if (divisor < 8)
+ return grub_error (GRUB_ERR_BAD_ARGUMENT, "invalid divisor");
+
+ /* Disable SMB. */
+ grub_outb (0, *smbbase + GRUB_CS5536_SMB_REG_CTRL2);
+
+ /* Disable interrupts. */
+ grub_outb (0, *smbbase + GRUB_CS5536_SMB_REG_CTRL1);
+
+ /* Set as master. */
+ grub_outb (GRUB_CS5536_SMB_REG_ADDR_MASTER,
+ *smbbase + GRUB_CS5536_SMB_REG_ADDR);
+
+ /* Launch. */
+ grub_outb (((divisor >> 7) & 0xff), *smbbase + GRUB_CS5536_SMB_REG_CTRL3);
+ grub_outb (((divisor << 1) & 0xfe) | GRUB_CS5536_SMB_REG_CTRL2_ENABLE,
+ *smbbase + GRUB_CS5536_SMB_REG_CTRL2);
+
+ return GRUB_ERR_NONE;
+}
+
+grub_err_t
+grub_cs5536_read_spd (grub_port_t smbbase, grub_uint8_t dev,
+ struct grub_smbus_spd *res)
+{
+ grub_err_t err;
+ grub_size_t size;
+ grub_uint8_t b;
+ grub_size_t ptr;
+
+ err = grub_cs5536_read_spd_byte (smbbase, dev, 0, &b);
+ if (err)
+ return err;
+ if (b == 0)
+ return grub_error (GRUB_ERR_IO, "no SPD found");
+ size = b;
+
+ ((grub_uint8_t *) res)[0] = b;
+ for (ptr = 1; ptr < size; ptr++)
+ {
+ err = grub_cs5536_read_spd_byte (smbbase, dev, ptr,
+ &((grub_uint8_t *) res)[ptr]);
+ if (err)
+ return err;
+ }
+ return GRUB_ERR_NONE;
+}
+
+static inline void
+set_io_space (grub_pci_device_t dev, int num, grub_uint16_t start,
+ grub_uint16_t len)
+{
+ grub_cs5536_write_msr (dev, GRUB_CS5536_MSR_GL_REGIONS_START + num,
+ ((((grub_uint64_t) start + len - 4)
+ << GRUB_CS5536_MSR_GL_REGION_IO_TOP_SHIFT)
+ & GRUB_CS5536_MSR_GL_REGION_TOP_MASK)
+ | (((grub_uint64_t) start
+ << GRUB_CS5536_MSR_GL_REGION_IO_BASE_SHIFT)
+ & GRUB_CS5536_MSR_GL_REGION_BASE_MASK)
+ | GRUB_CS5536_MSR_GL_REGION_IO
+ | GRUB_CS5536_MSR_GL_REGION_ENABLE);
+}
+
+static inline void
+set_iod (grub_pci_device_t dev, int num, int dest, int start, int mask)
+{
+ grub_cs5536_write_msr (dev, GRUB_CS5536_MSR_GL_IOD_START + num,
+ ((grub_uint64_t) dest << GRUB_CS5536_IOD_DEST_SHIFT)
+ | (((grub_uint64_t) start & GRUB_CS5536_IOD_ADDR_MASK)
+ << GRUB_CS5536_IOD_BASE_SHIFT)
+ | ((mask & GRUB_CS5536_IOD_ADDR_MASK)
+ << GRUB_CS5536_IOD_MASK_SHIFT));
+}
+
+static inline void
+set_p2d (grub_pci_device_t dev, int num, int dest, grub_uint32_t start)
+{
+ grub_cs5536_write_msr (dev, GRUB_CS5536_MSR_GL_P2D_START + num,
+ (((grub_uint64_t) dest) << GRUB_CS5536_P2D_DEST_SHIFT)
+ | ((grub_uint64_t) (start >> GRUB_CS5536_P2D_LOG_ALIGN)
+ << GRUB_CS5536_P2D_BASE_SHIFT)
+ | (((1 << (32 - GRUB_CS5536_P2D_LOG_ALIGN)) - 1)
+ << GRUB_CS5536_P2D_MASK_SHIFT));
+}
+
+void
+grub_cs5536_init_geode (grub_pci_device_t dev)
+{
+ /* Enable more BARs. */
+ grub_cs5536_write_msr (dev, GRUB_CS5536_MSR_IRQ_MAP_BAR,
+ GRUB_CS5536_LBAR_TURN_ON | GRUB_CS5536_LBAR_IRQ_MAP);
+ grub_cs5536_write_msr (dev, GRUB_CS5536_MSR_MFGPT_BAR,
+ GRUB_CS5536_LBAR_TURN_ON | GRUB_CS5536_LBAR_MFGPT);
+ grub_cs5536_write_msr (dev, GRUB_CS5536_MSR_ACPI_BAR,
+ GRUB_CS5536_LBAR_TURN_ON | GRUB_CS5536_LBAR_ACPI);
+ grub_cs5536_write_msr (dev, GRUB_CS5536_MSR_PM_BAR,
+ GRUB_CS5536_LBAR_TURN_ON | GRUB_CS5536_LBAR_PM);
+
+ /* Setup DIVIL. */
+#ifdef GRUB_MACHINE_MIPS_LOONGSON
+ switch (grub_arch_machine)
+ {
+ case GRUB_ARCH_MACHINE_YEELOONG:
+ grub_cs5536_write_msr (dev, GRUB_CS5536_MSR_DIVIL_LEG_IO,
+ GRUB_CS5536_MSR_DIVIL_LEG_IO_MODE_X86
+ | GRUB_CS5536_MSR_DIVIL_LEG_IO_F_REMAP
+ | GRUB_CS5536_MSR_DIVIL_LEG_IO_RTC_ENABLE0
+ | GRUB_CS5536_MSR_DIVIL_LEG_IO_RTC_ENABLE1);
+ break;
+ case GRUB_ARCH_MACHINE_FULOONG2F:
+ grub_cs5536_write_msr (dev, GRUB_CS5536_MSR_DIVIL_LEG_IO,
+ GRUB_CS5536_MSR_DIVIL_LEG_IO_UART2_COM3
+ | GRUB_CS5536_MSR_DIVIL_LEG_IO_UART1_COM1
+ | GRUB_CS5536_MSR_DIVIL_LEG_IO_MODE_X86
+ | GRUB_CS5536_MSR_DIVIL_LEG_IO_F_REMAP
+ | GRUB_CS5536_MSR_DIVIL_LEG_IO_RTC_ENABLE0
+ | GRUB_CS5536_MSR_DIVIL_LEG_IO_RTC_ENABLE1);
+ break;
+ }
+#endif
+ grub_cs5536_write_msr (dev, GRUB_CS5536_MSR_DIVIL_IRQ_MAPPER_PRIMARY_MASK,
+ (~GRUB_CS5536_DIVIL_LPC_INTERRUPTS) & 0xffff);
+ grub_cs5536_write_msr (dev, GRUB_CS5536_MSR_DIVIL_IRQ_MAPPER_LPC_MASK,
+ GRUB_CS5536_DIVIL_LPC_INTERRUPTS);
+ grub_cs5536_write_msr (dev, GRUB_CS5536_MSR_DIVIL_LPC_SERIAL_IRQ_CONTROL,
+ GRUB_CS5536_MSR_DIVIL_LPC_SERIAL_IRQ_CONTROL_ENABLE);
+
+ /* Initialise USB controller. */
+ /* FIXME: assign adresses dynamically. */
+ grub_cs5536_write_msr (dev, GRUB_CS5536_MSR_USB_OHCI_BASE,
+ GRUB_CS5536_MSR_USB_BASE_BUS_MASTER
+ | GRUB_CS5536_MSR_USB_BASE_MEMORY_ENABLE
+ | 0x05024000);
+ grub_cs5536_write_msr (dev, GRUB_CS5536_MSR_USB_EHCI_BASE,
+ GRUB_CS5536_MSR_USB_BASE_BUS_MASTER
+ | GRUB_CS5536_MSR_USB_BASE_MEMORY_ENABLE
+ | (0x20ULL << GRUB_CS5536_MSR_USB_EHCI_BASE_FLDJ_SHIFT)
+ | 0x05023000);
+ grub_cs5536_write_msr (dev, GRUB_CS5536_MSR_USB_CONTROLLER_BASE,
+ GRUB_CS5536_MSR_USB_BASE_BUS_MASTER
+ | GRUB_CS5536_MSR_USB_BASE_MEMORY_ENABLE | 0x05020000);
+ grub_cs5536_write_msr (dev, GRUB_CS5536_MSR_USB_OPTION_CONTROLLER_BASE,
+ GRUB_CS5536_MSR_USB_BASE_MEMORY_ENABLE | 0x05022000);
+ set_p2d (dev, 0, GRUB_CS5536_DESTINATION_USB, 0x05020000);
+ set_p2d (dev, 1, GRUB_CS5536_DESTINATION_USB, 0x05022000);
+ set_p2d (dev, 5, GRUB_CS5536_DESTINATION_USB, 0x05024000);
+ set_p2d (dev, 6, GRUB_CS5536_DESTINATION_USB, 0x05023000);
+
+ {
+ volatile grub_uint32_t *oc;
+ oc = grub_pci_device_map_range (dev, 0x05022000,
+ GRUB_CS5536_USB_OPTION_REGS_SIZE);
+
+ oc[GRUB_CS5536_USB_OPTION_REG_UOCMUX] =
+ (oc[GRUB_CS5536_USB_OPTION_REG_UOCMUX]
+ & ~GRUB_CS5536_USB_OPTION_REG_UOCMUX_PMUX_MASK)
+ | GRUB_CS5536_USB_OPTION_REG_UOCMUX_PMUX_HC;
+ grub_pci_device_unmap_range (dev, oc, GRUB_CS5536_USB_OPTION_REGS_SIZE);
+ }
+
+ /* Setup IDE controller. */
+ grub_cs5536_write_msr (dev, GRUB_CS5536_MSR_IDE_IO_BAR,
+ GRUB_CS5536_LBAR_IDE
+ | GRUB_CS5536_MSR_IDE_IO_BAR_UNITS);
+ grub_cs5536_write_msr (dev, GRUB_CS5536_MSR_IDE_CFG,
+ GRUB_CS5536_MSR_IDE_CFG_CHANNEL_ENABLE);
+ grub_cs5536_write_msr (dev, GRUB_CS5536_MSR_IDE_TIMING,
+ (GRUB_CS5536_MSR_IDE_TIMING_PIO0
+ << GRUB_CS5536_MSR_IDE_TIMING_DRIVE0_SHIFT)
+ | (GRUB_CS5536_MSR_IDE_TIMING_PIO0
+ << GRUB_CS5536_MSR_IDE_TIMING_DRIVE1_SHIFT));
+ grub_cs5536_write_msr (dev, GRUB_CS5536_MSR_IDE_CAS_TIMING,
+ (GRUB_CS5536_MSR_IDE_CAS_TIMING_CMD_PIO0
+ << GRUB_CS5536_MSR_IDE_CAS_TIMING_CMD_SHIFT)
+ | (GRUB_CS5536_MSR_IDE_CAS_TIMING_PIO0
+ << GRUB_CS5536_MSR_IDE_CAS_TIMING_DRIVE0_SHIFT)
+ | (GRUB_CS5536_MSR_IDE_CAS_TIMING_PIO0
+ << GRUB_CS5536_MSR_IDE_CAS_TIMING_DRIVE1_SHIFT));
+
+ /* Setup Geodelink PCI. */
+ grub_cs5536_write_msr (dev, GRUB_CS5536_MSR_GL_PCI_CTRL,
+ (4ULL << GRUB_CS5536_MSR_GL_PCI_CTRL_OUT_THR_SHIFT)
+ | (4ULL << GRUB_CS5536_MSR_GL_PCI_CTRL_IN_THR_SHIFT)
+ | (8ULL << GRUB_CS5536_MSR_GL_PCI_CTRL_LATENCY_SHIFT)
+ | GRUB_CS5536_MSR_GL_PCI_CTRL_IO_ENABLE
+ | GRUB_CS5536_MSR_GL_PCI_CTRL_MEMORY_ENABLE);
+
+ /* Setup windows. */
+ set_io_space (dev, 0, GRUB_CS5536_LBAR_SMBUS, GRUB_CS5536_SMBUS_REGS_SIZE);
+ set_io_space (dev, 1, GRUB_CS5536_LBAR_GPIO, GRUB_CS5536_GPIO_REGS_SIZE);
+ set_io_space (dev, 2, GRUB_CS5536_LBAR_MFGPT, GRUB_CS5536_MFGPT_REGS_SIZE);
+ set_io_space (dev, 3, GRUB_CS5536_LBAR_IRQ_MAP, GRUB_CS5536_IRQ_MAP_REGS_SIZE);
+ set_io_space (dev, 4, GRUB_CS5536_LBAR_PM, GRUB_CS5536_PM_REGS_SIZE);
+ set_io_space (dev, 5, GRUB_CS5536_LBAR_ACPI, GRUB_CS5536_ACPI_REGS_SIZE);
+ set_iod (dev, 0, GRUB_CS5536_DESTINATION_IDE, GRUB_ATA_CH0_PORT1, 0xffff8);
+ set_iod (dev, 1, GRUB_CS5536_DESTINATION_ACC, GRUB_CS5536_LBAR_ACC, 0xfff80);
+ set_iod (dev, 2, GRUB_CS5536_DESTINATION_IDE, GRUB_CS5536_LBAR_IDE, 0xffff0);
+}
diff --git a/grub-core/bus/emu/pci.c b/grub-core/bus/emu/pci.c
new file mode 100644
index 0000000..267f262
--- /dev/null
+++ b/grub-core/bus/emu/pci.c
@@ -0,0 +1,78 @@
+/* pci.c - Generic PCI interfaces. */
+/*
+ * GRUB -- GRand Unified Bootloader
+ * Copyright (C) 2007,2009 Free Software Foundation, Inc.
+ *
+ * GRUB is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * GRUB is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with GRUB. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <grub/pci.h>
+#include <grub/dl.h>
+#include <grub/emu/misc.h>
+#include <grub/util/misc.h>
+
+grub_pci_address_t
+grub_pci_make_address (grub_pci_device_t dev, int reg)
+{
+ grub_pci_address_t ret;
+ ret.dev = dev;
+ ret.pos = reg;
+ return ret;
+}
+
+void
+grub_pci_iterate (grub_pci_iteratefunc_t hook, void *hook_data)
+{
+ struct pci_device_iterator *iter;
+ struct pci_slot_match slot;
+ struct pci_device *dev;
+ slot.domain = PCI_MATCH_ANY;
+ slot.bus = PCI_MATCH_ANY;
+ slot.dev = PCI_MATCH_ANY;
+ slot.func = PCI_MATCH_ANY;
+ iter = pci_slot_match_iterator_create (&slot);
+ while ((dev = pci_device_next (iter)))
+ hook (dev, dev->vendor_id | (dev->device_id << 16), hook_data);
+ pci_iterator_destroy (iter);
+}
+
+void *
+grub_pci_device_map_range (grub_pci_device_t dev, grub_addr_t base,
+ grub_size_t size)
+{
+ void *addr;
+ int err;
+ err = pci_device_map_range (dev, base, size, PCI_DEV_MAP_FLAG_WRITABLE, &addr);
+ if (err)
+ grub_util_error ("mapping 0x%llx failed (error %d)",
+ (unsigned long long) base, err);
+ return addr;
+}
+
+void
+grub_pci_device_unmap_range (grub_pci_device_t dev, void *mem,
+ grub_size_t size)
+{
+ pci_device_unmap_range (dev, mem, size);
+}
+
+GRUB_MOD_INIT (emupci)
+{
+ pci_system_init ();
+}
+
+GRUB_MOD_FINI (emupci)
+{
+ pci_system_cleanup ();
+}
diff --git a/grub-core/bus/fdt.c b/grub-core/bus/fdt.c
new file mode 100644
index 0000000..135da49
--- /dev/null
+++ b/grub-core/bus/fdt.c
@@ -0,0 +1,256 @@
+/*
+ * GRUB -- GRand Unified Bootloader
+ * Copyright (C) 2016 Free Software Foundation, Inc.
+ *
+ * GRUB is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * GRUB is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with GRUB. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <grub/fdtbus.h>
+#include <grub/fdt.h>
+#include <grub/term.h>
+
+static const void *dtb;
+static grub_size_t root_address_cells, root_size_cells;
+/* Pointer to this symbol signals invalid mapping. */
+char grub_fdtbus_invalid_mapping[1];
+
+struct grub_fdtbus_dev *devs;
+struct grub_fdtbus_driver *drivers;
+
+int
+grub_fdtbus_is_compatible (const char *compat_string,
+ const struct grub_fdtbus_dev *dev)
+{
+ grub_size_t compatible_size;
+ const char *compatible = grub_fdt_get_prop (dtb, dev->node, "compatible",
+ &compatible_size);
+ if (!compatible)
+ return 0;
+ const char *compatible_end = compatible + compatible_size;
+ while (compatible < compatible_end)
+ {
+ if (grub_strcmp (compat_string, compatible) == 0)
+ return 1;
+ compatible += grub_strlen (compatible) + 1;
+ }
+ return 0;
+}
+
+static void
+fdtbus_scan (struct grub_fdtbus_dev *parent)
+{
+ int node;
+ for (node = grub_fdt_first_node (dtb, parent ? parent->node : 0); node >= 0;
+ node = grub_fdt_next_node (dtb, node))
+ {
+ struct grub_fdtbus_dev *dev;
+ struct grub_fdtbus_driver *driver;
+ dev = grub_zalloc (sizeof (*dev));
+ if (!dev)
+ {
+ grub_print_error ();
+ return;
+ }
+ dev->node = node;
+ dev->next = devs;
+ dev->parent = parent;
+ devs = dev;
+ FOR_LIST_ELEMENTS(driver, drivers)
+ if (!dev->driver && grub_fdtbus_is_compatible (driver->compatible, dev))
+ {
+ grub_dprintf ("fdtbus", "Attaching %s\n", driver->compatible);
+ if (driver->attach (dev) == GRUB_ERR_NONE)
+ {
+ grub_dprintf ("fdtbus", "Attached %s\n", driver->compatible);
+ dev->driver = driver;
+ break;
+ }
+ grub_print_error ();
+ }
+ fdtbus_scan (dev);
+ }
+}
+
+void
+grub_fdtbus_register (struct grub_fdtbus_driver *driver)
+{
+ struct grub_fdtbus_dev *dev;
+ grub_dprintf ("fdtbus", "Registering %s\n", driver->compatible);
+ grub_list_push (GRUB_AS_LIST_P (&drivers),
+ GRUB_AS_LIST (driver));
+ for (dev = devs; dev; dev = dev->next)
+ if (!dev->driver && grub_fdtbus_is_compatible (driver->compatible, dev))
+ {
+ grub_dprintf ("fdtbus", "Attaching %s (%p)\n", driver->compatible, dev);
+ if (driver->attach (dev) == GRUB_ERR_NONE)
+ {
+ grub_dprintf ("fdtbus", "Attached %s\n", driver->compatible);
+ dev->driver = driver;
+ }
+ grub_print_error ();
+ }
+}
+
+void
+grub_fdtbus_unregister (struct grub_fdtbus_driver *driver)
+{
+ grub_list_remove (GRUB_AS_LIST (driver));
+ struct grub_fdtbus_dev *dev;
+ for (dev = devs; dev; dev = dev->next)
+ if (dev->driver == driver)
+ {
+ if (driver->detach)
+ driver->detach(dev);
+ dev->driver = 0;
+ }
+}
+
+void
+grub_fdtbus_init (const void *dtb_in, grub_size_t size)
+{
+ if (!dtb_in || grub_fdt_check_header (dtb_in, size) < 0)
+ grub_fatal ("invalid FDT");
+ dtb = dtb_in;
+ const grub_uint32_t *prop = grub_fdt_get_prop (dtb, 0, "#address-cells", 0);
+ if (prop)
+ root_address_cells = grub_be_to_cpu32 (*prop);
+ else
+ root_address_cells = 1;
+
+ prop = grub_fdt_get_prop (dtb, 0, "#size-cells", 0);
+ if (prop)
+ root_size_cells = grub_be_to_cpu32 (*prop);
+ else
+ root_size_cells = 1;
+
+ fdtbus_scan (0);
+}
+
+static int
+get_address_cells (const struct grub_fdtbus_dev *dev)
+{
+ const grub_uint32_t *prop;
+ if (!dev)
+ return root_address_cells;
+ prop = grub_fdt_get_prop (dtb, dev->node, "#address-cells", 0);
+ if (prop)
+ return grub_be_to_cpu32 (*prop);
+ return 1;
+}
+
+static int
+get_size_cells (const struct grub_fdtbus_dev *dev)
+{
+ const grub_uint32_t *prop;
+ if (!dev)
+ return root_size_cells;
+ prop = grub_fdt_get_prop (dtb, dev->node, "#size-cells", 0);
+ if (prop)
+ return grub_be_to_cpu32 (*prop);
+ return 1;
+}
+
+static grub_uint64_t
+get64 (const grub_uint32_t *reg, grub_size_t cells)
+{
+ grub_uint64_t val = 0;
+ if (cells >= 1)
+ val = grub_be_to_cpu32 (reg[cells - 1]);
+ if (cells >= 2)
+ val |= ((grub_uint64_t) grub_be_to_cpu32 (reg[cells - 2])) << 32;
+ return val;
+}
+
+static volatile void *
+translate (const struct grub_fdtbus_dev *dev, const grub_uint32_t *reg)
+{
+ volatile void *ret;
+ const grub_uint32_t *ranges;
+ grub_size_t ranges_size, cells_per_mapping;
+ grub_size_t parent_address_cells, child_address_cells, child_size_cells;
+ grub_size_t nmappings, i;
+ if (dev == 0)
+ {
+ grub_uint64_t val;
+ val = get64 (reg, root_address_cells);
+ if (sizeof (void *) == 4 && (val >> 32))
+ return grub_fdtbus_invalid_mapping;
+ return (void *) (grub_addr_t) val;
+ }
+ ranges = grub_fdt_get_prop (dtb, dev->node, "ranges", &ranges_size);
+ if (!ranges)
+ return grub_fdtbus_invalid_mapping;
+ if (ranges_size == 0)
+ return translate (dev->parent, reg);
+ parent_address_cells = get_address_cells (dev->parent);
+ child_address_cells = get_address_cells (dev);
+ child_size_cells = get_size_cells (dev);
+ cells_per_mapping = parent_address_cells + child_address_cells + child_size_cells;
+ nmappings = ranges_size / 4 / cells_per_mapping;
+ for (i = 0; i < nmappings; i++)
+ {
+ const grub_uint32_t *child_addr = &ranges[i * cells_per_mapping];
+ const grub_uint32_t *parent_addr = child_addr + child_address_cells;
+ grub_uint64_t child_size = get64 (parent_addr + parent_address_cells, child_size_cells);
+
+ if (child_address_cells > 2 && grub_memcmp (reg, child_addr, (child_address_cells - 2) * 4) != 0)
+ continue;
+ if (get64 (reg, child_address_cells) < get64 (child_addr, child_address_cells))
+ continue;
+
+ grub_uint64_t offset = get64 (reg, child_address_cells) - get64 (child_addr, child_address_cells);
+ if (offset >= child_size)
+ continue;
+
+ ret = translate (dev->parent, parent_addr);
+ if (grub_fdtbus_is_mapping_valid (ret))
+ ret = (volatile char *) ret + offset;
+ return ret;
+ }
+ return grub_fdtbus_invalid_mapping;
+}
+
+volatile void *
+grub_fdtbus_map_reg (const struct grub_fdtbus_dev *dev, int regno, grub_size_t *size)
+{
+ grub_size_t address_cells, size_cells;
+ address_cells = get_address_cells (dev->parent);
+ size_cells = get_size_cells (dev->parent);
+ const grub_uint32_t *reg = grub_fdt_get_prop (dtb, dev->node, "reg", 0);
+ if (size && size_cells)
+ *size = reg[(address_cells + size_cells) * regno + address_cells];
+ if (size && !size_cells)
+ *size = 0;
+ return translate (dev->parent, reg + (address_cells + size_cells) * regno);
+}
+
+const char *
+grub_fdtbus_get_name (const struct grub_fdtbus_dev *dev)
+{
+ return grub_fdt_get_nodename (dtb, dev->node);
+}
+
+const void *
+grub_fdtbus_get_prop (const struct grub_fdtbus_dev *dev,
+ const char *name,
+ grub_uint32_t *len)
+{
+ return grub_fdt_get_prop (dtb, dev->node, name, len);
+}
+
+const void *
+grub_fdtbus_get_fdt (void)
+{
+ return dtb;
+}
diff --git a/grub-core/bus/i386/ieee1275/pci.c b/grub-core/bus/i386/ieee1275/pci.c
new file mode 100644
index 0000000..1fd3b56
--- /dev/null
+++ b/grub-core/bus/i386/ieee1275/pci.c
@@ -0,0 +1,42 @@
+/*
+ * GRUB -- GRand Unified Bootloader
+ * Copyright (C) 2013 Free Software Foundation, Inc.
+ *
+ * GRUB is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * GRUB is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with GRUB. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <grub/dl.h>
+#include <grub/pci.h>
+#include <grub/mm.h>
+#include <grub/misc.h>
+#include <grub/ieee1275/ieee1275.h>
+
+volatile void *
+grub_pci_device_map_range (grub_pci_device_t dev __attribute__ ((unused)),
+ grub_addr_t base,
+ grub_size_t size)
+{
+ if (grub_ieee1275_test_flag (GRUB_IEEE1275_FLAG_REAL_MODE))
+ return (volatile void *) base;
+ if (grub_ieee1275_map (base, base, size, 7))
+ grub_fatal ("couldn't map 0x%lx", base);
+ return (volatile void *) base;
+}
+
+void
+grub_pci_device_unmap_range (grub_pci_device_t dev __attribute__ ((unused)),
+ volatile void *mem __attribute__ ((unused)),
+ grub_size_t size __attribute__ ((unused)))
+{
+}
diff --git a/grub-core/bus/pci.c b/grub-core/bus/pci.c
new file mode 100644
index 0000000..b388ce5
--- /dev/null
+++ b/grub-core/bus/pci.c
@@ -0,0 +1,173 @@
+/* pci.c - Generic PCI interfaces. */
+/*
+ * GRUB -- GRand Unified Bootloader
+ * Copyright (C) 2007,2009 Free Software Foundation, Inc.
+ *
+ * GRUB is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * GRUB is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with GRUB. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <grub/dl.h>
+#include <grub/pci.h>
+#include <grub/mm.h>
+#include <grub/misc.h>
+#include <grub/mm_private.h>
+#include <grub/cache.h>
+
+GRUB_MOD_LICENSE ("GPLv3+");
+
+/* FIXME: correctly support 64-bit architectures. */
+/* #if GRUB_TARGET_SIZEOF_VOID_P == 4 */
+struct grub_pci_dma_chunk *
+grub_memalign_dma32 (grub_size_t align, grub_size_t size)
+{
+ void *ret;
+ if (align < 64)
+ align = 64;
+ size = ALIGN_UP (size, align);
+ ret = grub_memalign (align, size);
+#if GRUB_CPU_SIZEOF_VOID_P == 8
+ if ((grub_addr_t) ret >> 32)
+ {
+ /* Shouldn't happend since the only platform in this case is
+ x86_64-efi and it skips any regions > 4GiB because
+ of EFI bugs anyway. */
+ grub_error (GRUB_ERR_BUG, "allocation outside 32-bit range");
+ return 0;
+ }
+#endif
+ if (!ret)
+ return 0;
+ grub_arch_sync_dma_caches (ret, size);
+ return ret;
+}
+
+/* FIXME: evil. */
+void
+grub_dma_free (struct grub_pci_dma_chunk *ch)
+{
+ grub_size_t size = (((struct grub_mm_header *) ch) - 1)->size * GRUB_MM_ALIGN;
+ grub_arch_sync_dma_caches (ch, size);
+ grub_free (ch);
+}
+/* #endif */
+
+#ifdef GRUB_MACHINE_MIPS_LOONGSON
+volatile void *
+grub_dma_get_virt (struct grub_pci_dma_chunk *ch)
+{
+ return (void *) ((((grub_uint32_t) ch) & 0x1fffffff) | 0xa0000000);
+}
+
+grub_uint32_t
+grub_dma_get_phys (struct grub_pci_dma_chunk *ch)
+{
+ return (((grub_uint32_t) ch) & 0x1fffffff) | 0x80000000;
+}
+#else
+
+volatile void *
+grub_dma_get_virt (struct grub_pci_dma_chunk *ch)
+{
+ return (void *) ch;
+}
+
+grub_uint32_t
+grub_dma_get_phys (struct grub_pci_dma_chunk *ch)
+{
+ return (grub_uint32_t) (grub_addr_t) ch;
+}
+
+#endif
+
+grub_pci_address_t
+grub_pci_make_address (grub_pci_device_t dev, int reg)
+{
+ return (1 << 31) | (dev.bus << 16) | (dev.device << 11)
+ | (dev.function << 8) | reg;
+}
+
+void
+grub_pci_iterate (grub_pci_iteratefunc_t hook, void *hook_data)
+{
+ grub_pci_device_t dev;
+ grub_pci_address_t addr;
+ grub_pci_id_t id;
+ grub_uint32_t hdr;
+
+ for (dev.bus = 0; dev.bus < GRUB_PCI_NUM_BUS; dev.bus++)
+ {
+ for (dev.device = 0; dev.device < GRUB_PCI_NUM_DEVICES; dev.device++)
+ {
+ for (dev.function = 0; dev.function < 8; dev.function++)
+ {
+ addr = grub_pci_make_address (dev, GRUB_PCI_REG_PCI_ID);
+ id = grub_pci_read (addr);
+
+ /* Check if there is a device present. */
+ if (id >> 16 == 0xFFFF)
+ {
+ if (dev.function == 0)
+ /* Devices are required to implement function 0, so if
+ it's missing then there is no device here. */
+ break;
+ else
+ continue;
+ }
+
+ if (hook (dev, id, hook_data))
+ return;
+
+ /* Probe only func = 0 if the device if not multifunction */
+ if (dev.function == 0)
+ {
+ addr = grub_pci_make_address (dev, GRUB_PCI_REG_CACHELINE);
+ hdr = grub_pci_read (addr);
+ if (!(hdr & 0x800000))
+ break;
+ }
+ }
+ }
+ }
+}
+
+grub_uint8_t
+grub_pci_find_capability (grub_pci_device_t dev, grub_uint8_t cap)
+{
+ grub_uint8_t pos = 0x34;
+ int ttl = 48;
+
+ while (ttl--)
+ {
+ grub_uint8_t id;
+ grub_pci_address_t addr;
+
+ addr = grub_pci_make_address (dev, pos);
+ pos = grub_pci_read_byte (addr);
+ if (pos < 0x40)
+ break;
+
+ pos &= ~3;
+
+ addr = grub_pci_make_address (dev, pos);
+ id = grub_pci_read_byte (addr);
+
+ if (id == 0xff)
+ break;
+
+ if (id == cap)
+ return pos;
+ pos++;
+ }
+ return 0;
+}
diff --git a/grub-core/bus/spi/rk3288_spi.c b/grub-core/bus/spi/rk3288_spi.c
new file mode 100644
index 0000000..aacb79f
--- /dev/null
+++ b/grub-core/bus/spi/rk3288_spi.c
@@ -0,0 +1,103 @@
+/*
+ * GRUB -- GRand Unified Bootloader
+ *
+ * Copyright (C) 2012 Google Inc.
+ * Copyright (C) 2016 Free Software Foundation, Inc.
+ *
+ * This is based on depthcharge code.
+ *
+ * GRUB is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * GRUB is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with GRUB. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <grub/mm.h>
+#include <grub/time.h>
+#include <grub/misc.h>
+#include <grub/fdtbus.h>
+#include <grub/machine/kernel.h>
+
+static grub_err_t
+spi_send (const struct grub_fdtbus_dev *dev, const void *data, grub_size_t sz)
+{
+ const grub_uint8_t *ptr = data, *end = ptr + sz;
+ volatile grub_uint32_t *spi = grub_fdtbus_map_reg (dev, 0, 0);
+ spi[2] = 0;
+ spi[1] = sz - 1;
+ spi[0] = ((1 << 18) | spi[0]) & ~(1 << 19);
+ spi[2] = 1;
+ while (ptr < end)
+ {
+ while (spi[9] & 2);
+ spi[256] = *ptr++;
+ }
+ while (spi[9] & 1);
+ return GRUB_ERR_NONE;
+}
+
+static grub_err_t
+spi_receive (const struct grub_fdtbus_dev *dev, void *data, grub_size_t sz)
+{
+ grub_uint8_t *ptr = data, *end = ptr + sz;
+ volatile grub_uint32_t *spi = grub_fdtbus_map_reg (dev, 0, 0);
+ spi[2] = 0;
+ spi[1] = sz - 1;
+ spi[0] = ((1 << 19) | spi[0]) & ~(1 << 18);
+ spi[2] = 1;
+ while (ptr < end)
+ {
+ while (spi[9] & 8);
+ *ptr++ = spi[512];
+ }
+ while (spi[9] & 1);
+ return GRUB_ERR_NONE;
+}
+
+static grub_err_t
+spi_start (const struct grub_fdtbus_dev *dev)
+{
+ volatile grub_uint32_t *spi = grub_fdtbus_map_reg (dev, 0, 0);
+ spi[3] = 1;
+ return GRUB_ERR_NONE;
+}
+
+static void
+spi_stop (const struct grub_fdtbus_dev *dev)
+{
+ volatile grub_uint32_t *spi = grub_fdtbus_map_reg (dev, 0, 0);
+ spi[3] = 0;
+}
+
+static grub_err_t
+spi_attach(const struct grub_fdtbus_dev *dev)
+{
+ if (!grub_fdtbus_is_mapping_valid (grub_fdtbus_map_reg (dev, 0, 0)))
+ return GRUB_ERR_IO;
+
+ return GRUB_ERR_NONE;
+}
+
+static struct grub_fdtbus_driver spi =
+{
+ .compatible = "rockchip,rk3288-spi",
+ .attach = spi_attach,
+ .send = spi_send,
+ .receive = spi_receive,
+ .start = spi_start,
+ .stop = spi_stop,
+};
+
+void
+grub_rk3288_spi_init (void)
+{
+ grub_fdtbus_register (&spi);
+}
diff --git a/grub-core/bus/usb/ehci-fdt.c b/grub-core/bus/usb/ehci-fdt.c
new file mode 100644
index 0000000..29b50bd
--- /dev/null
+++ b/grub-core/bus/usb/ehci-fdt.c
@@ -0,0 +1,45 @@
+/* ehci.c - EHCI Support. */
+/*
+ * GRUB -- GRand Unified Bootloader
+ * Copyright (C) 2011 Free Software Foundation, Inc.
+ *
+ * GRUB is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * GRUB is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with GRUB. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <grub/misc.h>
+#include <grub/mm.h>
+#include <grub/time.h>
+#include <grub/usb.h>
+#include <grub/fdtbus.h>
+
+static grub_err_t
+ehci_attach(const struct grub_fdtbus_dev *dev)
+{
+ grub_dprintf ("ehci", "Found generic-ehci\n");
+
+ grub_ehci_init_device (grub_fdtbus_map_reg (dev, 0, 0));
+ return 0;
+}
+
+struct grub_fdtbus_driver ehci =
+{
+ .compatible = "generic-ehci",
+ .attach = ehci_attach
+};
+
+void
+grub_ehci_pci_scan (void)
+{
+ grub_fdtbus_register (&ehci);
+}
diff --git a/grub-core/bus/usb/ehci-pci.c b/grub-core/bus/usb/ehci-pci.c
new file mode 100644
index 0000000..65e6cb5
--- /dev/null
+++ b/grub-core/bus/usb/ehci-pci.c
@@ -0,0 +1,208 @@
+/* ehci.c - EHCI Support. */
+/*
+ * GRUB -- GRand Unified Bootloader
+ * Copyright (C) 2011 Free Software Foundation, Inc.
+ *
+ * GRUB is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * GRUB is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with GRUB. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <grub/pci.h>
+#include <grub/cpu/pci.h>
+#include <grub/cs5536.h>
+#include <grub/misc.h>
+#include <grub/mm.h>
+#include <grub/time.h>
+#include <grub/usb.h>
+
+#define GRUB_EHCI_PCI_SBRN_REG 0x60
+#define GRUB_EHCI_ADDR_MEM_MASK (~0xff)
+
+/* USBLEGSUP bits and related OS OWNED byte offset */
+enum
+{
+ GRUB_EHCI_BIOS_OWNED = (1 << 16),
+ GRUB_EHCI_OS_OWNED = (1 << 24)
+};
+
+/* PCI iteration function... */
+static int
+grub_ehci_pci_iter (grub_pci_device_t dev, grub_pci_id_t pciid,
+ void *data __attribute__ ((unused)))
+{
+ volatile grub_uint32_t *regs;
+ grub_uint32_t base, base_h;
+ grub_uint32_t eecp_offset;
+ grub_uint32_t usblegsup = 0;
+ grub_uint64_t maxtime;
+ grub_uint32_t interf;
+ grub_uint32_t subclass;
+ grub_uint32_t class;
+ grub_uint8_t release;
+ grub_uint32_t class_code;
+
+ grub_dprintf ("ehci", "EHCI grub_ehci_pci_iter: begin\n");
+
+ if (pciid == GRUB_CS5536_PCIID)
+ {
+ grub_uint64_t basereg;
+
+ basereg = grub_cs5536_read_msr (dev, GRUB_CS5536_MSR_USB_EHCI_BASE);
+ if (!(basereg & GRUB_CS5536_MSR_USB_BASE_MEMORY_ENABLE))
+ {
+ /* Shouldn't happen. */
+ grub_dprintf ("ehci", "No EHCI address is assigned\n");
+ return 0;
+ }
+ base = (basereg & GRUB_CS5536_MSR_USB_BASE_ADDR_MASK);
+ basereg |= GRUB_CS5536_MSR_USB_BASE_BUS_MASTER;
+ basereg &= ~GRUB_CS5536_MSR_USB_BASE_PME_ENABLED;
+ basereg &= ~GRUB_CS5536_MSR_USB_BASE_PME_STATUS;
+ basereg &= ~GRUB_CS5536_MSR_USB_BASE_SMI_ENABLE;
+ grub_cs5536_write_msr (dev, GRUB_CS5536_MSR_USB_EHCI_BASE, basereg);
+ }
+ else
+ {
+ grub_pci_address_t addr;
+ addr = grub_pci_make_address (dev, GRUB_PCI_REG_CLASS);
+ class_code = grub_pci_read (addr) >> 8;
+ interf = class_code & 0xFF;
+ subclass = (class_code >> 8) & 0xFF;
+ class = class_code >> 16;
+
+ /* If this is not an EHCI controller, just return. */
+ if (class != 0x0c || subclass != 0x03 || interf != 0x20)
+ return 0;
+
+ grub_dprintf ("ehci", "EHCI grub_ehci_pci_iter: class OK\n");
+
+ /* Check Serial Bus Release Number */
+ addr = grub_pci_make_address (dev, GRUB_EHCI_PCI_SBRN_REG);
+ release = grub_pci_read_byte (addr);
+ if (release != 0x20)
+ {
+ grub_dprintf ("ehci", "EHCI grub_ehci_pci_iter: Wrong SBRN: %0x\n",
+ release);
+ return 0;
+ }
+ grub_dprintf ("ehci", "EHCI grub_ehci_pci_iter: bus rev. num. OK\n");
+
+ /* Determine EHCI EHCC registers base address. */
+ addr = grub_pci_make_address (dev, GRUB_PCI_REG_ADDRESS_REG0);
+ base = grub_pci_read (addr);
+ addr = grub_pci_make_address (dev, GRUB_PCI_REG_ADDRESS_REG1);
+ base_h = grub_pci_read (addr);
+ /* Stop if registers are mapped above 4G - GRUB does not currently
+ * work with registers mapped above 4G */
+ if (((base & GRUB_PCI_ADDR_MEM_TYPE_MASK) != GRUB_PCI_ADDR_MEM_TYPE_32)
+ && (base_h != 0))
+ {
+ grub_dprintf ("ehci",
+ "EHCI grub_ehci_pci_iter: registers above 4G are not supported\n");
+ return 0;
+ }
+ base &= GRUB_PCI_ADDR_MEM_MASK;
+ if (!base)
+ {
+ grub_dprintf ("ehci",
+ "EHCI: EHCI is not mapped\n");
+ return 0;
+ }
+
+ /* Set bus master - needed for coreboot, VMware, broken BIOSes etc. */
+ addr = grub_pci_make_address (dev, GRUB_PCI_REG_COMMAND);
+ grub_pci_write_word(addr,
+ GRUB_PCI_COMMAND_MEM_ENABLED
+ | GRUB_PCI_COMMAND_BUS_MASTER
+ | grub_pci_read_word(addr));
+
+ grub_dprintf ("ehci", "EHCI grub_ehci_pci_iter: 32-bit EHCI OK\n");
+ }
+
+ grub_dprintf ("ehci", "EHCI grub_ehci_pci_iter: iobase of EHCC: %08x\n",
+ (base & GRUB_EHCI_ADDR_MEM_MASK));
+
+ regs = grub_pci_device_map_range (dev,
+ (base & GRUB_EHCI_ADDR_MEM_MASK),
+ 0x100);
+
+ /* Is there EECP ? */
+ eecp_offset = (grub_le_to_cpu32 (regs[2]) >> 8) & 0xff;
+
+ /* Determine and change ownership. */
+ /* EECP offset valid in HCCPARAMS */
+ /* Ownership can be changed via EECP only */
+ if (pciid != GRUB_CS5536_PCIID && eecp_offset >= 0x40)
+ {
+ grub_pci_address_t pciaddr_eecp;
+ pciaddr_eecp = grub_pci_make_address (dev, eecp_offset);
+
+ usblegsup = grub_pci_read (pciaddr_eecp);
+ if (usblegsup & GRUB_EHCI_BIOS_OWNED)
+ {
+ grub_boot_time ("Taking ownership of EHCI controller");
+ grub_dprintf ("ehci",
+ "EHCI grub_ehci_pci_iter: EHCI owned by: BIOS\n");
+ /* Ownership change - set OS_OWNED bit */
+ grub_pci_write (pciaddr_eecp, usblegsup | GRUB_EHCI_OS_OWNED);
+ /* Ensure PCI register is written */
+ grub_pci_read (pciaddr_eecp);
+
+ /* Wait for finish of ownership change, EHCI specification
+ * doesn't say how long it can take... */
+ maxtime = grub_get_time_ms () + 1000;
+ while ((grub_pci_read (pciaddr_eecp) & GRUB_EHCI_BIOS_OWNED)
+ && (grub_get_time_ms () < maxtime));
+ if (grub_pci_read (pciaddr_eecp) & GRUB_EHCI_BIOS_OWNED)
+ {
+ grub_dprintf ("ehci",
+ "EHCI grub_ehci_pci_iter: EHCI change ownership timeout");
+ /* Change ownership in "hard way" - reset BIOS ownership */
+ grub_pci_write (pciaddr_eecp, GRUB_EHCI_OS_OWNED);
+ /* Ensure PCI register is written */
+ grub_pci_read (pciaddr_eecp);
+ }
+ }
+ else if (usblegsup & GRUB_EHCI_OS_OWNED)
+ /* XXX: What to do in this case - nothing ? Can it happen ? */
+ grub_dprintf ("ehci", "EHCI grub_ehci_pci_iter: EHCI owned by: OS\n");
+ else
+ {
+ grub_dprintf ("ehci",
+ "EHCI grub_ehci_pci_iter: EHCI owned by: NONE\n");
+ /* XXX: What to do in this case ? Can it happen ?
+ * Is code below correct ? */
+ /* Ownership change - set OS_OWNED bit */
+ grub_pci_write (pciaddr_eecp, GRUB_EHCI_OS_OWNED);
+ /* Ensure PCI register is written */
+ grub_pci_read (pciaddr_eecp);
+ }
+
+ /* Disable SMI, just to be sure. */
+ pciaddr_eecp = grub_pci_make_address (dev, eecp_offset + 4);
+ grub_pci_write (pciaddr_eecp, 0);
+ /* Ensure PCI register is written */
+ grub_pci_read (pciaddr_eecp);
+ }
+
+ grub_dprintf ("ehci", "inithw: EHCI grub_ehci_pci_iter: ownership OK\n");
+
+ grub_ehci_init_device (regs);
+ return 0;
+}
+
+void
+grub_ehci_pci_scan (void)
+{
+ grub_pci_iterate (grub_ehci_pci_iter, NULL);
+}
diff --git a/grub-core/bus/usb/ehci.c b/grub-core/bus/usb/ehci.c
new file mode 100644
index 0000000..d966fc2
--- /dev/null
+++ b/grub-core/bus/usb/ehci.c
@@ -0,0 +1,1839 @@
+/* ehci.c - EHCI Support. */
+/*
+ * GRUB -- GRand Unified Bootloader
+ * Copyright (C) 2011 Free Software Foundation, Inc.
+ *
+ * GRUB is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * GRUB is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with GRUB. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <grub/dl.h>
+#include <grub/mm.h>
+#include <grub/usb.h>
+#include <grub/usbtrans.h>
+#include <grub/misc.h>
+#include <grub/time.h>
+#include <grub/loader.h>
+#include <grub/disk.h>
+#include <grub/dma.h>
+#include <grub/cache.h>
+
+GRUB_MOD_LICENSE ("GPLv3+");
+
+/* This simple GRUB implementation of EHCI driver:
+ * - assumes no IRQ
+ * - is not supporting isochronous transfers (iTD, siTD)
+ * - is not supporting interrupt transfers
+ */
+
+/* Capability registers offsets */
+enum
+{
+ GRUB_EHCI_EHCC_CAPLEN = 0x00, /* byte */
+ GRUB_EHCI_EHCC_VERSION = 0x02, /* word */
+ GRUB_EHCI_EHCC_SPARAMS = 0x04, /* dword */
+ GRUB_EHCI_EHCC_CPARAMS = 0x08, /* dword */
+ GRUB_EHCI_EHCC_PROUTE = 0x0c, /* 60 bits */
+};
+
+#define GRUB_EHCI_EECP_MASK (0xff << 8)
+#define GRUB_EHCI_EECP_SHIFT 8
+
+#define GRUB_EHCI_POINTER_MASK (~0x1f)
+
+/* Capability register SPARAMS bits */
+enum
+{
+ GRUB_EHCI_SPARAMS_N_PORTS = (0xf << 0),
+ GRUB_EHCI_SPARAMS_PPC = (1 << 4), /* Power port control */
+ GRUB_EHCI_SPARAMS_PRR = (1 << 7), /* Port routing rules */
+ GRUB_EHCI_SPARAMS_N_PCC = (0xf << 8), /* No of ports per comp. */
+ GRUB_EHCI_SPARAMS_NCC = (0xf << 12), /* No of com. controllers */
+ GRUB_EHCI_SPARAMS_P_IND = (1 << 16), /* Port indicators present */
+ GRUB_EHCI_SPARAMS_DEBUG_P = (0xf << 20) /* Debug port */
+};
+
+#define GRUB_EHCI_MAX_N_PORTS 15 /* Max. number of ports */
+
+/* Capability register CPARAMS bits */
+enum
+{
+ GRUB_EHCI_CPARAMS_64BIT = (1 << 0),
+ GRUB_EHCI_CPARAMS_PROG_FRAMELIST = (1 << 1),
+ GRUB_EHCI_CPARAMS_PARK_CAP = (1 << 2)
+};
+
+#define GRUB_EHCI_N_FRAMELIST 1024
+#define GRUB_EHCI_N_QH 256
+#define GRUB_EHCI_N_TD 640
+
+#define GRUB_EHCI_QH_EMPTY 1
+
+/* Operational registers offsets */
+enum
+{
+ GRUB_EHCI_COMMAND = 0x00,
+ GRUB_EHCI_STATUS = 0x04,
+ GRUB_EHCI_INTERRUPT = 0x08,
+ GRUB_EHCI_FRAME_INDEX = 0x0c,
+ GRUB_EHCI_64BIT_SEL = 0x10,
+ GRUB_EHCI_FL_BASE = 0x14,
+ GRUB_EHCI_CUR_AL_ADDR = 0x18,
+ GRUB_EHCI_CONFIG_FLAG = 0x40,
+ GRUB_EHCI_PORT_STAT_CMD = 0x44
+};
+
+/* Operational register COMMAND bits */
+enum
+{
+ GRUB_EHCI_CMD_RUNSTOP = (1 << 0),
+ GRUB_EHCI_CMD_HC_RESET = (1 << 1),
+ GRUB_EHCI_CMD_FL_SIZE = (3 << 2),
+ GRUB_EHCI_CMD_PS_ENABL = (1 << 4),
+ GRUB_EHCI_CMD_AS_ENABL = (1 << 5),
+ GRUB_EHCI_CMD_AS_ADV_D = (1 << 6),
+ GRUB_EHCI_CMD_L_HC_RES = (1 << 7),
+ GRUB_EHCI_CMD_AS_PARKM = (3 << 8),
+ GRUB_EHCI_CMD_AS_PARKE = (1 << 11),
+ GRUB_EHCI_CMD_INT_THRS = (0xff << 16)
+};
+
+/* Operational register STATUS bits */
+enum
+{
+ GRUB_EHCI_ST_INTERRUPT = (1 << 0),
+ GRUB_EHCI_ST_ERROR_INT = (1 << 1),
+ GRUB_EHCI_ST_PORT_CHG = (1 << 2),
+ GRUB_EHCI_ST_FL_ROLLOVR = (1 << 3),
+ GRUB_EHCI_ST_HS_ERROR = (1 << 4),
+ GRUB_EHCI_ST_AS_ADVANCE = (1 << 5),
+ GRUB_EHCI_ST_HC_HALTED = (1 << 12),
+ GRUB_EHCI_ST_RECLAM = (1 << 13),
+ GRUB_EHCI_ST_PS_STATUS = (1 << 14),
+ GRUB_EHCI_ST_AS_STATUS = (1 << 15)
+};
+
+/* Operational register PORT_STAT_CMD bits */
+enum
+{
+ GRUB_EHCI_PORT_CONNECT = (1 << 0),
+ GRUB_EHCI_PORT_CONNECT_CH = (1 << 1),
+ GRUB_EHCI_PORT_ENABLED = (1 << 2),
+ GRUB_EHCI_PORT_ENABLED_CH = (1 << 3),
+ GRUB_EHCI_PORT_OVERCUR = (1 << 4),
+ GRUB_EHCI_PORT_OVERCUR_CH = (1 << 5),
+ GRUB_EHCI_PORT_RESUME = (1 << 6),
+ GRUB_EHCI_PORT_SUSPEND = (1 << 7),
+ GRUB_EHCI_PORT_RESET = (1 << 8),
+ GRUB_EHCI_PORT_LINE_STAT = (3 << 10),
+ GRUB_EHCI_PORT_POWER = (1 << 12),
+ GRUB_EHCI_PORT_OWNER = (1 << 13),
+ GRUB_EHCI_PORT_INDICATOR = (3 << 14),
+ GRUB_EHCI_PORT_TEST = (0xf << 16),
+ GRUB_EHCI_PORT_WON_CONN_E = (1 << 20),
+ GRUB_EHCI_PORT_WON_DISC_E = (1 << 21),
+ GRUB_EHCI_PORT_WON_OVER_E = (1 << 22),
+
+ GRUB_EHCI_PORT_LINE_SE0 = (0 << 10),
+ GRUB_EHCI_PORT_LINE_K = (1 << 10),
+ GRUB_EHCI_PORT_LINE_J = (2 << 10),
+ GRUB_EHCI_PORT_LINE_UNDEF = (3 << 10),
+ GRUB_EHCI_PORT_LINE_LOWSP = GRUB_EHCI_PORT_LINE_K, /* K state means low speed */
+ GRUB_EHCI_PORT_WMASK = ~(GRUB_EHCI_PORT_CONNECT_CH
+ | GRUB_EHCI_PORT_ENABLED_CH
+ | GRUB_EHCI_PORT_OVERCUR_CH)
+};
+
+/* Operational register CONFIGFLAGS bits */
+enum
+{
+ GRUB_EHCI_CF_EHCI_OWNER = (1 << 0)
+};
+
+/* Queue Head & Transfer Descriptor constants */
+#define GRUB_EHCI_HPTR_OFF 5 /* Horiz. pointer bit offset */
+enum
+{
+ GRUB_EHCI_HPTR_TYPE_MASK = (3 << 1),
+ GRUB_EHCI_HPTR_TYPE_ITD = (0 << 1),
+ GRUB_EHCI_HPTR_TYPE_QH = (1 << 1),
+ GRUB_EHCI_HPTR_TYPE_SITD = (2 << 1),
+ GRUB_EHCI_HPTR_TYPE_FSTN = (3 << 1)
+};
+
+enum
+{
+ GRUB_EHCI_C = (1 << 27),
+ GRUB_EHCI_MAXPLEN_MASK = (0x7ff << 16),
+ GRUB_EHCI_H = (1 << 15),
+ GRUB_EHCI_DTC = (1 << 14),
+ GRUB_EHCI_SPEED_MASK = (3 << 12),
+ GRUB_EHCI_SPEED_FULL = (0 << 12),
+ GRUB_EHCI_SPEED_LOW = (1 << 12),
+ GRUB_EHCI_SPEED_HIGH = (2 << 12),
+ GRUB_EHCI_SPEED_RESERVED = (3 << 12),
+ GRUB_EHCI_EP_NUM_MASK = (0xf << 8),
+ GRUB_EHCI_DEVADDR_MASK = 0x7f,
+ GRUB_EHCI_TARGET_MASK = (GRUB_EHCI_EP_NUM_MASK | GRUB_EHCI_DEVADDR_MASK)
+};
+
+enum
+{
+ GRUB_EHCI_MAXPLEN_OFF = 16,
+ GRUB_EHCI_SPEED_OFF = 12,
+ GRUB_EHCI_EP_NUM_OFF = 8
+};
+
+enum
+{
+ GRUB_EHCI_MULT_MASK = (3 << 30),
+ GRUB_EHCI_MULT_RESERVED = (0 << 30),
+ GRUB_EHCI_MULT_ONE = (1 << 30),
+ GRUB_EHCI_MULT_TWO = (2 << 30),
+ GRUB_EHCI_MULT_THREE = (3 << 30),
+ GRUB_EHCI_DEVPORT_MASK = (0x7f << 23),
+ GRUB_EHCI_HUBADDR_MASK = (0x7f << 16),
+ GRUB_EHCI_CMASK_MASK = (0xff << 8),
+ GRUB_EHCI_SMASK_MASK = (0xff << 0),
+};
+
+enum
+{
+ GRUB_EHCI_MULT_OFF = 30,
+ GRUB_EHCI_DEVPORT_OFF = 23,
+ GRUB_EHCI_HUBADDR_OFF = 16,
+ GRUB_EHCI_CMASK_OFF = 8,
+ GRUB_EHCI_SMASK_OFF = 0,
+};
+
+#define GRUB_EHCI_TERMINATE (1<<0)
+
+#define GRUB_EHCI_TOGGLE (1<<31)
+
+enum
+{
+ GRUB_EHCI_TOTAL_MASK = (0x7fff << 16),
+ GRUB_EHCI_CERR_MASK = (3 << 10),
+ GRUB_EHCI_CERR_0 = (0 << 10),
+ GRUB_EHCI_CERR_1 = (1 << 10),
+ GRUB_EHCI_CERR_2 = (2 << 10),
+ GRUB_EHCI_CERR_3 = (3 << 10),
+ GRUB_EHCI_PIDCODE_OUT = (0 << 8),
+ GRUB_EHCI_PIDCODE_IN = (1 << 8),
+ GRUB_EHCI_PIDCODE_SETUP = (2 << 8),
+ GRUB_EHCI_STATUS_MASK = 0xff,
+ GRUB_EHCI_STATUS_ACTIVE = (1 << 7),
+ GRUB_EHCI_STATUS_HALTED = (1 << 6),
+ GRUB_EHCI_STATUS_BUFERR = (1 << 5),
+ GRUB_EHCI_STATUS_BABBLE = (1 << 4),
+ GRUB_EHCI_STATUS_TRANERR = (1 << 3),
+ GRUB_EHCI_STATUS_MISSDMF = (1 << 2),
+ GRUB_EHCI_STATUS_SPLITST = (1 << 1),
+ GRUB_EHCI_STATUS_PINGERR = (1 << 0)
+};
+
+enum
+{
+ GRUB_EHCI_TOTAL_OFF = 16,
+ GRUB_EHCI_CERR_OFF = 10
+};
+
+#define GRUB_EHCI_BUFPTR_MASK (0xfffff<<12)
+#define GRUB_EHCI_QHTDPTR_MASK 0xffffffe0
+
+#define GRUB_EHCI_TD_BUF_PAGES 5
+
+#define GRUB_EHCI_BUFPAGELEN 0x1000
+#define GRUB_EHCI_MAXBUFLEN 0x5000
+
+struct grub_ehci_td;
+struct grub_ehci_qh;
+typedef volatile struct grub_ehci_td *grub_ehci_td_t;
+typedef volatile struct grub_ehci_qh *grub_ehci_qh_t;
+
+/* EHCI Isochronous Transfer Descriptor */
+/* Currently not supported */
+
+/* EHCI Split Transaction Isochronous Transfer Descriptor */
+/* Currently not supported */
+
+/* EHCI Queue Element Transfer Descriptor (qTD) */
+/* Align to 32-byte boundaries */
+struct grub_ehci_td
+{
+ /* EHCI HW part */
+ grub_uint32_t next_td; /* Pointer to next qTD */
+ grub_uint32_t alt_next_td; /* Pointer to alternate next qTD */
+ grub_uint32_t token; /* Toggle, Len, Interrupt, Page, Error, PID, Status */
+ grub_uint32_t buffer_page[GRUB_EHCI_TD_BUF_PAGES]; /* Buffer pointer (+ cur. offset in page 0 */
+ /* 64-bits part */
+ grub_uint32_t buffer_page_high[GRUB_EHCI_TD_BUF_PAGES];
+ /* EHCI driver part */
+ grub_uint32_t link_td; /* pointer to next free/chained TD */
+ grub_uint32_t size;
+ grub_uint32_t pad[1]; /* padding to some multiple of 32 bytes */
+};
+
+/* EHCI Queue Head */
+/* Align to 32-byte boundaries */
+/* QH allocation is made in the similar/same way as in OHCI driver,
+ * because unlninking QH from the Asynchronous list is not so
+ * trivial as on UHCI (at least it is time consuming) */
+struct grub_ehci_qh
+{
+ /* EHCI HW part */
+ grub_uint32_t qh_hptr; /* Horiz. pointer & Terminate */
+ grub_uint32_t ep_char; /* EP characteristics */
+ grub_uint32_t ep_cap; /* EP capabilities */
+ grub_uint32_t td_current; /* current TD link pointer */
+ struct grub_ehci_td td_overlay; /* TD overlay area = 64 bytes */
+ /* EHCI driver part */
+ grub_uint32_t pad[4]; /* padding to some multiple of 32 bytes */
+};
+
+/* EHCI Periodic Frame Span Traversal Node */
+/* Currently not supported */
+
+struct grub_ehci
+{
+ volatile grub_uint32_t *iobase_ehcc; /* Capability registers */
+ volatile grub_uint32_t *iobase; /* Operational registers */
+ struct grub_pci_dma_chunk *framelist_chunk; /* Currently not used */
+ volatile grub_uint32_t *framelist_virt;
+ grub_uint32_t framelist_phys;
+ struct grub_pci_dma_chunk *qh_chunk; /* GRUB_EHCI_N_QH Queue Heads */
+ grub_ehci_qh_t qh_virt;
+ grub_uint32_t qh_phys;
+ struct grub_pci_dma_chunk *td_chunk; /* GRUB_EHCI_N_TD Transfer Descriptors */
+ grub_ehci_td_t td_virt;
+ grub_uint32_t td_phys;
+ grub_ehci_td_t tdfree_virt; /* Free Transfer Descriptors */
+ int flag64;
+ grub_uint32_t reset; /* bits 1-15 are flags if port was reset from connected time or not */
+ struct grub_ehci *next;
+};
+
+static struct grub_ehci *ehci;
+
+static void
+sync_all_caches (struct grub_ehci *e)
+{
+ if (!e)
+ return;
+ if (e->td_virt)
+ grub_arch_sync_dma_caches (e->td_virt, sizeof (struct grub_ehci_td) *
+ GRUB_EHCI_N_TD);
+ if (e->qh_virt)
+ grub_arch_sync_dma_caches (e->qh_virt, sizeof (struct grub_ehci_qh) *
+ GRUB_EHCI_N_QH);
+ if (e->framelist_virt)
+ grub_arch_sync_dma_caches (e->framelist_virt, 4096);
+}
+
+/* EHCC registers access functions */
+static inline grub_uint32_t
+grub_ehci_ehcc_read32 (struct grub_ehci *e, grub_uint32_t addr)
+{
+ return
+ grub_le_to_cpu32 (*((volatile grub_uint32_t *) e->iobase_ehcc +
+ (addr / sizeof (grub_uint32_t))));
+}
+
+static inline grub_uint16_t
+grub_ehci_ehcc_read16 (struct grub_ehci *e, grub_uint32_t addr)
+{
+ return
+ grub_le_to_cpu16 (*((volatile grub_uint16_t *) e->iobase_ehcc +
+ (addr / sizeof (grub_uint16_t))));
+}
+
+static inline grub_uint8_t
+grub_ehci_ehcc_read8 (struct grub_ehci *e, grub_uint32_t addr)
+{
+ return *((volatile grub_uint8_t *) e->iobase_ehcc + addr);
+}
+
+/* Operational registers access functions */
+static inline grub_uint32_t
+grub_ehci_oper_read32 (struct grub_ehci *e, grub_uint32_t addr)
+{
+ return
+ grub_le_to_cpu32 (*
+ ((volatile grub_uint32_t *) e->iobase +
+ (addr / sizeof (grub_uint32_t))));
+}
+
+static inline void
+grub_ehci_oper_write32 (struct grub_ehci *e, grub_uint32_t addr,
+ grub_uint32_t value)
+{
+ *((volatile grub_uint32_t *) e->iobase + (addr / sizeof (grub_uint32_t))) =
+ grub_cpu_to_le32 (value);
+}
+
+static inline grub_uint32_t
+grub_ehci_port_read (struct grub_ehci *e, grub_uint32_t port)
+{
+ return grub_ehci_oper_read32 (e, GRUB_EHCI_PORT_STAT_CMD + port * 4);
+}
+
+static inline void
+grub_ehci_port_resbits (struct grub_ehci *e, grub_uint32_t port,
+ grub_uint32_t bits)
+{
+ grub_ehci_oper_write32 (e, GRUB_EHCI_PORT_STAT_CMD + port * 4,
+ grub_ehci_port_read (e,
+ port) & GRUB_EHCI_PORT_WMASK &
+ ~(bits));
+ grub_ehci_port_read (e, port);
+}
+
+static inline void
+grub_ehci_port_setbits (struct grub_ehci *e, grub_uint32_t port,
+ grub_uint32_t bits)
+{
+ grub_ehci_oper_write32 (e, GRUB_EHCI_PORT_STAT_CMD + port * 4,
+ (grub_ehci_port_read (e, port) &
+ GRUB_EHCI_PORT_WMASK) | bits);
+ grub_ehci_port_read (e, port);
+}
+
+/* Halt if EHCI HC not halted */
+static grub_usb_err_t
+grub_ehci_halt (struct grub_ehci *e)
+{
+ grub_uint64_t maxtime;
+
+ if ((grub_ehci_oper_read32 (e, GRUB_EHCI_STATUS) & GRUB_EHCI_ST_HC_HALTED) == 0) /* EHCI is not halted */
+ {
+ /* Halt EHCI */
+ grub_ehci_oper_write32 (e, GRUB_EHCI_COMMAND,
+ ~GRUB_EHCI_CMD_RUNSTOP
+ & grub_ehci_oper_read32 (e, GRUB_EHCI_COMMAND));
+ /* Ensure command is written */
+ grub_ehci_oper_read32 (e, GRUB_EHCI_COMMAND);
+ maxtime = grub_get_time_ms () + 1000; /* Fix: Should be 2ms ! */
+ while (((grub_ehci_oper_read32 (e, GRUB_EHCI_STATUS)
+ & GRUB_EHCI_ST_HC_HALTED) == 0)
+ && (grub_get_time_ms () < maxtime));
+ if ((grub_ehci_oper_read32 (e, GRUB_EHCI_STATUS)
+ & GRUB_EHCI_ST_HC_HALTED) == 0)
+ return GRUB_USB_ERR_TIMEOUT;
+ }
+
+ return GRUB_USB_ERR_NONE;
+}
+
+/* EHCI HC reset */
+static grub_usb_err_t
+grub_ehci_reset (struct grub_ehci *e)
+{
+ grub_uint64_t maxtime;
+
+ sync_all_caches (e);
+
+ grub_dprintf ("ehci", "reset\n");
+
+ grub_ehci_oper_write32 (e, GRUB_EHCI_COMMAND,
+ GRUB_EHCI_CMD_HC_RESET);
+ /* Ensure command is written */
+ grub_ehci_oper_read32 (e, GRUB_EHCI_COMMAND);
+ /* XXX: How long time could take reset of HC ? */
+ maxtime = grub_get_time_ms () + 1000;
+ while (((grub_ehci_oper_read32 (e, GRUB_EHCI_COMMAND)
+ & GRUB_EHCI_CMD_HC_RESET) != 0)
+ && (grub_get_time_ms () < maxtime));
+ if ((grub_ehci_oper_read32 (e, GRUB_EHCI_COMMAND)
+ & GRUB_EHCI_CMD_HC_RESET) != 0)
+ return GRUB_USB_ERR_TIMEOUT;
+
+ return GRUB_USB_ERR_NONE;
+}
+
+/* PCI iteration function... */
+void
+grub_ehci_init_device (volatile void *regs)
+{
+ struct grub_ehci *e;
+ grub_uint32_t fp;
+ int i;
+ grub_uint32_t n_ports;
+ grub_uint8_t caplen;
+
+ /* Allocate memory for the controller and fill basic values. */
+ e = grub_zalloc (sizeof (*e));
+ if (!e)
+ return;
+ e->framelist_chunk = NULL;
+ e->td_chunk = NULL;
+ e->qh_chunk = NULL;
+ e->iobase_ehcc = regs;
+
+ grub_dprintf ("ehci", "EHCI grub_ehci_pci_iter: CAPLEN: %02x\n",
+ grub_ehci_ehcc_read8 (e, GRUB_EHCI_EHCC_CAPLEN));
+ grub_dprintf ("ehci", "EHCI grub_ehci_pci_iter: VERSION: %04x\n",
+ grub_ehci_ehcc_read16 (e, GRUB_EHCI_EHCC_VERSION));
+ grub_dprintf ("ehci", "EHCI grub_ehci_pci_iter: SPARAMS: %08x\n",
+ grub_ehci_ehcc_read32 (e, GRUB_EHCI_EHCC_SPARAMS));
+ grub_dprintf ("ehci", "EHCI grub_ehci_pci_iter: CPARAMS: %08x\n",
+ grub_ehci_ehcc_read32 (e, GRUB_EHCI_EHCC_CPARAMS));
+
+ /* Determine base address of EHCI operational registers */
+ caplen = grub_ehci_ehcc_read8 (e, GRUB_EHCI_EHCC_CAPLEN);
+#ifndef GRUB_HAVE_UNALIGNED_ACCESS
+ if (caplen & (sizeof (grub_uint32_t) - 1))
+ {
+ grub_dprintf ("ehci", "Unaligned caplen\n");
+ return;
+ }
+ e->iobase = ((volatile grub_uint32_t *) e->iobase_ehcc
+ + (caplen / sizeof (grub_uint32_t)));
+#else
+ e->iobase = (volatile grub_uint32_t *)
+ ((grub_uint8_t *) e->iobase_ehcc + caplen);
+#endif
+
+ grub_dprintf ("ehci",
+ "EHCI grub_ehci_pci_iter: iobase of oper. regs: %08llxx\n",
+ (unsigned long long) (grub_addr_t) e->iobase_ehcc + caplen);
+ grub_dprintf ("ehci", "EHCI grub_ehci_pci_iter: COMMAND: %08x\n",
+ grub_ehci_oper_read32 (e, GRUB_EHCI_COMMAND));
+ grub_dprintf ("ehci", "EHCI grub_ehci_pci_iter: STATUS: %08x\n",
+ grub_ehci_oper_read32 (e, GRUB_EHCI_STATUS));
+ grub_dprintf ("ehci", "EHCI grub_ehci_pci_iter: INTERRUPT: %08x\n",
+ grub_ehci_oper_read32 (e, GRUB_EHCI_INTERRUPT));
+ grub_dprintf ("ehci", "EHCI grub_ehci_pci_iter: FRAME_INDEX: %08x\n",
+ grub_ehci_oper_read32 (e, GRUB_EHCI_FRAME_INDEX));
+ grub_dprintf ("ehci", "EHCI grub_ehci_pci_iter: FL_BASE: %08x\n",
+ grub_ehci_oper_read32 (e, GRUB_EHCI_FL_BASE));
+ grub_dprintf ("ehci", "EHCI grub_ehci_pci_iter: CUR_AL_ADDR: %08x\n",
+ grub_ehci_oper_read32 (e, GRUB_EHCI_CUR_AL_ADDR));
+ grub_dprintf ("ehci", "EHCI grub_ehci_pci_iter: CONFIG_FLAG: %08x\n",
+ grub_ehci_oper_read32 (e, GRUB_EHCI_CONFIG_FLAG));
+
+ /* Check format of data structures requested by EHCI */
+ /* XXX: In fact it is not used at any place, it is prepared for future
+ * This implementation uses 32-bits pointers only */
+ e->flag64 = ((grub_ehci_ehcc_read32 (e, GRUB_EHCI_EHCC_CPARAMS)
+ & GRUB_EHCI_CPARAMS_64BIT) != 0);
+
+ grub_dprintf ("ehci", "EHCI grub_ehci_pci_iter: flag64=%d\n", e->flag64);
+
+ /* Reserve a page for the frame list - it is accurate for max.
+ * possible size of framelist. But currently it is not used. */
+ e->framelist_chunk = grub_memalign_dma32 (4096, 4096);
+ if (!e->framelist_chunk)
+ goto fail;
+ e->framelist_virt = grub_dma_get_virt (e->framelist_chunk);
+ e->framelist_phys = grub_dma_get_phys (e->framelist_chunk);
+ grub_memset ((void *) e->framelist_virt, 0, 4096);
+
+ grub_dprintf ("ehci", "EHCI grub_ehci_pci_iter: framelist mem=%p. OK\n",
+ e->framelist_virt);
+
+ /* Allocate memory for the QHs and register it in "e". */
+ e->qh_chunk = grub_memalign_dma32 (4096,
+ sizeof (struct grub_ehci_qh) *
+ GRUB_EHCI_N_QH);
+ if (!e->qh_chunk)
+ goto fail;
+ e->qh_virt = (grub_ehci_qh_t) grub_dma_get_virt (e->qh_chunk);
+ e->qh_phys = grub_dma_get_phys (e->qh_chunk);
+ grub_memset ((void *) e->qh_virt, 0,
+ sizeof (struct grub_ehci_qh) * GRUB_EHCI_N_QH);
+
+ grub_dprintf ("ehci", "EHCI grub_ehci_pci_iter: QH mem=%p. OK\n",
+ e->qh_virt);
+
+ /* Allocate memory for the TDs and register it in "e". */
+ e->td_chunk = grub_memalign_dma32 (4096,
+ sizeof (struct grub_ehci_td) *
+ GRUB_EHCI_N_TD);
+ if (!e->td_chunk)
+ goto fail;
+ e->td_virt = (grub_ehci_td_t) grub_dma_get_virt (e->td_chunk);
+ e->td_phys = grub_dma_get_phys (e->td_chunk);
+ grub_memset ((void *) e->td_virt, 0,
+ sizeof (struct grub_ehci_td) * GRUB_EHCI_N_TD);
+
+ grub_dprintf ("ehci", "EHCI grub_ehci_pci_iter: TD mem=%p. OK\n",
+ e->td_virt);
+
+ /* Setup all frame list pointers. Since no isochronous transfers
+ are supported, they all point to the (same!) queue
+ head with index 0. */
+ fp = grub_cpu_to_le32 ((e->qh_phys & GRUB_EHCI_POINTER_MASK)
+ | GRUB_EHCI_HPTR_TYPE_QH);
+ for (i = 0; i < GRUB_EHCI_N_FRAMELIST; i++)
+ e->framelist_virt[i] = fp;
+ /* Prepare chain of all TDs and set Terminate in all TDs */
+ for (i = 0; i < (GRUB_EHCI_N_TD - 1); i++)
+ {
+ e->td_virt[i].link_td = e->td_phys + (i + 1) * sizeof (struct grub_ehci_td);
+ e->td_virt[i].next_td = grub_cpu_to_le32_compile_time (GRUB_EHCI_TERMINATE);
+ e->td_virt[i].alt_next_td = grub_cpu_to_le32_compile_time (GRUB_EHCI_TERMINATE);
+ }
+ e->td_virt[GRUB_EHCI_N_TD - 1].next_td =
+ grub_cpu_to_le32_compile_time (GRUB_EHCI_TERMINATE);
+ e->td_virt[GRUB_EHCI_N_TD - 1].alt_next_td =
+ grub_cpu_to_le32_compile_time (GRUB_EHCI_TERMINATE);
+ e->tdfree_virt = e->td_virt;
+ /* Set Terminate in first QH, which is used in framelist */
+ e->qh_virt[0].qh_hptr = grub_cpu_to_le32_compile_time (GRUB_EHCI_TERMINATE | GRUB_EHCI_HPTR_TYPE_QH);
+ e->qh_virt[0].td_overlay.next_td = grub_cpu_to_le32_compile_time (GRUB_EHCI_TERMINATE);
+ e->qh_virt[0].td_overlay.alt_next_td =
+ grub_cpu_to_le32_compile_time (GRUB_EHCI_TERMINATE);
+ /* Also set Halted bit in token */
+ e->qh_virt[0].td_overlay.token = grub_cpu_to_le32_compile_time (GRUB_EHCI_STATUS_HALTED);
+ /* Set the H bit in first QH used for AL */
+ e->qh_virt[1].ep_char = grub_cpu_to_le32_compile_time (GRUB_EHCI_H);
+ /* Set Terminate into TD in rest of QHs and set horizontal link
+ * pointer to itself - these QHs will be used for asynchronous
+ * schedule and they should have valid value in horiz. link */
+ for (i = 1; i < GRUB_EHCI_N_QH; i++)
+ {
+ e->qh_virt[i].qh_hptr =
+ grub_cpu_to_le32 ((grub_dma_virt2phys (&e->qh_virt[i],
+ e->qh_chunk) &
+ GRUB_EHCI_POINTER_MASK) | GRUB_EHCI_HPTR_TYPE_QH);
+ e->qh_virt[i].td_overlay.next_td =
+ grub_cpu_to_le32_compile_time (GRUB_EHCI_TERMINATE);
+ e->qh_virt[i].td_overlay.alt_next_td =
+ grub_cpu_to_le32_compile_time (GRUB_EHCI_TERMINATE);
+ /* Also set Halted bit in token */
+ e->qh_virt[i].td_overlay.token =
+ grub_cpu_to_le32_compile_time (GRUB_EHCI_STATUS_HALTED);
+ }
+
+ /* Note: QH 0 and QH 1 are reserved and must not be used anywhere.
+ * QH 0 is used as empty QH for framelist
+ * QH 1 is used as starting empty QH for asynchronous schedule
+ * QH 1 must exist at any time because at least one QH linked to
+ * itself must exist in asynchronous schedule
+ * QH 1 has the H flag set to one */
+
+ grub_dprintf ("ehci", "EHCI grub_ehci_pci_iter: QH/TD init. OK\n");
+
+ /* Now we can setup EHCI (maybe...) */
+
+ /* Check if EHCI is halted and halt it if not */
+ if (grub_ehci_halt (e) != GRUB_USB_ERR_NONE)
+ {
+ grub_error (GRUB_ERR_TIMEOUT,
+ "EHCI grub_ehci_pci_iter: EHCI halt timeout");
+ goto fail;
+ }
+
+ grub_dprintf ("ehci", "EHCI grub_ehci_pci_iter: halted OK\n");
+
+ /* Reset EHCI */
+ if (grub_ehci_reset (e) != GRUB_USB_ERR_NONE)
+ {
+ grub_error (GRUB_ERR_TIMEOUT,
+ "EHCI grub_ehci_pci_iter: EHCI reset timeout");
+ goto fail;
+ }
+
+ grub_dprintf ("ehci", "EHCI grub_ehci_pci_iter: reset OK\n");
+
+ /* Setup list address registers */
+ grub_ehci_oper_write32 (e, GRUB_EHCI_FL_BASE, e->framelist_phys);
+ grub_ehci_oper_write32 (e, GRUB_EHCI_CUR_AL_ADDR,
+ grub_dma_virt2phys (&e->qh_virt[1],
+ e->qh_chunk));
+
+ /* Set ownership of root hub ports to EHCI */
+ grub_ehci_oper_write32 (e, GRUB_EHCI_CONFIG_FLAG, GRUB_EHCI_CF_EHCI_OWNER);
+
+ /* Enable both lists */
+ grub_ehci_oper_write32 (e, GRUB_EHCI_COMMAND,
+ GRUB_EHCI_CMD_AS_ENABL
+ | GRUB_EHCI_CMD_PS_ENABL
+ | grub_ehci_oper_read32 (e, GRUB_EHCI_COMMAND));
+
+ /* Now should be possible to power-up and enumerate ports etc. */
+ if ((grub_ehci_ehcc_read32 (e, GRUB_EHCI_EHCC_SPARAMS)
+ & GRUB_EHCI_SPARAMS_PPC) != 0)
+ { /* EHCI has port powering control */
+ /* Power on all ports */
+ n_ports = grub_ehci_ehcc_read32 (e, GRUB_EHCI_EHCC_SPARAMS)
+ & GRUB_EHCI_SPARAMS_N_PORTS;
+ for (i = 0; i < (int) n_ports; i++)
+ grub_ehci_oper_write32 (e, GRUB_EHCI_PORT_STAT_CMD + i * 4,
+ GRUB_EHCI_PORT_POWER
+ | grub_ehci_oper_read32 (e,
+ GRUB_EHCI_PORT_STAT_CMD
+ + i * 4));
+ }
+
+ /* Ensure all commands are written */
+ grub_ehci_oper_read32 (e, GRUB_EHCI_COMMAND);
+
+ /* Enable EHCI */
+ grub_ehci_oper_write32 (e, GRUB_EHCI_COMMAND,
+ GRUB_EHCI_CMD_RUNSTOP
+ | grub_ehci_oper_read32 (e, GRUB_EHCI_COMMAND));
+
+ /* Ensure command is written */
+ grub_ehci_oper_read32 (e, GRUB_EHCI_COMMAND);
+
+ /* Link to ehci now that initialisation is successful. */
+ e->next = ehci;
+ ehci = e;
+
+ sync_all_caches (e);
+
+ grub_dprintf ("ehci", "EHCI grub_ehci_pci_iter: OK at all\n");
+
+ grub_dprintf ("ehci",
+ "EHCI grub_ehci_pci_iter: iobase of oper. regs: %08llx\n",
+ (unsigned long long) (grub_addr_t) regs);
+ grub_dprintf ("ehci", "EHCI grub_ehci_pci_iter: COMMAND: %08x\n",
+ grub_ehci_oper_read32 (e, GRUB_EHCI_COMMAND));
+ grub_dprintf ("ehci", "EHCI grub_ehci_pci_iter: STATUS: %08x\n",
+ grub_ehci_oper_read32 (e, GRUB_EHCI_STATUS));
+ grub_dprintf ("ehci", "EHCI grub_ehci_pci_iter: INTERRUPT: %08x\n",
+ grub_ehci_oper_read32 (e, GRUB_EHCI_INTERRUPT));
+ grub_dprintf ("ehci", "EHCI grub_ehci_pci_iter: FRAME_INDEX: %08x\n",
+ grub_ehci_oper_read32 (e, GRUB_EHCI_FRAME_INDEX));
+ grub_dprintf ("ehci", "EHCI grub_ehci_pci_iter: FL_BASE: %08x\n",
+ grub_ehci_oper_read32 (e, GRUB_EHCI_FL_BASE));
+ grub_dprintf ("ehci", "EHCI grub_ehci_pci_iter: CUR_AL_ADDR: %08x\n",
+ grub_ehci_oper_read32 (e, GRUB_EHCI_CUR_AL_ADDR));
+ grub_dprintf ("ehci", "EHCI grub_ehci_pci_iter: CONFIG_FLAG: %08x\n",
+ grub_ehci_oper_read32 (e, GRUB_EHCI_CONFIG_FLAG));
+
+ return;
+
+fail:
+ if (e)
+ {
+ if (e->td_chunk)
+ grub_dma_free ((void *) e->td_chunk);
+ if (e->qh_chunk)
+ grub_dma_free ((void *) e->qh_chunk);
+ if (e->framelist_chunk)
+ grub_dma_free (e->framelist_chunk);
+ }
+ grub_free (e);
+
+ return;
+}
+
+static int
+grub_ehci_iterate (grub_usb_controller_iterate_hook_t hook, void *hook_data)
+{
+ struct grub_ehci *e;
+ struct grub_usb_controller dev;
+
+ for (e = ehci; e; e = e->next)
+ {
+ dev.data = e;
+ if (hook (&dev, hook_data))
+ return 1;
+ }
+
+ return 0;
+}
+
+static void
+grub_ehci_setup_qh (grub_ehci_qh_t qh, grub_usb_transfer_t transfer)
+{
+ grub_uint32_t ep_char = 0;
+ grub_uint32_t ep_cap = 0;
+
+ /* Note: Another part of code is responsible to this QH is
+ * Halted ! But it can be linked in AL, so we cannot erase or
+ * change qh_hptr ! */
+ /* We will not change any TD field because they should/must be
+ * in safe state from previous use. */
+
+ /* EP characteristic setup */
+ /* Currently not used NAK counter (RL=0),
+ * C bit set if EP is not HIGH speed and is control,
+ * Max Packet Length is taken from transfer structure,
+ * H bit = 0 (because QH[1] has this bit set),
+ * DTC bit set to 1 because we are using our own toggle bit control,
+ * SPEED is selected according to value from transfer structure,
+ * EP number is taken from transfer structure
+ * "I" bit must not be set,
+ * Device Address is taken from transfer structure
+ * */
+ if ((transfer->dev->speed != GRUB_USB_SPEED_HIGH)
+ && (transfer->type == GRUB_USB_TRANSACTION_TYPE_CONTROL))
+ ep_char |= GRUB_EHCI_C;
+ ep_char |= (transfer->max << GRUB_EHCI_MAXPLEN_OFF)
+ & GRUB_EHCI_MAXPLEN_MASK;
+ ep_char |= GRUB_EHCI_DTC;
+ switch (transfer->dev->speed)
+ {
+ case GRUB_USB_SPEED_LOW:
+ ep_char |= GRUB_EHCI_SPEED_LOW;
+ break;
+ case GRUB_USB_SPEED_FULL:
+ ep_char |= GRUB_EHCI_SPEED_FULL;
+ break;
+ case GRUB_USB_SPEED_HIGH:
+ default:
+ ep_char |= GRUB_EHCI_SPEED_HIGH;
+ /* XXX: How we will handle unknown value of speed? */
+ }
+ ep_char |= (transfer->endpoint << GRUB_EHCI_EP_NUM_OFF)
+ & GRUB_EHCI_EP_NUM_MASK;
+ ep_char |= transfer->devaddr & GRUB_EHCI_DEVADDR_MASK;
+ qh->ep_char = grub_cpu_to_le32 (ep_char);
+ /* EP capabilities setup */
+ /* MULT field - we try to use max. number
+ * PortNumber - included now in device structure referenced
+ * inside transfer structure
+ * HubAddress - included now in device structure referenced
+ * inside transfer structure
+ * SplitCompletionMask - AFAIK it is ignored in asynchronous list,
+ * InterruptScheduleMask - AFAIK it should be zero in async. list */
+ ep_cap |= GRUB_EHCI_MULT_THREE;
+ ep_cap |= (transfer->dev->split_hubport << GRUB_EHCI_DEVPORT_OFF)
+ & GRUB_EHCI_DEVPORT_MASK;
+ ep_cap |= (transfer->dev->split_hubaddr << GRUB_EHCI_HUBADDR_OFF)
+ & GRUB_EHCI_HUBADDR_MASK;
+ if (transfer->dev->speed == GRUB_USB_SPEED_LOW
+ && transfer->type != GRUB_USB_TRANSACTION_TYPE_CONTROL)
+ {
+ ep_cap |= (1<<0) << GRUB_EHCI_SMASK_OFF;
+ ep_cap |= (7<<2) << GRUB_EHCI_CMASK_OFF;
+ }
+ qh->ep_cap = grub_cpu_to_le32 (ep_cap);
+
+ grub_dprintf ("ehci", "setup_qh: qh=%p, not changed: qh_hptr=%08x\n",
+ qh, grub_le_to_cpu32 (qh->qh_hptr));
+ grub_dprintf ("ehci", "setup_qh: ep_char=%08x, ep_cap=%08x\n",
+ ep_char, ep_cap);
+ grub_dprintf ("ehci", "setup_qh: end\n");
+ grub_dprintf ("ehci", "setup_qh: not changed: td_current=%08x\n",
+ grub_le_to_cpu32 (qh->td_current));
+ grub_dprintf ("ehci", "setup_qh: not changed: next_td=%08x\n",
+ grub_le_to_cpu32 (qh->td_overlay.next_td));
+ grub_dprintf ("ehci", "setup_qh: not changed: alt_next_td=%08x\n",
+ grub_le_to_cpu32 (qh->td_overlay.alt_next_td));
+ grub_dprintf ("ehci", "setup_qh: not changed: token=%08x\n",
+ grub_le_to_cpu32 (qh->td_overlay.token));
+}
+
+static grub_ehci_qh_t
+grub_ehci_find_qh (struct grub_ehci *e, grub_usb_transfer_t transfer)
+{
+ grub_uint32_t target, mask;
+ int i;
+ grub_ehci_qh_t qh = e->qh_virt;
+ grub_ehci_qh_t head;
+ grub_uint32_t qh_phys;
+ grub_uint32_t qh_terminate =
+ GRUB_EHCI_TERMINATE | GRUB_EHCI_HPTR_TYPE_QH;
+ grub_ehci_qh_t qh_iter;
+
+ /* Prepare part of EP Characteristic to find existing QH */
+ target = ((transfer->endpoint << GRUB_EHCI_EP_NUM_OFF) |
+ transfer->devaddr) & GRUB_EHCI_TARGET_MASK;
+ target = grub_cpu_to_le32 (target);
+ mask = grub_cpu_to_le32_compile_time (GRUB_EHCI_TARGET_MASK);
+
+ /* low speed interrupt transfers are linked to the periodic */
+ /* schedule, everything else to the asynchronous schedule */
+ if (transfer->dev->speed == GRUB_USB_SPEED_LOW
+ && transfer->type != GRUB_USB_TRANSACTION_TYPE_CONTROL)
+ head = &qh[0];
+ else
+ head = &qh[1];
+
+ /* First try to find existing QH with proper target in proper list */
+ qh_phys = grub_le_to_cpu32( head->qh_hptr );
+ if (qh_phys != qh_terminate)
+ qh_iter = grub_dma_phys2virt ( qh_phys & GRUB_EHCI_QHTDPTR_MASK,
+ e->qh_chunk );
+ else
+ qh_iter = NULL;
+
+ for (
+ i = 0;
+ (qh_phys != qh_terminate) && (qh_iter != NULL) &&
+ (qh_iter != head) && (i < GRUB_EHCI_N_QH);
+ i++ )
+ {
+ if (target == (qh_iter->ep_char & mask))
+ {
+ /* Found proper existing (and linked) QH, do setup of QH */
+ grub_dprintf ("ehci", "find_qh: found, QH=%p\n", qh_iter);
+ grub_ehci_setup_qh (qh_iter, transfer);
+ sync_all_caches (e);
+ return qh_iter;
+ }
+
+ qh_phys = grub_le_to_cpu32( qh_iter->qh_hptr );
+ if (qh_phys != qh_terminate)
+ qh_iter = grub_dma_phys2virt ( qh_phys & GRUB_EHCI_QHTDPTR_MASK,
+ e->qh_chunk );
+ else
+ qh_iter = NULL;
+ }
+
+ /* variable "i" should be never equal to GRUB_EHCI_N_QH here */
+ if (i >= GRUB_EHCI_N_QH)
+ { /* Something very bad happened in QH list(s) ! */
+ grub_dprintf ("ehci", "find_qh: Mismatch in QH list! head=%p\n",
+ head);
+ }
+
+ /* QH with target_addr does not exist, we have to find and add it */
+ for (i = 2; i < GRUB_EHCI_N_QH; i++) /* We ignore zero and first QH */
+ {
+ if (!qh[i].ep_char)
+ break; /* Found first not-allocated QH, finish */
+ }
+
+ /* Have we any free QH in array ? */
+ if (i >= GRUB_EHCI_N_QH) /* No. */
+ {
+ grub_dprintf ("ehci", "find_qh: end - no free QH\n");
+ return NULL;
+ }
+ grub_dprintf ("ehci", "find_qh: new, i=%d, QH=%p\n",
+ i, &qh[i]);
+ /* Currently we simply take next (current) QH in array, no allocation
+ * function is used. It should be no problem until we will need to
+ * de-allocate QHs of unplugged devices. */
+ /* We should preset new QH and link it into AL */
+ grub_ehci_setup_qh (&qh[i], transfer);
+
+ /* Linking - this new (last) QH will copy the QH from the head QH */
+ qh[i].qh_hptr = head->qh_hptr;
+ /* Linking - the head QH will point to this new QH */
+ head->qh_hptr = grub_cpu_to_le32 (GRUB_EHCI_HPTR_TYPE_QH
+ | grub_dma_virt2phys (&qh[i],
+ e->qh_chunk));
+
+ return &qh[i];
+}
+
+static grub_ehci_td_t
+grub_ehci_alloc_td (struct grub_ehci *e)
+{
+ grub_ehci_td_t ret;
+
+ /* Check if there is a Transfer Descriptor available. */
+ if (!e->tdfree_virt)
+ {
+ grub_dprintf ("ehci", "alloc_td: end - no free TD\n");
+ return NULL;
+ }
+
+ ret = e->tdfree_virt; /* Take current free TD */
+ /* Advance to next free TD in chain */
+ if (ret->link_td)
+ e->tdfree_virt = grub_dma_phys2virt (ret->link_td, e->td_chunk);
+ else
+ e->tdfree_virt = NULL;
+ ret->link_td = 0; /* Reset link_td in allocated TD */
+ return ret;
+}
+
+static void
+grub_ehci_free_td (struct grub_ehci *e, grub_ehci_td_t td)
+{
+ /* Chain new free TD & rest */
+ if (e->tdfree_virt)
+ td->link_td = grub_dma_virt2phys (e->tdfree_virt, e->td_chunk);
+ else
+ td->link_td = 0;
+ e->tdfree_virt = td; /* Change address of first free TD */
+}
+
+static void
+grub_ehci_free_tds (struct grub_ehci *e, grub_ehci_td_t td,
+ grub_usb_transfer_t transfer, grub_size_t * actual)
+{
+ int i; /* Index of TD in transfer */
+ grub_uint32_t token, to_transfer;
+
+ /* Note: Another part of code is responsible to this QH is
+ * INACTIVE ! */
+ *actual = 0;
+
+ /* Free the TDs in this queue and set last_trans. */
+ for (i = 0; td; i++)
+ {
+ grub_ehci_td_t tdprev;
+
+ token = grub_le_to_cpu32 (td->token);
+ to_transfer = (token & GRUB_EHCI_TOTAL_MASK) >> GRUB_EHCI_TOTAL_OFF;
+
+ /* Check state of TD - if it did not transfer
+ * whole data then set last_trans - it should be last executed TD
+ * in case when something went wrong. */
+ if (transfer && (td->size != to_transfer))
+ transfer->last_trans = i;
+
+ *actual += td->size - to_transfer;
+
+ /* Unlink the TD */
+ tdprev = td;
+ if (td->link_td)
+ td = grub_dma_phys2virt (td->link_td, e->td_chunk);
+ else
+ td = NULL;
+
+ /* Free the TD. */
+ grub_ehci_free_td (e, tdprev);
+ }
+
+ /* Check if last_trans was set. If not and something was
+ * transferred (it should be all data in this case), set it
+ * to index of last TD, i.e. i-1 */
+ if (transfer && (transfer->last_trans < 0) && (*actual != 0))
+ transfer->last_trans = i - 1;
+
+ /* XXX: Fix it: last_trans may be set to bad index.
+ * Probably we should test more error flags to distinguish
+ * if TD was at least partialy executed or not at all.
+ * Generaly, we still could have problem with toggling because
+ * EHCI can probably split transactions into smaller parts then
+ * we defined in transaction even if we did not exceed MaxFrame
+ * length - it probably could happen at the end of microframe (?)
+ * and if the buffer is crossing page boundary (?). */
+}
+
+static grub_ehci_td_t
+grub_ehci_transaction (struct grub_ehci *e,
+ grub_transfer_type_t type,
+ unsigned int toggle, grub_size_t size,
+ grub_uint32_t data, grub_ehci_td_t td_alt)
+{
+ grub_ehci_td_t td;
+ grub_uint32_t token;
+ grub_uint32_t bufadr;
+ int i;
+
+ /* Test of transfer size, it can be:
+ * <= GRUB_EHCI_MAXBUFLEN if data aligned to page boundary
+ * <= GRUB_EHCI_MAXBUFLEN - GRUB_EHCI_BUFPAGELEN if not aligned
+ * (worst case)
+ */
+ if ((((data % GRUB_EHCI_BUFPAGELEN) == 0)
+ && (size > GRUB_EHCI_MAXBUFLEN))
+ ||
+ (((data % GRUB_EHCI_BUFPAGELEN) != 0)
+ && (size > (GRUB_EHCI_MAXBUFLEN - GRUB_EHCI_BUFPAGELEN))))
+ {
+ grub_error (GRUB_ERR_OUT_OF_MEMORY,
+ "too long data buffer for EHCI transaction");
+ return 0;
+ }
+
+ /* Grab a free Transfer Descriptor and initialize it. */
+ td = grub_ehci_alloc_td (e);
+ if (!td)
+ {
+ grub_error (GRUB_ERR_OUT_OF_MEMORY,
+ "no transfer descriptors available for EHCI transfer");
+ return 0;
+ }
+
+ grub_dprintf ("ehci",
+ "transaction: type=%d, toggle=%d, size=%lu data=0x%x td=%p\n",
+ type, toggle, (unsigned long) size, data, td);
+
+ /* Fill whole TD by zeros */
+ grub_memset ((void *) td, 0, sizeof (struct grub_ehci_td));
+
+ /* Don't point to any TD yet, just terminate. */
+ td->next_td = grub_cpu_to_le32_compile_time (GRUB_EHCI_TERMINATE);
+ /* Set alternate pointer. When short packet occurs, alternate TD
+ * will not be really fetched because it is not active. But don't
+ * forget, EHCI will try to fetch alternate TD every scan of AL
+ * until QH is halted. */
+ td->alt_next_td = grub_cpu_to_le32 (grub_dma_virt2phys (td_alt,
+ e->td_chunk));
+ /* token:
+ * TOGGLE - according to toggle
+ * TOTAL SIZE = size
+ * Interrupt On Complete = FALSE, we don't need IRQ
+ * Current Page = 0
+ * Error Counter = max. value = 3
+ * PID Code - according to type
+ * STATUS:
+ * ACTIVE bit should be set to one
+ * SPLIT TRANS. STATE bit should be zero. It is ignored
+ * in HIGH speed transaction, and should be zero for LOW/FULL
+ * speed to indicate state Do Split Transaction */
+ token = toggle ? GRUB_EHCI_TOGGLE : 0;
+ token |= (size << GRUB_EHCI_TOTAL_OFF) & GRUB_EHCI_TOTAL_MASK;
+ token |= GRUB_EHCI_CERR_3;
+ switch (type)
+ {
+ case GRUB_USB_TRANSFER_TYPE_IN:
+ token |= GRUB_EHCI_PIDCODE_IN;
+ break;
+ case GRUB_USB_TRANSFER_TYPE_OUT:
+ token |= GRUB_EHCI_PIDCODE_OUT;
+ break;
+ case GRUB_USB_TRANSFER_TYPE_SETUP:
+ token |= GRUB_EHCI_PIDCODE_SETUP;
+ break;
+ default: /* XXX: Should not happen, but what to do if it does ? */
+ break;
+ }
+ token |= GRUB_EHCI_STATUS_ACTIVE;
+ td->token = grub_cpu_to_le32 (token);
+
+ /* Fill buffer pointers according to size */
+ bufadr = data;
+ td->buffer_page[0] = grub_cpu_to_le32 (bufadr);
+ bufadr = ((bufadr / GRUB_EHCI_BUFPAGELEN) + 1) * GRUB_EHCI_BUFPAGELEN;
+ for (i = 1; ((bufadr - data) < size) && (i < GRUB_EHCI_TD_BUF_PAGES); i++)
+ {
+ td->buffer_page[i] = grub_cpu_to_le32 (bufadr & GRUB_EHCI_BUFPTR_MASK);
+ bufadr = ((bufadr / GRUB_EHCI_BUFPAGELEN) + 1) * GRUB_EHCI_BUFPAGELEN;
+ }
+
+ /* Remember data size for future use... */
+ td->size = (grub_uint32_t) size;
+
+ grub_dprintf ("ehci", "td=%p\n", td);
+ grub_dprintf ("ehci", "HW: next_td=%08x, alt_next_td=%08x\n",
+ grub_le_to_cpu32 (td->next_td),
+ grub_le_to_cpu32 (td->alt_next_td));
+ grub_dprintf ("ehci", "HW: token=%08x, buffer[0]=%08x\n",
+ grub_le_to_cpu32 (td->token),
+ grub_le_to_cpu32 (td->buffer_page[0]));
+ grub_dprintf ("ehci", "HW: buffer[1]=%08x, buffer[2]=%08x\n",
+ grub_le_to_cpu32 (td->buffer_page[1]),
+ grub_le_to_cpu32 (td->buffer_page[2]));
+ grub_dprintf ("ehci", "HW: buffer[3]=%08x, buffer[4]=%08x\n",
+ grub_le_to_cpu32 (td->buffer_page[3]),
+ grub_le_to_cpu32 (td->buffer_page[4]));
+ grub_dprintf ("ehci", "link_td=%08x, size=%08x\n",
+ td->link_td, td->size);
+
+ return td;
+}
+
+struct grub_ehci_transfer_controller_data
+{
+ grub_ehci_qh_t qh_virt;
+ grub_ehci_td_t td_first_virt;
+ grub_ehci_td_t td_alt_virt;
+ grub_ehci_td_t td_last_virt;
+ grub_uint32_t td_last_phys;
+};
+
+static grub_usb_err_t
+grub_ehci_setup_transfer (grub_usb_controller_t dev,
+ grub_usb_transfer_t transfer)
+{
+ struct grub_ehci *e = (struct grub_ehci *) dev->data;
+ grub_ehci_td_t td = NULL;
+ grub_ehci_td_t td_prev = NULL;
+ int i;
+ struct grub_ehci_transfer_controller_data *cdata;
+ grub_uint32_t status;
+
+ sync_all_caches (e);
+
+ /* Check if EHCI is running and AL is enabled */
+ status = grub_ehci_oper_read32 (e, GRUB_EHCI_STATUS);
+ if ((status & GRUB_EHCI_ST_HC_HALTED) != 0)
+ /* XXX: Fix it: Currently we don't do anything to restart EHCI */
+ {
+ grub_dprintf ("ehci", "setup_transfer: halted, status = 0x%x\n",
+ status);
+ return GRUB_USB_ERR_INTERNAL;
+ }
+ status = grub_ehci_oper_read32 (e, GRUB_EHCI_STATUS);
+ if ((status
+ & (GRUB_EHCI_ST_AS_STATUS | GRUB_EHCI_ST_PS_STATUS)) == 0)
+ /* XXX: Fix it: Currently we don't do anything to restart EHCI */
+ {
+ grub_dprintf ("ehci", "setup_transfer: no AS/PS, status = 0x%x\n",
+ status);
+ return GRUB_USB_ERR_INTERNAL;
+ }
+
+ /* Allocate memory for controller transfer data. */
+ cdata = grub_malloc (sizeof (*cdata));
+ if (!cdata)
+ return GRUB_USB_ERR_INTERNAL;
+ cdata->td_first_virt = NULL;
+
+ /* Allocate a queue head for the transfer queue. */
+ cdata->qh_virt = grub_ehci_find_qh (e, transfer);
+ if (!cdata->qh_virt)
+ {
+ grub_dprintf ("ehci", "setup_transfer: no QH\n");
+ grub_free (cdata);
+ return GRUB_USB_ERR_INTERNAL;
+ }
+
+ /* To detect short packet we need some additional "alternate" TD,
+ * allocate it first. */
+ cdata->td_alt_virt = grub_ehci_alloc_td (e);
+ if (!cdata->td_alt_virt)
+ {
+ grub_dprintf ("ehci", "setup_transfer: no TDs\n");
+ grub_free (cdata);
+ return GRUB_USB_ERR_INTERNAL;
+ }
+ /* Fill whole alternate TD by zeros (= inactive) and set
+ * Terminate bits and Halt bit */
+ grub_memset ((void *) cdata->td_alt_virt, 0, sizeof (struct grub_ehci_td));
+ cdata->td_alt_virt->next_td = grub_cpu_to_le32_compile_time (GRUB_EHCI_TERMINATE);
+ cdata->td_alt_virt->alt_next_td = grub_cpu_to_le32_compile_time (GRUB_EHCI_TERMINATE);
+ cdata->td_alt_virt->token = grub_cpu_to_le32_compile_time (GRUB_EHCI_STATUS_HALTED);
+
+ /* Allocate appropriate number of TDs and set */
+ for (i = 0; i < transfer->transcnt; i++)
+ {
+ grub_usb_transaction_t tr = &transfer->transactions[i];
+
+ td = grub_ehci_transaction (e, tr->pid, tr->toggle, tr->size,
+ tr->data, cdata->td_alt_virt);
+
+ if (!td) /* de-allocate and free all */
+ {
+ grub_size_t actual = 0;
+
+ if (cdata->td_first_virt)
+ grub_ehci_free_tds (e, cdata->td_first_virt, NULL, &actual);
+
+ grub_free (cdata);
+ grub_dprintf ("ehci", "setup_transfer: no TD\n");
+ return GRUB_USB_ERR_INTERNAL;
+ }
+
+ /* Register new TD in cdata or previous TD */
+ if (!cdata->td_first_virt)
+ cdata->td_first_virt = td;
+ else
+ {
+ td_prev->link_td = grub_dma_virt2phys (td, e->td_chunk);
+ td_prev->next_td =
+ grub_cpu_to_le32 (grub_dma_virt2phys (td, e->td_chunk));
+ }
+ td_prev = td;
+ }
+
+ /* Remember last TD */
+ cdata->td_last_virt = td;
+ cdata->td_last_phys = grub_dma_virt2phys (td, e->td_chunk);
+ /* Last TD should not have set alternate TD */
+ cdata->td_last_virt->alt_next_td = grub_cpu_to_le32_compile_time (GRUB_EHCI_TERMINATE);
+
+ grub_dprintf ("ehci", "setup_transfer: cdata=%p, qh=%p\n",
+ cdata,cdata->qh_virt);
+ grub_dprintf ("ehci", "setup_transfer: td_first=%p, td_alt=%p\n",
+ cdata->td_first_virt,
+ cdata->td_alt_virt);
+ grub_dprintf ("ehci", "setup_transfer: td_last=%p\n",
+ cdata->td_last_virt);
+
+ /* Start transfer: */
+ /* Unlink possible alternate pointer in QH */
+ cdata->qh_virt->td_overlay.alt_next_td =
+ grub_cpu_to_le32_compile_time (GRUB_EHCI_TERMINATE);
+ /* Link new TDs with QH via next_td */
+ cdata->qh_virt->td_overlay.next_td =
+ grub_cpu_to_le32 (grub_dma_virt2phys
+ (cdata->td_first_virt, e->td_chunk));
+ /* Reset Active and Halted bits in QH to activate Advance Queue,
+ * i.e. reset token */
+ cdata->qh_virt->td_overlay.token = grub_cpu_to_le32_compile_time (0);
+
+ sync_all_caches (e);
+
+ /* Finito */
+ transfer->controller_data = cdata;
+
+ return GRUB_USB_ERR_NONE;
+}
+
+/* This function expects QH is not active.
+ * Function set Halt bit in QH TD overlay and possibly prints
+ * necessary debug information. */
+static void
+grub_ehci_pre_finish_transfer (grub_usb_transfer_t transfer)
+{
+ struct grub_ehci_transfer_controller_data *cdata =
+ transfer->controller_data;
+
+ /* Collect debug data here if necessary */
+
+ /* Set Halt bit in not active QH. AL will not attempt to do
+ * Advance Queue on QH with Halt bit set, i.e., we can then
+ * safely manipulate with QH TD part. */
+ cdata->qh_virt->td_overlay.token = (cdata->qh_virt->td_overlay.token
+ |
+ grub_cpu_to_le32_compile_time
+ (GRUB_EHCI_STATUS_HALTED)) &
+ grub_cpu_to_le32_compile_time (~GRUB_EHCI_STATUS_ACTIVE);
+
+ /* Print debug data here if necessary */
+
+}
+
+static grub_usb_err_t
+grub_ehci_parse_notrun (grub_usb_controller_t dev,
+ grub_usb_transfer_t transfer, grub_size_t * actual)
+{
+ struct grub_ehci *e = dev->data;
+ struct grub_ehci_transfer_controller_data *cdata =
+ transfer->controller_data;
+
+ grub_dprintf ("ehci", "parse_notrun: info\n");
+
+ /* QH can be in any state in this case. */
+ /* But EHCI or AL is not running, so QH is surely not active
+ * even if it has Active bit set... */
+ grub_ehci_pre_finish_transfer (transfer);
+ grub_ehci_free_tds (e, cdata->td_first_virt, transfer, actual);
+ grub_ehci_free_td (e, cdata->td_alt_virt);
+ grub_free (cdata);
+
+ sync_all_caches (e);
+
+ /* Additionally, do something with EHCI to make it running (what?) */
+ /* Try enable EHCI and AL */
+ grub_ehci_oper_write32 (e, GRUB_EHCI_COMMAND,
+ GRUB_EHCI_CMD_RUNSTOP | GRUB_EHCI_CMD_AS_ENABL
+ | GRUB_EHCI_CMD_PS_ENABL
+ | grub_ehci_oper_read32 (e, GRUB_EHCI_COMMAND));
+ /* Ensure command is written */
+ grub_ehci_oper_read32 (e, GRUB_EHCI_COMMAND);
+
+ return GRUB_USB_ERR_UNRECOVERABLE;
+}
+
+static grub_usb_err_t
+grub_ehci_parse_halt (grub_usb_controller_t dev,
+ grub_usb_transfer_t transfer, grub_size_t * actual)
+{
+ struct grub_ehci *e = dev->data;
+ struct grub_ehci_transfer_controller_data *cdata =
+ transfer->controller_data;
+ grub_uint32_t token;
+ grub_usb_err_t err = GRUB_USB_ERR_NAK;
+
+ /* QH should be halted and not active in this case. */
+
+ grub_dprintf ("ehci", "parse_halt: info\n");
+
+ /* Remember token before call pre-finish function */
+ token = grub_le_to_cpu32 (cdata->qh_virt->td_overlay.token);
+
+ /* Do things like in normal finish */
+ grub_ehci_pre_finish_transfer (transfer);
+ grub_ehci_free_tds (e, cdata->td_first_virt, transfer, actual);
+ grub_ehci_free_td (e, cdata->td_alt_virt);
+ grub_free (cdata);
+
+ sync_all_caches (e);
+
+ /* Evaluation of error code - currently we don't have GRUB USB error
+ * codes for some EHCI states, GRUB_USB_ERR_DATA is used for them.
+ * Order of evaluation is critical, specially bubble/stall. */
+ if ((token & GRUB_EHCI_STATUS_BABBLE) != 0)
+ err = GRUB_USB_ERR_BABBLE;
+ else if ((token & GRUB_EHCI_CERR_MASK) != 0)
+ err = GRUB_USB_ERR_STALL;
+ else if ((token & GRUB_EHCI_STATUS_TRANERR) != 0)
+ err = GRUB_USB_ERR_DATA;
+ else if ((token & GRUB_EHCI_STATUS_BUFERR) != 0)
+ err = GRUB_USB_ERR_DATA;
+ else if ((token & GRUB_EHCI_STATUS_MISSDMF) != 0)
+ err = GRUB_USB_ERR_DATA;
+
+ return err;
+}
+
+static grub_usb_err_t
+grub_ehci_parse_success (grub_usb_controller_t dev,
+ grub_usb_transfer_t transfer, grub_size_t * actual)
+{
+ struct grub_ehci *e = dev->data;
+ struct grub_ehci_transfer_controller_data *cdata =
+ transfer->controller_data;
+
+ grub_dprintf ("ehci", "parse_success: info\n");
+
+ /* QH should be not active in this case, but it is not halted. */
+ grub_ehci_pre_finish_transfer (transfer);
+ grub_ehci_free_tds (e, cdata->td_first_virt, transfer, actual);
+ grub_ehci_free_td (e, cdata->td_alt_virt);
+ grub_free (cdata);
+
+ sync_all_caches (e);
+
+ return GRUB_USB_ERR_NONE;
+}
+
+
+static grub_usb_err_t
+grub_ehci_check_transfer (grub_usb_controller_t dev,
+ grub_usb_transfer_t transfer, grub_size_t * actual)
+{
+ struct grub_ehci *e = dev->data;
+ struct grub_ehci_transfer_controller_data *cdata =
+ transfer->controller_data;
+ grub_uint32_t token, token_ftd;
+
+ sync_all_caches (e);
+
+ grub_dprintf ("ehci",
+ "check_transfer: EHCI STATUS=%08x, cdata=%p, qh=%p\n",
+ grub_ehci_oper_read32 (e, GRUB_EHCI_STATUS),
+ cdata, cdata->qh_virt);
+ grub_dprintf ("ehci", "check_transfer: qh_hptr=%08x, ep_char=%08x\n",
+ grub_le_to_cpu32 (cdata->qh_virt->qh_hptr),
+ grub_le_to_cpu32 (cdata->qh_virt->ep_char));
+ grub_dprintf ("ehci", "check_transfer: ep_cap=%08x, td_current=%08x\n",
+ grub_le_to_cpu32 (cdata->qh_virt->ep_cap),
+ grub_le_to_cpu32 (cdata->qh_virt->td_current));
+ grub_dprintf ("ehci", "check_transfer: next_td=%08x, alt_next_td=%08x\n",
+ grub_le_to_cpu32 (cdata->qh_virt->td_overlay.next_td),
+ grub_le_to_cpu32 (cdata->qh_virt->td_overlay.alt_next_td));
+ grub_dprintf ("ehci", "check_transfer: token=%08x, buffer[0]=%08x\n",
+ grub_le_to_cpu32 (cdata->qh_virt->td_overlay.token),
+ grub_le_to_cpu32 (cdata->qh_virt->td_overlay.buffer_page[0]));
+
+ /* Check if EHCI is running and AL is enabled */
+ if ((grub_ehci_oper_read32 (e, GRUB_EHCI_STATUS)
+ & GRUB_EHCI_ST_HC_HALTED) != 0)
+ return grub_ehci_parse_notrun (dev, transfer, actual);
+ if ((grub_ehci_oper_read32 (e, GRUB_EHCI_STATUS)
+ & (GRUB_EHCI_ST_AS_STATUS | GRUB_EHCI_ST_PS_STATUS)) == 0)
+ return grub_ehci_parse_notrun (dev, transfer, actual);
+
+ token = grub_le_to_cpu32 (cdata->qh_virt->td_overlay.token);
+ /* If the transfer consist from only one TD, we should check */
+ /* if the TD was really executed and deactivated - to prevent */
+ /* false detection of transfer finish. */
+ token_ftd = grub_le_to_cpu32 (cdata->td_first_virt->token);
+
+ /* Detect QH halted */
+ if ((token & GRUB_EHCI_STATUS_HALTED) != 0)
+ return grub_ehci_parse_halt (dev, transfer, actual);
+
+ /* Detect QH not active - QH is not active and no next TD */
+ if (token && ((token & GRUB_EHCI_STATUS_ACTIVE) == 0)
+ && ((token_ftd & GRUB_EHCI_STATUS_ACTIVE) == 0))
+ {
+ /* It could be finish at all or short packet condition */
+ if ((grub_le_to_cpu32 (cdata->qh_virt->td_overlay.next_td)
+ & GRUB_EHCI_TERMINATE) &&
+ ((grub_le_to_cpu32 (cdata->qh_virt->td_current)
+ & GRUB_EHCI_QHTDPTR_MASK) == cdata->td_last_phys))
+ /* Normal finish */
+ return grub_ehci_parse_success (dev, transfer, actual);
+ else if ((token & GRUB_EHCI_TOTAL_MASK) != 0)
+ /* Short packet condition */
+ /* But currently we don't handle it - higher level will do it */
+ return grub_ehci_parse_success (dev, transfer, actual);
+ }
+
+ return GRUB_USB_ERR_WAIT;
+}
+
+static grub_usb_err_t
+grub_ehci_cancel_transfer (grub_usb_controller_t dev,
+ grub_usb_transfer_t transfer)
+{
+ struct grub_ehci *e = dev->data;
+ struct grub_ehci_transfer_controller_data *cdata =
+ transfer->controller_data;
+ grub_size_t actual;
+ int i;
+ grub_uint64_t maxtime;
+ grub_uint32_t qh_phys;
+
+ sync_all_caches (e);
+
+ grub_uint32_t interrupt =
+ cdata->qh_virt->ep_cap & GRUB_EHCI_SMASK_MASK;
+
+ /* QH can be active and should be de-activated and halted */
+
+ grub_dprintf ("ehci", "cancel_transfer: begin\n");
+
+ /* First check if EHCI is running - if not, there is no problem */
+ /* to cancel any transfer. Or, if transfer is asynchronous, check */
+ /* if AL is enabled - if not, transfer can be canceled also. */
+ if (((grub_ehci_oper_read32 (e, GRUB_EHCI_STATUS) &
+ GRUB_EHCI_ST_HC_HALTED) != 0) ||
+ (!interrupt && ((grub_ehci_oper_read32 (e, GRUB_EHCI_STATUS) &
+ (GRUB_EHCI_ST_AS_STATUS | GRUB_EHCI_ST_PS_STATUS)) == 0)))
+ {
+ grub_ehci_pre_finish_transfer (transfer);
+ grub_ehci_free_tds (e, cdata->td_first_virt, transfer, &actual);
+ grub_ehci_free_td (e, cdata->td_alt_virt);
+ grub_free (cdata);
+ sync_all_caches (e);
+ grub_dprintf ("ehci", "cancel_transfer: end - EHCI not running\n");
+ return GRUB_USB_ERR_NONE;
+ }
+
+ /* EHCI and (AL or SL) are running. What to do? */
+ /* Try to Halt QH via de-scheduling QH. */
+ /* Find index of previous QH */
+ qh_phys = grub_dma_virt2phys(cdata->qh_virt, e->qh_chunk);
+ for (i = 0; i < GRUB_EHCI_N_QH; i++)
+ {
+ if ((grub_le_to_cpu32(e->qh_virt[i].qh_hptr)
+ & GRUB_EHCI_QHTDPTR_MASK) == qh_phys)
+ break;
+ }
+ if (i == GRUB_EHCI_N_QH)
+ {
+ grub_printf ("%s: prev not found, queues are corrupt\n", __func__);
+ return GRUB_USB_ERR_UNRECOVERABLE;
+ }
+ /* Unlink QH from AL */
+ e->qh_virt[i].qh_hptr = cdata->qh_virt->qh_hptr;
+
+ sync_all_caches (e);
+
+ /* If this is an interrupt transfer, we just wait for the periodic
+ * schedule to advance a few times and then assume that the EHCI
+ * controller has read the updated QH. */
+ if (cdata->qh_virt->ep_cap & GRUB_EHCI_SMASK_MASK)
+ {
+ grub_millisleep(20);
+ }
+ else
+ {
+ /* For the asynchronous schedule we use the advance doorbell to find
+ * out when the EHCI controller has read the updated QH. */
+
+ /* Ring the doorbell */
+ grub_ehci_oper_write32 (e, GRUB_EHCI_COMMAND,
+ GRUB_EHCI_CMD_AS_ADV_D
+ | grub_ehci_oper_read32 (e, GRUB_EHCI_COMMAND));
+ /* Ensure command is written */
+ grub_ehci_oper_read32 (e, GRUB_EHCI_COMMAND);
+ /* Wait answer with timeout */
+ maxtime = grub_get_time_ms () + 2;
+ while (((grub_ehci_oper_read32 (e, GRUB_EHCI_STATUS)
+ & GRUB_EHCI_ST_AS_ADVANCE) == 0)
+ && (grub_get_time_ms () < maxtime));
+
+ /* We do not detect the timeout because if timeout occurs, it most
+ * probably means something wrong with EHCI - maybe stopped etc. */
+
+ /* Shut up the doorbell */
+ grub_ehci_oper_write32 (e, GRUB_EHCI_COMMAND,
+ ~GRUB_EHCI_CMD_AS_ADV_D
+ & grub_ehci_oper_read32 (e, GRUB_EHCI_COMMAND));
+ grub_ehci_oper_write32 (e, GRUB_EHCI_STATUS,
+ GRUB_EHCI_ST_AS_ADVANCE
+ | grub_ehci_oper_read32 (e, GRUB_EHCI_STATUS));
+ /* Ensure command is written */
+ grub_ehci_oper_read32 (e, GRUB_EHCI_STATUS);
+ }
+
+ /* Now is QH out of AL and we can do anything with it... */
+ grub_ehci_pre_finish_transfer (transfer);
+ grub_ehci_free_tds (e, cdata->td_first_virt, transfer, &actual);
+ grub_ehci_free_td (e, cdata->td_alt_virt);
+
+ /* "Free" the QH - link it to itself */
+ cdata->qh_virt->ep_char = 0;
+ cdata->qh_virt->qh_hptr =
+ grub_cpu_to_le32 ((grub_dma_virt2phys (cdata->qh_virt,
+ e->qh_chunk)
+ & GRUB_EHCI_POINTER_MASK) | GRUB_EHCI_HPTR_TYPE_QH);
+
+ grub_free (cdata);
+
+ grub_dprintf ("ehci", "cancel_transfer: end\n");
+
+ sync_all_caches (e);
+
+ return GRUB_USB_ERR_NONE;
+}
+
+static int
+grub_ehci_hubports (grub_usb_controller_t dev)
+{
+ struct grub_ehci *e = (struct grub_ehci *) dev->data;
+ grub_uint32_t portinfo;
+
+ portinfo = grub_ehci_ehcc_read32 (e, GRUB_EHCI_EHCC_SPARAMS)
+ & GRUB_EHCI_SPARAMS_N_PORTS;
+ grub_dprintf ("ehci", "root hub ports=%d\n", portinfo);
+ return portinfo;
+}
+
+static grub_usb_err_t
+grub_ehci_portstatus (grub_usb_controller_t dev,
+ unsigned int port, unsigned int enable)
+{
+ struct grub_ehci *e = (struct grub_ehci *) dev->data;
+ grub_uint64_t endtime;
+
+ grub_dprintf ("ehci", "portstatus: EHCI STATUS: %08x\n",
+ grub_ehci_oper_read32 (e, GRUB_EHCI_STATUS));
+ grub_dprintf ("ehci",
+ "portstatus: begin, iobase=%p, port=%d, status=0x%02x\n",
+ e->iobase, port, grub_ehci_port_read (e, port));
+
+ /* In any case we need to disable port:
+ * - if enable==false - we should disable port
+ * - if enable==true we will do the reset and the specification says
+ * PortEnable should be FALSE in such case */
+ /* Disable the port and wait for it. */
+ grub_ehci_port_resbits (e, port, GRUB_EHCI_PORT_ENABLED);
+ endtime = grub_get_time_ms () + 1000;
+ while (grub_ehci_port_read (e, port) & GRUB_EHCI_PORT_ENABLED)
+ if (grub_get_time_ms () > endtime)
+ return GRUB_USB_ERR_TIMEOUT;
+
+ if (!enable) /* We don't need reset port */
+ {
+ grub_dprintf ("ehci", "portstatus: Disabled.\n");
+ grub_dprintf ("ehci", "portstatus: end, status=0x%02x\n",
+ grub_ehci_port_read (e, port));
+ return GRUB_USB_ERR_NONE;
+ }
+
+ grub_dprintf ("ehci", "portstatus: enable\n");
+
+ grub_boot_time ("Resetting port %d", port);
+
+ /* Now we will do reset - if HIGH speed device connected, it will
+ * result in Enabled state, otherwise port remains disabled. */
+ /* Set RESET bit for 50ms */
+ grub_ehci_port_setbits (e, port, GRUB_EHCI_PORT_RESET);
+ grub_millisleep (50);
+
+ /* Reset RESET bit and wait for the end of reset */
+ grub_ehci_port_resbits (e, port, GRUB_EHCI_PORT_RESET);
+ endtime = grub_get_time_ms () + 1000;
+ while (grub_ehci_port_read (e, port) & GRUB_EHCI_PORT_RESET)
+ if (grub_get_time_ms () > endtime)
+ return GRUB_USB_ERR_TIMEOUT;
+ grub_boot_time ("Port %d reset", port);
+ /* Remember "we did the reset" - needed by detect_dev */
+ e->reset |= (1 << port);
+ /* Test if port enabled, i.e. HIGH speed device connected */
+ if ((grub_ehci_port_read (e, port) & GRUB_EHCI_PORT_ENABLED) != 0) /* yes! */
+ {
+ grub_dprintf ("ehci", "portstatus: Enabled!\n");
+ /* "Reset recovery time" (USB spec.) */
+ grub_millisleep (10);
+ }
+ else /* no... */
+ {
+ /* FULL speed device connected - change port ownership.
+ * It results in disconnected state of this EHCI port. */
+ grub_ehci_port_setbits (e, port, GRUB_EHCI_PORT_OWNER);
+ return GRUB_USB_ERR_BADDEVICE;
+ }
+
+ /* XXX: Fix it! There is possible problem - we can say to calling
+ * function that we lost device if it is FULL speed onlu via
+ * return value <> GRUB_ERR_NONE. It (maybe) displays also error
+ * message on screen - but this situation is not error, it is normal
+ * state! */
+
+ grub_dprintf ("ehci", "portstatus: end, status=0x%02x\n",
+ grub_ehci_port_read (e, port));
+
+ return GRUB_USB_ERR_NONE;
+}
+
+static grub_usb_speed_t
+grub_ehci_detect_dev (grub_usb_controller_t dev, int port, int *changed)
+{
+ struct grub_ehci *e = (struct grub_ehci *) dev->data;
+ grub_uint32_t status, line_state;
+
+ status = grub_ehci_port_read (e, port);
+
+ /* Connect Status Change bit - it detects change of connection */
+ if (status & GRUB_EHCI_PORT_CONNECT_CH)
+ {
+ *changed = 1;
+ /* Reset bit Connect Status Change */
+ grub_ehci_port_setbits (e, port, GRUB_EHCI_PORT_CONNECT_CH);
+ }
+ else
+ *changed = 0;
+
+ if (!(status & GRUB_EHCI_PORT_CONNECT))
+ { /* We should reset related "reset" flag in not connected state */
+ e->reset &= ~(1 << port);
+ return GRUB_USB_SPEED_NONE;
+ }
+ /* Detected connected state, so we should return speed.
+ * But we can detect only LOW speed device and only at connection
+ * time when PortEnabled=FALSE. FULL / HIGH speed detection is made
+ * later by EHCI-specific reset procedure.
+ * Another thing - if detected speed is LOW at connection time,
+ * we should change port ownership to companion controller.
+ * So:
+ * 1. If we detect connected and enabled and EHCI-owned port,
+ * we can say it is HIGH speed.
+ * 2. If we detect connected and not EHCI-owned port, we can say
+ * NONE speed, because such devices are not handled by EHCI.
+ * 3. If we detect connected, not enabled but reset port, we can say
+ * NONE speed, because it means FULL device connected to port and
+ * such devices are not handled by EHCI.
+ * 4. If we detect connected, not enabled and not reset port, which
+ * has line state != "K", we will say HIGH - it could be FULL or HIGH
+ * device, we will see it later after end of EHCI-specific reset
+ * procedure.
+ * 5. If we detect connected, not enabled and not reset port, which
+ * has line state == "K", we can say NONE speed, because LOW speed
+ * device is connected and we should change port ownership. */
+ if ((status & GRUB_EHCI_PORT_ENABLED) != 0) /* Port already enabled, return high speed. */
+ return GRUB_USB_SPEED_HIGH;
+ if ((status & GRUB_EHCI_PORT_OWNER) != 0) /* EHCI is not port owner */
+ return GRUB_USB_SPEED_NONE; /* EHCI driver is ignoring this port. */
+ if ((e->reset & (1 << port)) != 0) /* Port reset was done = FULL speed */
+ return GRUB_USB_SPEED_NONE; /* EHCI driver is ignoring this port. */
+ else /* Port connected but not enabled - test port speed. */
+ {
+ line_state = status & GRUB_EHCI_PORT_LINE_STAT;
+ if (line_state != GRUB_EHCI_PORT_LINE_LOWSP)
+ return GRUB_USB_SPEED_HIGH;
+ /* Detected LOW speed device, we should change
+ * port ownership.
+ * XXX: Fix it!: There should be test if related companion
+ * controler is available ! And what to do if it does not exist ? */
+ grub_ehci_port_setbits (e, port, GRUB_EHCI_PORT_OWNER);
+ return GRUB_USB_SPEED_NONE; /* Ignore this port */
+ /* Note: Reset of PORT_OWNER bit is done by EHCI HW when
+ * device is really disconnected from port.
+ * Don't do PORT_OWNER bit reset by SW when not connected signal
+ * is detected in port register ! */
+ }
+}
+
+static grub_err_t
+grub_ehci_restore_hw (void)
+{
+ struct grub_ehci *e;
+ grub_uint32_t n_ports;
+ int i;
+
+ /* We should re-enable all EHCI HW similarly as on inithw */
+ for (e = ehci; e; e = e->next)
+ {
+ /* Check if EHCI is halted and halt it if not */
+ if (grub_ehci_halt (e) != GRUB_USB_ERR_NONE)
+ grub_error (GRUB_ERR_TIMEOUT, "restore_hw: EHCI halt timeout");
+
+ /* Reset EHCI */
+ if (grub_ehci_reset (e) != GRUB_USB_ERR_NONE)
+ grub_error (GRUB_ERR_TIMEOUT, "restore_hw: EHCI reset timeout");
+
+ /* Setup some EHCI registers and enable EHCI */
+ grub_ehci_oper_write32 (e, GRUB_EHCI_FL_BASE, e->framelist_phys);
+ grub_ehci_oper_write32 (e, GRUB_EHCI_CUR_AL_ADDR,
+ grub_dma_virt2phys (&e->qh_virt[1],
+ e->qh_chunk));
+ grub_ehci_oper_write32 (e, GRUB_EHCI_COMMAND,
+ GRUB_EHCI_CMD_RUNSTOP |
+ grub_ehci_oper_read32 (e, GRUB_EHCI_COMMAND));
+
+ /* Set ownership of root hub ports to EHCI */
+ grub_ehci_oper_write32 (e, GRUB_EHCI_CONFIG_FLAG,
+ GRUB_EHCI_CF_EHCI_OWNER);
+
+ /* Enable asynchronous list */
+ grub_ehci_oper_write32 (e, GRUB_EHCI_COMMAND,
+ GRUB_EHCI_CMD_AS_ENABL
+ | GRUB_EHCI_CMD_PS_ENABL
+ | grub_ehci_oper_read32 (e, GRUB_EHCI_COMMAND));
+
+ /* Now should be possible to power-up and enumerate ports etc. */
+ if ((grub_ehci_ehcc_read32 (e, GRUB_EHCI_EHCC_SPARAMS)
+ & GRUB_EHCI_SPARAMS_PPC) != 0)
+ { /* EHCI has port powering control */
+ /* Power on all ports */
+ n_ports = grub_ehci_ehcc_read32 (e, GRUB_EHCI_EHCC_SPARAMS)
+ & GRUB_EHCI_SPARAMS_N_PORTS;
+ for (i = 0; i < (int) n_ports; i++)
+ grub_ehci_oper_write32 (e, GRUB_EHCI_PORT_STAT_CMD + i * 4,
+ GRUB_EHCI_PORT_POWER
+ | grub_ehci_oper_read32 (e,
+ GRUB_EHCI_PORT_STAT_CMD
+ + i * 4));
+ }
+ }
+
+ return GRUB_ERR_NONE;
+}
+
+static grub_err_t
+grub_ehci_fini_hw (int noreturn __attribute__ ((unused)))
+{
+ struct grub_ehci *e;
+
+ /* We should disable all EHCI HW to prevent any DMA access etc. */
+ for (e = ehci; e; e = e->next)
+ {
+ /* Disable both lists */
+ grub_ehci_oper_write32 (e, GRUB_EHCI_COMMAND,
+ ~(GRUB_EHCI_CMD_AS_ENABL | GRUB_EHCI_CMD_PS_ENABL)
+ & grub_ehci_oper_read32 (e, GRUB_EHCI_COMMAND));
+
+ /* Check if EHCI is halted and halt it if not */
+ grub_ehci_halt (e);
+
+ /* Reset EHCI */
+ grub_ehci_reset (e);
+ }
+
+ return GRUB_ERR_NONE;
+}
+
+static struct grub_usb_controller_dev usb_controller = {
+ .name = "ehci",
+ .iterate = grub_ehci_iterate,
+ .setup_transfer = grub_ehci_setup_transfer,
+ .check_transfer = grub_ehci_check_transfer,
+ .cancel_transfer = grub_ehci_cancel_transfer,
+ .hubports = grub_ehci_hubports,
+ .portstatus = grub_ehci_portstatus,
+ .detect_dev = grub_ehci_detect_dev,
+ /* estimated max. count of TDs for one bulk transfer */
+ .max_bulk_tds = GRUB_EHCI_N_TD * 3 / 4
+};
+
+GRUB_MOD_INIT (ehci)
+{
+ COMPILE_TIME_ASSERT (sizeof (struct grub_ehci_td) == 64);
+ COMPILE_TIME_ASSERT (sizeof (struct grub_ehci_qh) == 96);
+
+ grub_stop_disk_firmware ();
+
+ grub_boot_time ("Initing EHCI hardware");
+ grub_ehci_pci_scan ();
+ grub_boot_time ("Registering EHCI driver");
+ grub_usb_controller_dev_register (&usb_controller);
+ grub_boot_time ("EHCI driver registered");
+ grub_loader_register_preboot_hook (grub_ehci_fini_hw, grub_ehci_restore_hw,
+ GRUB_LOADER_PREBOOT_HOOK_PRIO_DISK);
+}
+
+GRUB_MOD_FINI (ehci)
+{
+ grub_ehci_fini_hw (0);
+ grub_usb_controller_dev_unregister (&usb_controller);
+}
diff --git a/grub-core/bus/usb/ohci.c b/grub-core/bus/usb/ohci.c
new file mode 100644
index 0000000..f0be533
--- /dev/null
+++ b/grub-core/bus/usb/ohci.c
@@ -0,0 +1,1468 @@
+/* ohci.c - OHCI Support. */
+/*
+ * GRUB -- GRand Unified Bootloader
+ * Copyright (C) 2008 Free Software Foundation, Inc.
+ *
+ * GRUB is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * GRUB is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with GRUB. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <grub/dl.h>
+#include <grub/mm.h>
+#include <grub/usb.h>
+#include <grub/usbtrans.h>
+#include <grub/misc.h>
+#include <grub/pci.h>
+#include <grub/cpu/pci.h>
+#include <grub/cpu/io.h>
+#include <grub/time.h>
+#include <grub/cs5536.h>
+#include <grub/loader.h>
+#include <grub/disk.h>
+
+GRUB_MOD_LICENSE ("GPLv3+");
+
+struct grub_ohci_hcca
+{
+ /* Pointers to Interrupt Endpoint Descriptors. Not used by
+ GRUB. */
+ grub_uint32_t inttable[32];
+
+ /* Current frame number. */
+ grub_uint16_t framenumber;
+
+ grub_uint16_t pad;
+
+ /* List of completed TDs. */
+ grub_uint32_t donehead;
+
+ grub_uint8_t reserved[116];
+} GRUB_PACKED;
+
+/* OHCI General Transfer Descriptor */
+struct grub_ohci_td
+{
+ /* Information used to construct the TOKEN packet. */
+ grub_uint32_t token;
+ grub_uint32_t buffer; /* LittleEndian physical address */
+ grub_uint32_t next_td; /* LittleEndian physical address */
+ grub_uint32_t buffer_end; /* LittleEndian physical address */
+ /* next values are not for OHCI HW */
+ volatile struct grub_ohci_td *link_td; /* pointer to next free/chained TD
+ * pointer as uint32 */
+ grub_uint32_t prev_td_phys; /* we need it to find previous TD
+ * physical address in CPU endian */
+ grub_uint32_t tr_index; /* index of TD in transfer */
+ grub_uint8_t pad[8 - sizeof (volatile struct grub_ohci_td *)]; /* padding to 32 bytes */
+} GRUB_PACKED;
+
+/* OHCI Endpoint Descriptor. */
+struct grub_ohci_ed
+{
+ grub_uint32_t target;
+ grub_uint32_t td_tail;
+ grub_uint32_t td_head;
+ grub_uint32_t next_ed;
+} GRUB_PACKED;
+
+typedef volatile struct grub_ohci_td *grub_ohci_td_t;
+typedef volatile struct grub_ohci_ed *grub_ohci_ed_t;
+
+/* Experimental change of ED/TD allocation */
+/* Little bit similar as in UHCI */
+/* Implementation assumes:
+ * 32-bits architecture - XXX: fix for 64-bits
+ * memory allocated by grub_memalign_dma32 must be continuous
+ * in virtual and also in physical memory */
+struct grub_ohci
+{
+ volatile grub_uint32_t *iobase;
+ volatile struct grub_ohci_hcca *hcca;
+ grub_uint32_t hcca_addr;
+ struct grub_pci_dma_chunk *hcca_chunk;
+ grub_ohci_ed_t ed_ctrl; /* EDs for CONTROL */
+ grub_uint32_t ed_ctrl_addr;
+ struct grub_pci_dma_chunk *ed_ctrl_chunk;
+ grub_ohci_ed_t ed_bulk; /* EDs for BULK */
+ grub_uint32_t ed_bulk_addr;
+ struct grub_pci_dma_chunk *ed_bulk_chunk;
+ grub_ohci_td_t td; /* TDs */
+ grub_uint32_t td_addr;
+ struct grub_pci_dma_chunk *td_chunk;
+ struct grub_ohci *next;
+ grub_ohci_td_t td_free; /* Pointer to first free TD */
+};
+
+static struct grub_ohci *ohci;
+
+typedef enum
+{
+ GRUB_OHCI_REG_REVISION = 0x00,
+ GRUB_OHCI_REG_CONTROL,
+ GRUB_OHCI_REG_CMDSTATUS,
+ GRUB_OHCI_REG_INTSTATUS,
+ GRUB_OHCI_REG_INTENA,
+ GRUB_OHCI_REG_INTDIS,
+ GRUB_OHCI_REG_HCCA,
+ GRUB_OHCI_REG_PERIODIC,
+ GRUB_OHCI_REG_CONTROLHEAD,
+ GRUB_OHCI_REG_CONTROLCURR,
+ GRUB_OHCI_REG_BULKHEAD,
+ GRUB_OHCI_REG_BULKCURR,
+ GRUB_OHCI_REG_DONEHEAD,
+ GRUB_OHCI_REG_FRAME_INTERVAL,
+ GRUB_OHCI_REG_PERIODIC_START = 16,
+ GRUB_OHCI_REG_RHUBA = 18,
+ GRUB_OHCI_REG_RHUBPORT = 21,
+ GRUB_OHCI_REG_LEGACY_CONTROL = 0x100,
+ GRUB_OHCI_REG_LEGACY_INPUT = 0x104,
+ GRUB_OHCI_REG_LEGACY_OUTPUT = 0x108,
+ GRUB_OHCI_REG_LEGACY_STATUS = 0x10c
+} grub_ohci_reg_t;
+
+#define GRUB_OHCI_RHUB_PORT_POWER_MASK 0x300
+#define GRUB_OHCI_RHUB_PORT_ALL_POWERED 0x200
+
+#define GRUB_OHCI_REG_FRAME_INTERVAL_FSMPS_MASK 0x8fff0000
+#define GRUB_OHCI_REG_FRAME_INTERVAL_FSMPS_SHIFT 16
+#define GRUB_OHCI_REG_FRAME_INTERVAL_FI_SHIFT 0
+
+/* XXX: Is this choice of timings sane? */
+#define GRUB_OHCI_FSMPS 0x2778
+#define GRUB_OHCI_PERIODIC_START 0x257f
+#define GRUB_OHCI_FRAME_INTERVAL 0x2edf
+
+#define GRUB_OHCI_SET_PORT_ENABLE (1 << 1)
+#define GRUB_OHCI_CLEAR_PORT_ENABLE (1 << 0)
+#define GRUB_OHCI_SET_PORT_RESET (1 << 4)
+#define GRUB_OHCI_SET_PORT_RESET_STATUS_CHANGE (1 << 20)
+
+#define GRUB_OHCI_REG_CONTROL_BULK_ENABLE (1 << 5)
+#define GRUB_OHCI_REG_CONTROL_CONTROL_ENABLE (1 << 4)
+
+#define GRUB_OHCI_RESET_CONNECT_CHANGE (1 << 16)
+#define GRUB_OHCI_CTRL_EDS 256
+#define GRUB_OHCI_BULK_EDS 510
+#define GRUB_OHCI_TDS 640
+
+#define GRUB_OHCI_ED_ADDR_MASK 0x7ff
+
+static inline grub_ohci_ed_t
+grub_ohci_ed_phys2virt (struct grub_ohci *o, int bulk, grub_uint32_t x)
+{
+ if (!x)
+ return NULL;
+ if (bulk)
+ return (grub_ohci_ed_t) (x - o->ed_bulk_addr
+ + (grub_uint8_t *) o->ed_bulk);
+ return (grub_ohci_ed_t) (x - o->ed_ctrl_addr
+ + (grub_uint8_t *) o->ed_ctrl);
+}
+
+static grub_uint32_t
+grub_ohci_virt_to_phys (struct grub_ohci *o, int bulk, grub_ohci_ed_t x)
+{
+ if (!x)
+ return 0;
+
+ if (bulk)
+ return (grub_uint8_t *) x - (grub_uint8_t *) o->ed_bulk + o->ed_bulk_addr;
+ return (grub_uint8_t *) x - (grub_uint8_t *) o->ed_ctrl + o->ed_ctrl_addr;
+}
+
+static inline grub_ohci_td_t
+grub_ohci_td_phys2virt (struct grub_ohci *o, grub_uint32_t x)
+{
+ if (!x)
+ return NULL;
+ return (grub_ohci_td_t) (x - o->td_addr + (grub_uint8_t *) o->td);
+}
+
+static grub_uint32_t
+grub_ohci_td_virt2phys (struct grub_ohci *o, grub_ohci_td_t x)
+{
+ if (!x)
+ return 0;
+ return (grub_uint8_t *)x - (grub_uint8_t *)o->td + o->td_addr;
+}
+
+
+static grub_uint32_t
+grub_ohci_readreg32 (struct grub_ohci *o, grub_ohci_reg_t reg)
+{
+ return grub_le_to_cpu32 (*(o->iobase + reg));
+}
+
+static void
+grub_ohci_writereg32 (struct grub_ohci *o,
+ grub_ohci_reg_t reg, grub_uint32_t val)
+{
+ *(o->iobase + reg) = grub_cpu_to_le32 (val);
+}
+
+
+
+/* Iterate over all PCI devices. Determine if a device is an OHCI
+ controller. If this is the case, initialize it. */
+static int
+grub_ohci_pci_iter (grub_pci_device_t dev, grub_pci_id_t pciid,
+ void *data __attribute__ ((unused)))
+{
+ grub_uint32_t interf;
+ grub_uint32_t base;
+ grub_pci_address_t addr;
+ struct grub_ohci *o;
+ grub_uint32_t revision;
+ int j;
+
+ /* Determine IO base address. */
+ grub_dprintf ("ohci", "pciid = %x\n", pciid);
+
+ if (pciid == GRUB_CS5536_PCIID)
+ {
+ grub_uint64_t basereg;
+
+ basereg = grub_cs5536_read_msr (dev, GRUB_CS5536_MSR_USB_OHCI_BASE);
+ if (!(basereg & GRUB_CS5536_MSR_USB_BASE_MEMORY_ENABLE))
+ {
+ /* Shouldn't happen. */
+ grub_dprintf ("ohci", "No OHCI address is assigned\n");
+ return 0;
+ }
+ base = (basereg & GRUB_CS5536_MSR_USB_BASE_ADDR_MASK);
+ basereg |= GRUB_CS5536_MSR_USB_BASE_BUS_MASTER;
+ basereg &= ~GRUB_CS5536_MSR_USB_BASE_PME_ENABLED;
+ basereg &= ~GRUB_CS5536_MSR_USB_BASE_PME_STATUS;
+ grub_cs5536_write_msr (dev, GRUB_CS5536_MSR_USB_OHCI_BASE, basereg);
+ }
+ else
+ {
+ grub_uint32_t class_code;
+ grub_uint32_t class;
+ grub_uint32_t subclass;
+
+ addr = grub_pci_make_address (dev, GRUB_PCI_REG_CLASS);
+ class_code = grub_pci_read (addr) >> 8;
+
+ interf = class_code & 0xFF;
+ subclass = (class_code >> 8) & 0xFF;
+ class = class_code >> 16;
+
+ /* If this is not an OHCI controller, just return. */
+ if (class != 0x0c || subclass != 0x03 || interf != 0x10)
+ return 0;
+
+ addr = grub_pci_make_address (dev, GRUB_PCI_REG_ADDRESS_REG0);
+ base = grub_pci_read (addr);
+
+ base &= GRUB_PCI_ADDR_MEM_MASK;
+ if (!base)
+ {
+ grub_dprintf ("ehci",
+ "EHCI: EHCI is not mapper\n");
+ return 0;
+ }
+
+ /* Set bus master - needed for coreboot, VMware, broken BIOSes etc. */
+ addr = grub_pci_make_address (dev, GRUB_PCI_REG_COMMAND);
+ grub_pci_write_word(addr,
+ GRUB_PCI_COMMAND_MEM_ENABLED
+ | GRUB_PCI_COMMAND_BUS_MASTER
+ | grub_pci_read_word(addr));
+
+ grub_dprintf ("ohci", "class=0x%02x 0x%02x interface 0x%02x\n",
+ class, subclass, interf);
+ }
+
+ /* Allocate memory for the controller and register it. */
+ o = grub_malloc (sizeof (*o));
+ if (! o)
+ return 1;
+ grub_memset ((void*)o, 0, sizeof (*o));
+ o->iobase = grub_pci_device_map_range (dev, base, 0x800);
+
+ grub_dprintf ("ohci", "base=%p\n", o->iobase);
+
+ /* Reserve memory for the HCCA. */
+ o->hcca_chunk = grub_memalign_dma32 (256, 256);
+ if (! o->hcca_chunk)
+ goto fail;
+ o->hcca = grub_dma_get_virt (o->hcca_chunk);
+ o->hcca_addr = grub_dma_get_phys (o->hcca_chunk);
+ grub_memset ((void*)o->hcca, 0, sizeof(*o->hcca));
+ grub_dprintf ("ohci", "hcca: chunk=%p, virt=%p, phys=0x%02x\n",
+ o->hcca_chunk, o->hcca, o->hcca_addr);
+
+ /* Reserve memory for ctrl EDs. */
+ o->ed_ctrl_chunk = grub_memalign_dma32 (16, sizeof(struct grub_ohci_ed)
+ * GRUB_OHCI_CTRL_EDS);
+ if (! o->ed_ctrl_chunk)
+ goto fail;
+ o->ed_ctrl = grub_dma_get_virt (o->ed_ctrl_chunk);
+ o->ed_ctrl_addr = grub_dma_get_phys (o->ed_ctrl_chunk);
+ /* Preset EDs */
+ grub_memset ((void *) o->ed_ctrl, 0, sizeof (struct grub_ohci_ed)
+ * GRUB_OHCI_CTRL_EDS);
+ for (j=0; j < GRUB_OHCI_CTRL_EDS; j++)
+ o->ed_ctrl[j].target = grub_cpu_to_le32_compile_time (1 << 14); /* skip */
+
+ grub_dprintf ("ohci", "EDs-C: chunk=%p, virt=%p, phys=0x%02x\n",
+ o->ed_ctrl_chunk, o->ed_ctrl, o->ed_ctrl_addr);
+
+ /* Reserve memory for bulk EDs. */
+ o->ed_bulk_chunk = grub_memalign_dma32 (16, sizeof (struct grub_ohci_ed)
+ * GRUB_OHCI_BULK_EDS);
+ if (! o->ed_bulk_chunk)
+ goto fail;
+ o->ed_bulk = grub_dma_get_virt (o->ed_bulk_chunk);
+ o->ed_bulk_addr = grub_dma_get_phys (o->ed_bulk_chunk);
+ /* Preset EDs */
+ grub_memset ((void*)o->ed_bulk, 0, sizeof(struct grub_ohci_ed) * GRUB_OHCI_BULK_EDS);
+ for (j=0; j < GRUB_OHCI_BULK_EDS; j++)
+ o->ed_bulk[j].target = grub_cpu_to_le32_compile_time (1 << 14); /* skip */
+
+ grub_dprintf ("ohci", "EDs-B: chunk=%p, virt=%p, phys=0x%02x\n",
+ o->ed_bulk_chunk, o->ed_bulk, o->ed_bulk_addr);
+
+ /* Reserve memory for TDs. */
+ o->td_chunk = grub_memalign_dma32 (32, sizeof(struct grub_ohci_td)*GRUB_OHCI_TDS);
+ /* Why is it aligned on 32 boundary if spec. says 16 ?
+ * We have structure 32 bytes long and we don't want cross
+ * 4K boundary inside structure. */
+ if (! o->td_chunk)
+ goto fail;
+ o->td_free = o->td = grub_dma_get_virt (o->td_chunk);
+ o->td_addr = grub_dma_get_phys (o->td_chunk);
+ /* Preset free TDs chain in TDs */
+ grub_memset ((void*)o->td, 0, sizeof(struct grub_ohci_td) * GRUB_OHCI_TDS);
+ for (j=0; j < (GRUB_OHCI_TDS-1); j++)
+ o->td[j].link_td = &o->td[j+1];
+
+ grub_dprintf ("ohci", "TDs: chunk=%p, virt=%p, phys=0x%02x\n",
+ o->td_chunk, o->td, o->td_addr);
+
+ /* Check if the OHCI revision is actually 1.0 as supported. */
+ revision = grub_ohci_readreg32 (o, GRUB_OHCI_REG_REVISION);
+ grub_dprintf ("ohci", "OHCI revision=0x%02x\n", revision & 0xFF);
+ if ((revision & 0xFF) != 0x10)
+ goto fail;
+
+ {
+ grub_uint32_t control;
+ /* Check SMM/BIOS ownership of OHCI (SMM = USB Legacy Support driver for BIOS) */
+ control = grub_ohci_readreg32 (o, GRUB_OHCI_REG_CONTROL);
+ if ((control & 0x100) != 0)
+ {
+ unsigned i;
+ grub_dprintf("ohci", "OHCI is owned by SMM\n");
+ /* Do change of ownership */
+ /* Ownership change request */
+ grub_ohci_writereg32 (o, GRUB_OHCI_REG_CMDSTATUS, (1<<3)); /* XXX: Magic. */
+ /* Waiting for SMM deactivation */
+ for (i=0; i < 10; i++)
+ {
+ if ((grub_ohci_readreg32 (o, GRUB_OHCI_REG_CONTROL) & 0x100) == 0)
+ {
+ grub_dprintf("ohci", "Ownership changed normally.\n");
+ break;
+ }
+ grub_millisleep (100);
+ }
+ if (i >= 10)
+ {
+ grub_ohci_writereg32 (o, GRUB_OHCI_REG_CONTROL,
+ grub_ohci_readreg32 (o, GRUB_OHCI_REG_CONTROL) & ~0x100);
+ grub_dprintf("ohci", "Ownership changing timeout, change forced !\n");
+ }
+ }
+ else if (((control & 0x100) == 0) &&
+ ((control & 0xc0) != 0)) /* Not owned by SMM nor reset */
+ {
+ grub_dprintf("ohci", "OHCI is owned by BIOS\n");
+ /* Do change of ownership - not implemented yet... */
+ /* In fact we probably need to do nothing ...? */
+ }
+ else
+ {
+ grub_dprintf("ohci", "OHCI is not owned by SMM nor BIOS\n");
+ /* We can setup OHCI. */
+ }
+ }
+
+ /* Suspend the OHCI by issuing a reset. */
+ grub_ohci_writereg32 (o, GRUB_OHCI_REG_CMDSTATUS, 1); /* XXX: Magic. */
+ grub_millisleep (1);
+ grub_dprintf ("ohci", "OHCI reset\n");
+
+ grub_ohci_writereg32 (o, GRUB_OHCI_REG_FRAME_INTERVAL,
+ (GRUB_OHCI_FSMPS
+ << GRUB_OHCI_REG_FRAME_INTERVAL_FSMPS_SHIFT)
+ | (GRUB_OHCI_FRAME_INTERVAL
+ << GRUB_OHCI_REG_FRAME_INTERVAL_FI_SHIFT));
+
+ grub_ohci_writereg32 (o, GRUB_OHCI_REG_PERIODIC_START,
+ GRUB_OHCI_PERIODIC_START);
+
+ /* Setup the HCCA. */
+ o->hcca->donehead = 0;
+ grub_ohci_writereg32 (o, GRUB_OHCI_REG_HCCA, o->hcca_addr);
+ grub_dprintf ("ohci", "OHCI HCCA\n");
+
+ /* Misc. pre-sets. */
+ o->hcca->donehead = 0;
+ grub_ohci_writereg32 (o, GRUB_OHCI_REG_INTSTATUS, 0x7f); /* Clears everything */
+ /* We don't want modify CONTROL/BULK HEAD registers.
+ * So we assign to HEAD registers zero ED from related array
+ * and we will not use this ED, it will be always skipped.
+ * It should not produce notable performance penalty (I hope). */
+ grub_ohci_writereg32 (o, GRUB_OHCI_REG_CONTROLHEAD, o->ed_ctrl_addr);
+ grub_ohci_writereg32 (o, GRUB_OHCI_REG_CONTROLCURR, 0);
+ grub_ohci_writereg32 (o, GRUB_OHCI_REG_BULKHEAD, o->ed_bulk_addr);
+ grub_ohci_writereg32 (o, GRUB_OHCI_REG_BULKCURR, 0);
+
+ /* Check OHCI Legacy Support */
+ if ((revision & 0x100) != 0)
+ {
+ grub_dprintf ("ohci", "Legacy Support registers detected\n");
+ grub_dprintf ("ohci", "Current state of legacy control reg.: 0x%04x\n",
+ grub_ohci_readreg32 (o, GRUB_OHCI_REG_LEGACY_CONTROL));
+ grub_ohci_writereg32 (o, GRUB_OHCI_REG_LEGACY_CONTROL,
+ (grub_ohci_readreg32 (o, GRUB_OHCI_REG_LEGACY_CONTROL)) & ~1);
+ grub_dprintf ("ohci", "OHCI Legacy Support disabled.\n");
+ }
+
+ /* Enable the OHCI + enable CONTROL and BULK LIST. */
+ grub_ohci_writereg32 (o, GRUB_OHCI_REG_CONTROL,
+ (2 << 6)
+ | GRUB_OHCI_REG_CONTROL_CONTROL_ENABLE
+ | GRUB_OHCI_REG_CONTROL_BULK_ENABLE );
+ grub_dprintf ("ohci", "OHCI enable: 0x%02x\n",
+ (grub_ohci_readreg32 (o, GRUB_OHCI_REG_CONTROL) >> 6) & 3);
+
+ /* Power on all ports */
+ grub_ohci_writereg32 (o, GRUB_OHCI_REG_RHUBA,
+ (grub_ohci_readreg32 (o, GRUB_OHCI_REG_RHUBA)
+ & ~GRUB_OHCI_RHUB_PORT_POWER_MASK)
+ | GRUB_OHCI_RHUB_PORT_ALL_POWERED);
+#if 0 /* We don't need it at all, handled via hotplugging */
+ /* Now we have hot-plugging, we need to wait for stable power only */
+ grub_millisleep (100);
+#endif
+
+ /* Link to ohci now that initialisation is successful. */
+ o->next = ohci;
+ ohci = o;
+
+ return 0;
+
+ fail:
+ if (o)
+ {
+ grub_dma_free (o->td_chunk);
+ grub_dma_free (o->ed_bulk_chunk);
+ grub_dma_free (o->ed_ctrl_chunk);
+ grub_dma_free (o->hcca_chunk);
+ }
+ grub_free (o);
+
+ return 0;
+}
+
+
+static void
+grub_ohci_inithw (void)
+{
+ grub_pci_iterate (grub_ohci_pci_iter, NULL);
+}
+
+
+
+static int
+grub_ohci_iterate (grub_usb_controller_iterate_hook_t hook, void *hook_data)
+{
+ struct grub_ohci *o;
+ struct grub_usb_controller dev;
+
+ for (o = ohci; o; o = o->next)
+ {
+ dev.data = o;
+ if (hook (&dev, hook_data))
+ return 1;
+ }
+
+ return 0;
+}
+
+static grub_ohci_ed_t
+grub_ohci_find_ed (struct grub_ohci *o, int bulk, grub_uint32_t target)
+{
+ grub_ohci_ed_t ed, ed_next;
+ grub_uint32_t target_addr = target & GRUB_OHCI_ED_ADDR_MASK;
+ int count;
+ int i;
+
+ /* Use proper values and structures. */
+ if (bulk)
+ {
+ count = GRUB_OHCI_BULK_EDS;
+ ed = o->ed_bulk;
+ ed_next = grub_ohci_ed_phys2virt(o, bulk,
+ grub_le_to_cpu32 (ed->next_ed) );
+ }
+ else
+ {
+ count = GRUB_OHCI_CTRL_EDS;
+ ed = o->ed_ctrl;
+ ed_next = grub_ohci_ed_phys2virt(o, bulk,
+ grub_le_to_cpu32 (ed->next_ed) );
+ }
+
+ /* First try to find existing ED with proper target address */
+ for (i = 0; ; )
+ {
+ if (i && /* We ignore zero ED */
+ ((ed->target & GRUB_OHCI_ED_ADDR_MASK) == target_addr))
+ return ed; /* Found proper existing ED */
+ i++;
+ if (ed_next && (i < count))
+ {
+ ed = ed_next;
+ ed_next = grub_ohci_ed_phys2virt(o, bulk,
+ grub_le_to_cpu32 (ed->next_ed) );
+ continue;
+ }
+ break;
+ }
+ /* ED with target_addr does not exist, we have to add it */
+ /* Have we any free ED in array ? */
+ if (i >= count) /* No. */
+ return NULL;
+ /* Currently we simply take next ED in array, no allocation
+ * function is used. It should be no problem until hot-plugging
+ * will be implemented, i.e. until we will need to de-allocate EDs
+ * of unplugged devices. */
+ /* We can link new ED to previous ED safely as the new ED should
+ * still have set skip bit. */
+ ed->next_ed = grub_cpu_to_le32 ( grub_ohci_virt_to_phys (o,
+ bulk, &ed[1]));
+ return &ed[1];
+}
+
+static grub_ohci_td_t
+grub_ohci_alloc_td (struct grub_ohci *o)
+{
+ grub_ohci_td_t ret;
+
+ /* Check if there is a Transfer Descriptor available. */
+ if (! o->td_free)
+ return NULL;
+
+ ret = o->td_free; /* Take current free TD */
+ o->td_free = (grub_ohci_td_t)ret->link_td; /* Advance to next free TD in chain */
+ ret->link_td = 0; /* Reset link_td in allocated TD */
+ return ret;
+}
+
+static void
+grub_ohci_free_td (struct grub_ohci *o, grub_ohci_td_t td)
+{
+ grub_memset ( (void*)td, 0, sizeof(struct grub_ohci_td) );
+ td->link_td = o->td_free; /* Cahin new free TD & rest */
+ o->td_free = td; /* Change address of first free TD */
+}
+
+static void
+grub_ohci_free_tds (struct grub_ohci *o, grub_ohci_td_t td)
+{
+ if (!td)
+ return;
+
+ /* Unchain first TD from previous TD if it is chained */
+ if (td->prev_td_phys)
+ {
+ grub_ohci_td_t td_prev_virt = grub_ohci_td_phys2virt(o,
+ td->prev_td_phys);
+
+ if (td == (grub_ohci_td_t) td_prev_virt->link_td)
+ td_prev_virt->link_td = 0;
+ }
+
+ /* Free all TDs from td (chained by link_td) */
+ while (td)
+ {
+ grub_ohci_td_t tdprev;
+
+ /* Unlink the queue. */
+ tdprev = td;
+ td = (grub_ohci_td_t) td->link_td;
+
+ /* Free the TD. */
+ grub_ohci_free_td (o, tdprev);
+ }
+}
+
+static void
+grub_ohci_transaction (grub_ohci_td_t td,
+ grub_transfer_type_t type, unsigned int toggle,
+ grub_size_t size, grub_uint32_t data)
+{
+ grub_uint32_t token;
+ grub_uint32_t buffer;
+ grub_uint32_t buffer_end;
+
+ grub_dprintf ("ohci", "OHCI transaction td=%p type=%d, toggle=%d, size=%lu\n",
+ td, type, toggle, (unsigned long) size);
+
+ switch (type)
+ {
+ case GRUB_USB_TRANSFER_TYPE_SETUP:
+ token = 0 << 19;
+ break;
+ case GRUB_USB_TRANSFER_TYPE_IN:
+ token = 2 << 19;
+ break;
+ case GRUB_USB_TRANSFER_TYPE_OUT:
+ token = 1 << 19;
+ break;
+ default:
+ token = 0;
+ break;
+ }
+
+ /* Set the token */
+ token |= ( 7 << 21); /* Never generate interrupt */
+ token |= toggle << 24;
+ token |= 1 << 25;
+
+ /* Set "Not accessed" error code */
+ token |= 15 << 28;
+
+ buffer = data;
+ buffer_end = buffer + size - 1;
+
+ /* Set correct buffer values in TD if zero transfer occurs */
+ if (size)
+ {
+ buffer = (grub_uint32_t) data;
+ buffer_end = buffer + size - 1;
+ td->buffer = grub_cpu_to_le32 (buffer);
+ td->buffer_end = grub_cpu_to_le32 (buffer_end);
+ }
+ else
+ {
+ td->buffer = 0;
+ td->buffer_end = 0;
+ }
+
+ /* Set the rest of TD */
+ td->token = grub_cpu_to_le32 (token);
+ td->next_td = 0;
+}
+
+struct grub_ohci_transfer_controller_data
+{
+ grub_uint32_t tderr_phys;
+ grub_uint32_t td_last_phys;
+ grub_ohci_ed_t ed_virt;
+ grub_ohci_td_t td_current_virt;
+ grub_ohci_td_t td_head_virt;
+};
+
+static grub_usb_err_t
+grub_ohci_setup_transfer (grub_usb_controller_t dev,
+ grub_usb_transfer_t transfer)
+{
+ struct grub_ohci *o = (struct grub_ohci *) dev->data;
+ int bulk = 0;
+ grub_ohci_td_t td_next_virt;
+ grub_uint32_t target;
+ grub_uint32_t td_head_phys;
+ grub_uint32_t td_tail_phys;
+ int i;
+ struct grub_ohci_transfer_controller_data *cdata;
+
+ cdata = grub_zalloc (sizeof (*cdata));
+ if (!cdata)
+ return GRUB_USB_ERR_INTERNAL;
+
+ /* Pre-set target for ED - we need it to find proper ED */
+ /* Set the device address. */
+ target = transfer->devaddr;
+ /* Set the endpoint. It should be masked, we need 4 bits only. */
+ target |= (transfer->endpoint & 15) << 7;
+ /* Set the device speed. */
+ target |= (transfer->dev->speed == GRUB_USB_SPEED_LOW) << 13;
+ /* Set the maximum packet size. */
+ target |= transfer->max << 16;
+
+ /* Determine if transfer type is bulk - we need to select proper ED */
+ switch (transfer->type)
+ {
+ case GRUB_USB_TRANSACTION_TYPE_BULK:
+ bulk = 1;
+ break;
+
+ case GRUB_USB_TRANSACTION_TYPE_CONTROL:
+ break;
+
+ default:
+ grub_free (cdata);
+ return GRUB_USB_ERR_INTERNAL;
+ }
+
+ /* Find proper ED or add new ED */
+ cdata->ed_virt = grub_ohci_find_ed (o, bulk, target);
+ if (!cdata->ed_virt)
+ {
+ grub_dprintf ("ohci","Fatal: No free ED !\n");
+ grub_free (cdata);
+ return GRUB_USB_ERR_INTERNAL;
+ }
+
+ /* Take pointer to first TD from ED */
+ td_head_phys = grub_le_to_cpu32 (cdata->ed_virt->td_head) & ~0xf;
+ td_tail_phys = grub_le_to_cpu32 (cdata->ed_virt->td_tail) & ~0xf;
+
+ /* Sanity check - td_head should be equal to td_tail */
+ if (td_head_phys != td_tail_phys) /* Should never happen ! */
+ {
+ grub_dprintf ("ohci", "Fatal: HEAD is not equal to TAIL !\n");
+ grub_dprintf ("ohci", "HEAD = 0x%02x, TAIL = 0x%02x\n",
+ td_head_phys, td_tail_phys);
+ /* XXX: Fix: What to do ? */
+ grub_free (cdata);
+ return GRUB_USB_ERR_INTERNAL;
+ }
+
+ /* Now we should handle first TD. If ED is newly allocated,
+ * we must allocate the first TD. */
+ if (!td_head_phys)
+ {
+ cdata->td_head_virt = grub_ohci_alloc_td (o);
+ if (!cdata->td_head_virt)
+ {
+ grub_free (cdata);
+ return GRUB_USB_ERR_INTERNAL; /* We don't need de-allocate ED */
+ }
+ /* We can set td_head only when ED is not active, i.e.
+ * when it is newly allocated. */
+ cdata->ed_virt->td_head
+ = grub_cpu_to_le32 (grub_ohci_td_virt2phys (o, cdata->td_head_virt));
+ cdata->ed_virt->td_tail = cdata->ed_virt->td_head;
+ }
+ else
+ cdata->td_head_virt = grub_ohci_td_phys2virt ( o, td_head_phys );
+
+ /* Set TDs */
+ cdata->td_last_phys = td_head_phys; /* initial value to make compiler happy... */
+ for (i = 0, cdata->td_current_virt = cdata->td_head_virt;
+ i < transfer->transcnt; i++)
+ {
+ grub_usb_transaction_t tr = &transfer->transactions[i];
+
+ grub_ohci_transaction (cdata->td_current_virt, tr->pid, tr->toggle,
+ tr->size, tr->data);
+
+ /* Set index of TD in transfer */
+ cdata->td_current_virt->tr_index = (grub_uint32_t) i;
+
+ /* Remember last used (processed) TD phys. addr. */
+ cdata->td_last_phys = grub_ohci_td_virt2phys (o, cdata->td_current_virt);
+
+ /* Allocate next TD */
+ td_next_virt = grub_ohci_alloc_td (o);
+ if (!td_next_virt) /* No free TD, cancel transfer and free TDs except head TD */
+ {
+ if (i) /* if i==0 we have nothing to free... */
+ grub_ohci_free_tds (o, grub_ohci_td_phys2virt(o,
+ grub_le_to_cpu32 (cdata->td_head_virt->next_td)));
+ /* Reset head TD */
+ grub_memset ( (void*)cdata->td_head_virt, 0,
+ sizeof(struct grub_ohci_td) );
+ grub_dprintf ("ohci", "Fatal: No free TD !");
+ grub_free (cdata);
+ return GRUB_USB_ERR_INTERNAL;
+ }
+
+ /* Chain TDs */
+
+ cdata->td_current_virt->link_td = td_next_virt;
+ cdata->td_current_virt->next_td = grub_cpu_to_le32 (
+ grub_ohci_td_virt2phys (o,
+ td_next_virt) );
+ td_next_virt->prev_td_phys = grub_ohci_td_virt2phys (o,
+ cdata->td_current_virt);
+ cdata->td_current_virt = td_next_virt;
+ }
+
+ grub_dprintf ("ohci", "Tail TD (not processed) = %p\n",
+ cdata->td_current_virt);
+
+ /* Setup the Endpoint Descriptor for transfer. */
+ /* First set necessary fields in TARGET but keep (or set) skip bit */
+ /* Note: It could be simpler if speed, format and max. packet
+ * size never change after first allocation of ED.
+ * But unfortunately max. packet size may change during initial
+ * setup sequence and we must handle it. */
+ cdata->ed_virt->target = grub_cpu_to_le32 (target | (1 << 14));
+ /* Set td_tail */
+ cdata->ed_virt->td_tail
+ = grub_cpu_to_le32 (grub_ohci_td_virt2phys (o, cdata->td_current_virt));
+ /* Now reset skip bit */
+ cdata->ed_virt->target = grub_cpu_to_le32 (target);
+ /* ed_virt->td_head = grub_cpu_to_le32 (td_head); Must not be changed, it is maintained by OHCI */
+ /* ed_virt->next_ed = grub_cpu_to_le32 (0); Handled by grub_ohci_find_ed, do not change ! */
+
+ grub_dprintf ("ohci", "program OHCI\n");
+
+ /* Program the OHCI to actually transfer. */
+ switch (transfer->type)
+ {
+ case GRUB_USB_TRANSACTION_TYPE_BULK:
+ {
+ grub_dprintf ("ohci", "BULK list filled\n");
+ /* Set BulkListFilled. */
+ grub_ohci_writereg32 (o, GRUB_OHCI_REG_CMDSTATUS, 1 << 2);
+ /* Read back of register should ensure it is really written */
+ grub_ohci_readreg32 (o, GRUB_OHCI_REG_CMDSTATUS);
+ break;
+ }
+
+ case GRUB_USB_TRANSACTION_TYPE_CONTROL:
+ {
+ grub_dprintf ("ohci", "CONTROL list filled\n");
+ /* Set ControlListFilled. */
+ grub_ohci_writereg32 (o, GRUB_OHCI_REG_CMDSTATUS, 1 << 1);
+ /* Read back of register should ensure it is really written */
+ grub_ohci_readreg32 (o, GRUB_OHCI_REG_CMDSTATUS);
+ break;
+ }
+ }
+
+ transfer->controller_data = cdata;
+
+ return GRUB_USB_ERR_NONE;
+}
+
+static void
+pre_finish_transfer (grub_usb_controller_t dev,
+ grub_usb_transfer_t transfer)
+{
+ struct grub_ohci *o = dev->data;
+ struct grub_ohci_transfer_controller_data *cdata = transfer->controller_data;
+ grub_uint32_t target;
+ grub_uint32_t status;
+ grub_uint32_t control;
+ grub_uint32_t intstatus;
+
+ /* There are many ways how the loop above can finish:
+ * - normally without any error via INTSTATUS WDH bit
+ * : tderr_phys == td_last_phys, td_head == td_tail
+ * - normally with error via HALT bit in ED TD HEAD
+ * : td_head = next TD after TD with error
+ * : tderr_phys = last processed and retired TD with error,
+ * i.e. should be != 0
+ * : if bad_OHCI == TRUE, tderr_phys will be probably invalid
+ * - unrecoverable error - I never seen it but it could be
+ * : err_unrec == TRUE, other values can contain anything...
+ * - timeout, it can be caused by:
+ * -- bad USB device - some devices have some bugs, see Linux source
+ * and related links
+ * -- bad OHCI controller - e.g. lost interrupts or does not set
+ * proper bits in INTSTATUS when real IRQ not enabled etc.,
+ * see Linux source and related links
+ * One known bug is handled - if transfer finished
+ * successfully (i.e. HEAD==TAIL, last transfer TD is retired,
+ * HALT bit is not set) and WDH bit is not set in INTSTATUS - in
+ * this case we set o->bad_OHCI=TRUE and do alternate loop
+ * and error handling - but there is problem how to find retired
+ * TD with error code if HALT occurs and if DONEHEAD is not
+ * working - we need to find TD previous to current ED HEAD
+ * -- bad code of this driver or some unknown reasons - :-(
+ * it can be e.g. bad handling of EDs/TDs/toggle bit...
+ */
+
+ /* Remember target for debug and set skip flag in ED */
+ /* It should be normaly not necessary but we need it at least
+ * in case of timeout */
+ target = grub_le_to_cpu32 ( cdata->ed_virt->target );
+ cdata->ed_virt->target = grub_cpu_to_le32 (target | (1 << 14));
+ /* Read registers for debug - they should be read now because
+ * debug prints case unwanted delays, so something can happen
+ * in the meantime... */
+ control = grub_ohci_readreg32 (o, GRUB_OHCI_REG_CONTROL);
+ status = grub_ohci_readreg32 (o, GRUB_OHCI_REG_CMDSTATUS);
+ intstatus = grub_ohci_readreg32 (o, GRUB_OHCI_REG_INTSTATUS);
+ /* Now print debug values - to have full info what happened */
+ grub_dprintf ("ohci", "loop finished: control=0x%02x status=0x%02x\n",
+ control, status);
+ grub_dprintf ("ohci", "intstatus=0x%02x, td_last_phys=0x%02x\n",
+ intstatus, cdata->td_last_phys);
+ grub_dprintf ("ohci", "TARGET=0x%02x, HEAD=0x%02x, TAIL=0x%02x\n",
+ target,
+ grub_le_to_cpu32 (cdata->ed_virt->td_head),
+ grub_le_to_cpu32 (cdata->ed_virt->td_tail) );
+
+}
+
+static void
+finish_transfer (grub_usb_controller_t dev,
+ grub_usb_transfer_t transfer)
+{
+ struct grub_ohci *o = dev->data;
+ struct grub_ohci_transfer_controller_data *cdata = transfer->controller_data;
+
+ /* Set empty ED - set HEAD = TAIL = last (not processed) TD */
+ cdata->ed_virt->td_head = grub_cpu_to_le32 (grub_le_to_cpu32 (cdata->ed_virt->td_tail) & ~0xf);
+
+ /* At this point always should be:
+ * ED has skip bit set and halted or empty or after next SOF,
+ * i.e. it is safe to free all TDs except last not processed
+ * ED HEAD == TAIL == phys. addr. of td_current_virt */
+
+ /* Un-chainig of last TD */
+ if (cdata->td_current_virt->prev_td_phys)
+ {
+ grub_ohci_td_t td_prev_virt
+ = grub_ohci_td_phys2virt (o, cdata->td_current_virt->prev_td_phys);
+
+ if (cdata->td_current_virt == (grub_ohci_td_t) td_prev_virt->link_td)
+ td_prev_virt->link_td = 0;
+
+ cdata->td_current_virt->prev_td_phys = 0;
+ }
+
+ grub_dprintf ("ohci", "OHCI finished, freeing\n");
+ grub_ohci_free_tds (o, cdata->td_head_virt);
+ grub_free (cdata);
+}
+
+static grub_usb_err_t
+parse_halt (grub_usb_controller_t dev,
+ grub_usb_transfer_t transfer,
+ grub_size_t *actual)
+{
+ struct grub_ohci *o = dev->data;
+ struct grub_ohci_transfer_controller_data *cdata = transfer->controller_data;
+ grub_uint8_t errcode = 0;
+ grub_usb_err_t err = GRUB_USB_ERR_NAK;
+ grub_ohci_td_t tderr_virt = NULL;
+
+ *actual = 0;
+
+ pre_finish_transfer (dev, transfer);
+
+ /* First we must get proper tderr_phys value */
+ /* Retired TD with error should be previous TD to ED->td_head */
+ cdata->tderr_phys = grub_ohci_td_phys2virt (o,
+ grub_le_to_cpu32 (cdata->ed_virt->td_head) & ~0xf )
+ ->prev_td_phys;
+
+ /* Prepare pointer to last processed TD and get error code */
+ tderr_virt = grub_ohci_td_phys2virt (o, cdata->tderr_phys);
+ /* Set index of last processed TD */
+ if (tderr_virt)
+ {
+ errcode = grub_le_to_cpu32 (tderr_virt->token) >> 28;
+ transfer->last_trans = tderr_virt->tr_index;
+ }
+ else
+ transfer->last_trans = -1;
+
+ /* Evaluation of error code */
+ grub_dprintf ("ohci", "OHCI tderr_phys=0x%02x, errcode=0x%02x\n",
+ cdata->tderr_phys, errcode);
+ switch (errcode)
+ {
+ case 0:
+ /* XXX: Should not happen! */
+ grub_error (GRUB_ERR_IO, "OHCI failed without reporting the reason");
+ err = GRUB_USB_ERR_INTERNAL;
+ break;
+
+ case 1:
+ /* XXX: CRC error. */
+ err = GRUB_USB_ERR_TIMEOUT;
+ break;
+
+ case 2:
+ err = GRUB_USB_ERR_BITSTUFF;
+ break;
+
+ case 3:
+ /* XXX: Data Toggle error. */
+ err = GRUB_USB_ERR_DATA;
+ break;
+
+ case 4:
+ err = GRUB_USB_ERR_STALL;
+ break;
+
+ case 5:
+ /* XXX: Not responding. */
+ err = GRUB_USB_ERR_TIMEOUT;
+ break;
+
+ case 6:
+ /* XXX: PID Check bits failed. */
+ err = GRUB_USB_ERR_BABBLE;
+ break;
+
+ case 7:
+ /* XXX: PID unexpected failed. */
+ err = GRUB_USB_ERR_BABBLE;
+ break;
+
+ case 8:
+ /* XXX: Data overrun error. */
+ err = GRUB_USB_ERR_DATA;
+ grub_dprintf ("ohci", "Overrun, failed TD address: %p, index: %d\n",
+ tderr_virt, tderr_virt->tr_index);
+ break;
+
+ case 9:
+ /* XXX: Data underrun error. */
+ grub_dprintf ("ohci", "Underrun, failed TD address: %p, index: %d\n",
+ tderr_virt, tderr_virt->tr_index);
+ 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;
+ err = GRUB_USB_ERR_NONE;
+ break;
+
+ case 10:
+ /* XXX: Reserved. */
+ err = GRUB_USB_ERR_NAK;
+ break;
+
+ case 11:
+ /* XXX: Reserved. */
+ err = GRUB_USB_ERR_NAK;
+ break;
+
+ case 12:
+ /* XXX: Buffer overrun. */
+ err = GRUB_USB_ERR_DATA;
+ break;
+
+ case 13:
+ /* XXX: Buffer underrun. */
+ err = GRUB_USB_ERR_DATA;
+ break;
+
+ default:
+ err = GRUB_USB_ERR_NAK;
+ break;
+ }
+
+ finish_transfer (dev, transfer);
+
+ return err;
+}
+
+static grub_usb_err_t
+parse_success (grub_usb_controller_t dev,
+ grub_usb_transfer_t transfer,
+ grub_size_t *actual)
+{
+ struct grub_ohci *o = dev->data;
+ struct grub_ohci_transfer_controller_data *cdata = transfer->controller_data;
+ grub_ohci_td_t tderr_virt = NULL;
+
+ pre_finish_transfer (dev, transfer);
+
+ /* I hope we can do it as transfer (most probably) finished OK */
+ cdata->tderr_phys = cdata->td_last_phys;
+
+ /* Prepare pointer to last processed TD */
+ tderr_virt = grub_ohci_td_phys2virt (o, cdata->tderr_phys);
+
+ /* Set index of last processed TD */
+ if (tderr_virt)
+ transfer->last_trans = tderr_virt->tr_index;
+ else
+ transfer->last_trans = -1;
+ *actual = transfer->size + 1;
+
+ finish_transfer (dev, transfer);
+
+ return GRUB_USB_ERR_NONE;
+}
+
+static grub_usb_err_t
+parse_unrec (grub_usb_controller_t dev,
+ grub_usb_transfer_t transfer,
+ grub_size_t *actual)
+{
+ struct grub_ohci *o = dev->data;
+
+ *actual = 0;
+
+ pre_finish_transfer (dev, transfer);
+
+ /* Don't try to get error code and last processed TD for proper
+ * toggle bit value - anything can be invalid */
+ grub_dprintf("ohci", "Unrecoverable error!");
+
+ /* Do OHCI reset in case of unrecoverable error - maybe we will need
+ * do more - re-enumerate bus etc. (?) */
+
+ /* Suspend the OHCI by issuing a reset. */
+ grub_ohci_writereg32 (o, GRUB_OHCI_REG_CMDSTATUS, 1); /* XXX: Magic. */
+ /* Read back of register should ensure it is really written */
+ grub_ohci_readreg32 (o, GRUB_OHCI_REG_CMDSTATUS);
+ grub_millisleep (1);
+ grub_dprintf ("ohci", "Unrecoverable error - OHCI reset\n");
+
+ /* Misc. resets. */
+ o->hcca->donehead = 0;
+ grub_ohci_writereg32 (o, GRUB_OHCI_REG_INTSTATUS, 0x7f); /* Clears everything */
+ grub_ohci_writereg32 (o, GRUB_OHCI_REG_CONTROLHEAD, o->ed_ctrl_addr);
+ grub_ohci_writereg32 (o, GRUB_OHCI_REG_CONTROLCURR, 0);
+ grub_ohci_writereg32 (o, GRUB_OHCI_REG_BULKHEAD, o->ed_bulk_addr);
+ grub_ohci_writereg32 (o, GRUB_OHCI_REG_BULKCURR, 0);
+ /* Read back of register should ensure it is really written */
+ grub_ohci_readreg32 (o, GRUB_OHCI_REG_INTSTATUS);
+
+ /* Enable the OHCI. */
+ grub_ohci_writereg32 (o, GRUB_OHCI_REG_CONTROL,
+ (2 << 6)
+ | GRUB_OHCI_REG_CONTROL_CONTROL_ENABLE
+ | GRUB_OHCI_REG_CONTROL_BULK_ENABLE );
+ finish_transfer (dev, transfer);
+
+ return GRUB_USB_ERR_UNRECOVERABLE;
+}
+
+static grub_usb_err_t
+grub_ohci_check_transfer (grub_usb_controller_t dev,
+ grub_usb_transfer_t transfer,
+ grub_size_t *actual)
+{
+ struct grub_ohci *o = dev->data;
+ struct grub_ohci_transfer_controller_data *cdata = transfer->controller_data;
+ grub_uint32_t intstatus;
+
+ /* Check transfer status */
+ intstatus = grub_ohci_readreg32 (o, GRUB_OHCI_REG_INTSTATUS);
+
+ if ((intstatus & 0x10) != 0)
+ /* Unrecoverable error - only reset can help...! */
+ return parse_unrec (dev, transfer, actual);
+
+ /* Detected a HALT. */
+ if ((grub_le_to_cpu32 (cdata->ed_virt->td_head) & 1))
+ return parse_halt (dev, transfer, actual);
+
+ /* Finished ED detection */
+ if ( (grub_le_to_cpu32 (cdata->ed_virt->td_head) & ~0xfU) ==
+ (grub_le_to_cpu32 (cdata->ed_virt->td_tail) & ~0xfU) ) /* Empty ED */
+ {
+ /* Check the HALT bit */
+ /* It looks like nonsense - it was tested previously...
+ * but it can change because OHCI is working
+ * simultaneously via DMA... */
+ if (grub_le_to_cpu32 (cdata->ed_virt->td_head) & 1)
+ return parse_halt (dev, transfer, actual);
+ else
+ return parse_success (dev, transfer, actual);
+ }
+
+ return GRUB_USB_ERR_WAIT;
+}
+
+static grub_usb_err_t
+grub_ohci_cancel_transfer (grub_usb_controller_t dev,
+ grub_usb_transfer_t transfer)
+{
+ struct grub_ohci *o = dev->data;
+ struct grub_ohci_transfer_controller_data *cdata = transfer->controller_data;
+ grub_ohci_td_t tderr_virt = NULL;
+
+ pre_finish_transfer (dev, transfer);
+
+ grub_dprintf("ohci", "Timeout !\n");
+
+ /* We should wait for next SOF to be sure that ED is unaccessed
+ * by OHCI */
+ /* SF bit reset. (SF bit indicates Start Of Frame (SOF) packet) */
+ grub_ohci_writereg32 (o, GRUB_OHCI_REG_INTSTATUS, (1<<2));
+ /* Wait for new SOF */
+ while ((grub_ohci_readreg32 (o, GRUB_OHCI_REG_INTSTATUS) & 0x4) == 0);
+
+ /* Possible retired TD with error should be previous TD to ED->td_head */
+ cdata->tderr_phys
+ = grub_ohci_td_phys2virt (o, grub_le_to_cpu32 (cdata->ed_virt->td_head)
+ & ~0xf)->prev_td_phys;
+
+ tderr_virt = grub_ohci_td_phys2virt (o,cdata-> tderr_phys);
+
+ grub_dprintf ("ohci", "Cancel: tderr_phys=0x%x, tderr_virt=%p\n",
+ cdata->tderr_phys, tderr_virt);
+
+ if (tderr_virt)
+ transfer->last_trans = tderr_virt->tr_index;
+ else
+ transfer->last_trans = -1;
+
+ finish_transfer (dev, transfer);
+
+ return GRUB_USB_ERR_NONE;
+}
+
+static grub_usb_err_t
+grub_ohci_portstatus (grub_usb_controller_t dev,
+ unsigned int port, unsigned int enable)
+{
+ struct grub_ohci *o = (struct grub_ohci *) dev->data;
+ grub_uint64_t endtime;
+ int i;
+
+ grub_dprintf ("ohci", "begin of portstatus=0x%02x\n",
+ grub_ohci_readreg32 (o, GRUB_OHCI_REG_RHUBPORT + port));
+
+ if (!enable) /* We don't need reset port */
+ {
+ /* Disable the port and wait for it. */
+ grub_ohci_writereg32 (o, GRUB_OHCI_REG_RHUBPORT + port,
+ GRUB_OHCI_CLEAR_PORT_ENABLE);
+ endtime = grub_get_time_ms () + 1000;
+ while ((grub_ohci_readreg32 (o, GRUB_OHCI_REG_RHUBPORT + port)
+ & (1 << 1)))
+ if (grub_get_time_ms () > endtime)
+ return GRUB_USB_ERR_TIMEOUT;
+
+ grub_dprintf ("ohci", "end of portstatus=0x%02x\n",
+ grub_ohci_readreg32 (o, GRUB_OHCI_REG_RHUBPORT + port));
+ return GRUB_USB_ERR_NONE;
+ }
+
+ /* OHCI does one reset signal 10ms long but USB spec.
+ * requests 50ms for root hub (no need to be continuous).
+ * So, we do reset 5 times... */
+ for (i = 0; i < 5; i++)
+ {
+ /* Reset the port - timing of reset is done by OHCI */
+ grub_ohci_writereg32 (o, GRUB_OHCI_REG_RHUBPORT + port,
+ GRUB_OHCI_SET_PORT_RESET);
+
+ /* Wait for reset completion */
+ endtime = grub_get_time_ms () + 1000;
+ while (! (grub_ohci_readreg32 (o, GRUB_OHCI_REG_RHUBPORT + port)
+ & GRUB_OHCI_SET_PORT_RESET_STATUS_CHANGE))
+ if (grub_get_time_ms () > endtime)
+ return GRUB_USB_ERR_TIMEOUT;
+
+ /* End the reset signaling - reset the reset status change */
+ grub_ohci_writereg32 (o, GRUB_OHCI_REG_RHUBPORT + port,
+ GRUB_OHCI_SET_PORT_RESET_STATUS_CHANGE);
+ grub_ohci_readreg32 (o, GRUB_OHCI_REG_RHUBPORT + port);
+ }
+
+ /* Enable port */
+ grub_ohci_writereg32 (o, GRUB_OHCI_REG_RHUBPORT + port,
+ GRUB_OHCI_SET_PORT_ENABLE);
+ grub_ohci_readreg32 (o, GRUB_OHCI_REG_RHUBPORT + port);
+
+ /* Wait for signal enabled */
+ endtime = grub_get_time_ms () + 1000;
+ while (! (grub_ohci_readreg32 (o, GRUB_OHCI_REG_RHUBPORT + port)
+ & (1 << 1)))
+ if (grub_get_time_ms () > endtime)
+ return GRUB_USB_ERR_TIMEOUT;
+
+ /* Reset bit Connect Status Change */
+ grub_ohci_writereg32 (o, GRUB_OHCI_REG_RHUBPORT + port,
+ GRUB_OHCI_RESET_CONNECT_CHANGE);
+
+ /* "Reset recovery time" (USB spec.) */
+ grub_millisleep (10);
+
+ grub_dprintf ("ohci", "end of portstatus=0x%02x\n",
+ grub_ohci_readreg32 (o, GRUB_OHCI_REG_RHUBPORT + port));
+
+ return GRUB_USB_ERR_NONE;
+}
+
+static grub_usb_speed_t
+grub_ohci_detect_dev (grub_usb_controller_t dev, int port, int *changed)
+{
+ struct grub_ohci *o = (struct grub_ohci *) dev->data;
+ grub_uint32_t status;
+
+ status = grub_ohci_readreg32 (o, GRUB_OHCI_REG_RHUBPORT + port);
+
+ grub_dprintf ("ohci", "detect_dev status=0x%02x\n", status);
+
+ /* Connect Status Change bit - it detects change of connection */
+ if (status & GRUB_OHCI_RESET_CONNECT_CHANGE)
+ {
+ *changed = 1;
+ /* Reset bit Connect Status Change */
+ grub_ohci_writereg32 (o, GRUB_OHCI_REG_RHUBPORT + port,
+ GRUB_OHCI_RESET_CONNECT_CHANGE);
+ }
+ else
+ *changed = 0;
+
+ if (! (status & 1))
+ return GRUB_USB_SPEED_NONE;
+ else if (status & (1 << 9))
+ return GRUB_USB_SPEED_LOW;
+ else
+ return GRUB_USB_SPEED_FULL;
+}
+
+static int
+grub_ohci_hubports (grub_usb_controller_t dev)
+{
+ struct grub_ohci *o = (struct grub_ohci *) dev->data;
+ grub_uint32_t portinfo;
+
+ portinfo = grub_ohci_readreg32 (o, GRUB_OHCI_REG_RHUBA);
+
+ grub_dprintf ("ohci", "root hub ports=%d\n", portinfo & 0xFF);
+
+ return portinfo & 0xFF;
+}
+
+static grub_err_t
+grub_ohci_fini_hw (int noreturn __attribute__ ((unused)))
+{
+ struct grub_ohci *o;
+
+ for (o = ohci; o; o = o->next)
+ {
+ int i, nports = grub_ohci_readreg32 (o, GRUB_OHCI_REG_RHUBA) & 0xff;
+ grub_uint64_t maxtime;
+
+ /* Set skip in all EDs */
+ if (o->ed_bulk)
+ for (i=0; i < GRUB_OHCI_BULK_EDS; i++)
+ o->ed_bulk[i].target |= grub_cpu_to_le32_compile_time (1 << 14); /* skip */
+ if (o->ed_ctrl)
+ for (i=0; i < GRUB_OHCI_CTRL_EDS; i++)
+ o->ed_ctrl[i].target |= grub_cpu_to_le32_compile_time (1 << 14); /* skip */
+
+ /* We should wait for next SOF to be sure that all EDs are
+ * unaccessed by OHCI. But OHCI can be non-functional, so
+ * more than 1ms timeout have to be applied. */
+ /* SF bit reset. (SF bit indicates Start Of Frame (SOF) packet) */
+ grub_ohci_writereg32 (o, GRUB_OHCI_REG_INTSTATUS, (1<<2));
+ maxtime = grub_get_time_ms () + 2;
+ /* Wait for new SOF or timeout */
+ while ( ((grub_ohci_readreg32 (o, GRUB_OHCI_REG_INTSTATUS) & 0x4)
+ == 0) || (grub_get_time_ms () >= maxtime) );
+
+ for (i = 0; i < nports; i++)
+ grub_ohci_writereg32 (o, GRUB_OHCI_REG_RHUBPORT + i,
+ GRUB_OHCI_CLEAR_PORT_ENABLE);
+
+ grub_ohci_writereg32 (o, GRUB_OHCI_REG_CMDSTATUS, 1);
+ grub_millisleep (1);
+ grub_ohci_writereg32 (o, GRUB_OHCI_REG_HCCA, 0);
+ grub_ohci_writereg32 (o, GRUB_OHCI_REG_CONTROLHEAD, 0);
+ grub_ohci_writereg32 (o, GRUB_OHCI_REG_CONTROLCURR, 0);
+ grub_ohci_writereg32 (o, GRUB_OHCI_REG_BULKHEAD, 0);
+ grub_ohci_writereg32 (o, GRUB_OHCI_REG_BULKCURR, 0);
+ grub_ohci_writereg32 (o, GRUB_OHCI_REG_DONEHEAD, 0);
+ grub_ohci_writereg32 (o, GRUB_OHCI_REG_CONTROL, 0);
+ /* Read back of register should ensure it is really written */
+ grub_ohci_readreg32 (o, GRUB_OHCI_REG_INTSTATUS);
+
+#if 0 /* Is this necessary before booting? Probably not .(?)
+ * But it must be done if module is removed ! (Or not ?)
+ * How to do it ? - Probably grub_ohci_restore_hw should be more
+ * complicated. (?)
+ * (If we do it, we need to reallocate EDs and TDs in function
+ * grub_ohci_restore_hw ! */
+
+ /* Free allocated EDs and TDs */
+ grub_dma_free (o->td_chunk);
+ grub_dma_free (o->ed_bulk_chunk);
+ grub_dma_free (o->ed_ctrl_chunk);
+ grub_dma_free (o->hcca_chunk);
+#endif
+ }
+ grub_millisleep (10);
+
+ return GRUB_ERR_NONE;
+}
+
+static grub_err_t
+grub_ohci_restore_hw (void)
+{
+ struct grub_ohci *o;
+
+ for (o = ohci; o; o = o->next)
+ {
+ grub_ohci_writereg32 (o, GRUB_OHCI_REG_HCCA, o->hcca_addr);
+ o->hcca->donehead = 0;
+ grub_ohci_writereg32 (o, GRUB_OHCI_REG_INTSTATUS, 0x7f); /* Clears everything */
+ grub_ohci_writereg32 (o, GRUB_OHCI_REG_CONTROLHEAD, o->ed_ctrl_addr);
+ grub_ohci_writereg32 (o, GRUB_OHCI_REG_CONTROLCURR, 0);
+ grub_ohci_writereg32 (o, GRUB_OHCI_REG_BULKHEAD, o->ed_bulk_addr);
+ grub_ohci_writereg32 (o, GRUB_OHCI_REG_BULKCURR, 0);
+ /* Read back of register should ensure it is really written */
+ grub_ohci_readreg32 (o, GRUB_OHCI_REG_INTSTATUS);
+
+ /* Enable the OHCI. */
+ grub_ohci_writereg32 (o, GRUB_OHCI_REG_CONTROL,
+ (2 << 6)
+ | GRUB_OHCI_REG_CONTROL_CONTROL_ENABLE
+ | GRUB_OHCI_REG_CONTROL_BULK_ENABLE );
+ }
+
+ return GRUB_ERR_NONE;
+}
+
+
+static struct grub_usb_controller_dev usb_controller =
+{
+ .name = "ohci",
+ .iterate = grub_ohci_iterate,
+ .setup_transfer = grub_ohci_setup_transfer,
+ .check_transfer = grub_ohci_check_transfer,
+ .cancel_transfer = grub_ohci_cancel_transfer,
+ .hubports = grub_ohci_hubports,
+ .portstatus = grub_ohci_portstatus,
+ .detect_dev = grub_ohci_detect_dev,
+ /* estimated max. count of TDs for one bulk transfer */
+ .max_bulk_tds = GRUB_OHCI_TDS * 3 / 4
+};
+
+static struct grub_preboot *fini_hnd;
+
+GRUB_MOD_INIT(ohci)
+{
+ COMPILE_TIME_ASSERT (sizeof (struct grub_ohci_td) == 32);
+ COMPILE_TIME_ASSERT (sizeof (struct grub_ohci_ed) == 16);
+
+ grub_stop_disk_firmware ();
+
+ grub_ohci_inithw ();
+ grub_usb_controller_dev_register (&usb_controller);
+ fini_hnd = grub_loader_register_preboot_hook (grub_ohci_fini_hw,
+ grub_ohci_restore_hw,
+ GRUB_LOADER_PREBOOT_HOOK_PRIO_DISK);
+}
+
+GRUB_MOD_FINI(ohci)
+{
+ grub_ohci_fini_hw (0);
+ grub_loader_unregister_preboot_hook (fini_hnd);
+ grub_usb_controller_dev_unregister (&usb_controller);
+}
diff --git a/grub-core/bus/usb/serial/common.c b/grub-core/bus/usb/serial/common.c
new file mode 100644
index 0000000..8e94c7d
--- /dev/null
+++ b/grub-core/bus/usb/serial/common.c
@@ -0,0 +1,139 @@
+/*
+ * GRUB -- GRand Unified Bootloader
+ * Copyright (C) 2000,2001,2002,2003,2004,2005,2007,2008,2009,2010 Free Software Foundation, Inc.
+ *
+ * GRUB is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * GRUB is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with GRUB. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <grub/serial.h>
+#include <grub/usbserial.h>
+#include <grub/dl.h>
+
+GRUB_MOD_LICENSE ("GPLv3+");
+
+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, int in_endp,
+ int out_endp)
+{
+ struct grub_serial_port *port;
+ int j;
+ struct grub_usb_desc_if *interf;
+ grub_usb_err_t err = GRUB_USB_ERR_NONE;
+
+ interf = usbdev->config[configno].interf[interfno].descif;
+
+ port = grub_zalloc (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
+ && (in_endp == GRUB_USB_SERIAL_ENDPOINT_LAST_MATCHING
+ || in_endp == endp->endp_addr))
+ {
+ /* Bulk IN endpoint. */
+ port->in_endp = endp;
+ }
+ else if (!(endp->endp_addr & 128) && (endp->attrib & 3) == 2
+ && (out_endp == GRUB_USB_SERIAL_ENDPOINT_LAST_MATCHING
+ || out_endp == endp->endp_addr))
+ {
+ /* Bulk OUT endpoint. */
+ port->out_endp = endp;
+ }
+ }
+
+ /* Configure device */
+ if (port->out_endp && port->in_endp)
+ err = grub_usb_set_configuration (usbdev, configno + 1);
+
+ if (!port->out_endp || !port->in_endp || err)
+ {
+ 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,
+ 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/grub-core/bus/usb/serial/ftdi.c b/grub-core/bus/usb/serial/ftdi.c
new file mode 100644
index 0000000..1a99cba
--- /dev/null
+++ b/grub-core/bus/usb/serial/ftdi.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 <http://www.gnu.org/licenses/>.
+ */
+
+#include <grub/serial.h>
+#include <grub/types.h>
+#include <grub/dl.h>
+#include <grub/misc.h>
+#include <grub/mm.h>
+#include <grub/usb.h>
+#include <grub/usbserial.h>
+#include <grub/i18n.h>
+
+GRUB_MOD_LICENSE ("GPLv3+");
+
+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_1_5] = 0x0800,
+ [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,
+ port->config.rtscts ? GRUB_FTDI_MODEM_CTRL_DTRRTS : 0,
+ 0, 0, 0);
+
+ grub_usb_control_msg (port->usbdev, GRUB_USB_REQTYPE_VENDOR_OUT,
+ GRUB_FTDI_FLOW_CTRL,
+ port->config.rtscts ? GRUB_FTDI_FLOW_CTRL_DTRRTS : 0,
+ 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, 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,
+ N_("unsupported serial port speed"));
+
+ if (config->parity != GRUB_SERIAL_PARITY_NONE
+ && config->parity != GRUB_SERIAL_PARITY_ODD
+ && config->parity != GRUB_SERIAL_PARITY_EVEN)
+ return grub_error (GRUB_ERR_BAD_ARGUMENT,
+ N_("unsupported serial port parity"));
+
+ if (config->stop_bits != GRUB_SERIAL_STOP_BITS_1
+ && config->stop_bits != GRUB_SERIAL_STOP_BITS_1_5
+ && config->stop_bits != GRUB_SERIAL_STOP_BITS_2)
+ return grub_error (GRUB_ERR_BAD_ARGUMENT,
+ N_("unsupported serial port stop bits number"));
+
+ if (config->word_len < 5 || config->word_len > 8)
+ return grub_error (GRUB_ERR_BAD_ARGUMENT,
+ N_("unsupported serial port word length"));
+
+ port->config = *config;
+ port->configured = 0;
+
+ 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,
+ GRUB_USB_SERIAL_ENDPOINT_LAST_MATCHING,
+ GRUB_USB_SERIAL_ENDPOINT_LAST_MATCHING);
+}
+
+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/grub-core/bus/usb/serial/pl2303.c b/grub-core/bus/usb/serial/pl2303.c
new file mode 100644
index 0000000..d1945a2
--- /dev/null
+++ b/grub-core/bus/usb/serial/pl2303.c
@@ -0,0 +1,231 @@
+/*
+ * GRUB -- GRand Unified Bootloader
+ * Copyright (C) 2000,2001,2002,2003,2004,2005,2007,2008,2009,2010 Free Software Foundation, Inc.
+ *
+ * GRUB is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * GRUB is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with GRUB. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <grub/serial.h>
+#include <grub/types.h>
+#include <grub/dl.h>
+#include <grub/misc.h>
+#include <grub/mm.h>
+#include <grub/usb.h>
+#include <grub/usbserial.h>
+#include <grub/i18n.h>
+
+GRUB_MOD_LICENSE ("GPLv3+");
+
+/* 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_1_5 0x1
+#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;
+} GRUB_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 if (port->config.stop_bits == GRUB_SERIAL_STOP_BITS_1_5)
+ config_pl2303.stop_bits = GRUB_PL2303_STOP_BITS_1_5;
+ 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, port->config.rtscts ? 0x61 : 0, 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, 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,
+ N_("unsupported serial port speed"));
+
+ if (config->parity != GRUB_SERIAL_PARITY_NONE
+ && config->parity != GRUB_SERIAL_PARITY_ODD
+ && config->parity != GRUB_SERIAL_PARITY_EVEN)
+ return grub_error (GRUB_ERR_BAD_ARGUMENT,
+ N_("unsupported serial port parity"));
+
+ if (config->stop_bits != GRUB_SERIAL_STOP_BITS_1
+ && config->stop_bits != GRUB_SERIAL_STOP_BITS_1_5
+ && config->stop_bits != GRUB_SERIAL_STOP_BITS_2)
+ return grub_error (GRUB_ERR_BAD_ARGUMENT,
+ N_("unsupported serial port stop bits number"));
+
+ if (config->word_len < 5 || config->word_len > 8)
+ return grub_error (GRUB_ERR_BAD_ARGUMENT,
+ N_("unsupported serial port word length"));
+
+ port->config = *config;
+ port->configured = 0;
+
+ 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,
+ GRUB_USB_SERIAL_ENDPOINT_LAST_MATCHING,
+ GRUB_USB_SERIAL_ENDPOINT_LAST_MATCHING);
+}
+
+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/grub-core/bus/usb/serial/usbdebug_late.c b/grub-core/bus/usb/serial/usbdebug_late.c
new file mode 100644
index 0000000..e88ba13
--- /dev/null
+++ b/grub-core/bus/usb/serial/usbdebug_late.c
@@ -0,0 +1,93 @@
+/*
+ * GRUB -- GRand Unified Bootloader
+ * Copyright (C) 2000,2001,2002,2003,2004,2005,2007,2008,2009,2010,2013 Free Software Foundation, Inc.
+ *
+ * GRUB is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * GRUB is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with GRUB. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <grub/serial.h>
+#include <grub/types.h>
+#include <grub/dl.h>
+#include <grub/misc.h>
+#include <grub/mm.h>
+#include <grub/usb.h>
+#include <grub/usbserial.h>
+#include <grub/i18n.h>
+
+GRUB_MOD_LICENSE ("GPLv3+");
+
+
+/* Fetch a key. */
+static int
+usbdebug_late_hw_fetch (struct grub_serial_port *port)
+{
+ return grub_usbserial_fetch (port, 0);
+}
+
+/* Put a character. */
+static void
+usbdebug_late_hw_put (struct grub_serial_port *port, const int c)
+{
+ char cc = c;
+
+ grub_usb_bulk_write (port->usbdev, port->out_endp, 1, &cc);
+}
+
+static grub_err_t
+usbdebug_late_hw_configure (struct grub_serial_port *port __attribute__ ((unused)),
+ struct grub_serial_config *config __attribute__ ((unused)))
+{
+ return GRUB_ERR_NONE;
+}
+
+static struct grub_serial_driver grub_usbdebug_late_driver =
+ {
+ .configure = usbdebug_late_hw_configure,
+ .fetch = usbdebug_late_hw_fetch,
+ .put = usbdebug_late_hw_put,
+ .fini = grub_usbserial_fini
+ };
+
+static int
+grub_usbdebug_late_attach (grub_usb_device_t usbdev, int configno, int interfno)
+{
+ grub_usb_err_t err;
+ struct grub_usb_desc_debug debugdesc;
+
+ err = grub_usb_get_descriptor (usbdev, GRUB_USB_DESCRIPTOR_DEBUG, configno,
+ sizeof (debugdesc), (char *) &debugdesc);
+ if (err)
+ return 0;
+
+ return grub_usbserial_attach (usbdev, configno, interfno,
+ &grub_usbdebug_late_driver,
+ debugdesc.in_endp, debugdesc.out_endp);
+}
+
+static struct grub_usb_attach_desc attach_hook =
+{
+ .class = 0xff,
+ .hook = grub_usbdebug_late_attach
+};
+
+GRUB_MOD_INIT(usbserial_usbdebug_late)
+{
+ grub_usb_register_attach_hook_class (&attach_hook);
+}
+
+GRUB_MOD_FINI(usbserial_usbdebug_late)
+{
+ grub_serial_unregister_driver (&grub_usbdebug_late_driver);
+ grub_usb_unregister_attach_hook_class (&attach_hook);
+}
diff --git a/grub-core/bus/usb/uhci.c b/grub-core/bus/usb/uhci.c
new file mode 100644
index 0000000..7c5811f
--- /dev/null
+++ b/grub-core/bus/usb/uhci.c
@@ -0,0 +1,871 @@
+/* uhci.c - UHCI Support. */
+/*
+ * GRUB -- GRand Unified Bootloader
+ * Copyright (C) 2008 Free Software Foundation, Inc.
+ *
+ * GRUB is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * GRUB is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with GRUB. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <grub/dl.h>
+#include <grub/mm.h>
+#include <grub/misc.h>
+#include <grub/usb.h>
+#include <grub/usbtrans.h>
+#include <grub/pci.h>
+#include <grub/cpu/io.h>
+#include <grub/time.h>
+#include <grub/cpu/pci.h>
+#include <grub/disk.h>
+
+GRUB_MOD_LICENSE ("GPLv3+");
+
+#define GRUB_UHCI_IOMASK (0x7FF << 5)
+
+#define N_QH 256
+#define N_TD 640
+
+typedef enum
+ {
+ GRUB_UHCI_REG_USBCMD = 0x00,
+ GRUB_UHCI_REG_USBINTR = 0x04,
+ GRUB_UHCI_REG_FLBASEADD = 0x08,
+ GRUB_UHCI_REG_PORTSC1 = 0x10,
+ GRUB_UHCI_REG_PORTSC2 = 0x12,
+ GRUB_UHCI_REG_USBLEGSUP = 0xc0
+ } grub_uhci_reg_t;
+
+enum
+ {
+ GRUB_UHCI_DETECT_CHANGED = (1 << 1),
+ GRUB_UHCI_DETECT_HAVE_DEVICE = 1,
+ GRUB_UHCI_DETECT_LOW_SPEED = (1 << 8)
+ };
+
+/* R/WC legacy support bits */
+enum
+ {
+ GRUB_UHCI_LEGSUP_END_A20GATE = (1 << 15),
+ GRUB_UHCI_TRAP_BY_64H_WSTAT = (1 << 11),
+ GRUB_UHCI_TRAP_BY_64H_RSTAT = (1 << 10),
+ GRUB_UHCI_TRAP_BY_60H_WSTAT = (1 << 9),
+ GRUB_UHCI_TRAP_BY_60H_RSTAT = (1 << 8)
+ };
+
+/* Reset all legacy support - clear all R/WC bits and all R/W bits */
+#define GRUB_UHCI_RESET_LEGSUP_SMI ( GRUB_UHCI_LEGSUP_END_A20GATE \
+ | GRUB_UHCI_TRAP_BY_64H_WSTAT \
+ | GRUB_UHCI_TRAP_BY_64H_RSTAT \
+ | GRUB_UHCI_TRAP_BY_60H_WSTAT \
+ | GRUB_UHCI_TRAP_BY_60H_RSTAT )
+
+/* Some UHCI commands */
+#define GRUB_UHCI_CMD_RUN_STOP (1 << 0)
+#define GRUB_UHCI_CMD_HCRESET (1 << 1)
+#define GRUB_UHCI_CMD_MAXP (1 << 7)
+
+/* Important bits in structures */
+#define GRUB_UHCI_LINK_TERMINATE 1
+#define GRUB_UHCI_LINK_QUEUE_HEAD 2
+
+enum
+ {
+ GRUB_UHCI_REG_PORTSC_CONNECT_CHANGED = 0x0002,
+ GRUB_UHCI_REG_PORTSC_PORT_ENABLED = 0x0004,
+ GRUB_UHCI_REG_PORTSC_RESUME = 0x0040,
+ GRUB_UHCI_REG_PORTSC_RESET = 0x0200,
+ GRUB_UHCI_REG_PORTSC_SUSPEND = 0x1000,
+ GRUB_UHCI_REG_PORTSC_RW = GRUB_UHCI_REG_PORTSC_PORT_ENABLED
+ | GRUB_UHCI_REG_PORTSC_RESUME | GRUB_UHCI_REG_PORTSC_RESET
+ | GRUB_UHCI_REG_PORTSC_SUSPEND,
+ /* These bits should not be written as 1 unless we really need it */
+ GRUB_UHCI_PORTSC_RWC = ((1 << 1) | (1 << 3) | (1 << 11) | (3 << 13))
+ };
+
+/* UHCI Queue Head. */
+struct grub_uhci_qh
+{
+ /* Queue head link pointer which points to the next queue head. */
+ grub_uint32_t linkptr;
+
+ /* Queue element link pointer which points to the first data object
+ within the queue. */
+ grub_uint32_t elinkptr;
+
+ /* Queue heads are aligned on 16 bytes, pad so a queue head is 16
+ bytes so we can store many in a 4K page. */
+ grub_uint8_t pad[8];
+} GRUB_PACKED;
+
+/* UHCI Transfer Descriptor. */
+struct grub_uhci_td
+{
+ /* Pointer to the next TD in the list. */
+ grub_uint32_t linkptr;
+
+ /* Control and status bits. */
+ grub_uint32_t ctrl_status;
+
+ /* All information required to transfer the Token packet. */
+ grub_uint32_t token;
+
+ /* A pointer to the data buffer, UHCI requires this pointer to be 32
+ bits. */
+ grub_uint32_t buffer;
+
+ /* Another linkptr that is not overwritten by the Host Controller.
+ This is GRUB specific. */
+ grub_uint32_t linkptr2;
+
+ /* 3 additional 32 bits words reserved for the Host Controller Driver. */
+ grub_uint32_t data[3];
+} GRUB_PACKED;
+
+typedef volatile struct grub_uhci_td *grub_uhci_td_t;
+typedef volatile struct grub_uhci_qh *grub_uhci_qh_t;
+
+struct grub_uhci
+{
+ grub_port_t iobase;
+ volatile grub_uint32_t *framelist_virt;
+ grub_uint32_t framelist_phys;
+ struct grub_pci_dma_chunk *framelist_chunk;
+
+ /* N_QH Queue Heads. */
+ struct grub_pci_dma_chunk *qh_chunk;
+ volatile grub_uhci_qh_t qh_virt;
+ grub_uint32_t qh_phys;
+
+ /* N_TD Transfer Descriptors. */
+ struct grub_pci_dma_chunk *td_chunk;
+ volatile grub_uhci_td_t td_virt;
+ grub_uint32_t td_phys;
+
+ /* Free Transfer Descriptors. */
+ grub_uhci_td_t tdfree;
+
+ int qh_busy[N_QH];
+
+ struct grub_uhci *next;
+};
+
+static struct grub_uhci *uhci;
+
+static grub_uint16_t
+grub_uhci_readreg16 (struct grub_uhci *u, grub_uhci_reg_t reg)
+{
+ return grub_inw (u->iobase + reg);
+}
+
+#if 0
+static grub_uint32_t
+grub_uhci_readreg32 (struct grub_uhci *u, grub_uhci_reg_t reg)
+{
+ return grub_inl (u->iobase + reg);
+}
+#endif
+
+static void
+grub_uhci_writereg16 (struct grub_uhci *u,
+ grub_uhci_reg_t reg, grub_uint16_t val)
+{
+ grub_outw (val, u->iobase + reg);
+}
+
+static void
+grub_uhci_writereg32 (struct grub_uhci *u,
+ grub_uhci_reg_t reg, grub_uint32_t val)
+{
+ grub_outl (val, u->iobase + reg);
+}
+
+/* Iterate over all PCI devices. Determine if a device is an UHCI
+ controller. If this is the case, initialize it. */
+static int
+grub_uhci_pci_iter (grub_pci_device_t dev,
+ grub_pci_id_t pciid __attribute__((unused)),
+ void *data __attribute__ ((unused)))
+{
+ grub_uint32_t class_code;
+ grub_uint32_t class;
+ grub_uint32_t subclass;
+ grub_uint32_t interf;
+ grub_uint32_t base;
+ grub_uint32_t fp;
+ grub_pci_address_t addr;
+ struct grub_uhci *u;
+ int i;
+
+ addr = grub_pci_make_address (dev, GRUB_PCI_REG_CLASS);
+ class_code = grub_pci_read (addr) >> 8;
+
+ interf = class_code & 0xFF;
+ subclass = (class_code >> 8) & 0xFF;
+ class = class_code >> 16;
+
+ /* If this is not an UHCI controller, just return. */
+ if (class != 0x0c || subclass != 0x03 || interf != 0x00)
+ return 0;
+
+ /* Determine IO base address. */
+ addr = grub_pci_make_address (dev, GRUB_PCI_REG_ADDRESS_REG4);
+ base = grub_pci_read (addr);
+ /* Stop if there is no IO space base address defined. */
+ if ((base & GRUB_PCI_ADDR_SPACE_MASK) != GRUB_PCI_ADDR_SPACE_IO)
+ return 0;
+
+ if ((base & GRUB_UHCI_IOMASK) == 0)
+ return 0;
+
+ /* Set bus master - needed for coreboot or broken BIOSes */
+ addr = grub_pci_make_address (dev, GRUB_PCI_REG_COMMAND);
+ grub_pci_write_word(addr, GRUB_PCI_COMMAND_IO_ENABLED
+ | GRUB_PCI_COMMAND_BUS_MASTER
+ | GRUB_PCI_COMMAND_MEM_ENABLED
+ | grub_pci_read_word (addr));
+
+ grub_dprintf ("uhci", "base = %x\n", base);
+
+ /* Allocate memory for the controller and register it. */
+ u = grub_zalloc (sizeof (*u));
+ if (! u)
+ return 1;
+
+ u->iobase = (base & GRUB_UHCI_IOMASK) + GRUB_MACHINE_PCI_IO_BASE;
+
+ /* Reset PIRQ and SMI */
+ addr = grub_pci_make_address (dev, GRUB_UHCI_REG_USBLEGSUP);
+ grub_pci_write_word(addr, GRUB_UHCI_RESET_LEGSUP_SMI);
+ /* Reset the HC */
+ grub_uhci_writereg16(u, GRUB_UHCI_REG_USBCMD, GRUB_UHCI_CMD_HCRESET);
+ grub_millisleep(5);
+ /* Disable interrupts and commands (just to be safe) */
+ grub_uhci_writereg16(u, GRUB_UHCI_REG_USBINTR, 0);
+ /* Finish HC reset, HC remains disabled */
+ grub_uhci_writereg16(u, GRUB_UHCI_REG_USBCMD, 0);
+ /* Read back to be sure PCI write is done */
+ grub_uhci_readreg16(u, GRUB_UHCI_REG_USBCMD);
+
+ /* Reserve a page for the frame list. */
+ u->framelist_chunk = grub_memalign_dma32 (4096, 4096);
+ if (! u->framelist_chunk)
+ goto fail;
+ u->framelist_virt = grub_dma_get_virt (u->framelist_chunk);
+ u->framelist_phys = grub_dma_get_phys (u->framelist_chunk);
+
+ grub_dprintf ("uhci",
+ "class=0x%02x 0x%02x interface 0x%02x base=0x%x framelist=%p\n",
+ class, subclass, interf, u->iobase, u->framelist_virt);
+
+ /* The QH pointer of UHCI is only 32 bits, make sure this
+ code works on on 64 bits architectures. */
+ u->qh_chunk = grub_memalign_dma32 (4096, sizeof(struct grub_uhci_qh) * N_QH);
+ if (! u->qh_chunk)
+ goto fail;
+ u->qh_virt = grub_dma_get_virt (u->qh_chunk);
+ u->qh_phys = grub_dma_get_phys (u->qh_chunk);
+
+ /* The TD pointer of UHCI is only 32 bits, make sure this
+ code works on on 64 bits architectures. */
+ u->td_chunk = grub_memalign_dma32 (4096, sizeof(struct grub_uhci_td) * N_TD);
+ if (! u->td_chunk)
+ goto fail;
+ u->td_virt = grub_dma_get_virt (u->td_chunk);
+ u->td_phys = grub_dma_get_phys (u->td_chunk);
+
+ grub_dprintf ("uhci", "QH=%p, TD=%p\n",
+ u->qh_virt, u->td_virt);
+
+ /* Link all Transfer Descriptors in a list of available Transfer
+ Descriptors. */
+ for (i = 0; i < N_TD; i++)
+ u->td_virt[i].linkptr = u->td_phys + (i + 1) * sizeof(struct grub_uhci_td);
+ u->td_virt[N_TD - 2].linkptr = 0;
+ u->tdfree = u->td_virt;
+
+ /* Setup the frame list pointers. Since no isochronous transfers
+ are and will be supported, they all point to the (same!) queue
+ head. */
+ fp = u->qh_phys & (~15);
+ /* Mark this as a queue head. */
+ fp |= 2;
+ for (i = 0; i < 1024; i++)
+ u->framelist_virt[i] = fp;
+ /* Program the framelist address into the UHCI controller. */
+ grub_uhci_writereg32 (u, GRUB_UHCI_REG_FLBASEADD, u->framelist_phys);
+
+ /* Make the Queue Heads point to each other. */
+ for (i = 0; i < N_QH; i++)
+ {
+ /* Point to the next QH. */
+ u->qh_virt[i].linkptr = ((u->qh_phys
+ + (i + 1) * sizeof(struct grub_uhci_qh))
+ & (~15));
+
+ /* This is a QH. */
+ u->qh_virt[i].linkptr |= GRUB_UHCI_LINK_QUEUE_HEAD;
+
+ /* For the moment, do not point to a Transfer Descriptor. These
+ are set at transfer time, so just terminate it. */
+ u->qh_virt[i].elinkptr = 1;
+ }
+
+ /* The last Queue Head should terminate. */
+ u->qh_virt[N_QH - 1].linkptr = 1;
+
+ /* Enable UHCI again. */
+ grub_uhci_writereg16 (u, GRUB_UHCI_REG_USBCMD,
+ GRUB_UHCI_CMD_RUN_STOP | GRUB_UHCI_CMD_MAXP);
+
+ /* UHCI is initialized and ready for transfers. */
+ grub_dprintf ("uhci", "UHCI initialized\n");
+
+
+#if 0
+ {
+ int i;
+ for (i = 0; i < 10; i++)
+ {
+ grub_uint16_t frnum;
+
+ frnum = grub_uhci_readreg16 (u, 6);
+ grub_dprintf ("uhci", "Framenum=%d\n", frnum);
+ grub_millisleep (100);
+ }
+ }
+#endif
+
+ /* Link to uhci now that initialisation is successful. */
+ u->next = uhci;
+ uhci = u;
+
+ return 0;
+
+ fail:
+ if (u)
+ {
+ grub_dma_free (u->qh_chunk);
+ grub_dma_free (u->framelist_chunk);
+ }
+ grub_free (u);
+
+ return 1;
+}
+
+static void
+grub_uhci_inithw (void)
+{
+ grub_pci_iterate (grub_uhci_pci_iter, NULL);
+}
+
+static grub_uhci_td_t
+grub_alloc_td (struct grub_uhci *u)
+{
+ grub_uhci_td_t ret;
+
+ /* Check if there is a Transfer Descriptor available. */
+ if (! u->tdfree)
+ return NULL;
+
+ ret = u->tdfree;
+ u->tdfree = grub_dma_phys2virt (u->tdfree->linkptr, u->td_chunk);
+
+ return ret;
+}
+
+static void
+grub_free_td (struct grub_uhci *u, grub_uhci_td_t td)
+{
+ td->linkptr = grub_dma_virt2phys (u->tdfree, u->td_chunk);
+ u->tdfree = td;
+}
+
+static void
+grub_free_queue (struct grub_uhci *u, grub_uhci_qh_t qh, grub_uhci_td_t td,
+ grub_usb_transfer_t transfer, grub_size_t *actual)
+{
+ int i; /* Index of TD in transfer */
+
+ u->qh_busy[qh - u->qh_virt] = 0;
+
+ *actual = 0;
+
+ /* Free the TDs in this queue and set last_trans. */
+ for (i=0; td; i++)
+ {
+ grub_uhci_td_t tdprev;
+
+ grub_dprintf ("uhci", "Freeing %p\n", 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;
+ if (!td->linkptr2)
+ td = 0;
+ else
+ td = grub_dma_phys2virt (td->linkptr2, u->td_chunk);
+
+ /* Free the TD. */
+ grub_free_td (u, tdprev);
+ }
+}
+
+static grub_uhci_qh_t
+grub_alloc_qh (struct grub_uhci *u,
+ grub_transaction_type_t tr __attribute__((unused)))
+{
+ int i;
+ grub_uhci_qh_t qh;
+
+ /* Look for a Queue Head for this transfer. Skip the first QH if
+ this is a Interrupt Transfer. */
+#if 0
+ if (tr == GRUB_USB_TRANSACTION_TYPE_INTERRUPT)
+ i = 0;
+ else
+#endif
+ i = 1;
+
+ for (; i < N_QH; i++)
+ {
+ if (!u->qh_busy[i])
+ break;
+ }
+ qh = &u->qh_virt[i];
+ if (i == N_QH)
+ {
+ grub_error (GRUB_ERR_OUT_OF_MEMORY,
+ "no free queue heads available");
+ return NULL;
+ }
+
+ u->qh_busy[qh - u->qh_virt] = 1;
+
+ return qh;
+}
+
+static grub_uhci_td_t
+grub_uhci_transaction (struct grub_uhci *u, unsigned int endp,
+ grub_transfer_type_t type, unsigned int addr,
+ unsigned int toggle, grub_size_t size,
+ grub_uint32_t data, grub_usb_speed_t speed)
+{
+ grub_uhci_td_t td;
+ static const unsigned int tf[] = { 0x69, 0xE1, 0x2D };
+
+ /* XXX: Check if data is <4GB. If it isn't, just copy stuff around.
+ This is only relevant for 64 bits architectures. */
+
+ /* Grab a free Transfer Descriptor and initialize it. */
+ td = grub_alloc_td (u);
+ if (! td)
+ {
+ grub_error (GRUB_ERR_OUT_OF_MEMORY,
+ "no transfer descriptors available for UHCI transfer");
+ return 0;
+ }
+
+ grub_dprintf ("uhci",
+ "transaction: endp=%d, type=%d, addr=%d, toggle=%d, size=%lu data=0x%x td=%p\n",
+ endp, type, addr, toggle, (unsigned long) size, data, td);
+
+ /* Don't point to any TD, just terminate. */
+ td->linkptr = 1;
+
+ /* Active! Only retry a transfer 3 times. */
+ td->ctrl_status = (1 << 23) | (3 << 27) |
+ ((speed == GRUB_USB_SPEED_LOW) ? (1 << 26) : 0);
+
+ /* If zero bytes are transmitted, size is 0x7FF. Otherwise size is
+ size-1. */
+ if (size == 0)
+ size = 0x7FF;
+ else
+ size = size - 1;
+
+ /* Setup whatever is required for the token packet. */
+ td->token = ((size << 21) | (toggle << 19) | (endp << 15)
+ | (addr << 8) | tf[type]);
+
+ td->buffer = data;
+
+ return td;
+}
+
+struct grub_uhci_transfer_controller_data
+{
+ grub_uhci_qh_t qh;
+ grub_uhci_td_t td_first;
+};
+
+static grub_usb_err_t
+grub_uhci_setup_transfer (grub_usb_controller_t dev,
+ grub_usb_transfer_t transfer)
+{
+ struct grub_uhci *u = (struct grub_uhci *) dev->data;
+ grub_uhci_td_t td;
+ grub_uhci_td_t td_prev = NULL;
+ int i;
+ struct grub_uhci_transfer_controller_data *cdata;
+
+ cdata = grub_malloc (sizeof (*cdata));
+ if (!cdata)
+ return GRUB_USB_ERR_INTERNAL;
+
+ cdata->td_first = NULL;
+
+ /* Allocate a queue head for the transfer queue. */
+ cdata->qh = grub_alloc_qh (u, GRUB_USB_TRANSACTION_TYPE_CONTROL);
+ if (! cdata->qh)
+ {
+ grub_free (cdata);
+ return GRUB_USB_ERR_INTERNAL;
+ }
+
+ grub_dprintf ("uhci", "transfer, iobase:%08x\n", u->iobase);
+
+ for (i = 0; i < transfer->transcnt; i++)
+ {
+ grub_usb_transaction_t tr = &transfer->transactions[i];
+
+ td = grub_uhci_transaction (u, transfer->endpoint & 15, tr->pid,
+ transfer->devaddr, tr->toggle,
+ tr->size, tr->data,
+ transfer->dev->speed);
+ if (! td)
+ {
+ grub_size_t actual = 0;
+ /* Terminate and free. */
+ if (td_prev)
+ {
+ td_prev->linkptr2 = 0;
+ td_prev->linkptr = 1;
+ }
+
+ if (cdata->td_first)
+ grub_free_queue (u, cdata->qh, cdata->td_first, NULL, &actual);
+
+ grub_free (cdata);
+ return GRUB_USB_ERR_INTERNAL;
+ }
+
+ if (! cdata->td_first)
+ cdata->td_first = td;
+ else
+ {
+ td_prev->linkptr2 = grub_dma_virt2phys (td, u->td_chunk);
+ td_prev->linkptr = grub_dma_virt2phys (td, u->td_chunk);
+ td_prev->linkptr |= 4;
+ }
+ td_prev = td;
+ }
+ td_prev->linkptr2 = 0;
+ td_prev->linkptr = 1;
+
+ grub_dprintf ("uhci", "setup transaction %d\n", transfer->type);
+
+ /* Link it into the queue and terminate. Now the transaction can
+ take place. */
+ cdata->qh->elinkptr = grub_dma_virt2phys (cdata->td_first, u->td_chunk);
+
+ grub_dprintf ("uhci", "initiate transaction\n");
+
+ transfer->controller_data = cdata;
+
+ return GRUB_USB_ERR_NONE;
+}
+
+static grub_usb_err_t
+grub_uhci_check_transfer (grub_usb_controller_t dev,
+ grub_usb_transfer_t transfer,
+ grub_size_t *actual)
+{
+ struct grub_uhci *u = (struct grub_uhci *) dev->data;
+ grub_uhci_td_t errtd;
+ struct grub_uhci_transfer_controller_data *cdata = transfer->controller_data;
+
+ *actual = 0;
+
+ if (cdata->qh->elinkptr & ~0x0f)
+ errtd = grub_dma_phys2virt (cdata->qh->elinkptr & ~0x0f, u->qh_chunk);
+ else
+ errtd = 0;
+
+ if (errtd)
+ {
+ grub_dprintf ("uhci", ">t status=0x%02x data=0x%02x td=%p, %x\n",
+ errtd->ctrl_status, errtd->buffer & (~15), errtd,
+ cdata->qh->elinkptr);
+ }
+
+ /* Check if the transaction completed. */
+ if (cdata->qh->elinkptr & 1)
+ {
+ grub_dprintf ("uhci", "transaction complete\n");
+
+ /* Place the QH back in the free list and deallocate the associated
+ TDs. */
+ cdata->qh->elinkptr = 1;
+ grub_free_queue (u, cdata->qh, cdata->td_first, transfer, actual);
+ grub_free (cdata);
+ return GRUB_USB_ERR_NONE;
+ }
+
+ if (errtd && !(errtd->ctrl_status & (1 << 23)))
+ {
+ grub_usb_err_t err = GRUB_USB_ERR_NONE;
+
+ /* 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. */
+ else if (errtd->ctrl_status & (1 << 21))
+ err = GRUB_USB_ERR_DATA;
+
+ /* Check if a babble error occurred. */
+ else if (errtd->ctrl_status & (1 << 20))
+ err = GRUB_USB_ERR_BABBLE;
+
+ /* Check if a NAK occurred. */
+ else if (errtd->ctrl_status & (1 << 19))
+ err = GRUB_USB_ERR_NAK;
+
+ /* Check if a timeout occurred. */
+ else if (errtd->ctrl_status & (1 << 18))
+ err = GRUB_USB_ERR_TIMEOUT;
+
+ /* Check if a bitstuff error occurred. */
+ else if (errtd->ctrl_status & (1 << 17))
+ err = GRUB_USB_ERR_BITSTUFF;
+
+ if (err)
+ {
+ grub_dprintf ("uhci", "transaction failed\n");
+
+ /* Place the QH back in the free list and deallocate the associated
+ TDs. */
+ cdata->qh->elinkptr = 1;
+ grub_free_queue (u, cdata->qh, cdata->td_first, transfer, actual);
+ grub_free (cdata);
+
+ return err;
+ }
+ }
+
+ /* Fall through, no errors occurred, so the QH might be
+ updated. */
+ grub_dprintf ("uhci", "transaction fallthrough\n");
+
+ return GRUB_USB_ERR_WAIT;
+}
+
+static grub_usb_err_t
+grub_uhci_cancel_transfer (grub_usb_controller_t dev,
+ grub_usb_transfer_t transfer)
+{
+ struct grub_uhci *u = (struct grub_uhci *) dev->data;
+ grub_size_t actual;
+ struct grub_uhci_transfer_controller_data *cdata = transfer->controller_data;
+
+ grub_dprintf ("uhci", "transaction cancel\n");
+
+ /* Place the QH back in the free list and deallocate the associated
+ TDs. */
+ cdata->qh->elinkptr = 1;
+ grub_free_queue (u, cdata->qh, cdata->td_first, transfer, &actual);
+ grub_free (cdata);
+
+ return GRUB_USB_ERR_NONE;
+}
+
+static int
+grub_uhci_iterate (grub_usb_controller_iterate_hook_t hook, void *hook_data)
+{
+ struct grub_uhci *u;
+ struct grub_usb_controller dev;
+
+ for (u = uhci; u; u = u->next)
+ {
+ dev.data = u;
+ if (hook (&dev, hook_data))
+ return 1;
+ }
+
+ return 0;
+}
+
+static grub_usb_err_t
+grub_uhci_portstatus (grub_usb_controller_t dev,
+ unsigned int port, unsigned int enable)
+{
+ struct grub_uhci *u = (struct grub_uhci *) dev->data;
+ int reg;
+ unsigned int status;
+ grub_uint64_t endtime;
+
+ grub_dprintf ("uhci", "portstatus, iobase:%08x\n", u->iobase);
+
+ grub_dprintf ("uhci", "enable=%d port=%d\n", enable, port);
+
+ if (port == 0)
+ reg = GRUB_UHCI_REG_PORTSC1;
+ else if (port == 1)
+ reg = GRUB_UHCI_REG_PORTSC2;
+ else
+ return GRUB_USB_ERR_INTERNAL;
+
+ status = grub_uhci_readreg16 (u, reg);
+ grub_dprintf ("uhci", "detect=0x%02x\n", status);
+
+ if (!enable) /* We don't need reset port */
+ {
+ /* Disable the port. */
+ grub_uhci_writereg16 (u, reg, 0 << 2);
+ grub_dprintf ("uhci", "waiting for the port to be disabled\n");
+ endtime = grub_get_time_ms () + 1000;
+ while ((grub_uhci_readreg16 (u, reg) & (1 << 2)))
+ if (grub_get_time_ms () > endtime)
+ return GRUB_USB_ERR_TIMEOUT;
+
+ status = grub_uhci_readreg16 (u, reg);
+ grub_dprintf ("uhci", ">3detect=0x%02x\n", status);
+ return GRUB_USB_ERR_NONE;
+ }
+
+ /* Reset the port. */
+ status = grub_uhci_readreg16 (u, reg) & ~GRUB_UHCI_PORTSC_RWC;
+ grub_uhci_writereg16 (u, reg, status | (1 << 9));
+ grub_uhci_readreg16 (u, reg); /* Ensure it is writen... */
+
+ /* Wait for the reset to complete. XXX: How long exactly? */
+ grub_millisleep (50); /* For root hub should be nominaly 50ms */
+ status = grub_uhci_readreg16 (u, reg) & ~GRUB_UHCI_PORTSC_RWC;
+ grub_uhci_writereg16 (u, reg, status & ~(1 << 9));
+ grub_uhci_readreg16 (u, reg); /* Ensure it is writen... */
+
+ /* Note: some debug prints were removed because they affected reset/enable timing. */
+
+ grub_millisleep (1); /* Probably not needed at all or only few microsecs. */
+
+ /* Reset bits Connect & Enable Status Change */
+ status = grub_uhci_readreg16 (u, reg) & ~GRUB_UHCI_PORTSC_RWC;
+ grub_uhci_writereg16 (u, reg, status | (1 << 3) | GRUB_UHCI_REG_PORTSC_CONNECT_CHANGED);
+ grub_uhci_readreg16 (u, reg); /* Ensure it is writen... */
+
+ /* Enable the port. */
+ status = grub_uhci_readreg16 (u, reg) & ~GRUB_UHCI_PORTSC_RWC;
+ grub_uhci_writereg16 (u, reg, status | (1 << 2));
+ grub_uhci_readreg16 (u, reg); /* Ensure it is writen... */
+
+ endtime = grub_get_time_ms () + 1000;
+ while (! ((status = grub_uhci_readreg16 (u, reg)) & (1 << 2)))
+ if (grub_get_time_ms () > endtime)
+ return GRUB_USB_ERR_TIMEOUT;
+
+ /* Reset recovery time */
+ grub_millisleep (10);
+
+ /* Read final port status */
+ status = grub_uhci_readreg16 (u, reg);
+ grub_dprintf ("uhci", ">3detect=0x%02x\n", status);
+
+
+ return GRUB_USB_ERR_NONE;
+}
+
+static grub_usb_speed_t
+grub_uhci_detect_dev (grub_usb_controller_t dev, int port, int *changed)
+{
+ struct grub_uhci *u = (struct grub_uhci *) dev->data;
+ int reg;
+ unsigned int status;
+
+ grub_dprintf ("uhci", "detect_dev, iobase:%08x\n", u->iobase);
+
+ if (port == 0)
+ reg = GRUB_UHCI_REG_PORTSC1;
+ else if (port == 1)
+ reg = GRUB_UHCI_REG_PORTSC2;
+ else
+ return GRUB_USB_SPEED_NONE;
+
+ status = grub_uhci_readreg16 (u, reg);
+
+ grub_dprintf ("uhci", "detect=0x%02x port=%d\n", status, port);
+
+ /* Connect Status Change bit - it detects change of connection */
+ if (status & GRUB_UHCI_DETECT_CHANGED)
+ {
+ *changed = 1;
+ /* Reset bit Connect Status Change */
+ grub_uhci_writereg16 (u, reg, (status & GRUB_UHCI_REG_PORTSC_RW)
+ | GRUB_UHCI_REG_PORTSC_CONNECT_CHANGED);
+ }
+ else
+ *changed = 0;
+
+ if (! (status & GRUB_UHCI_DETECT_HAVE_DEVICE))
+ return GRUB_USB_SPEED_NONE;
+ else if (status & GRUB_UHCI_DETECT_LOW_SPEED)
+ return GRUB_USB_SPEED_LOW;
+ else
+ return GRUB_USB_SPEED_FULL;
+}
+
+static int
+grub_uhci_hubports (grub_usb_controller_t dev __attribute__((unused)))
+{
+ /* The root hub has exactly two ports. */
+ return 2;
+}
+
+
+static struct grub_usb_controller_dev usb_controller =
+{
+ .name = "uhci",
+ .iterate = grub_uhci_iterate,
+ .setup_transfer = grub_uhci_setup_transfer,
+ .check_transfer = grub_uhci_check_transfer,
+ .cancel_transfer = grub_uhci_cancel_transfer,
+ .hubports = grub_uhci_hubports,
+ .portstatus = grub_uhci_portstatus,
+ .detect_dev = grub_uhci_detect_dev,
+ /* estimated max. count of TDs for one bulk transfer */
+ .max_bulk_tds = N_TD * 3 / 4
+};
+
+GRUB_MOD_INIT(uhci)
+{
+ grub_stop_disk_firmware ();
+
+ grub_uhci_inithw ();
+ grub_usb_controller_dev_register (&usb_controller);
+ grub_dprintf ("uhci", "registered\n");
+}
+
+GRUB_MOD_FINI(uhci)
+{
+ struct grub_uhci *u;
+
+ /* Disable all UHCI controllers. */
+ for (u = uhci; u; u = u->next)
+ grub_uhci_writereg16 (u, GRUB_UHCI_REG_USBCMD, 0);
+
+ /* Unregister the controller. */
+ grub_usb_controller_dev_unregister (&usb_controller);
+}
diff --git a/grub-core/bus/usb/usb.c b/grub-core/bus/usb/usb.c
new file mode 100644
index 0000000..7cb3cc2
--- /dev/null
+++ b/grub-core/bus/usb/usb.c
@@ -0,0 +1,346 @@
+/* usb.c - Generic USB interfaces. */
+/*
+ * GRUB -- GRand Unified Bootloader
+ * Copyright (C) 2008 Free Software Foundation, Inc.
+ *
+ * GRUB is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * GRUB is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with GRUB. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <grub/dl.h>
+#include <grub/mm.h>
+#include <grub/usb.h>
+#include <grub/misc.h>
+#include <grub/list.h>
+#include <grub/term.h>
+
+GRUB_MOD_LICENSE ("GPLv3+");
+
+static struct grub_usb_attach_desc *attach_hooks;
+
+#if 0
+/* Context for grub_usb_controller_iterate. */
+struct grub_usb_controller_iterate_ctx
+{
+ grub_usb_controller_iterate_hook_t hook;
+ void *hook_data;
+ grub_usb_controller_dev_t p;
+};
+
+/* Helper for grub_usb_controller_iterate. */
+static int
+grub_usb_controller_iterate_iter (grub_usb_controller_t dev, void *data)
+{
+ struct grub_usb_controller_iterate_ctx *ctx = data;
+
+ dev->dev = ctx->p;
+ if (ctx->hook (dev, ctx->hook_data))
+ return 1;
+ return 0;
+}
+
+int
+grub_usb_controller_iterate (grub_usb_controller_iterate_hook_t hook,
+ void *hook_data)
+{
+ struct grub_usb_controller_iterate_ctx ctx = {
+ .hook = hook,
+ .hook_data = hook_data
+ };
+
+ /* Iterate over all controller drivers. */
+ for (ctx.p = grub_usb_list; ctx.p; ctx.p = ctx.p->next)
+ {
+ /* Iterate over the busses of the controllers. XXX: Actually, a
+ hub driver should do this. */
+ if (ctx.p->iterate (grub_usb_controller_iterate_iter, &ctx))
+ return 1;
+ }
+
+ return 0;
+}
+#endif
+
+
+grub_usb_err_t
+grub_usb_clear_halt (grub_usb_device_t dev, int endpoint)
+{
+ if (endpoint >= GRUB_USB_MAX_TOGGLE)
+ return GRUB_USB_ERR_BADDEVICE;
+
+ dev->toggle[endpoint] = 0;
+ return grub_usb_control_msg (dev, (GRUB_USB_REQTYPE_OUT
+ | GRUB_USB_REQTYPE_STANDARD
+ | GRUB_USB_REQTYPE_TARGET_ENDP),
+ GRUB_USB_REQ_CLEAR_FEATURE,
+ GRUB_USB_FEATURE_ENDP_HALT,
+ endpoint, 0, 0);
+}
+
+grub_usb_err_t
+grub_usb_set_configuration (grub_usb_device_t dev, int configuration)
+{
+ grub_memset (dev->toggle, 0, sizeof (dev->toggle));
+
+ return grub_usb_control_msg (dev, (GRUB_USB_REQTYPE_OUT
+ | GRUB_USB_REQTYPE_STANDARD
+ | GRUB_USB_REQTYPE_TARGET_DEV),
+ GRUB_USB_REQ_SET_CONFIGURATION, configuration,
+ 0, 0, NULL);
+}
+
+grub_usb_err_t
+grub_usb_get_descriptor (grub_usb_device_t dev,
+ grub_uint8_t type, grub_uint8_t index,
+ grub_size_t size, char *data)
+{
+ return grub_usb_control_msg (dev, (GRUB_USB_REQTYPE_IN
+ | GRUB_USB_REQTYPE_STANDARD
+ | GRUB_USB_REQTYPE_TARGET_DEV),
+ GRUB_USB_REQ_GET_DESCRIPTOR,
+ (type << 8) | index,
+ 0, size, data);
+}
+
+grub_usb_err_t
+grub_usb_device_initialize (grub_usb_device_t dev)
+{
+ struct grub_usb_desc_device *descdev;
+ struct grub_usb_desc_config config;
+ grub_usb_err_t err;
+ int i;
+
+ /* First we have to read first 8 bytes only and determine
+ * max. size of packet */
+ dev->descdev.maxsize0 = 0; /* invalidating, for safety only, can be removed if it is sure it is zero here */
+ err = grub_usb_get_descriptor (dev, GRUB_USB_DESCRIPTOR_DEVICE,
+ 0, 8, (char *) &dev->descdev);
+ if (err)
+ return err;
+
+ /* Now we have valid value in dev->descdev.maxsize0,
+ * so we can read whole device descriptor */
+ err = grub_usb_get_descriptor (dev, GRUB_USB_DESCRIPTOR_DEVICE,
+ 0, sizeof (struct grub_usb_desc_device),
+ (char *) &dev->descdev);
+ if (err)
+ return err;
+ descdev = &dev->descdev;
+
+ for (i = 0; i < GRUB_USB_MAX_CONF; i++)
+ dev->config[i].descconf = NULL;
+
+ if (descdev->configcnt == 0 || descdev->configcnt > GRUB_USB_MAX_CONF)
+ {
+ err = GRUB_USB_ERR_BADDEVICE;
+ goto fail;
+ }
+
+ for (i = 0; i < descdev->configcnt; i++)
+ {
+ int pos;
+ int currif;
+ char *data;
+ struct grub_usb_desc *desc;
+
+ /* First just read the first 4 bytes of the configuration
+ descriptor, after that it is known how many bytes really have
+ to be read. */
+ err = grub_usb_get_descriptor (dev, GRUB_USB_DESCRIPTOR_CONFIG, i, 4,
+ (char *) &config);
+
+ data = grub_malloc (config.totallen);
+ if (! data)
+ {
+ err = GRUB_USB_ERR_INTERNAL;
+ goto fail;
+ }
+
+ dev->config[i].descconf = (struct grub_usb_desc_config *) data;
+ err = grub_usb_get_descriptor (dev, GRUB_USB_DESCRIPTOR_CONFIG, i,
+ config.totallen, data);
+ if (err)
+ goto fail;
+
+ /* Skip the configuration descriptor. */
+ pos = dev->config[i].descconf->length;
+
+ if (dev->config[i].descconf->numif > GRUB_USB_MAX_IF)
+ {
+ err = GRUB_USB_ERR_BADDEVICE;
+ goto fail;
+ }
+
+ /* Read all interfaces. */
+ for (currif = 0; currif < dev->config[i].descconf->numif; currif++)
+ {
+ while (pos < config.totallen)
+ {
+ desc = (struct grub_usb_desc *)&data[pos];
+ if (desc->type == GRUB_USB_DESCRIPTOR_INTERFACE)
+ break;
+ if (!desc->length)
+ {
+ err = GRUB_USB_ERR_BADDEVICE;
+ goto fail;
+ }
+ pos += desc->length;
+ }
+
+ dev->config[i].interf[currif].descif
+ = (struct grub_usb_desc_if *) &data[pos];
+ pos += dev->config[i].interf[currif].descif->length;
+
+ while (pos < config.totallen)
+ {
+ desc = (struct grub_usb_desc *)&data[pos];
+ if (desc->type == GRUB_USB_DESCRIPTOR_ENDPOINT)
+ break;
+ if (!desc->length)
+ {
+ err = GRUB_USB_ERR_BADDEVICE;
+ goto fail;
+ }
+ pos += desc->length;
+ }
+
+ /* Point to the first endpoint. */
+ dev->config[i].interf[currif].descendp
+ = (struct grub_usb_desc_endp *) &data[pos];
+ pos += (sizeof (struct grub_usb_desc_endp)
+ * dev->config[i].interf[currif].descif->endpointcnt);
+ }
+ }
+
+ return GRUB_USB_ERR_NONE;
+
+ fail:
+
+ for (i = 0; i < GRUB_USB_MAX_CONF; i++)
+ grub_free (dev->config[i].descconf);
+
+ return err;
+}
+
+void grub_usb_device_attach (grub_usb_device_t dev)
+{
+ int i;
+
+ /* XXX: Just check configuration 0 for now. */
+ for (i = 0; i < dev->config[0].descconf->numif; i++)
+ {
+ struct grub_usb_desc_if *interf;
+ struct grub_usb_attach_desc *desc;
+
+ interf = dev->config[0].interf[i].descif;
+
+ grub_dprintf ("usb", "iterate: interf=%d, class=%d, subclass=%d, protocol=%d\n",
+ i, interf->class, interf->subclass, interf->protocol);
+
+ if (dev->config[0].interf[i].attached)
+ continue;
+
+ for (desc = attach_hooks; desc; desc = desc->next)
+ if (interf->class == desc->class)
+ {
+ grub_boot_time ("Probing USB device driver class %x", desc->class);
+ if (desc->hook (dev, 0, i))
+ dev->config[0].interf[i].attached = 1;
+ grub_boot_time ("Probed USB device driver class %x", desc->class);
+ }
+
+ if (dev->config[0].interf[i].attached)
+ continue;
+
+ switch (interf->class)
+ {
+ case GRUB_USB_CLASS_MASS_STORAGE:
+ grub_dl_load ("usbms");
+ grub_print_error ();
+ break;
+ case GRUB_USB_CLASS_HID:
+ grub_dl_load ("usb_keyboard");
+ grub_print_error ();
+ break;
+ case 0xff:
+ /* FIXME: don't load useless modules. */
+ grub_dl_load ("usbserial_ftdi");
+ grub_print_error ();
+ grub_dl_load ("usbserial_pl2303");
+ grub_print_error ();
+ grub_dl_load ("usbserial_usbdebug");
+ grub_print_error ();
+ break;
+ }
+ }
+}
+
+/* Helper for grub_usb_register_attach_hook_class. */
+static int
+grub_usb_register_attach_hook_class_iter (grub_usb_device_t usbdev, void *data)
+{
+ struct grub_usb_attach_desc *desc = data;
+ struct grub_usb_desc_device *descdev = &usbdev->descdev;
+ int i;
+
+ if (descdev->class != 0 || descdev->subclass || descdev->protocol != 0
+ || descdev->configcnt == 0)
+ return 0;
+
+ /* XXX: Just check configuration 0 for now. */
+ for (i = 0; i < usbdev->config[0].descconf->numif; i++)
+ {
+ struct grub_usb_desc_if *interf;
+
+ interf = usbdev->config[0].interf[i].descif;
+
+ grub_dprintf ("usb", "iterate: interf=%d, class=%d, subclass=%d, protocol=%d\n",
+ i, interf->class, interf->subclass, interf->protocol);
+
+ if (usbdev->config[0].interf[i].attached)
+ continue;
+
+ if (interf->class != desc->class)
+ continue;
+ if (desc->hook (usbdev, 0, i))
+ usbdev->config[0].interf[i].attached = 1;
+ }
+
+ return 0;
+}
+
+void
+grub_usb_register_attach_hook_class (struct grub_usb_attach_desc *desc)
+{
+ desc->next = attach_hooks;
+ attach_hooks = desc;
+
+ grub_usb_iterate (grub_usb_register_attach_hook_class_iter, desc);
+}
+
+void
+grub_usb_unregister_attach_hook_class (struct grub_usb_attach_desc *desc)
+{
+ grub_list_remove (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/grub-core/bus/usb/usbhub.c b/grub-core/bus/usb/usbhub.c
new file mode 100644
index 0000000..85ccf3a
--- /dev/null
+++ b/grub-core/bus/usb/usbhub.c
@@ -0,0 +1,756 @@
+/* usb.c - USB Hub Support. */
+/*
+ * GRUB -- GRand Unified Bootloader
+ * Copyright (C) 2008 Free Software Foundation, Inc.
+ *
+ * GRUB is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * GRUB is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with GRUB. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <grub/dl.h>
+#include <grub/mm.h>
+#include <grub/usb.h>
+#include <grub/misc.h>
+#include <grub/time.h>
+
+#define GRUB_USBHUB_MAX_DEVICES 128
+
+/* USB Supports 127 devices, with device 0 as special case. */
+static struct grub_usb_device *grub_usb_devs[GRUB_USBHUB_MAX_DEVICES];
+
+static int rescan = 0;
+static int npending = 0;
+
+struct grub_usb_hub
+{
+ struct grub_usb_hub *next;
+ grub_usb_controller_t controller;
+ int nports;
+ struct grub_usb_device **devices;
+ struct grub_usb_hub_port *ports;
+ grub_usb_device_t dev;
+};
+
+static struct grub_usb_hub *hubs;
+static grub_usb_controller_dev_t grub_usb_list;
+
+/* Add a device that currently has device number 0 and resides on
+ CONTROLLER, the Hub reported that the device speed is SPEED. */
+static grub_usb_device_t
+grub_usb_hub_add_dev (grub_usb_controller_t controller,
+ grub_usb_speed_t speed,
+ int split_hubport, int split_hubaddr)
+{
+ grub_usb_device_t dev;
+ int i;
+ grub_usb_err_t err;
+
+ grub_boot_time ("Attaching USB device");
+
+ dev = grub_zalloc (sizeof (struct grub_usb_device));
+ if (! dev)
+ return NULL;
+
+ dev->controller = *controller;
+ dev->speed = speed;
+ dev->split_hubport = split_hubport;
+ dev->split_hubaddr = split_hubaddr;
+
+ err = grub_usb_device_initialize (dev);
+ if (err)
+ {
+ grub_free (dev);
+ return NULL;
+ }
+
+ /* Assign a new address to the device. */
+ for (i = 1; i < GRUB_USBHUB_MAX_DEVICES; i++)
+ {
+ if (! grub_usb_devs[i])
+ break;
+ }
+ if (i == GRUB_USBHUB_MAX_DEVICES)
+ {
+ grub_error (GRUB_ERR_IO, "can't assign address to USB device");
+ for (i = 0; i < GRUB_USB_MAX_CONF; i++)
+ grub_free (dev->config[i].descconf);
+ grub_free (dev);
+ return NULL;
+ }
+
+ err = grub_usb_control_msg (dev,
+ (GRUB_USB_REQTYPE_OUT
+ | GRUB_USB_REQTYPE_STANDARD
+ | GRUB_USB_REQTYPE_TARGET_DEV),
+ GRUB_USB_REQ_SET_ADDRESS,
+ i, 0, 0, NULL);
+ if (err)
+ {
+ for (i = 0; i < GRUB_USB_MAX_CONF; i++)
+ grub_free (dev->config[i].descconf);
+ grub_free (dev);
+ return NULL;
+ }
+
+ dev->addr = i;
+ dev->initialized = 1;
+ grub_usb_devs[i] = dev;
+
+ grub_dprintf ("usb", "Added new usb device: %p, addr=%d\n",
+ dev, i);
+ grub_dprintf ("usb", "speed=%d, split_hubport=%d, split_hubaddr=%d\n",
+ speed, split_hubport, split_hubaddr);
+
+ /* Wait "recovery interval", spec. says 2ms */
+ grub_millisleep (2);
+
+ grub_boot_time ("Probing USB device driver");
+
+ grub_usb_device_attach (dev);
+
+ grub_boot_time ("Attached USB device");
+
+ return dev;
+}
+
+
+static grub_usb_err_t
+grub_usb_add_hub (grub_usb_device_t dev)
+{
+ struct grub_usb_usb_hubdesc hubdesc;
+ grub_usb_err_t err;
+ int i;
+
+ 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 err;
+ grub_dprintf ("usb", "Hub descriptor:\n\t\t len:%d, typ:0x%02x, cnt:%d, char:0x%02x, pwg:%d, curr:%d\n",
+ hubdesc.length, hubdesc.type, hubdesc.portcnt,
+ hubdesc.characteristics, hubdesc.pwdgood,
+ hubdesc.current);
+
+ /* Activate the first configuration. Hubs should have only one conf. */
+ grub_dprintf ("usb", "Hub set configuration\n");
+ grub_usb_set_configuration (dev, 1);
+
+ dev->nports = hubdesc.portcnt;
+ dev->children = grub_calloc (hubdesc.portcnt, sizeof (dev->children[0]));
+ dev->ports = grub_calloc (dev->nports, sizeof (dev->ports[0]));
+ if (!dev->children || !dev->ports)
+ {
+ grub_free (dev->children);
+ grub_free (dev->ports);
+ return GRUB_USB_ERR_INTERNAL;
+ }
+
+ /* Power on all Hub ports. */
+ for (i = 1; i <= hubdesc.portcnt; i++)
+ {
+ grub_dprintf ("usb", "Power on - port %d\n", i);
+ /* Power on the port and wait for possible device connect */
+ grub_usb_control_msg (dev, (GRUB_USB_REQTYPE_OUT
+ | GRUB_USB_REQTYPE_CLASS
+ | GRUB_USB_REQTYPE_TARGET_OTHER),
+ GRUB_USB_REQ_SET_FEATURE,
+ GRUB_USB_HUB_FEATURE_PORT_POWER,
+ i, 0, NULL);
+ }
+
+ /* Rest will be done on next usb poll. */
+ for (i = 0; i < dev->config[0].interf[0].descif->endpointcnt;
+ i++)
+ {
+ struct grub_usb_desc_endp *endp = NULL;
+ endp = &dev->config[0].interf[0].descendp[i];
+
+ if ((endp->endp_addr & 128) && grub_usb_get_ep_type(endp)
+ == GRUB_USB_EP_INTERRUPT)
+ {
+ grub_size_t len;
+ dev->hub_endpoint = endp;
+ len = endp->maxpacket;
+ if (len > sizeof (dev->statuschange))
+ len = sizeof (dev->statuschange);
+ dev->hub_transfer
+ = grub_usb_bulk_read_background (dev, endp, len,
+ (char *) &dev->statuschange);
+ break;
+ }
+ }
+
+ rescan = 1;
+
+ return GRUB_USB_ERR_NONE;
+}
+
+static void
+attach_root_port (struct grub_usb_hub *hub, int portno,
+ grub_usb_speed_t speed)
+{
+ grub_usb_device_t dev;
+ grub_usb_err_t err;
+
+ grub_boot_time ("After detect_dev");
+
+ /* Enable the port. */
+ err = hub->controller->dev->portstatus (hub->controller, portno, 1);
+ if (err)
+ return;
+ hub->controller->dev->pending_reset = grub_get_time_ms () + 5000;
+ npending++;
+
+ grub_millisleep (10);
+
+ grub_boot_time ("Port enabled");
+
+ /* Enable the port and create a device. */
+ /* High speed device needs not transaction translation
+ and full/low speed device cannot be connected to EHCI root hub
+ and full/low speed device connected to OHCI/UHCI needs not
+ transaction translation - e.g. hubport and hubaddr should be
+ always none (zero) for any device connected to any root hub. */
+ dev = grub_usb_hub_add_dev (hub->controller, speed, 0, 0);
+ hub->controller->dev->pending_reset = 0;
+ npending--;
+ 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);
+
+ grub_boot_time ("Attached root port");
+}
+
+/* Iterate over all controllers found by the driver. */
+static int
+grub_usb_controller_dev_register_iter (grub_usb_controller_t controller, void *data)
+{
+ grub_usb_controller_dev_t usb = data;
+ struct grub_usb_hub *hub;
+
+ controller->dev = usb;
+
+ grub_boot_time ("Registering USB root hub");
+
+ hub = grub_malloc (sizeof (*hub));
+ if (!hub)
+ return GRUB_USB_ERR_INTERNAL;
+
+ hub->next = hubs;
+ hubs = hub;
+ hub->controller = grub_malloc (sizeof (*controller));
+ if (!hub->controller)
+ {
+ grub_free (hub);
+ return GRUB_USB_ERR_INTERNAL;
+ }
+
+ grub_memcpy (hub->controller, controller, sizeof (*controller));
+ hub->dev = 0;
+
+ /* Query the number of ports the root Hub has. */
+ hub->nports = controller->dev->hubports (controller);
+ hub->devices = grub_calloc (hub->nports, sizeof (hub->devices[0]));
+ hub->ports = grub_calloc (hub->nports, sizeof (hub->ports[0]));
+ if (!hub->devices || !hub->ports)
+ {
+ grub_free (hub->devices);
+ grub_free (hub->ports);
+ grub_free (hub->controller);
+ grub_free (hub);
+ grub_print_error ();
+ return 0;
+ }
+
+ return 0;
+}
+
+void
+grub_usb_controller_dev_unregister (grub_usb_controller_dev_t usb)
+{
+ grub_usb_controller_dev_t *p, q;
+
+ for (p = &grub_usb_list, q = *p; q; p = &(q->next), q = q->next)
+ if (q == usb)
+ {
+ *p = q->next;
+ break;
+ }
+}
+
+void
+grub_usb_controller_dev_register (grub_usb_controller_dev_t usb)
+{
+ int portno;
+ int continue_waiting = 0;
+ struct grub_usb_hub *hub;
+
+ usb->next = grub_usb_list;
+ grub_usb_list = usb;
+
+ if (usb->iterate)
+ usb->iterate (grub_usb_controller_dev_register_iter, usb);
+
+ grub_boot_time ("waiting for stable power on USB root\n");
+
+ while (1)
+ {
+ for (hub = hubs; hub; hub = hub->next)
+ if (hub->controller->dev == usb)
+ {
+ /* Wait for completion of insertion and stable power (USB spec.)
+ * Should be at least 100ms, some devices requires more...
+ * There is also another thing - some devices have worse contacts
+ * and connected signal is unstable for some time - we should handle
+ * it - but prevent deadlock in case when device is too faulty... */
+ for (portno = 0; portno < hub->nports; portno++)
+ {
+ grub_usb_speed_t speed;
+ int changed = 0;
+
+ speed = hub->controller->dev->detect_dev (hub->controller, portno,
+ &changed);
+
+ if (hub->ports[portno].state == PORT_STATE_NORMAL
+ && speed != GRUB_USB_SPEED_NONE)
+ {
+ hub->ports[portno].soft_limit_time = grub_get_time_ms () + 250;
+ hub->ports[portno].hard_limit_time = hub->ports[portno].soft_limit_time + 1750;
+ hub->ports[portno].state = PORT_STATE_WAITING_FOR_STABLE_POWER;
+ grub_boot_time ("Scheduling stable power wait for port %p:%d",
+ usb, portno);
+ continue_waiting++;
+ continue;
+ }
+
+ if (hub->ports[portno].state == PORT_STATE_WAITING_FOR_STABLE_POWER
+ && speed == GRUB_USB_SPEED_NONE)
+ {
+ hub->ports[portno].soft_limit_time = grub_get_time_ms () + 250;
+ continue;
+ }
+ if (hub->ports[portno].state == PORT_STATE_WAITING_FOR_STABLE_POWER
+ && grub_get_time_ms () > hub->ports[portno].soft_limit_time)
+ {
+ hub->ports[portno].state = PORT_STATE_STABLE_POWER;
+ grub_boot_time ("Got stable power wait for port %p:%d",
+ usb, portno);
+ continue_waiting--;
+ continue;
+ }
+ if (hub->ports[portno].state == PORT_STATE_WAITING_FOR_STABLE_POWER
+ && grub_get_time_ms () > hub->ports[portno].hard_limit_time)
+ {
+ hub->ports[portno].state = PORT_STATE_FAILED_DEVICE;
+ continue_waiting--;
+ continue;
+ }
+ }
+ }
+ if (!continue_waiting)
+ break;
+ grub_millisleep (1);
+ }
+
+ grub_boot_time ("After the stable power wait on USB root");
+
+ for (hub = hubs; hub; hub = hub->next)
+ if (hub->controller->dev == usb)
+ for (portno = 0; portno < hub->nports; portno++)
+ if (hub->ports[portno].state == PORT_STATE_STABLE_POWER)
+ {
+ grub_usb_speed_t speed;
+ int changed = 0;
+ hub->ports[portno].state = PORT_STATE_NORMAL;
+ speed = hub->controller->dev->detect_dev (hub->controller, portno, &changed);
+ attach_root_port (hub, portno, speed);
+ }
+
+ grub_boot_time ("USB root hub registered");
+}
+
+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)
+ {
+ if (dev->hub_transfer)
+ grub_usb_cancel_transfer (dev->hub_transfer);
+
+ 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 int
+wait_power_nonroot_hub (grub_usb_device_t dev)
+{
+ grub_usb_err_t err;
+ int continue_waiting = 0;
+ unsigned i;
+
+ for (i = 1; i <= dev->nports; i++)
+ if (dev->ports[i - 1].state == PORT_STATE_WAITING_FOR_STABLE_POWER)
+ {
+ grub_uint64_t tm;
+ grub_uint32_t current_status = 0;
+
+ /* Get the port status. */
+ err = grub_usb_control_msg (dev, (GRUB_USB_REQTYPE_IN
+ | GRUB_USB_REQTYPE_CLASS
+ | GRUB_USB_REQTYPE_TARGET_OTHER),
+ GRUB_USB_REQ_GET_STATUS,
+ 0, i,
+ sizeof (current_status),
+ (char *) &current_status);
+ if (err)
+ {
+ dev->ports[i - 1].state = PORT_STATE_FAILED_DEVICE;
+ continue;
+ }
+ tm = grub_get_time_ms ();
+ if (!(current_status & GRUB_USB_HUB_STATUS_PORT_CONNECTED))
+ dev->ports[i - 1].soft_limit_time = tm + 250;
+ if (tm >= dev->ports[i - 1].soft_limit_time)
+ {
+ if (dev->controller.dev->pending_reset)
+ continue;
+ /* Now do reset of port. */
+ grub_usb_control_msg (dev, (GRUB_USB_REQTYPE_OUT
+ | GRUB_USB_REQTYPE_CLASS
+ | GRUB_USB_REQTYPE_TARGET_OTHER),
+ GRUB_USB_REQ_SET_FEATURE,
+ GRUB_USB_HUB_FEATURE_PORT_RESET,
+ i, 0, 0);
+ dev->ports[i - 1].state = PORT_STATE_NORMAL;
+ grub_boot_time ("Resetting port %p:%d", dev, i - 1);
+
+ rescan = 1;
+ /* We cannot reset more than one device at the same time !
+ * Resetting more devices together results in very bad
+ * situation: more than one device has default address 0
+ * at the same time !!!
+ * Additionaly, we cannot perform another reset
+ * anywhere on the same OHCI controller until
+ * we will finish addressing of reseted device ! */
+ dev->controller.dev->pending_reset = grub_get_time_ms () + 5000;
+ npending++;
+ continue;
+ }
+ if (tm >= dev->ports[i - 1].hard_limit_time)
+ {
+ dev->ports[i - 1].state = PORT_STATE_FAILED_DEVICE;
+ continue;
+ }
+ continue_waiting = 1;
+ }
+ return continue_waiting && dev->controller.dev->pending_reset == 0;
+}
+
+static void
+poll_nonroot_hub (grub_usb_device_t dev)
+{
+ grub_usb_err_t err;
+ unsigned i;
+ grub_uint32_t changed;
+ grub_size_t actual, len;
+
+ if (!dev->hub_transfer)
+ return;
+
+ err = grub_usb_check_transfer (dev->hub_transfer, &actual);
+
+ if (err == GRUB_USB_ERR_WAIT)
+ return;
+
+ changed = dev->statuschange;
+
+ len = dev->hub_endpoint->maxpacket;
+ if (len > sizeof (dev->statuschange))
+ len = sizeof (dev->statuschange);
+ dev->hub_transfer
+ = grub_usb_bulk_read_background (dev, dev->hub_endpoint, len,
+ (char *) &dev->statuschange);
+
+ if (err || actual == 0 || changed == 0)
+ return;
+
+ /* Iterate over the Hub ports. */
+ for (i = 1; i <= dev->nports; i++)
+ {
+ grub_uint32_t status;
+
+ if (!(changed & (1 << i))
+ || dev->ports[i - 1].state == PORT_STATE_WAITING_FOR_STABLE_POWER)
+ continue;
+
+ /* Get the port status. */
+ err = grub_usb_control_msg (dev, (GRUB_USB_REQTYPE_IN
+ | GRUB_USB_REQTYPE_CLASS
+ | GRUB_USB_REQTYPE_TARGET_OTHER),
+ GRUB_USB_REQ_GET_STATUS,
+ 0, i, sizeof (status), (char *) &status);
+
+ grub_dprintf ("usb", "dev = %p, i = %d, status = %08x\n",
+ dev, i, status);
+
+ if (err)
+ continue;
+
+ /* FIXME: properly handle these conditions. */
+ if (status & GRUB_USB_HUB_STATUS_C_PORT_ENABLED)
+ grub_usb_control_msg (dev, (GRUB_USB_REQTYPE_OUT
+ | GRUB_USB_REQTYPE_CLASS
+ | GRUB_USB_REQTYPE_TARGET_OTHER),
+ GRUB_USB_REQ_CLEAR_FEATURE,
+ GRUB_USB_HUB_FEATURE_C_PORT_ENABLED, i, 0, 0);
+
+ if (status & GRUB_USB_HUB_STATUS_C_PORT_SUSPEND)
+ grub_usb_control_msg (dev, (GRUB_USB_REQTYPE_OUT
+ | GRUB_USB_REQTYPE_CLASS
+ | GRUB_USB_REQTYPE_TARGET_OTHER),
+ GRUB_USB_REQ_CLEAR_FEATURE,
+ GRUB_USB_HUB_FEATURE_C_PORT_SUSPEND, i, 0, 0);
+
+ if (status & GRUB_USB_HUB_STATUS_C_PORT_OVERCURRENT)
+ grub_usb_control_msg (dev, (GRUB_USB_REQTYPE_OUT
+ | GRUB_USB_REQTYPE_CLASS
+ | GRUB_USB_REQTYPE_TARGET_OTHER),
+ GRUB_USB_REQ_CLEAR_FEATURE,
+ GRUB_USB_HUB_FEATURE_C_PORT_OVERCURRENT, i, 0, 0);
+
+ if (!dev->controller.dev->pending_reset &&
+ (status & GRUB_USB_HUB_STATUS_C_PORT_CONNECTED))
+ {
+ grub_usb_control_msg (dev, (GRUB_USB_REQTYPE_OUT
+ | GRUB_USB_REQTYPE_CLASS
+ | GRUB_USB_REQTYPE_TARGET_OTHER),
+ GRUB_USB_REQ_CLEAR_FEATURE,
+ GRUB_USB_HUB_FEATURE_C_PORT_CONNECTED, i, 0, 0);
+
+ detach_device (dev->children[i - 1]);
+ dev->children[i - 1] = NULL;
+
+ /* Connected and status of connection changed ? */
+ if (status & GRUB_USB_HUB_STATUS_PORT_CONNECTED)
+ {
+ grub_boot_time ("Before the stable power wait portno=%d", i);
+ /* A device is actually connected to this port. */
+ /* Wait for completion of insertion and stable power (USB spec.)
+ * Should be at least 100ms, some devices requires more...
+ * There is also another thing - some devices have worse contacts
+ * and connected signal is unstable for some time - we should handle
+ * it - but prevent deadlock in case when device is too faulty... */
+ dev->ports[i - 1].soft_limit_time = grub_get_time_ms () + 250;
+ dev->ports[i - 1].hard_limit_time = dev->ports[i - 1].soft_limit_time + 1750;
+ dev->ports[i - 1].state = PORT_STATE_WAITING_FOR_STABLE_POWER;
+ grub_boot_time ("Scheduling stable power wait for port %p:%d",
+ dev, i - 1);
+ continue;
+ }
+ }
+
+ if (status & GRUB_USB_HUB_STATUS_C_PORT_RESET)
+ {
+ grub_usb_control_msg (dev, (GRUB_USB_REQTYPE_OUT
+ | GRUB_USB_REQTYPE_CLASS
+ | GRUB_USB_REQTYPE_TARGET_OTHER),
+ GRUB_USB_REQ_CLEAR_FEATURE,
+ GRUB_USB_HUB_FEATURE_C_PORT_RESET, i, 0, 0);
+
+ grub_boot_time ("Port %d reset", i);
+
+ if (status & GRUB_USB_HUB_STATUS_PORT_CONNECTED)
+ {
+ grub_usb_speed_t speed;
+ grub_usb_device_t next_dev;
+ int split_hubport = 0;
+ int split_hubaddr = 0;
+
+ /* Determine the device speed. */
+ if (status & GRUB_USB_HUB_STATUS_PORT_LOWSPEED)
+ speed = GRUB_USB_SPEED_LOW;
+ else
+ {
+ if (status & GRUB_USB_HUB_STATUS_PORT_HIGHSPEED)
+ speed = GRUB_USB_SPEED_HIGH;
+ else
+ speed = GRUB_USB_SPEED_FULL;
+ }
+
+ /* Wait a recovery time after reset, spec. says 10ms */
+ grub_millisleep (10);
+
+ /* Find correct values for SPLIT hubport and hubaddr */
+ if (speed == GRUB_USB_SPEED_HIGH)
+ {
+ /* HIGH speed device needs not transaction translation */
+ split_hubport = 0;
+ split_hubaddr = 0;
+ }
+ else
+ /* FULL/LOW device needs hub port and hub address
+ for transaction translation (if connected to EHCI) */
+ if (dev->speed == GRUB_USB_SPEED_HIGH)
+ {
+ /* This port is the first FULL/LOW speed port
+ in the chain from root hub. Attached device
+ should use its port number and hub address */
+ split_hubport = i;
+ split_hubaddr = dev->addr;
+ }
+ else
+ {
+ /* This port is NOT the first FULL/LOW speed port
+ in the chain from root hub. Attached device
+ should use values inherited from some parent
+ HIGH speed hub - if any. */
+ split_hubport = dev->split_hubport;
+ split_hubaddr = dev->split_hubaddr;
+ }
+
+ /* Add the device and assign a device address to it. */
+ next_dev = grub_usb_hub_add_dev (&dev->controller, speed,
+ split_hubport, split_hubaddr);
+ if (dev->controller.dev->pending_reset)
+ {
+ dev->controller.dev->pending_reset = 0;
+ npending--;
+ }
+ if (! next_dev)
+ continue;
+
+ dev->children[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);
+ }
+ }
+ }
+}
+
+void
+grub_usb_poll_devices (int wait_for_completion)
+{
+ struct grub_usb_hub *hub;
+ int i;
+
+ for (hub = hubs; hub; hub = hub->next)
+ {
+ /* 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 = GRUB_USB_SPEED_NONE;
+ int changed = 0;
+
+ if (hub->controller->dev->pending_reset)
+ {
+ /* Check for possible timeout */
+ if (grub_get_time_ms () > hub->controller->dev->pending_reset)
+ {
+ /* Something went wrong, reset device was not
+ * addressed properly, timeout happened */
+ hub->controller->dev->pending_reset = 0;
+ npending--;
+ }
+ }
+ if (!hub->controller->dev->pending_reset)
+ speed = hub->controller->dev->detect_dev (hub->controller,
+ i, &changed);
+
+ if (changed)
+ {
+ detach_device (hub->devices[i]);
+ hub->devices[i] = NULL;
+ if (speed != GRUB_USB_SPEED_NONE)
+ attach_root_port (hub, i, speed);
+ }
+ }
+ }
+
+ while (1)
+ {
+ rescan = 0;
+
+ /* We should check changes of non-root hubs too. */
+ for (i = 0; i < GRUB_USBHUB_MAX_DEVICES; i++)
+ {
+ grub_usb_device_t dev = grub_usb_devs[i];
+
+ if (dev && dev->descdev.class == 0x09)
+ poll_nonroot_hub (dev);
+ }
+
+ while (1)
+ {
+ int continue_waiting = 0;
+ for (i = 0; i < GRUB_USBHUB_MAX_DEVICES; i++)
+ {
+ grub_usb_device_t dev = grub_usb_devs[i];
+
+ if (dev && dev->descdev.class == 0x09)
+ continue_waiting = continue_waiting || wait_power_nonroot_hub (dev);
+ }
+ if (!continue_waiting)
+ break;
+ grub_millisleep (1);
+ }
+
+ if (!(rescan || (npending && wait_for_completion)))
+ break;
+ grub_millisleep (25);
+ }
+}
+
+int
+grub_usb_iterate (grub_usb_iterate_hook_t hook, void *hook_data)
+{
+ int i;
+
+ for (i = 0; i < GRUB_USBHUB_MAX_DEVICES; i++)
+ {
+ if (grub_usb_devs[i])
+ {
+ if (hook (grub_usb_devs[i], hook_data))
+ return 1;
+ }
+ }
+
+ return 0;
+}
diff --git a/grub-core/bus/usb/usbtrans.c b/grub-core/bus/usb/usbtrans.c
new file mode 100644
index 0000000..85f081f
--- /dev/null
+++ b/grub-core/bus/usb/usbtrans.c
@@ -0,0 +1,462 @@
+/* usbtrans.c - USB Transfers and Transactions. */
+/*
+ * GRUB -- GRand Unified Bootloader
+ * Copyright (C) 2008 Free Software Foundation, Inc.
+ *
+ * GRUB is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * GRUB is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with GRUB. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <grub/dl.h>
+#include <grub/dma.h>
+#include <grub/mm.h>
+#include <grub/misc.h>
+#include <grub/usb.h>
+#include <grub/usbtrans.h>
+#include <grub/time.h>
+#include <grub/cache.h>
+
+
+static inline unsigned int
+grub_usb_bulk_maxpacket (grub_usb_device_t dev,
+ struct grub_usb_desc_endp *endpoint)
+{
+ /* Use the maximum packet size given in the endpoint descriptor. */
+ if (dev->initialized && endpoint && (unsigned int) endpoint->maxpacket)
+ return endpoint->maxpacket;
+
+ return 64;
+}
+
+
+static grub_usb_err_t
+grub_usb_execute_and_wait_transfer (grub_usb_device_t dev,
+ grub_usb_transfer_t transfer,
+ int timeout, grub_size_t *actual)
+{
+ grub_usb_err_t err;
+ grub_uint64_t endtime;
+
+ err = dev->controller.dev->setup_transfer (&dev->controller, transfer);
+ if (err)
+ return err;
+ /* endtime moved behind setup transfer to prevent false timeouts
+ * while debugging... */
+ endtime = grub_get_time_ms () + timeout;
+ while (1)
+ {
+ err = dev->controller.dev->check_transfer (&dev->controller, transfer,
+ actual);
+ if (!err)
+ return GRUB_USB_ERR_NONE;
+ if (err != GRUB_USB_ERR_WAIT)
+ return err;
+ if (grub_get_time_ms () > endtime)
+ {
+ err = dev->controller.dev->cancel_transfer (&dev->controller,
+ transfer);
+ if (err)
+ return err;
+ return GRUB_USB_ERR_TIMEOUT;
+ }
+ grub_cpu_idle ();
+ }
+}
+
+grub_usb_err_t
+grub_usb_control_msg (grub_usb_device_t dev,
+ grub_uint8_t reqtype,
+ grub_uint8_t request,
+ grub_uint16_t value,
+ grub_uint16_t index,
+ grub_size_t size0, char *data_in)
+{
+ int i;
+ grub_usb_transfer_t transfer;
+ int datablocks;
+ volatile struct grub_usb_packet_setup *setupdata;
+ grub_uint32_t setupdata_addr;
+ grub_usb_err_t err;
+ unsigned int max;
+ struct grub_pci_dma_chunk *data_chunk, *setupdata_chunk;
+ 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);
+ if (!data_chunk)
+ return GRUB_USB_ERR_INTERNAL;
+ data = grub_dma_get_virt (data_chunk);
+ data_addr = grub_dma_get_phys (data_chunk);
+ grub_memcpy ((char *) data, data_in, size);
+
+ grub_arch_sync_dma_caches (data, size);
+
+ grub_dprintf ("usb",
+ "control: reqtype=0x%02x req=0x%02x val=0x%02x idx=0x%02x size=%lu\n",
+ reqtype, request, value, index, (unsigned long)size);
+
+ /* Create a transfer. */
+ transfer = grub_malloc (sizeof (*transfer));
+ if (! transfer)
+ {
+ grub_dma_free (data_chunk);
+ return GRUB_USB_ERR_INTERNAL;
+ }
+
+ setupdata_chunk = grub_memalign_dma32 (32, sizeof (*setupdata));
+ if (! setupdata_chunk)
+ {
+ grub_free (transfer);
+ grub_dma_free (data_chunk);
+ return GRUB_USB_ERR_INTERNAL;
+ }
+
+ setupdata = grub_dma_get_virt (setupdata_chunk);
+ setupdata_addr = grub_dma_get_phys (setupdata_chunk);
+
+ /* Determine the maximum packet size. */
+ if (dev->descdev.maxsize0)
+ max = dev->descdev.maxsize0;
+ else
+ max = 64;
+
+ grub_dprintf ("usb", "control: transfer = %p, dev = %p\n", transfer, dev);
+
+ datablocks = (size + max - 1) / max;
+
+ /* XXX: Discriminate between different types of control
+ messages. */
+ transfer->transcnt = datablocks + 2;
+ transfer->size = size; /* XXX ? */
+ transfer->endpoint = 0;
+ transfer->devaddr = dev->addr;
+ transfer->type = GRUB_USB_TRANSACTION_TYPE_CONTROL;
+ transfer->max = max;
+ transfer->dev = dev;
+
+ /* Allocate an array of transfer data structures. */
+ transfer->transactions = grub_malloc (transfer->transcnt
+ * sizeof (struct grub_usb_transfer));
+ if (! transfer->transactions)
+ {
+ grub_free (transfer);
+ grub_dma_free (setupdata_chunk);
+ grub_dma_free (data_chunk);
+ return GRUB_USB_ERR_INTERNAL;
+ }
+
+ /* Build a Setup packet. XXX: Endianness. */
+ setupdata->reqtype = reqtype;
+ setupdata->request = request;
+ setupdata->value = value;
+ setupdata->index = index;
+ setupdata->length = size;
+ grub_arch_sync_dma_caches (setupdata, sizeof (*setupdata));
+
+ transfer->transactions[0].size = sizeof (*setupdata);
+ transfer->transactions[0].pid = GRUB_USB_TRANSFER_TYPE_SETUP;
+ transfer->transactions[0].data = setupdata_addr;
+ transfer->transactions[0].toggle = 0;
+
+ /* Now the data... XXX: Is this the right way to transfer control
+ transfers? */
+ for (i = 0; i < datablocks; i++)
+ {
+ grub_usb_transaction_t tr = &transfer->transactions[i + 1];
+
+ tr->size = (size > max) ? max : size;
+ /* Use the right most bit as the data toggle. Simple and
+ effective. */
+ tr->toggle = !(i & 1);
+ if (reqtype & 128)
+ tr->pid = GRUB_USB_TRANSFER_TYPE_IN;
+ else
+ tr->pid = GRUB_USB_TRANSFER_TYPE_OUT;
+ tr->data = data_addr + i * max;
+ tr->preceding = i * max;
+ size -= max;
+ }
+
+ /* End with an empty OUT transaction. */
+ transfer->transactions[datablocks + 1].size = 0;
+ transfer->transactions[datablocks + 1].data = 0;
+ if ((reqtype & 128) && datablocks)
+ transfer->transactions[datablocks + 1].pid = GRUB_USB_TRANSFER_TYPE_OUT;
+ else
+ transfer->transactions[datablocks + 1].pid = GRUB_USB_TRANSFER_TYPE_IN;
+
+ transfer->transactions[datablocks + 1].toggle = 1;
+
+ err = grub_usb_execute_and_wait_transfer (dev, transfer, 1000, &actual);
+
+ grub_dprintf ("usb", "control: err=%d\n", err);
+
+ grub_free (transfer->transactions);
+
+ grub_free (transfer);
+ grub_dma_free (setupdata_chunk);
+
+ grub_arch_sync_dma_caches (data, size0);
+ grub_memcpy (data_in, (char *) data, size0);
+
+ grub_dma_free (data_chunk);
+
+ return err;
+}
+
+static grub_usb_transfer_t
+grub_usb_bulk_setup_readwrite (grub_usb_device_t dev,
+ struct grub_usb_desc_endp *endpoint,
+ grub_size_t size0, char *data_in,
+ grub_transfer_type_t type)
+{
+ int i;
+ grub_usb_transfer_t transfer;
+ int datablocks;
+ unsigned int max;
+ volatile char *data;
+ grub_uint32_t data_addr;
+ struct grub_pci_dma_chunk *data_chunk;
+ grub_size_t size = size0;
+ int toggle = dev->toggle[endpoint->endp_addr];
+
+ grub_dprintf ("usb", "bulk: size=0x%02lx type=%d\n", (unsigned long) size,
+ type);
+
+ /* FIXME: avoid allocation any kind of buffer in a first place. */
+ data_chunk = grub_memalign_dma32 (128, size);
+ if (!data_chunk)
+ return NULL;
+ data = grub_dma_get_virt (data_chunk);
+ data_addr = grub_dma_get_phys (data_chunk);
+ if (type == GRUB_USB_TRANSFER_TYPE_OUT)
+ {
+ grub_memcpy ((char *) data, data_in, size);
+ grub_arch_sync_dma_caches (data, size);
+ }
+
+ /* Create a transfer. */
+ transfer = grub_malloc (sizeof (struct grub_usb_transfer));
+ if (! transfer)
+ {
+ grub_dma_free (data_chunk);
+ return NULL;
+ }
+
+ max = grub_usb_bulk_maxpacket (dev, endpoint);
+
+ datablocks = ((size + max - 1) / max);
+ transfer->transcnt = datablocks;
+ transfer->size = size - 1;
+ transfer->endpoint = endpoint->endp_addr;
+ transfer->devaddr = dev->addr;
+ transfer->type = GRUB_USB_TRANSACTION_TYPE_BULK;
+ transfer->dir = type;
+ transfer->max = max;
+ transfer->dev = dev;
+ transfer->last_trans = -1; /* Reset index of last processed transaction (TD) */
+ transfer->data_chunk = data_chunk;
+ transfer->data = data_in;
+
+ /* Allocate an array of transfer data structures. */
+ transfer->transactions = grub_malloc (transfer->transcnt
+ * sizeof (struct grub_usb_transfer));
+ if (! transfer->transactions)
+ {
+ grub_free (transfer);
+ grub_dma_free (data_chunk);
+ return NULL;
+ }
+
+ /* Set up all transfers. */
+ for (i = 0; i < datablocks; i++)
+ {
+ grub_usb_transaction_t tr = &transfer->transactions[i];
+
+ tr->size = (size > max) ? max : size;
+ /* XXX: Use the right most bit as the data toggle. Simple and
+ effective. */
+ tr->toggle = toggle;
+ toggle = toggle ? 0 : 1;
+ tr->pid = type;
+ tr->data = data_addr + i * max;
+ tr->preceding = i * max;
+ size -= tr->size;
+ }
+ return transfer;
+}
+
+static void
+grub_usb_bulk_finish_readwrite (grub_usb_transfer_t transfer)
+{
+ grub_usb_device_t dev = transfer->dev;
+ int toggle = dev->toggle[transfer->endpoint];
+
+ /* We must remember proper toggle value even if some transactions
+ * were not processed - correct value should be inversion of last
+ * processed transaction (TD). */
+ if (transfer->last_trans >= 0)
+ toggle = transfer->transactions[transfer->last_trans].toggle ? 0 : 1;
+ else
+ toggle = dev->toggle[transfer->endpoint]; /* Nothing done, take original */
+ grub_dprintf ("usb", "bulk: toggle=%d\n", toggle);
+ dev->toggle[transfer->endpoint] = toggle;
+
+ if (transfer->dir == GRUB_USB_TRANSFER_TYPE_IN)
+ {
+ grub_arch_sync_dma_caches (grub_dma_get_virt (transfer->data_chunk),
+ transfer->size + 1);
+ grub_memcpy (transfer->data, (void *)
+ grub_dma_get_virt (transfer->data_chunk),
+ transfer->size + 1);
+ }
+
+ grub_free (transfer->transactions);
+ grub_dma_free (transfer->data_chunk);
+ grub_free (transfer);
+}
+
+static grub_usb_err_t
+grub_usb_bulk_readwrite (grub_usb_device_t dev,
+ struct grub_usb_desc_endp *endpoint,
+ grub_size_t size0, char *data_in,
+ grub_transfer_type_t type, int timeout,
+ grub_size_t *actual)
+{
+ grub_usb_err_t err;
+ grub_usb_transfer_t transfer;
+
+ transfer = grub_usb_bulk_setup_readwrite (dev, endpoint, size0,
+ data_in, type);
+ if (!transfer)
+ return GRUB_USB_ERR_INTERNAL;
+ err = grub_usb_execute_and_wait_transfer (dev, transfer, timeout, actual);
+
+ grub_usb_bulk_finish_readwrite (transfer);
+
+ return err;
+}
+
+static grub_usb_err_t
+grub_usb_bulk_readwrite_packetize (grub_usb_device_t dev,
+ struct grub_usb_desc_endp *endpoint,
+ grub_transfer_type_t type,
+ grub_size_t size, char *data)
+{
+ grub_size_t actual, transferred;
+ grub_usb_err_t err = GRUB_USB_ERR_NONE;
+ grub_size_t current_size, position;
+ grub_size_t max_bulk_transfer_len = MAX_USB_TRANSFER_LEN;
+ grub_size_t max;
+
+ if (dev->controller.dev->max_bulk_tds)
+ {
+ max = grub_usb_bulk_maxpacket (dev, endpoint);
+
+ /* Calculate max. possible length of bulk transfer */
+ max_bulk_transfer_len = dev->controller.dev->max_bulk_tds * max;
+ }
+
+ for (position = 0, transferred = 0;
+ position < size; position += max_bulk_transfer_len)
+ {
+ current_size = size - position;
+ if (current_size >= max_bulk_transfer_len)
+ current_size = max_bulk_transfer_len;
+ err = grub_usb_bulk_readwrite (dev, endpoint, current_size,
+ &data[position], type, 1000, &actual);
+ transferred += actual;
+ if (err || (current_size != actual)) break;
+ }
+
+ if (!err && transferred != size)
+ err = GRUB_USB_ERR_DATA;
+ return err;
+}
+
+grub_usb_err_t
+grub_usb_bulk_write (grub_usb_device_t dev,
+ struct grub_usb_desc_endp *endpoint,
+ grub_size_t size, char *data)
+{
+ return grub_usb_bulk_readwrite_packetize (dev, endpoint,
+ GRUB_USB_TRANSFER_TYPE_OUT,
+ size, data);
+}
+
+grub_usb_err_t
+grub_usb_bulk_read (grub_usb_device_t dev,
+ struct grub_usb_desc_endp *endpoint,
+ grub_size_t size, char *data)
+{
+ return grub_usb_bulk_readwrite_packetize (dev, endpoint,
+ GRUB_USB_TRANSFER_TYPE_IN,
+ size, data);
+}
+
+grub_usb_err_t
+grub_usb_check_transfer (grub_usb_transfer_t transfer, grub_size_t *actual)
+{
+ grub_usb_err_t err;
+ grub_usb_device_t dev = transfer->dev;
+
+ err = dev->controller.dev->check_transfer (&dev->controller, transfer,
+ actual);
+ if (err == GRUB_USB_ERR_WAIT)
+ return err;
+
+ grub_usb_bulk_finish_readwrite (transfer);
+
+ return err;
+}
+
+grub_usb_transfer_t
+grub_usb_bulk_read_background (grub_usb_device_t dev,
+ struct grub_usb_desc_endp *endpoint,
+ grub_size_t size, void *data)
+{
+ grub_usb_err_t err;
+ grub_usb_transfer_t transfer;
+
+ transfer = grub_usb_bulk_setup_readwrite (dev, endpoint, size,
+ data, GRUB_USB_TRANSFER_TYPE_IN);
+ if (!transfer)
+ return NULL;
+
+ err = dev->controller.dev->setup_transfer (&dev->controller, transfer);
+ if (err)
+ return NULL;
+
+ return transfer;
+}
+
+void
+grub_usb_cancel_transfer (grub_usb_transfer_t transfer)
+{
+ grub_usb_device_t dev = transfer->dev;
+ dev->controller.dev->cancel_transfer (&dev->controller, transfer);
+ grub_errno = GRUB_ERR_NONE;
+}
+
+grub_usb_err_t
+grub_usb_bulk_read_extended (grub_usb_device_t dev,
+ struct grub_usb_desc_endp *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);
+}