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

* Re: [PATCH] EHCI driver - USB 2.0 support
  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-06-25 20:27 ` [PATCH] EHCI driver - USB 2.0 support Vladimir 'φ-coder/phcoder' Serbinenko
  1 sibling, 1 reply; 15+ messages in thread
From: Szymon Janc @ 2011-06-25 19:51 UTC (permalink / raw)
  To: grub-devel; +Cc: Aleš Nesrsta

On Saturday 25 June 2011 21:13:19 Aleš Nesrsta wrote:
> Hi,

Hi Aleš,
 
> 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).

This is a really cool news :)

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

In such case EHCI should handover port to a companion 1.1 controller.
(see section 4.2 of EHCI specification)

-- 
Szymon K. Janc
szymon@janc.net.pl // GG: 1383435



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

* Re: [PATCH] EHCI driver - USB 2.0 support
  2011-06-25 19:13 [PATCH] EHCI driver - USB 2.0 support Aleš Nesrsta
  2011-06-25 19:51 ` Szymon Janc
@ 2011-06-25 20:27 ` Vladimir 'φ-coder/phcoder' Serbinenko
  1 sibling, 0 replies; 15+ messages in thread
From: Vladimir 'φ-coder/phcoder' Serbinenko @ 2011-06-25 20:27 UTC (permalink / raw)
  To: The development of GNU GRUB

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

On 25.06.2011 21:13, Aleš Nesrsta wrote:
> 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).
Very good.

> 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)
Ir must be rerouted to companion. Some controllers (like in my thinkpad)
have no companion. I haven't played with internals to see how it's done.
> /* ehci.c - EHCI Support. */
> /*
>  *  GRUB  --  GRand Unified Bootloader
>  *  Copyright (C) 2008  Free Software Foundation, Inc.
Add 2011 here.
> GRUB_MOD_LICENSE ("GPLv3+");
>
> /* This simple GRUB implementation of EHCI driver:
>  *      - assumes 32 bits architecture, no IRQ
How exactly do you use this?
> #define GRUB_EHCI_SPARAMS_PPC     (1<<4) /* Power port control */
Please run indent on the file.
> #define GRUB_EHCI_PORT_READ(e, port) \
>   grub_ehci_oper_read32 ((e), GRUB_EHCI_PORT_STAT_CMD + (port)*4)
>   
Why not make it an inline static function?
> #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)); }
>
ditto
> #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)); }
>
ditto
> /* 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));
>
packed isn't necessary here
> /* 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));
Same here
> /* 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)));
> }
Convert to char * in order to do arithmetics, not grub_uint32_t. Also
you need to use volatile attribute in last conversion. Same for
following functions
> /* 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... */
No need to specifically exclude those. Just zero-pad address.
>   /* 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));
>
e->iobase should have volatile attribute
>   /* 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.  */
That's why you have to use dmaalign32
>   /* 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);
Arithmetics with PCI address aren't guaranteed to be available or to
behave in a sane way.
>           /* 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");
In this case you have to take the ownership the hard way. You clean
GRUB_EHCI_BIOS_OWNED yourself and disable all SMM interrupts (next EECP
field)

-- 
Regards
Vladimir 'φ-coder/phcoder' Serbinenko



[-- Attachment #2: OpenPGP digital signature --]
[-- Type: application/pgp-signature, Size: 294 bytes --]

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

* Re: [PATCH] EHCI driver - USB 2.0 support
  2011-06-25 19:51 ` Szymon Janc
@ 2011-06-26 20:37   ` Aleš Nesrsta
  2011-07-21 21:59     ` Vladimir 'φ-coder/phcoder' Serbinenko
       [not found]     ` <4E40A417.6000309@163.com>
  0 siblings, 2 replies; 15+ messages in thread
From: Aleš Nesrsta @ 2011-06-26 20:37 UTC (permalink / raw)
  To: The development of GNU GRUB

Hi,

To Szymon (& Vladimir too...):
...
> In such case EHCI should handover port to a companion 1.1 controller.
> (see section 4.2 of EHCI specification)

Yes, driver does it in such way - but I tested it first alone, without
any other USB driver, and in such case companion controllers of course
did not work. I wrote it maybe little bit not clearly in my e-mail...

I did two kind of tests:
1. Loaded only EHCI driver (paragraph beginning with "I shortly tested
main functions..."
2. Loaded EHCI driver and UHCI driver (for companion controllers;
paragraph beginning with "I tried also test EHCI and UHCI drivers
together..."

As You can see, in second kind of test is full/low speed working even if
it is connected to root port (EHCI/UHCI shared).

The only one thing, which is not working, is low speed HID device
connected via USB 2.0 hub (because in both cases the USB 2.0 hub is
handled  by EHCI). As I wrote, I suspect some bulk->interrupt
transaction conflict inside USB 2.0 hub when it is done via Split
Transaction from EHCI. I did not study USB 2.0 and EHCI specifications
too deeply to be sure...

There could be little bit another but related possible issue:
Driver is currently very simple and it does not test how many ports of
companion controllers are used and if some of them is free or not -> I
don't know what can happen in case when there is no free port of
companion controller - I cannot test it on my HW... Vladimir wrote it
has HW which has no companion controllers, so he can test it...


To Vladimir:

Ufff, You sent a lot of comments... :-)
Note, it is first working/tested version... My main problem is the time,
so I wrote it piece by piece through approx. half of year, often I
forgot what I did before... - so the code looks like it looks...

I will rewrite some part according to Your comments but don't expect it
too early...


Answers/questions to most of Your comments:

... macro->inline func. - I like macros... :-) But You are right.

... run indent - Why not. But what options of indent are preferred for
GRUB source ? I can set in the same way also my favorite editor (Geany)
to prevent necessity of such source code additional processing.

... __attribute__((packed)) - I remember, we probably discussed the same
thing on OHCI/UHCI in past. In these drivers are such attributes still
used for HW structures. I think it is more safe - we probably cannot
assume that compiler will align structure members to 32 bits.

... only 32 bits - The driver is very simple, similarly as UHCI driver.
EHCI can support memory higher than 4G but I currently don't care about
it, I fill all related registers with zero... I cannot test it (my
computer is little bit older and cannot use more than 4GB RAM...) and,
generally, I don't have experiences with 64 bits...
Similarly, to be simple, driver currently does not use DMA allocations
(as UHCI driver) - there is too much work with it... :-) - some thing
should be done little bit different because in GRUB is missing some
function which can convert DMA address to virtual address and vice
versa. I made some helping functions in OHCI driver but I am not sure if
these functions are safe because of possible not continuous DMA memory -
or does the function grub_memalign_dma32 ensure that whole allocated
memory is continuous in both DMA and virtual spaces in every case ? (It
is maybe stupid question but I don't want to study whole GRUB code
related to memory management, additionally, I don't know details about
memory management of non-x86 architectures...).

Under term "full 64-bit compatible" I understand that any register,
QH/TD structure and any buffer data can be above 4G - maybe it is wrong
from my side.
So, to be fully 64-bit compatible, if memory for framelist, QH and TD is
allocated above 4G, CTRLDSSEGMENT register should be properly set - and,
additionally, framelist, QH and TD should be in the same 4G memory area
and should not cross any 4G boundary.
Of course, CTRLDSSEGMENT register is set to zero in current driver
version and similarly also higher dwords of buffer address are set to
zero inside QH/TD structures - but it is also 32-bit simplification
because it assumes that memory of transfer buffer is below 4G in every
case.

... "No need to specifically exclude those. Just zero-pad address." -
Hm, I think it could be not so easy because (base_h !=0) means that EHCI
I/O memory registers are mapped above 4G... Maybe stupid question - does
it work access to mapped I/O in this case properly in GRUB ?

... "e->iobase should have volatile attribute" - I have related but
probably stupid question - why does work evaluation of QH/TD values in
runtime properly even if related variables are not declared as
"volatile" ? (It is the same in UHCI/OHCI drivers.)
AFAIK "volatile" should be used for variables which can be modified
"outside" of compiler code, i.e. by HW/DMA. So, I cannot understand why
for example e->iobase should be "volatile" (even if this variable cannot
be changed by HW/DMA - but, of course, values of memory area related to
this pointer can be changed by HW - maybe this is the reason (?)) and
structures QH/TD need not to be "volatile" even if their values can be
changed by HW/DMA... ???

... "Arithmetics with PCI address aren't guaranteed to be available or
to behave in a sane way." - Hm, yes, I change it to usual construction -
read whole DWORD, change only one bit and write it back.
I will also add the "hard way" of ownership as You propose.

Best regards
Ales



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

* Re: [PATCH] EHCI driver - USB 2.0 support
  2011-06-26 20:37   ` Aleš Nesrsta
@ 2011-07-21 21:59     ` Vladimir 'φ-coder/phcoder' Serbinenko
  2011-08-27 16:42       ` Aleš Nesrsta
       [not found]     ` <4E40A417.6000309@163.com>
  1 sibling, 1 reply; 15+ messages in thread
From: Vladimir 'φ-coder/phcoder' Serbinenko @ 2011-07-21 21:59 UTC (permalink / raw)
  To: grub-devel

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


> To Vladimir:
>
> Ufff, You sent a lot of comments... :-)
Don't take it as not appreciation of your work. I appreciate it and if
you have no time to do the requested fixes I can help you. It's very
important patch.
> Note, it is first working/tested version... My main problem is the time,
> so I wrote it piece by piece through approx. half of year, often I
> forgot what I did before... - so the code looks like it looks...
I understand.
> I will rewrite some part according to Your comments but don't expect it
> too early...
>
>
> Answers/questions to most of Your comments:
>
> ... macro->inline func. - I like macros... :-) But You are right.
>
Macros are a good way to hit quirks and unless really necessary are to
be avoided.
> ... run indent - Why not. But what options of indent are preferred for
> GRUB source ? I can set in the same way also my favorite editor (Geany)
> to prevent necessity of such source code additional processing.
>
No options, just defaults.
> ... __attribute__((packed)) - I remember, we probably discussed the same
> thing on OHCI/UHCI in past. In these drivers are such attributes still
> used for HW structures. I think it is more safe - we probably cannot
> assume that compiler will align structure members to 32 bits.
You already ensure that the structures are aligned at 16 bytes which is
more than 4 bytes.
> ... only 32 bits - The driver is very simple, similarly as UHCI driver.
> EHCI can support memory higher than 4G but I currently don't care about
> it, I fill all related registers with zero... I cannot test it (my
> computer is little bit older and cannot use more than 4GB RAM...) and,
> generally, I don't have experiences with 64 bits...
Even on 64-bit machines it's fine and recommended to keep all structures
in first 4G. We currently do so for AHCI
> Similarly, to be simple, driver currently does not use DMA allocations
> (as UHCI driver) - there is too much work with it... :-) - some thing
> should be done little bit different because in GRUB is missing some
> function which can convert DMA address to virtual address and vice
> versa.
P2V wouldn't be single-valued. We have grub_vtop but the DMA address to
write into registers is different from the physical address on Yeeloong
so DMA needs separate function to retrieve this value.
>  I made some helping functions in OHCI driver but I am not sure if
> these functions are safe because of possible not continuous DMA memory -
> or does the function grub_memalign_dma32 ensure that whole allocated
> memory is continuous in both DMA and virtual spaces in every case ?
It does.
> Under term "full 64-bit compatible" I understand that any register,
> QH/TD structure and any buffer data can be above 4G - maybe it is wrong
> from my side.
There is no need. We can just stick to use first 4G of memory to stay on
the safe side. For GRUB 3G of RAM is much more than we need.
> ... "No need to specifically exclude those. Just zero-pad address." -
> Hm, I think it could be not so easy because (base_h !=0) means that EHCI
> I/O memory registers are mapped above 4G... Maybe stupid question - does
> it work access to mapped I/O in this case properly in GRUB ?
>
It doesn't. on i386-pc GRUB is limited to first 4G
> ... "e->iobase should have volatile attribute" - I have related but
> probably stupid question - why does work evaluation of QH/TD values in
> runtime properly even if related variables are not declared as
> "volatile" ? (It is the same in UHCI/OHCI drivers.)
> AFAIK "volatile" should be used for variables which can be modified
> "outside" of compiler code, i.e. by HW/DMA. So, I cannot understand why
> for example e->iobase should be "volatile" (even if this variable cannot
> be changed by HW/DMA - but, of course, values of memory area related to
> this pointer can be changed by HW - maybe this is the reason (?)) and
> structures QH/TD need not to be "volatile" even if their values can be
> changed by HW/DMA... ???
But its a pointer to an area which has this property, hence it needs
volatile attribute.
> ... "Arithmetics with PCI address aren't guaranteed to be available or
> to behave in a sane way." - Hm, yes, I change it to usual construction -
> read whole DWORD, change only one bit and write it back.
Read bytes is both correct and fine. Trouble is incrementing an address
like e.g. addr = ...; addr++;
> I will also add the "hard way" of ownership as You propose.
>
I have a laptop we can test it on.
> Best regards
> Ales
>
>
> _______________________________________________
> Grub-devel mailing list
> Grub-devel@gnu.org
> https://lists.gnu.org/mailman/listinfo/grub-devel
>


-- 
Regards
Vladimir 'φ-coder/phcoder' Serbinenko



[-- Attachment #2: OpenPGP digital signature --]
[-- Type: application/pgp-signature, Size: 294 bytes --]

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

* [Resolved] Grub2 can not detect usb disk
       [not found]       ` <1312926878.2943.128.camel@pracovna>
@ 2011-08-19  2:58         ` Cui Lei
  0 siblings, 0 replies; 15+ messages in thread
From: Cui Lei @ 2011-08-19  2:58 UTC (permalink / raw)
  To: Aleš Nesrsta; +Cc: The development of GNU GRUB

Thank you for your help, very much! ^_^
This problem have been resolved and I can usb the usb_keyborard under 
grub-shell and I can boot ubuntu11.04 from usb disk.
My mainboard is via 8595a, the usb controller is uhci.
I resolved it by add these code in the grub-core/bus/usb/uhci.c:

(1)
182   /*Set bus master*/
183   addr = grub_pci_make_address (dev, GRUB_PCI_REG_COMMAND);
184   grub_uint16_t val = grub_pci_read_word(addr);
185   val = (val & ~0) | GRUB_PCI_COMMAND_BUS_MASTER;
186   grub_pci_write_word(addr, val);

(2)
203   // Reset PIRQ and SMI
204   addr = grub_pci_make_address (dev, 0xC0);       
//USBLEGSUP               0xc0
205   grub_pci_write_word(addr, 0x8f00);      //USBLEGSUP_RWC       
0x8f00  /* the R/WC bits */
206   // Reset the HC
207   grub_uhci_writereg16(u, GRUB_UHCI_REG_USBCMD, 0x0002); 
//USBCMD_HCRESET  0x0002
208   grub_millisleep(5);
209   // Disable interrupts and commands (just to be safe).
210   grub_outw (0, u->iobase + 4);       //USBINTR  4  /*Interrupt 
enable register*/
211   grub_uhci_writereg16 (u, GRUB_UHCI_REG_USBCMD, 0);

I don't know whether it is useful to the other one, but may be a reference.

BRs,

Rock.

> Hi,
>
> I am afraid, I maybe will not help You too much but I try it:
>
> I shortly looked into ML to Your posts. As I saw short part of debug
> output in one of Your e-mail, GRUB freezes when it wants to get device
> descriptor - more precisely, when it requests first 8 bytes of device
> descriptor. It is the first thing which is done after address is
> assigned to the device.
>
> So, it looks like device does not set address properly (even if control
> message Set Address returns success) or happened something else what
> prevent device to respond (but I don't know what...).
>
> For the first try You can increase related delays in usbhub.c:
>
> ...
>    /* Wait "recovery interval", spec. says 2ms */
>    grub_millisleep (2);<<<<---- HERE (try 4ms or more)
>
>    grub_usb_device_attach (dev);
> ...
>
> ...
>    /* Enable the port.  */
>    err = hub->controller->dev->portstatus (hub->controller, portno, 1);
>    if (err)
>      return;
>    hub->controller->dev->pending_reset = grub_get_time_ms () + 5000;
>
>    grub_millisleep (10);<<<<---- maybe here also
>
>    /* Enable the port and create a device.  */
>    dev = grub_usb_hub_add_dev (hub->controller, speed, portno, 0);
>    hub->controller->dev->pending_reset = 0;
>    if (! dev)
>      return;
> ...
>
> If this will not help You, I currently have no other idea what could be
> the reason of timeout.
> I think You don't need EHCI because it looks like Set Address control
> message works (at least it does not return error), i.e. You probably
> have OHCI or UHCI USB (companion) controller on computer and Your device
> is working at full or low speed with Your USB controller.
>
> By the way, for the first look into ML I did not find which USB
> controller You have - OHCI/UHCI ? (which driver/module are You using -
> ohci/uhci?) - and which machine/architecture is the computer You are
> trying to boot with GRUB2 - ?
> I sometimes had some unidentified problems on my UHCI/EHCI controller,
> mostly with port powering - UHCI does not have power management but EHCI
> does and if EHCI is not properly initialized by BIOS (it could be Your
> case with coreboot, maybe ?) then USB ports are not properly powered.
> Another BIOS (coreboot?) issue could be improper handling of USB
> controller ownership.
>
> Do You have USB device connected directly into root port or via some USB
> hub ? Try to do it in opposite way (i.e. if You are not using the USB
> hub, try use it and connect USB device via hub - maybe it helps...)
>
> Hmmm, I remember now one issue which could be related to Your problem.
> On my very old machine with OHCI USB controller some devices are not
> working "for the first time". I am still not able to debug why it
> happened (it does not happened when full debug is active - so it looks
> like it is related to some timing). But I am afraid it will be not Your
> case because device stops working after it is recognized, configured,
> usbms module loaded and GRUB USB device usb0 created.
> But - try load ohci/uhci module when USB disk is connected and then
> disconnect and connect it again after few seconds. In my case device
> becomes working as new usb device (i.e. usb1).
>
> Additionally, lot of manufacturers does not follow USB or USBMS
> specifications, as You can read in Linux source code of USB controllers
> and USB mass storage devices and related documentation.
> Did You tried more different USB mass storage devices ?
> What is manufacturer&  type of Your USB mass storage device ?
>
> Of course, You can also try EHCI driver, it maybe can solve Your problem
> because of little bit different ports/devices handling. But EHCI driver
> is currently highly experimental, it still exists only as uncorrected
> and not accepted "patch". I have to do some improvement but I don't have
> sufficient time still, unfortunately...
> If You want try to use it, You can get my patch from ML (sent at
> 25.6.2011) and use it with related source code trunk branch revision
> (maybe also any later or current revision, because USB parts of GRUB are
> not frequently changed). Please also read about know issue and another
> limitations of the "zero version" of EHCI driver - e.g. it may not work
> if Your PC is not x86 machine or USB registers are mapped above 4GB etc.
>
> Sorry if You will wait longer time for my response in future - I don't
> check the post so often and additionally currently I am (and probably
> will be) longer time too busy - I am not regular GRUB2 contributor, I do
> something for GRUB2 USB part only time to time...
>
> BRs,
> Ales
>
>
> Cui Lei píše v Út 09. 08. 2011 v 11:05 +0800:
>> Hi Aleš,
>> I am trying to boot OS from USB  disk, I use coreboot-v4 with grub2 as
>> payload, but my usb disk can not been
>> detect. I try to use usb-keyboard, it is not working.  I know you are
>> working on the EHCI driver from Vladimir ,
>> could you give me some advices? Vladimir said it may need EHCI driver,
>> but I think the usb device should run
>> with low-speed or full-speed if no EHCI driver.C
>>
>> Looking forward to your reply.
>> BRs,
>> Rock Cui.
>>
>>
>




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

* Re: [PATCH] EHCI driver - USB 2.0 support
  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-11-03  9:25         ` [PATCH] EHCI driver - USB 2.0 support Philipp Hahn
  0 siblings, 2 replies; 15+ messages in thread
From: Aleš Nesrsta @ 2011-08-27 16:42 UTC (permalink / raw)
  To: The development of GNU GRUB

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

Hi Vladimir,

there is little bit improved EHCI driver - ehci.c.
Changes in other files are still the same (more precisely, I hope - I
didn't check if there are some other unrelated changes from anybody else
in newest trunk revision...) - usb_ehci_110625_0.

Changes were done according to Your notes:
- ... run indent ... - done
- ... macro into inline function ... - done
- ... volatile attribute ... - done
- ... pointer arithmetic via char* ... - done
- ... access to PCI register in correct way in ownership change ... -
done
- ... "hard way" of ownership change ... - done
- ... use of grub_memalign_dma32 ... - done
- (corrected year in header of file...)

Result:
- driver should run on 64-bits machines with exception if EHCI I/O
registers are mapped above 4G
- driver should run on machines with different virtual/physical
addressing
- driver should run on "big endian" machines (but also original "zero
version" EHCI patch supports big endian)

Remaining issue:
- Any HID low speed device attached via USB 2.0 hub does not work. (It
is most probably because bulk split transfers are differently handled in
comparison with interrupt split transfers. Probably only one solution is
to add interrupt transfers into EHCI driver.)

I made short test, driver looks to be working.

But there can be still mistakes in CPU/LE and VIRT/PHYS conversions - I
cannot test them on x86 machine (or at least I don't know how to do
it...).
Could You (or, of course, anybody else...) test EHCI patch on:
- some "big endian" machine ?
- some machine with different virtual/physical addressing, i.e. like
Yeloong ?


What I didn't:
- ... packed isn't necessary here ... - GCC documentation says:
"packed
    This attribute, attached to struct or union type definition,
specifies that each member of the structure or union is placed to
minimize the memory required."

I.e., it is exactly what we need - members are stored in structure
without any additional space between them. Without this attribute
compiler can align structure members in any way (depend on its defaults
and global settings etc.) - so members can be aligned e.g. to 64 bits
inside structure and in this case we have structure which does not
correspond to EHCI HW data structure.
So, I left "packed" attribute in code.

Best regards
Ales

[-- Attachment #2: 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

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

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

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

* Re: [PATCH] EHCI driver - USB 2.0 support
  2011-08-27 16:42       ` Aleš Nesrsta
@ 2011-09-30 15:37         ` Vladimir 'φ-coder/phcoder' Serbinenko
  2011-10-01  8:15           ` Aleš Nesrsta
  2011-11-03  9:25         ` [PATCH] EHCI driver - USB 2.0 support Philipp Hahn
  1 sibling, 1 reply; 15+ messages in thread
From: Vladimir 'φ-coder/phcoder' Serbinenko @ 2011-09-30 15:37 UTC (permalink / raw)
  To: The development of GNU GRUB

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

That must be a too long interval of writing e-mail. I started it a month
ago, then interrupted and now finishing it
> there is little bit improved EHCI driver - ehci.c.
> Changes in other files are still the same (more precisely, I hope - I
> didn't check if there are some other unrelated changes from anybody else
> in newest trunk revision...) - usb_ehci_110625_0.
>
Very impressive.

> Remaining issue:
> - Any HID low speed device attached via USB 2.0 hub does not work. (It
> is most probably because bulk split transfers are differently handled in
> comparison with interrupt split transfers. Probably only one solution is
> to add interrupt transfers into EHCI driver.)
>
Have you tried a device using bulk transfers? (e.g. a usbserial adapter)
> I made short test, driver looks to be working.
>
> But there can be still mistakes in CPU/LE and VIRT/PHYS conversions - I
> cannot test them on x86 machine (or at least I don't know how to do
> it...).
Right now I'm not home and have only this laptop. Its pecularity is of
having EHCI without any apparent signs of companion controller. This
Sunday I should be able to test your patch on fuloong.
> Could You (or, of course, anybody else...) test EHCI patch on:
> - some "big endian" machine ?
Right now we don't have the PCI support for either sparc64 or powerpc.
But since the firmware there provides PCI functions it would be fixable.
But I don't remember if my machines have EHCI. They are some cheap old
stuff.
> - some machine with different virtual/physical addressing, i.e. like
> Yeloong ?
>
When I get home.
> What I didn't:
> - ... packed isn't necessary here ... - GCC documentation says:
> "packed
>     This attribute, attached to struct or union type definition,
> specifies that each member of the structure or union is placed to
> minimize the memory required."
>
> I.e., it is exactly what we need - members are stored in structure
> without any additional space between them. Without this attribute
> compiler can align structure members in any way (depend on its defaults
> and global settings etc.) - so members can be aligned e.g. to 64 bits
> inside structure and in this case we have structure which does not
> correspond to EHCI HW data structure.
> So, I left "packed" attribute in code.
>
No option will make GCC align on anything more than the size of element
itself (otherwise there would be trouble with arrays).

> static inline void *
> grub_ehci_phys2virt (grub_uint32_t phys, struct grub_pci_dma_chunk *chunk)
> {
>   if (!phys)
>     return NULL;
Address 0, as well as (void *) 0 may be valid in the hw context. Do you
really use 0 or NULL as incorectness marker somewhere?
>   return (void *) (phys - grub_dma_get_phys (chunk)
> 		   + (grub_uint32_t) grub_dma_get_virt (chunk));
> }
Even the low addresses can be mapped high in 64-bit systems. Correct
expression is:

return  (grub_uint8_t *) grub_dma_get_virt (chunk) + (phys -
grub_dma_get_phys (chunk));
> static inline grub_uint32_t
> grub_ehci_virt2phys (void *virt, struct grub_pci_dma_chunk *chunk)
> {
>   if (!virt)
>     return 0;
ditto
>   return ((grub_uint32_t) virt - (grub_uint32_t) grub_dma_get_virt (chunk)
> 	  + grub_dma_get_phys (chunk));
Should be:

  return ((grub_uint8_t *) virt - (grub_uint8_t *) grub_dma_get_virt (chunk))
	  + grub_dma_get_phys (chunk);
Actually these 2 functions can be moved into a .h file


>   /* If this is not an EHCI controller, just return.  */
>   if (class != 0x0c || subclass != 0x03 || interf != 0x20)
>     return 0;
I'll add geode here once I get home.
>   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);
>
You need to use grub_pci_device_map_range.
>   /* 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));
>
Should be:

  e->iobase = (volatile grub_uint32_t *) ((grub_uint8_t *) e->iobase_ehcc +
			    	 	  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);
There is %p for this.

>   /* 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);
In this case you need to disable SMI interrupts (it's in register EECP+1
AFAIR)
> 	    }
> 	}
>       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 ?
Yes.  It means controller is unused.
> 	   * 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);
I'd also clean SMI, just to be sure.

-- 
Regards
Vladimir 'φ-coder/phcoder' Serbinenko



[-- Attachment #2: OpenPGP digital signature --]
[-- Type: application/pgp-signature, Size: 294 bytes --]

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

* Re: [PATCH] EHCI driver - USB 2.0 support
  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 19:01             ` [PATCH] EHCI driver - USB 2.0 support - addendum Aleš Nesrsta
  0 siblings, 2 replies; 15+ messages in thread
From: Aleš Nesrsta @ 2011-10-01  8:15 UTC (permalink / raw)
  To: The development of GNU GRUB

Vladimir 'φ-coder/phcoder' Serbinenko píše v Pá 30. 09. 2011 v 17:37
+0200:
> That must be a too long interval of writing e-mail. I started it a month
> ago, then interrupted and now finishing it
No problem, I have often very, very long "response time" too...
(sometimes infinite...) :-)

According to all comments/changes below - in fact I agree with all, but
I will wait with changes in code until You try EHCI on fuloong or
another machine(s), as there could be some additional mistakes/changes
in code after test - or feel totally free to make Your own corrected
starting version of this driver code for any experimental branch or
trunk.

Currently I have only note to discussed "packed" attribute "problem":
I have no problem to delete it - especially if it is usual programmer
praxis in such cases (or You can do it instead of me without my
agreement - no problem, because You are the maintainer of whole code and
You are making code rules... :-) ).

Only to clarify my point of view:

1. I think there is difference between structures and arrays, compiler
can (must) use different alignment for each case:
For array members - there it must be according to type of array, resp.
length of array member
For structure members (and possibly whole structure alignment) - there
can be used any alignment, I see no reason why should be structure
member alignment generally restricted in some way even if the whole
structure is then used as array member

2. AFAIK (but maybe it is not true, maybe it was never true...?)
compilers are using default alignment of variables usually according to
native length of CPU "word". So, as we have 64-bit machines now, it
could be only question of time when compilers will use 64 bits alignment
as default.

3. Additionally, such attribute can be something like "warning" for
programmers which will want to modify this code in future - "...this is
some special structure related to HW, be careful!..." :-)

So, thats are reasons why I think it is more safe using of "packed"
attribute even if it is not necessary now - it is prepared for the
future...
But if some of assumptions mentioned above are not true then whole idea
is wrong  - I am not C compiler (nor driver) specialist as You can see
from my code... :-)
So, don't spend unusual time to discuss about some attribute, simply
delete it if You want - the code is open for change... :-)

Best regards
Ales

> > there is little bit improved EHCI driver - ehci.c.
> > Changes in other files are still the same (more precisely, I hope - I
> > didn't check if there are some other unrelated changes from anybody else
> > in newest trunk revision...) - usb_ehci_110625_0.
> >
> Very impressive.
> 
> > Remaining issue:
> > - Any HID low speed device attached via USB 2.0 hub does not work. (It
> > is most probably because bulk split transfers are differently handled in
> > comparison with interrupt split transfers. Probably only one solution is
> > to add interrupt transfers into EHCI driver.)
> >
> Have you tried a device using bulk transfers? (e.g. a usbserial adapter)
> > I made short test, driver looks to be working.
> >
> > But there can be still mistakes in CPU/LE and VIRT/PHYS conversions - I
> > cannot test them on x86 machine (or at least I don't know how to do
> > it...).
> Right now I'm not home and have only this laptop. Its pecularity is of
> having EHCI without any apparent signs of companion controller. This
> Sunday I should be able to test your patch on fuloong.
> > Could You (or, of course, anybody else...) test EHCI patch on:
> > - some "big endian" machine ?
> Right now we don't have the PCI support for either sparc64 or powerpc.
> But since the firmware there provides PCI functions it would be fixable.
> But I don't remember if my machines have EHCI. They are some cheap old
> stuff.
> > - some machine with different virtual/physical addressing, i.e. like
> > Yeloong ?
> >
> When I get home.
> > What I didn't:
> > - ... packed isn't necessary here ... - GCC documentation says:
> > "packed
> >     This attribute, attached to struct or union type definition,
> > specifies that each member of the structure or union is placed to
> > minimize the memory required."
> >
> > I.e., it is exactly what we need - members are stored in structure
> > without any additional space between them. Without this attribute
> > compiler can align structure members in any way (depend on its defaults
> > and global settings etc.) - so members can be aligned e.g. to 64 bits
> > inside structure and in this case we have structure which does not
> > correspond to EHCI HW data structure.
> > So, I left "packed" attribute in code.
> >
> No option will make GCC align on anything more than the size of element
> itself (otherwise there would be trouble with arrays).
> 
> > static inline void *
> > grub_ehci_phys2virt (grub_uint32_t phys, struct grub_pci_dma_chunk *chunk)
> > {
> >   if (!phys)
> >     return NULL;
> Address 0, as well as (void *) 0 may be valid in the hw context. Do you
> really use 0 or NULL as incorectness marker somewhere?
> >   return (void *) (phys - grub_dma_get_phys (chunk)
> > 		   + (grub_uint32_t) grub_dma_get_virt (chunk));
> > }
> Even the low addresses can be mapped high in 64-bit systems. Correct
> expression is:
> 
> return  (grub_uint8_t *) grub_dma_get_virt (chunk) + (phys -
> grub_dma_get_phys (chunk));
> > static inline grub_uint32_t
> > grub_ehci_virt2phys (void *virt, struct grub_pci_dma_chunk *chunk)
> > {
> >   if (!virt)
> >     return 0;
> ditto
> >   return ((grub_uint32_t) virt - (grub_uint32_t) grub_dma_get_virt (chunk)
> > 	  + grub_dma_get_phys (chunk));
> Should be:
> 
>   return ((grub_uint8_t *) virt - (grub_uint8_t *) grub_dma_get_virt (chunk))
> 	  + grub_dma_get_phys (chunk);
> Actually these 2 functions can be moved into a .h file
> 
> 
> >   /* If this is not an EHCI controller, just return.  */
> >   if (class != 0x0c || subclass != 0x03 || interf != 0x20)
> >     return 0;
> I'll add geode here once I get home.
> >   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);
> >
> You need to use grub_pci_device_map_range.
> >   /* 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));
> >
> Should be:
> 
>   e->iobase = (volatile grub_uint32_t *) ((grub_uint8_t *) e->iobase_ehcc +
> 			    	 	  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);
> There is %p for this.
> 
> >   /* 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);
> In this case you need to disable SMI interrupts (it's in register EECP+1
> AFAIR)
> > 	    }
> > 	}
> >       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 ?
> Yes.  It means controller is unused.
> > 	   * 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);
> I'd also clean SMI, just to be sure.
> 
> _______________________________________________
> Grub-devel mailing list
> Grub-devel@gnu.org
> https://lists.gnu.org/mailman/listinfo/grub-devel




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

* Re: [PATCH] EHCI driver - USB 2.0 support
  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
  1 sibling, 2 replies; 15+ messages in thread
From: Vladimir 'φ-coder/phcoder' Serbinenko @ 2011-10-01 18:05 UTC (permalink / raw)
  To: grub-devel

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

On 01.10.2011 10:15, Aleš Nesrsta wrote:
> Vladimir 'φ-coder/phcoder' Serbinenko píše v Pá 30. 09. 2011 v 17:37
> +0200:
>> That must be a too long interval of writing e-mail. I started it a month
>> ago, then interrupted and now finishing it
> No problem, I have often very, very long "response time" too...
> (sometimes infinite...) :-)
>
> According to all comments/changes below - in fact I agree with all, but
> I will wait with changes in code until You try EHCI on fuloong or
> another machine(s), as there could be some additional mistakes/changes
> in code after test - or feel totally free to make Your own corrected
> starting version of this driver code for any experimental branch or
> trunk.
>
Ok, I'll do it since EHCI is important. I also feel like there is no
need to maroon it in experimental
> Currently I have only note to discussed "packed" attribute "problem":
> I have no problem to delete it - especially if it is usual programmer
> praxis in such cases (or You can do it instead of me without my
> agreement - no problem, because You are the maintainer of whole code and
> You are making code rules... :-) ).
>
Don't consider me some kind of dictator, I just want some coherency in
design, rather than having incoherent problems.
> Only to clarify my point of view:
>
> 1. I think there is difference between structures and arrays, compiler
> can (must) use different alignment for each case:
> For array members - there it must be according to type of array, resp.
> length of array member
> For structure members (and possibly whole structure alignment) - there
> can be used any alignment, I see no reason why should be structure
> member alignment generally restricted in some way even if the whole
> structure is then used as array member
>
> 2. AFAIK (but maybe it is not true, maybe it was never true...?)
> compilers are using default alignment of variables usually according to
> native length of CPU "word". So, as we have 64-bit machines now, it
> could be only question of time when compilers will use 64 bits alignment
> as default.
Reading and writing into a byte aligned on word doesn't bring any
advantage and wastes memory and decreases portability so no compiler
will ever do it.
The whole reason to align is because of performance advantages and
instruction availability.

-- 
Regards
Vladimir 'φ-coder/phcoder' Serbinenko



[-- Attachment #2: OpenPGP digital signature --]
[-- Type: application/pgp-signature, Size: 294 bytes --]

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

* Re: [PATCH] EHCI driver - USB 2.0 support - addendum...
  2011-10-01  8:15           ` Aleš Nesrsta
  2011-10-01 18:05             ` Vladimir 'φ-coder/phcoder' Serbinenko
@ 2011-10-01 19:01             ` Aleš Nesrsta
  1 sibling, 0 replies; 15+ messages in thread
From: Aleš Nesrsta @ 2011-10-01 19:01 UTC (permalink / raw)
  To: The development of GNU GRUB

Hi,
I am sorry, I forgot to say something to Your question about testing in
previous e-mail:

I tested (but not so much deeply) in fact all combinations which I can
realize with EHCI/UHCI controller, USB 2.0 hub, approx. 5 different USB
disks and one USB keyboard. Serial adapter I did not test, unfortunately
I cannot use it currently.

I don't have another low or full speed device except of mentioned
keyboard and one USB disk (GARMIN GPS unit in mass-storage mode).
And only one thing which is not working is combination EHCI - USB 2.0
hub - USB keyboard.
Split transfer returns some error to driver when used for interrupt
transfer of low-speed device (I forgot which error code exactly, it is
long time ago...).
I checked if I set something wrong in split-transfer QD/TD for the low
speed device but I see nothing - maybe somebody other will find
mistake...?
So, it looks like problem with interrupt transfer - probably it cannot
be "replaced" by bulk transfer when used in split-transfers.

Unfortunately, using of USB keyboard via USB 2.0 hub could be often
case, as some keyboards have hub built inside and the keyboard itself is
connected via this internal hub. So maybe we will need to find some
workaround.

But first try to test EHCI yourself on Your machine(s) and then we will
see...

Best regards
Ales


Aleš Nesrsta píše v So 01. 10. 2011 v 10:15 +0200:
> Vladimir 'φ-coder/phcoder' Serbinenko píše v Pá 30. 09. 2011 v 17:37
> +0200:
> > That must be a too long interval of writing e-mail. I started it a month
> > ago, then interrupted and now finishing it
> No problem, I have often very, very long "response time" too...
> (sometimes infinite...) :-)
> 
> According to all comments/changes below - in fact I agree with all, but
> I will wait with changes in code until You try EHCI on fuloong or
> another machine(s), as there could be some additional mistakes/changes
> in code after test - or feel totally free to make Your own corrected
> starting version of this driver code for any experimental branch or
> trunk.
> 
> Currently I have only note to discussed "packed" attribute "problem":
> I have no problem to delete it - especially if it is usual programmer
> praxis in such cases (or You can do it instead of me without my
> agreement - no problem, because You are the maintainer of whole code and
> You are making code rules... :-) ).
> 
> Only to clarify my point of view:
> 
> 1. I think there is difference between structures and arrays, compiler
> can (must) use different alignment for each case:
> For array members - there it must be according to type of array, resp.
> length of array member
> For structure members (and possibly whole structure alignment) - there
> can be used any alignment, I see no reason why should be structure
> member alignment generally restricted in some way even if the whole
> structure is then used as array member
> 
> 2. AFAIK (but maybe it is not true, maybe it was never true...?)
> compilers are using default alignment of variables usually according to
> native length of CPU "word". So, as we have 64-bit machines now, it
> could be only question of time when compilers will use 64 bits alignment
> as default.
> 
> 3. Additionally, such attribute can be something like "warning" for
> programmers which will want to modify this code in future - "...this is
> some special structure related to HW, be careful!..." :-)
> 
> So, thats are reasons why I think it is more safe using of "packed"
> attribute even if it is not necessary now - it is prepared for the
> future...
> But if some of assumptions mentioned above are not true then whole idea
> is wrong  - I am not C compiler (nor driver) specialist as You can see
> from my code... :-)
> So, don't spend unusual time to discuss about some attribute, simply
> delete it if You want - the code is open for change... :-)
> 
> Best regards
> Ales
> 
> > > there is little bit improved EHCI driver - ehci.c.
> > > Changes in other files are still the same (more precisely, I hope - I
> > > didn't check if there are some other unrelated changes from anybody else
> > > in newest trunk revision...) - usb_ehci_110625_0.
> > >
> > Very impressive.
> > 
> > > Remaining issue:
> > > - Any HID low speed device attached via USB 2.0 hub does not work. (It
> > > is most probably because bulk split transfers are differently handled in
> > > comparison with interrupt split transfers. Probably only one solution is
> > > to add interrupt transfers into EHCI driver.)
> > >
> > Have you tried a device using bulk transfers? (e.g. a usbserial adapter)
> > > I made short test, driver looks to be working.
> > >
> > > But there can be still mistakes in CPU/LE and VIRT/PHYS conversions - I
> > > cannot test them on x86 machine (or at least I don't know how to do
> > > it...).
> > Right now I'm not home and have only this laptop. Its pecularity is of
> > having EHCI without any apparent signs of companion controller. This
> > Sunday I should be able to test your patch on fuloong.
> > > Could You (or, of course, anybody else...) test EHCI patch on:
> > > - some "big endian" machine ?
> > Right now we don't have the PCI support for either sparc64 or powerpc.
> > But since the firmware there provides PCI functions it would be fixable.
> > But I don't remember if my machines have EHCI. They are some cheap old
> > stuff.
> > > - some machine with different virtual/physical addressing, i.e. like
> > > Yeloong ?
> > >
> > When I get home.
> > > What I didn't:
> > > - ... packed isn't necessary here ... - GCC documentation says:
> > > "packed
> > >     This attribute, attached to struct or union type definition,
> > > specifies that each member of the structure or union is placed to
> > > minimize the memory required."
> > >
> > > I.e., it is exactly what we need - members are stored in structure
> > > without any additional space between them. Without this attribute
> > > compiler can align structure members in any way (depend on its defaults
> > > and global settings etc.) - so members can be aligned e.g. to 64 bits
> > > inside structure and in this case we have structure which does not
> > > correspond to EHCI HW data structure.
> > > So, I left "packed" attribute in code.
> > >
> > No option will make GCC align on anything more than the size of element
> > itself (otherwise there would be trouble with arrays).
> > 
> > > static inline void *
> > > grub_ehci_phys2virt (grub_uint32_t phys, struct grub_pci_dma_chunk *chunk)
> > > {
> > >   if (!phys)
> > >     return NULL;
> > Address 0, as well as (void *) 0 may be valid in the hw context. Do you
> > really use 0 or NULL as incorectness marker somewhere?
> > >   return (void *) (phys - grub_dma_get_phys (chunk)
> > > 		   + (grub_uint32_t) grub_dma_get_virt (chunk));
> > > }
> > Even the low addresses can be mapped high in 64-bit systems. Correct
> > expression is:
> > 
> > return  (grub_uint8_t *) grub_dma_get_virt (chunk) + (phys -
> > grub_dma_get_phys (chunk));
> > > static inline grub_uint32_t
> > > grub_ehci_virt2phys (void *virt, struct grub_pci_dma_chunk *chunk)
> > > {
> > >   if (!virt)
> > >     return 0;
> > ditto
> > >   return ((grub_uint32_t) virt - (grub_uint32_t) grub_dma_get_virt (chunk)
> > > 	  + grub_dma_get_phys (chunk));
> > Should be:
> > 
> >   return ((grub_uint8_t *) virt - (grub_uint8_t *) grub_dma_get_virt (chunk))
> > 	  + grub_dma_get_phys (chunk);
> > Actually these 2 functions can be moved into a .h file
> > 
> > 
> > >   /* If this is not an EHCI controller, just return.  */
> > >   if (class != 0x0c || subclass != 0x03 || interf != 0x20)
> > >     return 0;
> > I'll add geode here once I get home.
> > >   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);
> > >
> > You need to use grub_pci_device_map_range.
> > >   /* 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));
> > >
> > Should be:
> > 
> >   e->iobase = (volatile grub_uint32_t *) ((grub_uint8_t *) e->iobase_ehcc +
> > 			    	 	  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);
> > There is %p for this.
> > 
> > >   /* 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);
> > In this case you need to disable SMI interrupts (it's in register EECP+1
> > AFAIR)
> > > 	    }
> > > 	}
> > >       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 ?
> > Yes.  It means controller is unused.
> > > 	   * 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);
> > I'd also clean SMI, just to be sure.
> > 
> > _______________________________________________
> > Grub-devel mailing list
> > Grub-devel@gnu.org
> > https://lists.gnu.org/mailman/listinfo/grub-devel
> 
> 
> 
> _______________________________________________
> Grub-devel mailing list
> Grub-devel@gnu.org
> https://lists.gnu.org/mailman/listinfo/grub-devel




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

* Re: [PATCH] EHCI driver - USB 2.0 support
  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
  1 sibling, 0 replies; 15+ messages in thread
From: Vladimir 'φ-coder/phcoder' Serbinenko @ 2011-10-01 20:04 UTC (permalink / raw)
  To: grub-devel

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

On 01.10.2011 20:05, Vladimir 'φ-coder/phcoder' Serbinenko wrote:
> On 01.10.2011 10:15, Aleš Nesrsta wrote:
>> Vladimir 'φ-coder/phcoder' Serbinenko píše v Pá 30. 09. 2011 v 17:37
>> +0200:
>>> That must be a too long interval of writing e-mail. I started it a month
>>> ago, then interrupted and now finishing it
>> No problem, I have often very, very long "response time" too...
>> (sometimes infinite...) :-)
>>
>> According to all comments/changes below - in fact I agree with all, but
>> I will wait with changes in code until You try EHCI on fuloong or
>> another machine(s), as there could be some additional mistakes/changes
>> in code after test - or feel totally free to make Your own corrected
>> starting version of this driver code for any experimental branch or
>> trunk.
>>
> Ok, I'll do it since EHCI is important. I also feel like there is no
> need to maroon it in experimental
Pushed corrected version to branches/ehci. Not tested yet.

-- 
Regards
Vladimir 'φ-coder/phcoder' Serbinenko



[-- Attachment #2: OpenPGP digital signature --]
[-- Type: application/pgp-signature, Size: 294 bytes --]

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

* Re: [PATCH] EHCI driver - USB 2.0 support - alignment...
  2011-10-01 18:05             ` Vladimir 'φ-coder/phcoder' Serbinenko
  2011-10-01 20:04               ` Vladimir 'φ-coder/phcoder' Serbinenko
@ 2011-10-01 22:03               ` Aleš Nesrsta
  1 sibling, 0 replies; 15+ messages in thread
From: Aleš Nesrsta @ 2011-10-01 22:03 UTC (permalink / raw)
  To: The development of GNU GRUB

Hi Vladimir,
see below...

>> Currently I have only note to discussed "packed" attribute "problem":
>> ...
>> 2. AFAIK (but maybe it is not true, maybe it was never true...?)
>> compilers are using default alignment of variables usually according to
>> native length of CPU "word". So, as we have 64-bit machines now, it
>> could be only question of time when compilers will use 64 bits alignment
>> as default.
>Reading and writing into a byte aligned on word doesn't bring any
>advantage and wastes memory and decreases portability so no compiler
>will ever do it.
>The whole reason to align is because of performance advantages and
>instruction availability.

Aah, it looks like we are both speaking about different things !
I am talking about alignment of structure MEMBERS.
You are probably talking about alignment of WHOLE STRUCTURE.

First to alignment of whole structure:
Structure(s), we are talking about, are special structures related to HW 
which must be in any case aligned to 32 BYTES.
This is solved by grub_memalign allocation in driver code. I.e., structure 
will never 1 BYTE or 1 WORD aligned, so there should not be any performance 
problem.

Now to alignment of members in structure:
In these special HW related structures are DWORD variables which must be 
placed in memory immediately one after another without any space between 
them.
I.e., offsets of structure members in structure should look like that:
0x00000000    variable1
0x00000004    variable2
0x00000008    variable3
0x0000000c    variable4
I am afraid new compilers in future can use default alignment 64 bits, so 
offsets of structure members in structure can, without any attribute, look 
like that:
0x00000000    variable1
0x00000008    variable2
0x00000010    variable3
0x00000018    variable4
To prevent this wrong situation, I am using "packed" attribute for 
structure - it will prevent any change of structure members alignment in 
future. Independent of default compiler alignment, the variables will be 
placed in memory immediately one after another. As all variables are of 
DWORD type and whole structure (begin of structure) is always aligned to 32 
bytes (i.e. to multiple of DWORD), there also should not be any problem with 
performance.

Even if it looks maybe little bit confusing and surprising, using of 
"packed" attribute for whole structure does mean it is applied for any 
member of structure (and not for whole structure) alignment - according e.g. 
this
( http://gcc.gnu.org/onlinedocs/gcc/Type-Attributes.html ):
"...
packed
This attribute, attached to struct or union type definition, specifies that 
each member (other than zero-width bitfields) of the structure or union is 
placed to minimize the memory required. When attached to an enum definition, 
it indicates that the smallest integral type should be used.
Specifying this attribute for struct and union types is equivalent to 
specifying the packed attribute on each of the structure or union members. 
Specifying the -fshort-enums flag on the line is equivalent to specifying 
the packed attribute on all enum definitions.

..."

Best regards
Ales

> -- 
> Regards
> Vladimir 'φ-coder/phcoder' Serbinenko

Best regards
Ales 



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

* Re: [PATCH] EHCI driver - USB 2.0 support
  2011-08-27 16:42       ` Aleš Nesrsta
  2011-09-30 15:37         ` Vladimir 'φ-coder/phcoder' Serbinenko
@ 2011-11-03  9:25         ` Philipp Hahn
  2011-11-04 20:56           ` Aleš Nesrsta
  1 sibling, 1 reply; 15+ messages in thread
From: Philipp Hahn @ 2011-11-03  9:25 UTC (permalink / raw)
  To: The development of GNU GRUB, starous

Hello,

I had a look at your EHCI driver, while investigating why grub-1.99
needs 2 minutes to load my 58 MB InitRd.

On Sat, Aug 27, 2011 at 06:42:18PM +0200, Aleš Nesrsta wrote:
> Changes in other files are still the same (more precisely, I hope - I
> didn't check if there are some other unrelated changes from anybody else
> in newest trunk revision...) - usb_ehci_110625_0.

Compiling on x86_86 failed for me in grub-core/bus/usb/usbhub.c:104

>  grub_dprintf ("usb", "Added new usb device: %08x, addr=%d\n",
>    (grub_uint32_t)dev, i);

because dev is a 64 bit pointer which GCC didn't like to cast to an
uint32 with the compiler options used by Debian. I fixed this locally by
changing those two lines to use '%p' instead of '%08x' and without the
cast:

  grub_dprintf ("usb", "Added new usb device: %p, addr=%d\n",
    dev, i);

Even without that I wasn't able to get EHCI working: If I insmod 'usbms'
and 'ehci', no disk is detected. Only when I insmod 'ohci' is the stick
detected. With 'set debug=ehci pager=1' I get some lines, before the
screen is spammed with 'STATUS'-lines and the computer reboots.
Did I miss something obvious or can I help debug this?

Sincerely
Philipp
-- 
Philipp Hahn           Open Source Software Engineer      hahn@univention.de
Univention GmbH        Linux for Your Business        fon: +49 421 22 232- 0
Mary-Somerville-Str.1  D-28359 Bremen                 fax: +49 421 22 232-99
                                                   http://www.univention.de/


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

* Re: [PATCH] EHCI driver - USB 2.0 support
  2011-11-03  9:25         ` [PATCH] EHCI driver - USB 2.0 support Philipp Hahn
@ 2011-11-04 20:56           ` Aleš Nesrsta
  0 siblings, 0 replies; 15+ messages in thread
From: Aleš Nesrsta @ 2011-11-04 20:56 UTC (permalink / raw)
  To: The development of GNU GRUB

Hi Phillip,

driver is "near zero" version and the problem with pointer in debug
message is one of problematic or potentially problematic things which
are known and which are waiting for repair - see into e-mail from
Vladimir, sent Fri, 30 Sep 2011 17:37:16. Maybe some of these known
things is the reason why EHCI dosn't work for You...
Unfortunately, I cannot fix and more deeply test it now as I am very
busy in last time and it will be not better till new year...

Maybe I can help You if You send debug output to me.
But there is only one possibility to get usable debug output - capture
it via serial line.
Any of current USB drivers produces lot of debug messages - it is
according to "hot-plug" system algorithm, which generates lot of
repeating messages and, additionally, there are some loops in USB device
detection which caused very very long delay when debug is on. Also there
should be printed out lot of values to have the possibility to find
consequences etc.

To get usable debug output I recommend for example this procedure:
- connect another computer via serial line - this computer will capture
debug messages into some file
- disconnect USB drive
- start grub into command line without any USB driver loaded
- set serial parameters as You need
- set "terminal_output console serial"
- test serial communication with second computer and start capturing
into file
- set "debug=all" and wait for command line
- "insmod ehci" and connect USB device few seconds later (usbms will be
loaded automatically if necessary)
- be patient - there will be lot of messages displayed, wait at least
approx. two minutes (or till computer reboots, if it happens...)

Then send debug output to me and I will try to find what happened (but
don't expect fast answer in any case, sorry...).

Best regards
Ales



Philipp Hahn píše v Čt 03. 11. 2011 v 10:25 +0100:
> Hello,
> 
> I had a look at your EHCI driver, while investigating why grub-1.99
> needs 2 minutes to load my 58 MB InitRd.
> 
> On Sat, Aug 27, 2011 at 06:42:18PM +0200, Aleš Nesrsta wrote:
> > Changes in other files are still the same (more precisely, I hope - I
> > didn't check if there are some other unrelated changes from anybody else
> > in newest trunk revision...) - usb_ehci_110625_0.
> 
> Compiling on x86_86 failed for me in grub-core/bus/usb/usbhub.c:104
> 
> >  grub_dprintf ("usb", "Added new usb device: %08x, addr=%d\n",
> >    (grub_uint32_t)dev, i);
> 
> because dev is a 64 bit pointer which GCC didn't like to cast to an
> uint32 with the compiler options used by Debian. I fixed this locally by
> changing those two lines to use '%p' instead of '%08x' and without the
> cast:
> 
>   grub_dprintf ("usb", "Added new usb device: %p, addr=%d\n",
>     dev, i);
> 
> Even without that I wasn't able to get EHCI working: If I insmod 'usbms'
> and 'ehci', no disk is detected. Only when I insmod 'ohci' is the stick
> detected. With 'set debug=ehci pager=1' I get some lines, before the
> screen is spammed with 'STATUS'-lines and the computer reboots.
> Did I miss something obvious or can I help debug this?
> 
> Sincerely
> Philipp




^ 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.