[Top][All Lists]
[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
Re: [PATCH] EHCI driver - USB 2.0 support
From: |
Vladimir 'φ-coder/phcoder' Serbinenko |
Subject: |
Re: [PATCH] EHCI driver - USB 2.0 support |
Date: |
Sat, 25 Jun 2011 22:27:03 +0200 |
User-agent: |
Mozilla/5.0 (X11; U; Linux x86_64; en-US; rv:1.9.2.17) Gecko/20110606 Iceowl/1.0b2 Icedove/3.1.10 |
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
signature.asc
Description: OpenPGP digital signature