]> git.ipfire.org Git - thirdparty/grub.git/commitdiff
EHCI implementation by Aleš Nesrsta.
authorAleš Nesrsta <starous@volny.cz>
Sat, 1 Oct 2011 18:18:47 +0000 (20:18 +0200)
committerVladimir 'phcoder' Serbinenko <phcoder@gmail.com>
Sat, 1 Oct 2011 18:18:47 +0000 (20:18 +0200)
grub-core/Makefile.core.def
grub-core/bus/usb/ehci.c [new file with mode: 0644]
grub-core/bus/usb/usbhub.c
include/grub/usb.h

index e9e4f06d81ad39bb28d6ede88e7e3f4a41641a4b..346ec24ee70065b5ed6963e56c2c6818fe1a3d67 100644 (file)
@@ -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 (file)
index 0000000..12b5465
--- /dev/null
@@ -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 <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/loader.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
+ */
+
+#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);
+}
index b59f2f51d75533513cf041406559f30f10bf3148..f18aee95fc5370af41738624e5187b9bae552914 100644 (file)
@@ -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;
index ee133dbf51926bdd5d3e0a260cf478ef83008a96..522cc0c9c011b24304921c90d42e6582fa317da9 100644 (file)
@@ -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;
 };
 
 \f