diff --git a/grub-core/Makefile.core.def b/grub-core/Makefile.core.def
index e9e4f06d8..346ec24ee 100644
--- a/grub-core/Makefile.core.def
+++ b/grub-core/Makefile.core.def
@@ -437,6 +437,12 @@ module = {
enable = pci;
};
+module = {
+ name = ehci;
+ common = bus/usb/ehci.c;
+ enable = pci;
+};
+
module = {
name = pci;
noemu = bus/pci.c;
diff --git a/grub-core/bus/usb/ehci.c b/grub-core/bus/usb/ehci.c
new file mode 100644
index 000000000..12b54651b
--- /dev/null
+++ b/grub-core/bus/usb/ehci.c
@@ -0,0 +1,1778 @@
+/* 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 .
+ */
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+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
+ */
+
+#define GRUB_EHCI_PCI_SBRN_REG 0x60
+
+/* Capability registers offsets */
+#define GRUB_EHCI_EHCC_CAPLEN 0x00 /* byte */
+#define GRUB_EHCI_EHCC_VERSION 0x02 /* word */
+#define GRUB_EHCI_EHCC_SPARAMS 0x04 /* dword */
+#define GRUB_EHCI_EHCC_CPARAMS 0x08 /* dword */
+#define GRUB_EHCI_EHCC_PROUTE 0x0c /* 60 bits */
+
+#define GRUB_EHCI_EECP_MASK (0xff << 8)
+#define GRUB_EHCI_EECP_SHIFT 8
+
+#define GRUB_EHCI_ADDR_MEM_MASK (~0xff)
+#define GRUB_EHCI_POINTER_MASK (~0x1f)
+
+/* Capability register SPARAMS bits */
+#define GRUB_EHCI_SPARAMS_N_PORTS (0xf <<0)
+#define GRUB_EHCI_SPARAMS_PPC (1<<4) /* Power port control */
+#define GRUB_EHCI_SPARAMS_PRR (1<<7) /* Port routing rules */
+#define GRUB_EHCI_SPARAMS_N_PCC (0xf<<8) /* No of ports per comp. */
+#define GRUB_EHCI_SPARAMS_NCC (0xf<<12) /* No of com. controllers */
+#define GRUB_EHCI_SPARAMS_P_IND (1<<16) /* Port indicators present */
+#define GRUB_EHCI_SPARAMS_DEBUG_P (0xf<<20) /* Debug port */
+
+#define GRUB_EHCI_MAX_N_PORTS 15 /* Max. number of ports */
+
+/* Capability register CPARAMS bits */
+#define GRUB_EHCI_CPARAMS_64BIT (1<<0)
+#define GRUB_EHCI_CPARAMS_PROG_FRAMELIST (1<<1)
+#define 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
+
+/* USBLEGSUP bits and related OS OWNED byte offset */
+#define GRUB_EHCI_BIOS_OWNED (1<<16)
+#define GRUB_EHCI_OS_OWNED (1<<24)
+
+/* Operational registers offsets */
+#define GRUB_EHCI_COMMAND 0x00
+#define GRUB_EHCI_STATUS 0x04
+#define GRUB_EHCI_INTERRUPT 0x08
+#define GRUB_EHCI_FRAME_INDEX 0x0c
+#define GRUB_EHCI_64BIT_SEL 0x10
+#define GRUB_EHCI_FL_BASE 0x14
+#define GRUB_EHCI_CUR_AL_ADDR 0x18
+#define GRUB_EHCI_CONFIG_FLAG 0x40
+#define GRUB_EHCI_PORT_STAT_CMD 0x44
+
+/* Operational register COMMAND bits */
+#define GRUB_EHCI_CMD_RUNSTOP (1<<0)
+#define GRUB_EHCI_CMD_HC_RESET (1<<1)
+#define GRUB_EHCI_CMD_FL_SIZE (3<<2)
+#define GRUB_EHCI_CMD_PS_ENABL (1<<4)
+#define GRUB_EHCI_CMD_AS_ENABL (1<<5)
+#define GRUB_EHCI_CMD_AS_ADV_D (1<<6)
+#define GRUB_EHCI_CMD_L_HC_RES (1<<7)
+#define GRUB_EHCI_CMD_AS_PARKM (3<<8)
+#define GRUB_EHCI_CMD_AS_PARKE (1<<11)
+#define GRUB_EHCI_CMD_INT_THRS (0xff<<16)
+
+/* Operational register STATUS bits */
+#define GRUB_EHCI_ST_INTERRUPT (1<<0)
+#define GRUB_EHCI_ST_ERROR_INT (1<<1)
+#define GRUB_EHCI_ST_PORT_CHG (1<<2)
+#define GRUB_EHCI_ST_FL_ROLLOVR (1<<3)
+#define GRUB_EHCI_ST_HS_ERROR (1<<4)
+#define GRUB_EHCI_ST_AS_ADVANCE (1<<5)
+#define GRUB_EHCI_ST_HC_HALTED (1<<12)
+#define GRUB_EHCI_ST_RECLAM (1<<13)
+#define GRUB_EHCI_ST_PS_STATUS (1<<14)
+#define GRUB_EHCI_ST_AS_STATUS (1<<15)
+
+/* Operational register PORT_STAT_CMD bits */
+#define GRUB_EHCI_PORT_CONNECT (1<<0)
+#define GRUB_EHCI_PORT_CONNECT_CH (1<<1)
+#define GRUB_EHCI_PORT_ENABLED (1<<2)
+#define GRUB_EHCI_PORT_ENABLED_CH (1<<3)
+#define GRUB_EHCI_PORT_OVERCUR (1<<4)
+#define GRUB_EHCI_PORT_OVERCUR_CH (1<<5)
+#define GRUB_EHCI_PORT_RESUME (1<<6)
+#define GRUB_EHCI_PORT_SUSPEND (1<<7)
+#define GRUB_EHCI_PORT_RESET (1<<8)
+#define GRUB_EHCI_PORT_LINE_STAT (3<<10)
+#define GRUB_EHCI_PORT_POWER (1<<12)
+#define GRUB_EHCI_PORT_OWNER (1<<13)
+#define GRUB_EHCI_PORT_INDICATOR (3<<14)
+#define GRUB_EHCI_PORT_TEST (0xf<<16)
+#define GRUB_EHCI_PORT_WON_CONN_E (1<<20)
+#define GRUB_EHCI_PORT_WON_DISC_E (1<<21)
+#define GRUB_EHCI_PORT_WON_OVER_E (1<<22)
+
+#define GRUB_EHCI_PORT_LINE_SE0 (0<<10)
+#define GRUB_EHCI_PORT_LINE_K (1<<10)
+#define GRUB_EHCI_PORT_LINE_J (2<<10)
+#define GRUB_EHCI_PORT_LINE_UNDEF (3<<10)
+#define GRUB_EHCI_PORT_LINE_LOWSP GRUB_EHCI_PORT_LINE_K /* K state means low speed */
+
+#define GRUB_EHCI_PORT_WMASK ~(GRUB_EHCI_PORT_CONNECT_CH | \
+ GRUB_EHCI_PORT_ENABLED_CH | \
+ GRUB_EHCI_PORT_OVERCUR_CH)
+
+/* Operational register CONFIGFLAGS bits */
+#define GRUB_EHCI_CF_EHCI_OWNER (1<<0)
+
+/* Queue Head & Transfer Descriptor constants */
+#define GRUB_EHCI_HPTR_OFF 5 /* Horiz. pointer bit offset */
+#define GRUB_EHCI_HPTR_TYPE_MASK (3<<1)
+#define GRUB_EHCI_HPTR_TYPE_ITD (0<<1)
+#define GRUB_EHCI_HPTR_TYPE_QH (1<<1)
+#define GRUB_EHCI_HPTR_TYPE_SITD (2<<1)
+#define GRUB_EHCI_HPTR_TYPE_FSTN (3<<1)
+
+#define GRUB_EHCI_C (1<<27)
+#define GRUB_EHCI_MAXPLEN_MASK (0x7ff<<16)
+#define GRUB_EHCI_MAXPLEN_OFF 16
+#define GRUB_EHCI_H (1<<15)
+#define GRUB_EHCI_DTC (1<<14)
+#define GRUB_EHCI_SPEED_MASK (3<<12)
+#define GRUB_EHCI_SPEED_OFF 12
+#define GRUB_EHCI_SPEED_FULL (0<<12)
+#define GRUB_EHCI_SPEED_LOW (1<<12)
+#define GRUB_EHCI_SPEED_HIGH (2<<12)
+#define GRUB_EHCI_SPEED_RESERVED (3<<12)
+#define GRUB_EHCI_EP_NUM_MASK (0xf<<8)
+#define GRUB_EHCI_EP_NUM_OFF 8
+#define GRUB_EHCI_DEVADDR_MASK 0x7f
+
+#define GRUB_EHCI_TARGET_MASK (GRUB_EHCI_EP_NUM_MASK \
+ | GRUB_EHCI_DEVADDR_MASK)
+
+#define GRUB_EHCI_MULT_MASK (3<30)
+#define GRUB_EHCI_MULT_OFF 30
+#define GRUB_EHCI_MULT_RESERVED (0<<30)
+#define GRUB_EHCI_MULT_ONE (0<<30)
+#define GRUB_EHCI_MULT_TWO (0<<30)
+#define GRUB_EHCI_MULT_THREE (0<<30)
+#define GRUB_EHCI_DEVPORT_MASK (0x7f<<23)
+#define GRUB_EHCI_DEVPORT_OFF 23
+#define GRUB_EHCI_HUBADDR_MASK (0x7f<<16)
+#define GRUB_EHCI_HUBADDR_OFF 16
+
+#define GRUB_EHCI_TERMINATE (1<<0)
+
+#define GRUB_EHCI_TOGGLE (1<<31)
+
+#define GRUB_EHCI_TOTAL_MASK (0x7fff << 16)
+#define GRUB_EHCI_TOTAL_OFF 16
+#define GRUB_EHCI_CERR_MASK (3<<10)
+#define GRUB_EHCI_CERR_OFF 10
+#define GRUB_EHCI_CERR_0 (0<<10)
+#define GRUB_EHCI_CERR_1 (1<<10)
+#define GRUB_EHCI_CERR_2 (2<<10)
+#define GRUB_EHCI_CERR_3 (3<<10)
+#define GRUB_EHCI_PIDCODE_OUT (0<<8)
+#define GRUB_EHCI_PIDCODE_IN (1<<8)
+#define GRUB_EHCI_PIDCODE_SETUP (2<<8)
+#define GRUB_EHCI_STATUS_MASK 0xff
+#define GRUB_EHCI_STATUS_ACTIVE (1<<7)
+#define GRUB_EHCI_STATUS_HALTED (1<<6)
+#define GRUB_EHCI_STATUS_BUFERR (1<<5)
+#define GRUB_EHCI_STATUS_BABBLE (1<<4)
+#define GRUB_EHCI_STATUS_TRANERR (1<<3)
+#define GRUB_EHCI_STATUS_MISSDMF (1<<2)
+#define GRUB_EHCI_STATUS_SPLITST (1<<1)
+#define GRUB_EHCI_STATUS_PINGERR (1<<0)
+
+#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
+
+#define GRUB_EHCI_QHPTR_TO_INDEX (qh) \
+ ((grub_uint32_t)qh - (grub_uint32_t)e->qh) / \
+ sizeof(grub_ehci_qh_t)
+
+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_ehci_td_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 */
+} __attribute__ ((packed));
+
+/* 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 */
+} __attribute__ ((packed));
+
+/* 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 */
+ grub_pci_address_t pcibase_eecp; /* PCI extended capability registers base */
+ 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;
+
+/* 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 *) ((char *) e->iobase_ehcc +
+ addr)));
+}
+
+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 *) ((char *) e->iobase_ehcc +
+ addr)));
+}
+
+static inline grub_uint8_t
+grub_ehci_ehcc_read8 (struct grub_ehci *e, grub_uint32_t addr)
+{
+ return *((volatile grub_uint8_t *) ((char *) 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 *) ((char *) e->iobase +
+ addr)));
+}
+
+static inline void
+grub_ehci_oper_write32 (struct grub_ehci *e, grub_uint32_t addr,
+ grub_uint32_t value)
+{
+ *((volatile grub_uint32_t *) ((char *) e->iobase + addr)) =
+ 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);
+}
+
+static inline void *
+grub_ehci_phys2virt (grub_uint32_t phys, struct grub_pci_dma_chunk *chunk)
+{
+ if (!phys)
+ return NULL;
+ return (void *) (phys - grub_dma_get_phys (chunk)
+ + (grub_uint32_t) grub_dma_get_virt (chunk));
+}
+
+static inline grub_uint32_t
+grub_ehci_virt2phys (void *virt, struct grub_pci_dma_chunk *chunk)
+{
+ if (!virt)
+ return 0;
+ return ((grub_uint32_t) virt - (grub_uint32_t) grub_dma_get_virt (chunk)
+ + grub_dma_get_phys (chunk));
+}
+
+/* Halt if EHCI HC not halted */
+static grub_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_ERR_TIMEOUT;
+ }
+
+ return GRUB_ERR_NONE;
+}
+
+/* EHCI HC reset */
+static grub_err_t
+grub_ehci_reset (struct grub_ehci *e)
+{
+ grub_uint64_t maxtime;
+
+ grub_ehci_oper_write32 (e, GRUB_EHCI_COMMAND,
+ GRUB_EHCI_CMD_HC_RESET
+ | grub_ehci_oper_read32 (e, GRUB_EHCI_COMMAND));
+ /* 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_ERR_TIMEOUT;
+
+ return GRUB_ERR_NONE;
+}
+
+/* PCI iteration function... */
+static int NESTED_FUNC_ATTR
+grub_ehci_pci_iter (grub_pci_device_t dev,
+ grub_pci_id_t pciid __attribute__ ((unused)))
+{
+ grub_pci_address_t addr;
+ grub_uint8_t release;
+ grub_uint32_t class_code;
+ grub_uint32_t interf;
+ grub_uint32_t subclass;
+ grub_uint32_t class;
+ grub_uint32_t base, base_h;
+ struct grub_ehci *e;
+ grub_uint32_t eecp_offset;
+ grub_uint32_t fp;
+ int i;
+ grub_uint32_t usblegsup = 0;
+ grub_uint64_t maxtime;
+ grub_uint32_t n_ports;
+
+ grub_dprintf ("ehci", "EHCI grub_ehci_pci_iter: begin\n");
+
+ 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 1;
+ }
+
+ grub_dprintf ("ehci", "EHCI grub_ehci_pci_iter: 32-bit EHCI OK\n");
+
+
+ /* Allocate memory for the controller and fill basic values. */
+ e = grub_zalloc (sizeof (*e));
+ if (!e)
+ return 1;
+ e->framelist_chunk = NULL;
+ e->td_chunk = NULL;
+ e->qh_chunk = NULL;
+ e->iobase_ehcc = (grub_uint32_t *) (base & GRUB_EHCI_ADDR_MEM_MASK);
+
+ grub_dprintf ("ehci", "EHCI grub_ehci_pci_iter: iobase of EHCC: %08x\n",
+ (grub_uint32_t) e->iobase_ehcc);
+ 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 */
+ e->iobase = (grub_uint32_t *) ((grub_uint32_t) e->iobase_ehcc +
+ (grub_uint32_t) grub_ehci_ehcc_read8 (e,
+ GRUB_EHCI_EHCC_CAPLEN));
+
+ grub_dprintf ("ehci",
+ "EHCI grub_ehci_pci_iter: iobase of oper. regs: %08x\n",
+ (grub_uint32_t) e->iobase);
+ 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));
+
+ /* Is there EECP ? */
+ eecp_offset = (grub_ehci_ehcc_read32 (e, GRUB_EHCI_EHCC_CPARAMS)
+ & GRUB_EHCI_EECP_MASK) >> GRUB_EHCI_EECP_SHIFT;
+ if (eecp_offset >= 0x40) /* EECP offset valid in HCCPARAMS */
+ e->pcibase_eecp = grub_pci_make_address (dev, eecp_offset);
+ else
+ e->pcibase_eecp = 0;
+
+ /* 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=0x%08x. OK\n",
+ (grub_uint32_t) 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=0x%08x. OK\n",
+ (grub_uint32_t) 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=0x%08x. OK\n",
+ (grub_uint32_t) 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_virt[i + 1];
+ e->td_virt[i].next_td = grub_cpu_to_le32 (GRUB_EHCI_TERMINATE);
+ e->td_virt[i].alt_next_td = grub_cpu_to_le32 (GRUB_EHCI_TERMINATE);
+ }
+ e->td_virt[GRUB_EHCI_N_TD - 1].next_td =
+ grub_cpu_to_le32 (GRUB_EHCI_TERMINATE);
+ e->td_virt[GRUB_EHCI_N_TD - 1].alt_next_td =
+ grub_cpu_to_le32 (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 (GRUB_EHCI_TERMINATE);
+ e->qh_virt[0].td_overlay.next_td = grub_cpu_to_le32 (GRUB_EHCI_TERMINATE);
+ e->qh_virt[0].td_overlay.alt_next_td =
+ grub_cpu_to_le32 (GRUB_EHCI_TERMINATE);
+ /* Also set Halted bit in token */
+ e->qh_virt[0].td_overlay.token = grub_cpu_to_le32 (GRUB_EHCI_STATUS_HALTED);
+ /* Set the H bit in first QH used for AL */
+ e->qh_virt[1].ep_char = grub_cpu_to_le32 (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_ehci_virt2phys ((void *) &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 (GRUB_EHCI_TERMINATE);
+ e->qh_virt[i].td_overlay.alt_next_td =
+ grub_cpu_to_le32 (GRUB_EHCI_TERMINATE);
+ /* Also set Halted bit in token */
+ e->qh_virt[i].td_overlay.token =
+ grub_cpu_to_le32 (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");
+
+ /* Determine and change ownership. */
+ if (e->pcibase_eecp) /* Ownership can be changed via EECP only */
+ {
+ usblegsup = grub_pci_read (e->pcibase_eecp);
+ if (usblegsup & GRUB_EHCI_BIOS_OWNED)
+ {
+ grub_dprintf ("ehci",
+ "EHCI grub_ehci_pci_iter: EHCI owned by: BIOS\n");
+ /* Ownership change - set OS_OWNED bit */
+ grub_pci_write (e->pcibase_eecp, usblegsup | GRUB_EHCI_OS_OWNED);
+ /* Ensure PCI register is written */
+ grub_pci_read (e->pcibase_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 (e->pcibase_eecp) & GRUB_EHCI_BIOS_OWNED)
+ && (grub_get_time_ms () < maxtime));
+ if (grub_pci_read (e->pcibase_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 (e->pcibase_eecp, GRUB_EHCI_OS_OWNED);
+ /* Ensure PCI register is written */
+ grub_pci_read (e->pcibase_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 (e->pcibase_eecp, GRUB_EHCI_OS_OWNED);
+ /* Ensure PCI register is written */
+ grub_pci_read (e->pcibase_eecp);
+ }
+ }
+
+ grub_dprintf ("ehci", "inithw: EHCI grub_ehci_pci_iter: ownership 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_ehci_virt2phys ((void *) &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 asynchronous list */
+ grub_ehci_oper_write32 (e, GRUB_EHCI_COMMAND,
+ GRUB_EHCI_CMD_AS_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;
+
+ grub_dprintf ("ehci", "EHCI grub_ehci_pci_iter: OK at all\n");
+
+ grub_dprintf ("ehci",
+ "EHCI grub_ehci_pci_iter: iobase of oper. regs: %08x\n",
+ (grub_uint32_t) e->iobase);
+ 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 1;
+
+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 1;
+}
+
+static int
+grub_ehci_iterate (int (*hook) (grub_usb_controller_t dev))
+{
+ struct grub_ehci *e;
+ struct grub_usb_controller dev;
+
+ for (e = ehci; e; e = e->next)
+ {
+ dev.data = e;
+ if (hook (&dev))
+ 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->port << GRUB_EHCI_DEVPORT_OFF)
+ & GRUB_EHCI_DEVPORT_MASK;
+ ep_cap |= (transfer->dev->hubaddr << GRUB_EHCI_HUBADDR_OFF)
+ & GRUB_EHCI_HUBADDR_MASK;
+ qh->ep_cap = grub_cpu_to_le32 (ep_cap);
+
+ grub_dprintf ("ehci", "setup_qh: qh=%08x, not changed: qh_hptr=%08x\n",
+ (grub_uint32_t) 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;
+
+ /* 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 (GRUB_EHCI_TARGET_MASK);
+
+ /* First try to find existing QH with proper target */
+ 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 */
+ if (target == (qh[i].ep_char & mask))
+ { /* Found proper existing (and linked) QH, do setup of QH */
+ grub_dprintf ("ehci", "find_qh: found, i=%d, QH=%08x\n",
+ i, (grub_uint32_t) & qh[i]);
+ grub_ehci_setup_qh (&qh[i], transfer);
+ return &qh[i];
+ }
+ }
+ /* QH with target_addr does not exist, we have to add it */
+ /* 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=%08x\n",
+ i, (grub_uint32_t) & 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 point to first QH */
+ qh[i].qh_hptr = grub_cpu_to_le32 (GRUB_EHCI_HPTR_TYPE_QH
+ | grub_ehci_virt2phys ((void *) &qh[1],
+ e->qh_chunk));
+ /* Linking - previous last QH will point to this new QH */
+ qh[i - 1].qh_hptr = grub_cpu_to_le32 (GRUB_EHCI_HPTR_TYPE_QH
+ | grub_ehci_virt2phys ((void *)
+ &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 */
+ e->tdfree_virt = (grub_ehci_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_ehci_free_td (struct grub_ehci *e, grub_ehci_td_t td)
+{
+ td->link_td = e->tdfree_virt; /* Chain new free TD & rest */
+ 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 transfered
+ * 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;
+ td = (grub_ehci_td_t) td->link_td;
+
+ /* 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 (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_ehci_virt2phys ((void *) 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=%08x\n", (grub_uint32_t) 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",
+ (grub_uint32_t) 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;
+};
+
+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;
+
+ /* Check if EHCI is running and AL is enabled */
+ if ((grub_ehci_oper_read32 (e, GRUB_EHCI_STATUS)
+ & GRUB_EHCI_ST_HC_HALTED) != 0)
+ /* XXX: Fix it: Currently we don't do anything to restart EHCI */
+ return GRUB_USB_ERR_INTERNAL;
+ if ((grub_ehci_oper_read32 (e, GRUB_EHCI_STATUS)
+ & GRUB_EHCI_ST_AS_STATUS) == 0)
+ /* XXX: Fix it: Currently we don't do anything to restart EHCI */
+ return GRUB_USB_ERR_INTERNAL;
+
+ /* Check if transfer is not high speed and connected to root hub.
+ * It should not happened but... */
+ if ((transfer->dev->speed != GRUB_USB_SPEED_HIGH)
+ && !transfer->dev->hubaddr)
+ {
+ grub_error (GRUB_USB_ERR_BADDEVICE,
+ "FULL/LOW speed device on EHCI port!?!");
+ return GRUB_USB_ERR_BADDEVICE;
+ }
+
+ /* 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_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_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 (GRUB_EHCI_TERMINATE);
+ cdata->td_alt_virt->alt_next_td = grub_cpu_to_le32 (GRUB_EHCI_TERMINATE);
+ cdata->td_alt_virt->token = grub_cpu_to_le32 (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);
+ 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 = td;
+ td_prev->next_td =
+ grub_cpu_to_le32 (grub_ehci_virt2phys ((void *) td, e->td_chunk));
+ }
+ td_prev = td;
+ }
+
+ /* Remember last TD */
+ cdata->td_last_virt = td;
+ /* Last TD should not have set alternate TD */
+ cdata->td_last_virt->alt_next_td = grub_cpu_to_le32 (GRUB_EHCI_TERMINATE);
+
+ grub_dprintf ("ehci", "setup_transfer: cdata=%08x, qh=%08x\n",
+ (grub_uint32_t) cdata, (grub_uint32_t) cdata->qh_virt);
+ grub_dprintf ("ehci", "setup_transfer: td_first=%08x, td_alt=%08x\n",
+ (grub_uint32_t) cdata->td_first_virt,
+ (grub_uint32_t) cdata->td_alt_virt);
+ grub_dprintf ("ehci", "setup_transfer: td_last=%08x\n",
+ (grub_uint32_t) cdata->td_last_virt);
+
+ /* Start transfer: */
+ /* Unlink possible alternate pointer in QH */
+ cdata->qh_virt->td_overlay.alt_next_td =
+ grub_cpu_to_le32 (GRUB_EHCI_TERMINATE);
+ /* Link new TDs with QH via next_td */
+ cdata->qh_virt->td_overlay.next_td =
+ grub_cpu_to_le32 (grub_ehci_virt2phys
+ ((void *) 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 (0);
+
+ /* 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
+ (GRUB_EHCI_STATUS_HALTED)) &
+ grub_cpu_to_le32 (~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);
+
+ /* 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_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);
+
+ /* 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);
+
+ 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;
+
+ grub_dprintf ("ehci",
+ "check_transfer: EHCI STATUS=%08x, cdata=%08x, qh=%08x\n",
+ grub_ehci_oper_read32 (e, GRUB_EHCI_STATUS),
+ (grub_uint32_t) cdata, (grub_uint32_t) 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) == 0)
+ return grub_ehci_parse_notrun (dev, transfer, actual);
+
+ token = grub_le_to_cpu32 (cdata->qh_virt->td_overlay.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 & 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) == (grub_uint32_t) cdata->td_last_virt))
+ /* 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;
+
+ /* QH can be active and should be de-activated and halted */
+
+ grub_dprintf ("ehci", "cancel_transfer: begin\n");
+
+ /* First check if EHCI is running and AL is enabled and if not,
+ * there is no problem... */
+ if (((grub_ehci_oper_read32 (e, GRUB_EHCI_STATUS)
+ & GRUB_EHCI_ST_HC_HALTED) != 0) ||
+ ((grub_ehci_oper_read32 (e, GRUB_EHCI_STATUS)
+ & GRUB_EHCI_ST_AS_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);
+ grub_dprintf ("ehci", "cancel_transfer: end - EHCI not running\n");
+ return GRUB_USB_ERR_NONE;
+ }
+
+ /* EHCI and AL are running. What to do?
+ * Try to Halt QH via de-scheduling QH. */
+ /* Find index of current QH - we need previous QH, i.e. i-1 */
+ i = ((int) (e->qh_virt - cdata->qh_virt)) / sizeof (struct grub_ehci_qh);
+ /* Unlink QH from AL */
+ e->qh_virt[i - 1].qh_hptr = cdata->qh_virt->qh_hptr;
+ /* 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);
+
+ /* Finaly we should return QH back to the AL... */
+ e->qh_virt[i - 1].qh_hptr =
+ grub_cpu_to_le32 (grub_ehci_virt2phys
+ ((void *) cdata->qh_virt, e->qh_chunk));
+ grub_free (cdata);
+
+ grub_dprintf ("ehci", "cancel_transfer: end\n");
+
+ 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_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=0x%02x, port=%d, status=0x%02x\n",
+ (grub_uint32_t) 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_error (GRUB_ERR_IO, "portstatus: EHCI Timed out - disable");
+
+ 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_ERR_NONE;
+ }
+
+ grub_dprintf ("ehci", "portstatus: enable\n");
+
+ /* 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_error (GRUB_ERR_IO,
+ "portstatus: EHCI Timed out - 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_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);
+
+ grub_dprintf ("ehci", "detect_dev: EHCI STATUS: %08x\n",
+ grub_ehci_oper_read32 (e, GRUB_EHCI_STATUS));
+ grub_dprintf ("ehci", "detect_dev: iobase=0x%02x, port=%d, status=0x%02x\n",
+ (grub_uint32_t) e->iobase, port, status);
+
+ /* 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 void
+grub_ehci_inithw (void)
+{
+ grub_pci_iterate (grub_ehci_pci_iter);
+}
+
+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_ehci_virt2phys ((void *) &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_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_USB_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)
+ {
+ /* 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");
+ }
+
+ return GRUB_USB_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
+};
+
+GRUB_MOD_INIT (ehci)
+{
+ COMPILE_TIME_ASSERT (sizeof (struct grub_ehci_td) == 64);
+ COMPILE_TIME_ASSERT (sizeof (struct grub_ehci_qh) == 96);
+ grub_ehci_inithw ();
+ grub_usb_controller_dev_register (&usb_controller);
+ 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/usbhub.c b/grub-core/bus/usb/usbhub.c
index b59f2f51d..f18aee95f 100644
--- a/grub-core/bus/usb/usbhub.c
+++ b/grub-core/bus/usb/usbhub.c
@@ -44,7 +44,9 @@ static struct grub_usb_hub *hubs;
/* 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)
+grub_usb_hub_add_dev (grub_usb_controller_t controller,
+ grub_usb_speed_t speed,
+ int port, int hubaddr)
{
grub_usb_device_t dev;
int i;
@@ -56,6 +58,8 @@ grub_usb_hub_add_dev (grub_usb_controller_t controller, grub_usb_speed_t speed)
dev->controller = *controller;
dev->speed = speed;
+ dev->port = port;
+ dev->hubaddr = hubaddr;
err = grub_usb_device_initialize (dev);
if (err)
@@ -97,6 +101,11 @@ grub_usb_hub_add_dev (grub_usb_controller_t controller, grub_usb_speed_t speed)
dev->initialized = 1;
grub_usb_devs[i] = dev;
+ grub_dprintf ("usb", "Added new usb device: %08x, addr=%d\n",
+ (grub_uint32_t)dev, i);
+ grub_dprintf ("usb", "speed=%d, port=%d, hubaddr=%d\n",
+ speed, port, hubaddr);
+
/* Wait "recovery interval", spec. says 2ms */
grub_millisleep (2);
@@ -218,7 +227,7 @@ attach_root_port (struct grub_usb_hub *hub, int portno,
grub_millisleep (10);
/* Enable the port and create a device. */
- dev = grub_usb_hub_add_dev (hub->controller, speed);
+ dev = grub_usb_hub_add_dev (hub->controller, speed, portno, 0);
hub->controller->dev->pending_reset = 0;
if (! dev)
return;
@@ -353,7 +362,7 @@ poll_nonroot_hub (grub_usb_device_t dev)
0, i, sizeof (status), (char *) &status);
grub_dprintf ("usb", "dev = %p, i = %d, status = %08x\n",
- dev, i, status);
+ dev, i, status);
if (err)
continue;
@@ -472,7 +481,7 @@ poll_nonroot_hub (grub_usb_device_t dev)
grub_millisleep (10);
/* Add the device and assign a device address to it. */
- next_dev = grub_usb_hub_add_dev (&dev->controller, speed);
+ next_dev = grub_usb_hub_add_dev (&dev->controller, speed, i, dev->addr);
dev->controller.dev->pending_reset = 0;
if (! next_dev)
continue;
diff --git a/include/grub/usb.h b/include/grub/usb.h
index ee133dbf5..522cc0c9c 100644
--- a/include/grub/usb.h
+++ b/include/grub/usb.h
@@ -198,6 +198,11 @@ struct grub_usb_device
grub_uint32_t statuschange;
struct grub_usb_desc_endp *hub_endpoint;
+
+ /* EHCI Split Transfer information */
+ int port;
+
+ int hubaddr;
};