/* ohci.c - OHCI Support. */
/*
* GRUB -- GRand Unified Bootloader
* Copyright (C) 2008 Free Software Foundation, Inc.
*
* 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/usbtrans.h>
GRUB_MOD_LICENSE ("GPLv3+");
struct grub_ohci_hcca
{
/* Pointers to Interrupt Endpoint Descriptors. Not used by
GRUB. */
/* Current frame number. */
/* List of completed TDs. */
} __attribute__((packed));
/* OHCI General Transfer Descriptor */
struct grub_ohci_td
{
/* Information used to construct the TOKEN packet. */
/* next values are not for OHCI HW */
* pointer as uint32 */
* physical address in CPU endian */
} __attribute__((packed));
/* OHCI Endpoint Descriptor. */
struct grub_ohci_ed
{
} __attribute__((packed));
/* Little bit similar as in UHCI */
/* Implementation assumes:
* 32-bits architecture - XXX: fix for 64-bits
* memory allocated by grub_memalign_dma32 must be continuous
* in virtual and also in physical memory */
struct grub_ohci
{
};
typedef enum
{
#define GRUB_OHCI_REG_FRAME_INTERVAL_FI_SHIFT 0
/* XXX: Is this choice of timings sane? */
static inline grub_ohci_ed_t
{
if (!x)
return NULL;
if (bulk)
return (grub_ohci_ed_t) (x - o->ed_bulk_addr
+ (grub_uint8_t *) o->ed_bulk);
return (grub_ohci_ed_t) (x - o->ed_ctrl_addr
+ (grub_uint8_t *) o->ed_ctrl);
}
static grub_uint32_t
{
if (!x)
return 0;
if (bulk)
}
static inline grub_ohci_td_t
{
if (!x)
return NULL;
}
static grub_uint32_t
{
if (!x)
return 0;
}
static grub_uint32_t
{
}
static void
{
}
/* Iterate over all PCI devices. Determine if a device is an OHCI
controller. If this is the case, initialize it. */
static int NESTED_FUNC_ATTR
{
struct grub_ohci *o;
int j;
/* Determine IO base address. */
if (pciid == GRUB_CS5536_PCIID)
{
{
/* Shouldn't happen. */
return 0;
}
}
else
{
/* If this is not an OHCI controller, just return. */
return 0;
#if 0
/* Stop if there is no IO space base address defined. */
if (! (base & 1))
return 0;
#endif
}
/* Allocate memory for the controller and register it. */
o = grub_malloc (sizeof (*o));
if (! o)
return 1;
grub_memset ((void*)o, 0, sizeof (*o));
/* Reserve memory for the HCCA. */
if (! o->hcca_chunk)
goto fail;
/* Reserve memory for ctrl EDs. */
if (! o->ed_ctrl_chunk)
goto fail;
/* Preset EDs */
for (j=0; j < GRUB_OHCI_CTRL_EDS; j++)
/* Reserve memory for bulk EDs. */
if (! o->ed_bulk_chunk)
goto fail;
/* Preset EDs */
for (j=0; j < GRUB_OHCI_BULK_EDS; j++)
/* Reserve memory for TDs. */
/* Why is it aligned on 32 boundary if spec. says 16 ?
* We have structure 32 bytes long and we don't want cross
* 4K boundary inside structure. */
if (! o->td_chunk)
goto fail;
/* Preset free TDs chain in TDs */
for (j=0; j < (GRUB_OHCI_TDS-1); j++)
/* Check if the OHCI revision is actually 1.0 as supported. */
goto fail;
{
if ((control & 0x100) != 0)
{
unsigned i;
/* Do change of ownership */
/* Ownership change request */
/* Waiting for SMM deactivation */
for (i=0; i < 10; i++)
{
{
break;
}
grub_millisleep (100);
}
if (i >= 10)
{
}
}
else if (((control & 0x100) == 0) &&
{
/* Do change of ownership - not implemented yet... */
/* In fact we probably need to do nothing ...? */
}
else
{
/* We can setup OHCI. */
}
}
/* Suspend the OHCI by issuing a reset. */
grub_millisleep (1);
/* Setup the HCCA. */
/* Misc. pre-sets. */
* So we assign to HEAD registers zero ED from related array
* and we will not use this ED, it will be always skipped.
* It should not produce notable performance penalty (I hope). */
/* Check OHCI Legacy Support */
if ((revision & 0x100) != 0)
{
}
/* Enable the OHCI + enable CONTROL and BULK LIST. */
(2 << 6)
/* Power on all ports */
#if 0 /* We don't need it at all, handled via hotplugging */
/* Now we have hot-plugging, we need to wait for stable power only */
grub_millisleep (100);
#endif
/* Link to ohci now that initialisation is successful. */
ohci = o;
return 0;
fail:
if (o)
{
grub_dma_free (o->td_chunk);
grub_dma_free (o->ed_bulk_chunk);
grub_dma_free (o->ed_ctrl_chunk);
grub_dma_free (o->hcca_chunk);
}
grub_free (o);
return 0;
}
static void
grub_ohci_inithw (void)
{
}
static int
{
struct grub_ohci *o;
{
return 1;
}
return 0;
}
static grub_ohci_ed_t
{
int count;
int i;
/* Use proper values and structures. */
if (bulk)
{
}
else
{
}
/* First try to find existing ED with proper target address */
for (i = 0; ; )
{
if (i && /* We ignore zero ED */
return ed; /* Found proper existing ED */
i++;
{
continue;
}
break;
}
/* ED with target_addr does not exist, we have to add it */
/* Have we any free ED in array ? */
if (i >= count) /* No. */
return NULL;
/* Currently we simply take next ED in array, no allocation
* function is used. It should be no problem until hot-plugging
* will be implemented, i.e. until we will need to de-allocate EDs
* of unplugged devices. */
/* We can link new ED to previous ED safely as the new ED should
* still have set skip bit. */
return &ed[1];
}
static grub_ohci_td_t
{
/* Check if there is a Transfer Descriptor available. */
if (! o->td_free)
return NULL;
return ret;
}
static void
{
}
static void
{
if (!td)
return;
/* Unchain first TD from previous TD if it is chained */
if (td->prev_td_phys)
{
td->prev_td_phys);
td_prev_virt->link_td = 0;
}
/* Free all TDs from td (chained by link_td) */
while (td)
{
/* Unlink the queue. */
/* Free the TD. */
grub_ohci_free_td (o, tdprev);
}
}
static void
{
switch (type)
{
token = 0 << 19;
break;
break;
break;
default:
token = 0;
break;
}
/* Set the token */
/* Set "Not accessed" error code */
/* Set correct buffer values in TD if zero transfer occurs */
if (size)
{
}
else
{
td->buffer_end = 0;
}
/* Set the rest of TD */
}
{
};
static grub_usb_err_t
{
int bulk = 0;
int i;
if (!cdata)
return GRUB_USB_ERR_INTERNAL;
/* Pre-set target for ED - we need it to find proper ED */
/* Set the device address. */
/* Set the endpoint. It should be masked, we need 4 bits only. */
/* Set the device speed. */
/* Set the maximum packet size. */
/* Determine if transfer type is bulk - we need to select proper ED */
{
bulk = 1;
break;
break;
default:
return GRUB_USB_ERR_INTERNAL;
}
/* Find proper ED or add new ED */
{
return GRUB_USB_ERR_INTERNAL;
}
/* Take pointer to first TD from ED */
/* Sanity check - td_head should be equal to td_tail */
{
/* XXX: Fix: What to do ? */
return GRUB_USB_ERR_INTERNAL;
}
/* Now we should handle first TD. If ED is newly allocated,
* we must allocate the first TD. */
if (!td_head_phys)
{
if (!cdata->td_head_virt)
return GRUB_USB_ERR_INTERNAL; /* We don't need de-allocate ED */
/* We can set td_head only when ED is not active, i.e.
* when it is newly allocated. */
}
else
/* Set TDs */
{
/* Set index of TD in transfer */
/* Remember last used (processed) TD phys. addr. */
/* Allocate next TD */
td_next_virt = grub_ohci_alloc_td (o);
if (!td_next_virt) /* No free TD, cancel transfer and free TDs except head TD */
{
if (i) /* if i==0 we have nothing to free... */
/* Reset head TD */
sizeof(struct grub_ohci_td) );
return GRUB_USB_ERR_INTERNAL;
}
/* Chain TDs */
td_next_virt) );
}
/* Setup the Endpoint Descriptor for transfer. */
/* First set necessary fields in TARGET but keep (or set) skip bit */
/* Note: It could be simpler if speed, format and max. packet
* size never change after first allocation of ED.
* But unfortunately max. packet size may change during initial
* setup sequence and we must handle it. */
/* Set td_tail */
/* Now reset skip bit */
/* ed_virt->td_head = grub_cpu_to_le32 (td_head); Must not be changed, it is maintained by OHCI */
/* ed_virt->next_ed = grub_cpu_to_le32 (0); Handled by grub_ohci_find_ed, do not change ! */
/* Program the OHCI to actually transfer. */
{
{
/* Set BulkListFilled. */
/* Read back of register should ensure it is really written */
break;
}
{
/* Set ControlListFilled. */
/* Read back of register should ensure it is really written */
break;
}
}
return GRUB_USB_ERR_NONE;
}
static void
{
/* There are many ways how the loop above can finish:
* - normally without any error via INTSTATUS WDH bit
* : tderr_phys == td_last_phys, td_head == td_tail
* - normally with error via HALT bit in ED TD HEAD
* : td_head = next TD after TD with error
* : tderr_phys = last processed and retired TD with error,
* i.e. should be != 0
* : if bad_OHCI == TRUE, tderr_phys will be probably invalid
* - unrecoverable error - I never seen it but it could be
* : err_unrec == TRUE, other values can contain anything...
* - timeout, it can be caused by:
* -- bad USB device - some devices have some bugs, see Linux source
* and related links
* -- bad OHCI controller - e.g. lost interrupts or does not set
* proper bits in INTSTATUS when real IRQ not enabled etc.,
* see Linux source and related links
* One known bug is handled - if transfer finished
* successfully (i.e. HEAD==TAIL, last transfer TD is retired,
* HALT bit is not set) and WDH bit is not set in INTSTATUS - in
* this case we set o->bad_OHCI=TRUE and do alternate loop
* and error handling - but there is problem how to find retired
* TD with error code if HALT occurs and if DONEHEAD is not
* working - we need to find TD previous to current ED HEAD
* -- bad code of this driver or some unknown reasons - :-(
*/
/* Remember target for debug and set skip flag in ED */
/* It should be normaly not necessary but we need it at least
* in case of timeout */
/* Read registers for debug - they should be read now because
* debug prints case unwanted delays, so something can happen
* in the meantime... */
/* Now print debug values - to have full info what happened */
}
static void
{
/* Set empty ED - set HEAD = TAIL = last (not processed) TD */
/* At this point always should be:
* ED has skip bit set and halted or empty or after next SOF,
* i.e. it is safe to free all TDs except last not processed
* ED HEAD == TAIL == phys. addr. of td_current_virt */
/* Un-chainig of last TD */
{
td_prev_virt->link_td = 0;
}
}
static grub_usb_err_t
{
*actual = 0;
/* First we must get proper tderr_phys value */
/* Retired TD with error should be previous TD to ED->td_head */
->prev_td_phys;
/* Prepare pointer to last processed TD and get error code */
/* Set index of last processed TD */
if (tderr_virt)
{
}
else
/* Evaluation of error code */
switch (errcode)
{
case 0:
/* XXX: Should not happen! */
break;
case 1:
/* XXX: CRC error. */
break;
case 2:
break;
case 3:
/* XXX: Data Toggle error. */
break;
case 4:
break;
case 5:
/* XXX: Not responding. */
break;
case 6:
/* XXX: PID Check bits failed. */
break;
case 7:
/* XXX: PID unexpected failed. */
break;
case 8:
/* XXX: Data overrun error. */
break;
case 9:
/* XXX: Data underrun error. */
break;
break;
case 10:
/* XXX: Reserved. */
break;
case 11:
/* XXX: Reserved. */
break;
case 12:
/* XXX: Buffer overrun. */
break;
case 13:
/* XXX: Buffer underrun. */
break;
default:
break;
}
return err;
}
static grub_usb_err_t
{
/* I hope we can do it as transfer (most probably) finished OK */
/* Prepare pointer to last processed TD */
/* Set index of last processed TD */
if (tderr_virt)
else
return GRUB_USB_ERR_NONE;
}
static grub_usb_err_t
{
*actual = 0;
/* Don't try to get error code and last processed TD for proper
* toggle bit value - anything can be invalid */
/* Do OHCI reset in case of unrecoverable error - maybe we will need
* do more - re-enumerate bus etc. (?) */
/* Suspend the OHCI by issuing a reset. */
/* Read back of register should ensure it is really written */
grub_millisleep (1);
/* Misc. resets. */
/* Read back of register should ensure it is really written */
/* Enable the OHCI. */
(2 << 6)
return GRUB_USB_ERR_UNRECOVERABLE;
}
static grub_usb_err_t
{
/* Check transfer status */
if ((intstatus & 0x10) != 0)
/* Unrecoverable error - only reset can help...! */
/* Detected a HALT. */
/* Finished ED detection */
{
/* Check the HALT bit */
/* It looks like nonsense - it was tested previously...
* but it can change because OHCI is working
* simultaneously via DMA... */
else
}
return GRUB_USB_ERR_WAIT;
}
static grub_usb_err_t
{
/* We should wait for next SOF to be sure that ED is unaccessed
* by OHCI */
/* SF bit reset. (SF bit indicates Start Of Frame (SOF) packet) */
/* Wait for new SOF */
/* Possible retired TD with error should be previous TD to ED->td_head */
& ~0xf)->prev_td_phys;
if (tderr_virt)
else
return GRUB_USB_ERR_NONE;
}
static grub_err_t
{
int i;
if (!enable) /* We don't need reset port */
{
/* Disable the port and wait for it. */
& (1 << 1)))
if (grub_get_time_ms () > endtime)
return GRUB_ERR_NONE;
}
/* OHCI does one reset signal 10ms long but USB spec.
* requests 50ms for root hub (no need to be continuous).
* So, we do reset 5 times... */
for (i = 0; i < 5; i++)
{
/* Reset the port - timing of reset is done by OHCI */
/* Wait for reset completion */
if (grub_get_time_ms () > endtime)
/* End the reset signaling - reset the reset status change */
}
/* Enable port */
/* Wait for signal enabled */
& (1 << 1)))
if (grub_get_time_ms () > endtime)
/* Reset bit Connect Status Change */
/* "Reset recovery time" (USB spec.) */
grub_millisleep (10);
return GRUB_ERR_NONE;
}
static grub_usb_speed_t
{
/* Connect Status Change bit - it detects change of connection */
{
*changed = 1;
/* Reset bit Connect Status Change */
}
else
*changed = 0;
if (! (status & 1))
return GRUB_USB_SPEED_NONE;
return GRUB_USB_SPEED_LOW;
else
return GRUB_USB_SPEED_FULL;
}
static int
{
return portinfo & 0xFF;
}
static grub_err_t
{
struct grub_ohci *o;
{
/* Set skip in all EDs */
if (o->ed_bulk)
for (i=0; i < GRUB_OHCI_BULK_EDS; i++)
if (o->ed_ctrl)
for (i=0; i < GRUB_OHCI_CTRL_EDS; i++)
/* We should wait for next SOF to be sure that all EDs are
* unaccessed by OHCI. But OHCI can be non-functional, so
* more than 1ms timeout have to be applied. */
/* SF bit reset. (SF bit indicates Start Of Frame (SOF) packet) */
/* Wait for new SOF or timeout */
== 0) || (grub_get_time_ms () >= maxtime) );
for (i = 0; i < nports; i++)
grub_millisleep (1);
grub_ohci_writereg32 (o, GRUB_OHCI_REG_HCCA, 0);
/* Read back of register should ensure it is really written */
#if 0 /* Is this necessary before booting? Probably not .(?)
* But it must be done if module is removed ! (Or not ?)
* How to do it ? - Probably grub_ohci_restore_hw should be more
* complicated. (?)
* (If we do it, we need to reallocate EDs and TDs in function
* grub_ohci_restore_hw ! */
/* Free allocated EDs and TDs */
grub_dma_free (o->td_chunk);
grub_dma_free (o->ed_bulk_chunk);
grub_dma_free (o->ed_ctrl_chunk);
grub_dma_free (o->hcca_chunk);
#endif
}
grub_millisleep (10);
return GRUB_ERR_NONE;
}
static grub_err_t
grub_ohci_restore_hw (void)
{
struct grub_ohci *o;
{
/* Read back of register should ensure it is really written */
/* Enable the OHCI. */
(2 << 6)
}
return GRUB_ERR_NONE;
}
{
.name = "ohci",
};
{
grub_ohci_inithw ();
}
{
grub_ohci_fini_hw (0);
}