/* 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 .
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
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< 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<reset & (1<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);
}