All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH] EHCI driver - USB 2.0 support
@ 2011-06-25 19:13 Aleš Nesrsta
  2011-06-25 19:51 ` Szymon Janc
  2011-06-25 20:27 ` [PATCH] EHCI driver - USB 2.0 support Vladimir 'φ-coder/phcoder' Serbinenko
  0 siblings, 2 replies; 15+ messages in thread
From: Aleš Nesrsta @ 2011-06-25 19:13 UTC (permalink / raw)
  To: The development of GNU GRUB

[-- Attachment #1: Type: text/plain, Size: 1599 bytes --]

Hi,

because I still see no EHCI driver in GRUB for long time, I slowly
prepared myself something what looks to be working...
EHCI driver code is based on UHCI (OHCI) GRUB driver, no other source
code was used (copied).

There are included two files:
ehci.c - The driver itself. It should be located on the same place as
uhci.c, ohci.c etc., i.e. in grub-core/bus/usb.
usb_ehci_110625_0 - Patch which includes changes in some other existing
files in GRUB. Patch was made against today's GRUB trunk revision 3350.

Limitations - see ehci.c.

Known issue:
USB keyboard (and probably any low speed HID device) is not working when
connected via USB 2.0 hub. I don't know why - control transfers are OK
but bulk transfer halts with STALL. Maybe USB 2.0 hub strictly
distinguishes between bulk / interrupt transfers when Split Transaction
is used. (?)

I shortly tested main functions:
- high speed device connected directly to EHCI port - working, OK
- low/full speed device connected directly to EHCI port - not working
but it is OK (it cannot work according to specification)
- high speed device connected via USB 2.0 hub - working, OK
- full speed device connected via USB 2.0 hub - working, OK
- low speed device connected via USB 2.0 hub - I have only keyboard and
it is not working - see known issue above.

I tried also test EHCI and UHCI drivers together - it also looks to be
working:
- full speed device connected directly to EHCI/UHCI port is working (via
UHCI driver), OK
- low speed device (keyboard) connected directly to EHCI/UHCI port is
working (via UHCI driver), OK

Best regards
Ales


[-- Attachment #2: ehci.c --]
[-- Type: text/x-csrc, Size: 62853 bytes --]

/* ehci.c - EHCI Support.  */
/*
 *  GRUB  --  GRand Unified Bootloader
 *  Copyright (C) 2008  Free Software Foundation, Inc.
 *
 *  GRUB is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  GRUB is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with GRUB.  If not, see <http://www.gnu.org/licenses/>.
 */

#include <grub/dl.h>
#include <grub/mm.h>
#include <grub/usb.h>
#include <grub/usbtrans.h>
#include <grub/misc.h>
#include <grub/pci.h>
#include <grub/cpu/pci.h>
#include <grub/cpu/io.h>
#include <grub/time.h>
#include <grub/loader.h>

GRUB_MOD_LICENSE ("GPLv3+");

/* This simple GRUB implementation of EHCI driver:
 *      - assumes 32 bits architecture, no IRQ
 *      - is not supporting isochronous transfers (iTD, siTD)
 *      - is not supporting scheduled interrupt transfers
 *      XXX: fix it for 64 bits 
 *      XXX: fix it for different virtual/physical memory pointers
 *           (like it is done in OHCI driver)
 */

#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)

#define GRUB_EHCI_OS_OWNED_OFF  3

/* 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)

#define GRUB_EHCI_PORT_READ(e, port) \
  grub_ehci_oper_read32 ((e), GRUB_EHCI_PORT_STAT_CMD + (port)*4)
  
#define GRUB_EHCI_PORT_RESBITS(e, port, 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)); }

#define GRUB_EHCI_PORT_SETBITS(e, port, 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)); }

/* 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 */
  grub_uint32_t *framelist; /* Currently not used */
  grub_ehci_qh_t qh; /* GRUB_EHCI_N_QH Queue Heads */
  grub_ehci_td_t td; /* GRUB_EHCI_N_TD Transfer Descriptors */
  grub_ehci_td_t tdfree; /* 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 (
    *((grub_uint32_t *)((grub_uint32_t)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 (
    *((grub_uint16_t *)((grub_uint32_t)e->iobase_ehcc + addr)));
}

static inline grub_uint8_t
grub_ehci_ehcc_read8 (struct grub_ehci *e, grub_uint32_t addr)
{
  return *((grub_uint8_t *)((grub_uint32_t)e->iobase_ehcc + addr));
}

/* Operational registers access functions */
static inline grub_uint32_t
grub_ehci_oper_read32 (struct grub_ehci *e, grub_uint32_t addr)
{
  return grub_le_to_cpu32 (
    *((grub_uint32_t *)((grub_uint32_t)e->iobase + addr)));
}

static inline void
grub_ehci_oper_write32 (struct grub_ehci *e, grub_uint32_t addr,
                                             grub_uint32_t value)
{
  *((grub_uint32_t *)((grub_uint32_t)e->iobase + addr)) =
    grub_cpu_to_le32 (value);
}


/* 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 not 32bit address type - this driver does not currently
   * work with 64bit - maybe later... */
  /* XXX: Is following test for 64-bit address correct ? */ 
  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: 64-bit addressing 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->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 = grub_memalign (4096, 4096);
  if (! e->framelist)
    goto fail;
  /* XXX: The currently allowed EHCI pointers are only 32 bits,
   * make sure this code works on on 64 bits architectures.  */
#if GRUB_CPU_SIZEOF_VOID_P == 8
  if ((grub_uint64_t) e->framelist >> 32)
    {
      grub_error (GRUB_ERR_OUT_OF_MEMORY,
		  "EHCI grub_ehci_pci_iter: allocated frame list memory not <4GB");
      goto fail;
    }
#endif
  grub_memset((void*)e->framelist, 0, 4096); 

grub_dprintf ("ehci", "EHCI grub_ehci_pci_iter: framelist mem=0x%08x. OK\n",
              (grub_uint32_t)e->framelist);

  /* Allocate memory for the QHs and register it in "e".  */
  e->qh = (grub_ehci_qh_t) grub_memalign (4096, sizeof(struct grub_ehci_qh)*GRUB_EHCI_N_QH);
  if (! e->qh)
    goto fail;
  /* XXX: The currently allowed EHCI pointers are only 32 bits,
   * make sure this code works on on 64 bits architectures.  */
#if GRUB_CPU_SIZEOF_VOID_P == 8
  if ((grub_uint64_t) e->qh >> 32)
    {
      grub_error (GRUB_ERR_OUT_OF_MEMORY, "EHCI grub_ehci_pci_iter: allocated QH memory not <4GB");
      goto fail;
    }
#endif
  grub_memset((void*)e->qh, 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);

  /* Allocate memory for the TDs and register it in "e".  */
  e->td = (grub_ehci_td_t) grub_memalign (4096, sizeof(struct grub_ehci_td)*GRUB_EHCI_N_TD);
  if (! e->td)
    goto fail;
  /* XXX: The currently allowed EHCI pointers are only 32 bits,
   * make sure this code works on on 64 bits architectures.  */
#if GRUB_CPU_SIZEOF_VOID_P == 8
  if ((grub_uint64_t) e->td >> 32)
    {
      grub_error (GRUB_ERR_OUT_OF_MEMORY, "EHCI grub_ehci_pci_iter: allocated TD memory not <4GB");
      goto fail;
    }
#endif
  grub_memset((void*)e->td, 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);

  /* 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 (
         ((grub_uint32_t)e->qh & GRUB_EHCI_POINTER_MASK)
         | GRUB_EHCI_HPTR_TYPE_QH);
  for (i = 0; i < GRUB_EHCI_N_FRAMELIST; i++)
    e->framelist[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[i].link_td = &e->td[i+1];
      e->td[i].next_td = grub_cpu_to_le32 (GRUB_EHCI_TERMINATE);
      e->td[i].alt_next_td = grub_cpu_to_le32 (GRUB_EHCI_TERMINATE);
    }
  e->td[GRUB_EHCI_N_TD-1].next_td =
    grub_cpu_to_le32 (GRUB_EHCI_TERMINATE);
  e->td[GRUB_EHCI_N_TD-1].alt_next_td =
    grub_cpu_to_le32 (GRUB_EHCI_TERMINATE);
  e->tdfree = e->td;
  /* Set Terminate in first QH, which is used in framelist */
  e->qh[0].qh_hptr = grub_cpu_to_le32 (GRUB_EHCI_TERMINATE);
  e->qh[0].td_overlay.next_td = grub_cpu_to_le32 (GRUB_EHCI_TERMINATE);
  e->qh[0].td_overlay.alt_next_td =
    grub_cpu_to_le32 (GRUB_EHCI_TERMINATE);
  /* Also set Halted bit in token */
  e->qh[0].td_overlay.token =
    grub_cpu_to_le32 (GRUB_EHCI_STATUS_HALTED);
  /* Set the H bit in first QH used for AL */
  e->qh[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[i].qh_hptr =
        grub_cpu_to_le32 (
          (((grub_uint32_t)&(e->qh[i])) & GRUB_EHCI_POINTER_MASK)
          | GRUB_EHCI_HPTR_TYPE_QH);
      e->qh[i].td_overlay.next_td =
        grub_cpu_to_le32 (GRUB_EHCI_TERMINATE);
      e->qh[i].td_overlay.alt_next_td =
        grub_cpu_to_le32 (GRUB_EHCI_TERMINATE);
      /* Also set Halted bit in token */
      e->qh[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. */
  /* XXX: Really should we handle it ?
   * Is BIOS code active when GRUB is loaded and can BIOS properly
   * "assist" in change of EHCI 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 */
          /* XXX: Is PCI address for grub_pci_write_byte() correct ? */
          grub_pci_write_byte (e->pcibase_eecp + GRUB_EHCI_OS_OWNED_OFF, 1);
          /* 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_error (GRUB_ERR_TIMEOUT, "EHCI grub_ehci_pci_iter:EHCI change ownership timeout");
              goto fail;
            }
        }
      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_byte (e->pcibase_eecp + GRUB_EHCI_OS_OWNED_OFF, 1);
          /* 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,
    (grub_uint32_t)e->framelist);
  grub_ehci_oper_write32 (e, GRUB_EHCI_CUR_AL_ADDR,
    (grub_uint32_t)&e->qh[1]); /* qh[0] if referenced by framelist! */

  /* 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)
    {
      grub_free ((void *) e->td);
      grub_free ((void *) e->qh);
      grub_free (e->framelist);
    }
  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;

  /* 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_uint32_t)&qh[1]);
  /* 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_uint32_t)&qh[i]);

  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)
    {
      grub_dprintf ("ehci", "alloc_td: end - no free TD\n");
      return NULL;
    }

  ret = e->tdfree; /* Take current free TD */
  e->tdfree = (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; /* Chain new free TD & rest */
  e->tdfree = 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(td_alt);
  /* 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;
  grub_ehci_td_t td_first;
  grub_ehci_td_t td_alt;
  grub_ehci_td_t td_last;
};

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 = NULL;

  /* Allocate a queue head for the transfer queue.  */
  cdata->qh = grub_ehci_find_qh (e, transfer);
  if (! cdata->qh)
    {
      grub_free (cdata);
      return GRUB_USB_ERR_INTERNAL;
    }

  /* To detect short packet we need some additional "alternate" TD,
   * allocate it first. */
  cdata->td_alt = grub_ehci_alloc_td (e);
  if (! cdata->td_alt)
    {
      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, 0, sizeof(struct grub_ehci_td) ); 
  cdata->td_alt->next_td = grub_cpu_to_le32 (GRUB_EHCI_TERMINATE);
  cdata->td_alt->alt_next_td = grub_cpu_to_le32 (GRUB_EHCI_TERMINATE);
  cdata->td_alt->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);

      if (! td) /* de-allocate and free all */
	      {
      	  grub_size_t actual = 0;

	        if (cdata->td_first)
	          grub_ehci_free_tds (e, cdata->td_first, NULL, &actual);

	        grub_free (cdata);
	        return GRUB_USB_ERR_INTERNAL;
	      }

      /* Register new TD in cdata or previous TD */
      if (! cdata->td_first)
	      cdata->td_first = td;
      else
	      {
	        td_prev->link_td = td;
	        td_prev->next_td = grub_cpu_to_le32 ((grub_uint32_t) td);
	      }
      td_prev = td;
    }

  /* Remember last TD */
  cdata->td_last = td;
  /* Last TD should not have set alternate TD */
  cdata->td_last->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);
  grub_dprintf ("ehci", "setup_transfer: td_first=%08x, td_alt=%08x\n",
    (grub_uint32_t)cdata->td_first, (grub_uint32_t)cdata->td_alt);
  grub_dprintf ("ehci", "setup_transfer: td_last=%08x\n",
    (grub_uint32_t)cdata->td_last);
    
  /* Start transfer: */
  /* Unlink possible alternate pointer in QH */
  cdata->qh->td_overlay.alt_next_td = grub_cpu_to_le32(GRUB_EHCI_TERMINATE);
  /* Link new TDs with QH via next_td */
  cdata->qh->td_overlay.next_td = grub_cpu_to_le32((grub_uint32_t)cdata->td_first);
  /* Reset Active and Halted bits in QH to activate Advance Queue,
   * i.e. reset token */
  cdata->qh->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->td_overlay.token = (cdata->qh->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, transfer, actual);
  grub_ehci_free_td (e, cdata->td_alt);
  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->td_overlay.token);

  /* Do things like in normal finish */
  grub_ehci_pre_finish_transfer (transfer);
  grub_ehci_free_tds (e, cdata->td_first, transfer, actual);
  grub_ehci_free_td (e, cdata->td_alt);
  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, transfer, actual);
  grub_ehci_free_td (e, cdata->td_alt);
  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);
  grub_dprintf ("ehci", "check_transfer: qh_hptr=%08x, ep_char=%08x\n",
    grub_le_to_cpu32(cdata->qh->qh_hptr),
    grub_le_to_cpu32(cdata->qh->ep_char));
  grub_dprintf ("ehci", "check_transfer: ep_cap=%08x, td_current=%08x\n",
    grub_le_to_cpu32(cdata->qh->ep_cap),
    grub_le_to_cpu32(cdata->qh->td_current));
  grub_dprintf ("ehci", "check_transfer: next_td=%08x, alt_next_td=%08x\n",
    grub_le_to_cpu32(cdata->qh->td_overlay.next_td),
    grub_le_to_cpu32(cdata->qh->td_overlay.alt_next_td));
  grub_dprintf ("ehci", "check_transfer: token=%08x, buffer[0]=%08x\n",
    grub_le_to_cpu32(cdata->qh->td_overlay.token),
    grub_le_to_cpu32(cdata->qh->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->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->td_overlay.next_td)
        & GRUB_EHCI_TERMINATE) &&
        ((grub_le_to_cpu32(cdata->qh->td_current)
          & GRUB_EHCI_QHTDPTR_MASK) == (grub_uint32_t)cdata->td_last))
        /* 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, transfer, &actual);
      grub_ehci_free_td (e, cdata->td_alt);
      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 - cdata->qh)) / sizeof(struct grub_ehci_qh);
  /* Unlink QH from AL */
  e->qh[i-1].qh_hptr = cdata->qh->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 did 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, transfer, &actual);
  grub_ehci_free_td (e, cdata->td_alt);

  /* Finaly we should return QH back to the AL... */  
  e->qh[i-1].qh_hptr = grub_cpu_to_le32((grub_uint32_t)cdata->qh);
  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. But 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,
        (grub_uint32_t)e->framelist);
      grub_ehci_oper_write32 (e, GRUB_EHCI_CUR_AL_ADDR,
        (grub_uint32_t)&e->qh[1]); /* qh[0] if referenced by framelist! */
      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);
}

[-- Attachment #3: usb_ehci_110625_0 --]
[-- Type: text/x-patch, Size: 3166 bytes --]

diff -purB ./grub/grub-core/bus/usb/usbhub.c ./grub_patched/grub-core/bus/usb/usbhub.c
--- ./grub/grub-core/bus/usb/usbhub.c	2011-06-25 19:48:11.000000000 +0200
+++ ./grub_patched/grub-core/bus/usb/usbhub.c	2011-06-25 20:30:56.000000000 +0200
@@ -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_controlle
 
   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_controlle
   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 *h
   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 -purB ./grub/grub-core/Makefile.core.def ./grub_patched/grub-core/Makefile.core.def
--- ./grub/grub-core/Makefile.core.def	2011-06-25 19:48:11.000000000 +0200
+++ ./grub_patched/grub-core/Makefile.core.def	2011-06-25 20:21:14.000000000 +0200
@@ -435,6 +435,12 @@ module = {
 };
 
 module = {
+  name = ehci;
+  common = bus/usb/ehci.c;
+  enable = pci;
+};
+
+module = {
   name = pci;
   noemu = bus/pci.c;
   emu = bus/emu/pci.c;
diff -purB ./grub/include/grub/usb.h ./grub_patched/include/grub/usb.h
--- ./grub/include/grub/usb.h	2011-05-17 09:11:27.000000000 +0200
+++ ./grub_patched/include/grub/usb.h	2011-06-25 20:32:53.000000000 +0200
@@ -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

^ permalink raw reply	[flat|nested] 15+ messages in thread

end of thread, other threads:[~2011-11-04 20:56 UTC | newest]

Thread overview: 15+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2011-06-25 19:13 [PATCH] EHCI driver - USB 2.0 support Aleš Nesrsta
2011-06-25 19:51 ` Szymon Janc
2011-06-26 20:37   ` Aleš Nesrsta
2011-07-21 21:59     ` Vladimir 'φ-coder/phcoder' Serbinenko
2011-08-27 16:42       ` Aleš Nesrsta
2011-09-30 15:37         ` Vladimir 'φ-coder/phcoder' Serbinenko
2011-10-01  8:15           ` Aleš Nesrsta
2011-10-01 18:05             ` Vladimir 'φ-coder/phcoder' Serbinenko
2011-10-01 20:04               ` Vladimir 'φ-coder/phcoder' Serbinenko
2011-10-01 22:03               ` [PATCH] EHCI driver - USB 2.0 support - alignment Aleš Nesrsta
2011-10-01 19:01             ` [PATCH] EHCI driver - USB 2.0 support - addendum Aleš Nesrsta
2011-11-03  9:25         ` [PATCH] EHCI driver - USB 2.0 support Philipp Hahn
2011-11-04 20:56           ` Aleš Nesrsta
     [not found]     ` <4E40A417.6000309@163.com>
     [not found]       ` <1312926878.2943.128.camel@pracovna>
2011-08-19  2:58         ` [Resolved] Grub2 can not detect usb disk Cui Lei
2011-06-25 20:27 ` [PATCH] EHCI driver - USB 2.0 support Vladimir 'φ-coder/phcoder' Serbinenko

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.