/* uhci.c - UHCI 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+");
typedef enum
{
/* Some UHCI commands */
/* Important bits in structures */
enum
{
/* These bits should not be written as 1 unless we really need it */
};
/* UHCI Queue Head. */
struct grub_uhci_qh
{
/* Queue head link pointer which points to the next queue head. */
/* Queue element link pointer which points to the first data object
within the queue. */
/* Queue heads are aligned on 16 bytes, pad so a queue head is 16
bytes so we can store many in a 4K page. */
} __attribute__ ((packed));
/* UHCI Transfer Descriptor. */
struct grub_uhci_td
{
/* Pointer to the next TD in the list. */
/* Control and status bits. */
/* All information required to transfer the Token packet. */
/* A pointer to the data buffer, UHCI requires this pointer to be 32
bits. */
/* Another linkptr that is not overwritten by the Host Controller.
This is GRUB specific. */
/* 3 additional 32 bits words reserved for the Host Controller Driver. */
} __attribute__ ((packed));
struct grub_uhci
{
int iobase;
/* N_QH Queue Heads. */
/* N_TD Transfer Descriptors. */
/* Free Transfer Descriptors. */
};
static grub_uint16_t
{
}
#if 0
static grub_uint32_t
{
}
#endif
static void
{
}
static void
{
}
static grub_err_t
/* Iterate over all PCI devices. Determine if a device is an UHCI
controller. If this is the case, initialize it. */
static int NESTED_FUNC_ATTR
{
struct grub_uhci *u;
int i;
/* If this is not an UHCI controller, just return. */
return 0;
/* Set bus master - needed for coreboot or broken BIOSes */
/* Determine IO base address. */
/* Stop if there is no IO space base address defined. */
if (! (base & 1))
return 0;
/* Allocate memory for the controller and register it. */
u = grub_zalloc (sizeof (*u));
if (! u)
return 1;
/* Reset PIRQ and SMI */
/* Reset the HC */
grub_millisleep(5);
/* Disable interrupts and commands (just to be safe) */
/* Finish HC reset, HC remains disabled */
/* Read back to be sure PCI write is done */
/* Reserve a page for the frame list. */
if (! u->framelist)
goto fail;
/* The framelist pointer of UHCI is only 32 bits, make sure this
code works on on 64 bits architectures. */
#if GRUB_CPU_SIZEOF_VOID_P == 8
{
"allocated frame list memory not <4GB");
goto fail;
}
#endif
/* The QH pointer of UHCI is only 32 bits, make sure this
code works on on 64 bits architectures. */
if (! u->qh)
goto fail;
#if GRUB_CPU_SIZEOF_VOID_P == 8
{
goto fail;
}
#endif
/* The TD pointer of UHCI is only 32 bits, make sure this
code works on on 64 bits architectures. */
if (! u->td)
goto fail;
#if GRUB_CPU_SIZEOF_VOID_P == 8
{
goto fail;
}
#endif
/* Link all Transfer Descriptors in a list of available Transfer
Descriptors. */
for (i = 0; i < N_TD; i++)
/* Setup the frame list pointers. Since no isochronous transfers
are and will be supported, they all point to the (same!) queue
head. */
/* Mark this as a queue head. */
fp |= 2;
for (i = 0; i < 1024; i++)
/* Program the framelist address into the UHCI controller. */
/* Make the Queue Heads point to each other. */
for (i = 0; i < N_QH; i++)
{
/* Point to the next QH. */
/* This is a QH. */
/* For the moment, do not point to a Transfer Descriptor. These
are set at transfer time, so just terminate it. */
}
/* The last Queue Head should terminate. */
/* Enable UHCI again. */
/* UHCI is initialized and ready for transfers. */
#if 0
{
int i;
for (i = 0; i < 10; i++)
{
grub_millisleep (100);
}
}
#endif
/* Link to uhci now that initialisation is successful. */
uhci = u;
return 0;
fail:
if (u)
{
}
grub_free (u);
return 1;
}
static void
grub_uhci_inithw (void)
{
}
static grub_uhci_td_t
{
/* Check if there is a Transfer Descriptor available. */
if (! u->tdfree)
return NULL;
return ret;
}
static void
{
}
static void
{
int i; /* Index of TD in transfer */
*actual = 0;
/* Free the TDs in this queue and set last_trans. */
for (i=0; td; i++)
{
/* Check state of TD and possibly set last_trans */
transfer->last_trans = i;
/* Unlink the queue. */
/* Free the TD. */
grub_free_td (u, tdprev);
}
}
static grub_uhci_qh_t
{
int i;
/* Look for a Queue Head for this transfer. Skip the first QH if
this is a Interrupt Transfer. */
#if 0
i = 0;
else
#endif
i = 1;
for (; i < N_QH; i++)
{
if (!u->qh_busy[i])
break;
}
if (i == N_QH)
{
"no free queue heads available");
return NULL;
}
return qh;
}
static grub_uhci_td_t
{
/* XXX: Check if data is <4GB. If it isn't, just copy stuff around.
This is only relevant for 64 bits architectures. */
/* Grab a free Transfer Descriptor and initialize it. */
td = grub_alloc_td (u);
if (! td)
{
"no transfer descriptors available for UHCI transfer");
return 0;
}
grub_dprintf ("uhci",
"transaction: endp=%d, type=%d, addr=%d, toggle=%d, size=%lu data=0x%x td=%p\n",
/* Don't point to any TD, just terminate. */
/* Active! Only retry a transfer 3 times. */
/* If zero bytes are transmitted, size is 0x7FF. Otherwise size is
size-1. */
if (size == 0)
size = 0x7FF;
else
/* Setup whatever is required for the token packet. */
return td;
}
{
};
static grub_usb_err_t
{
int i;
if (!cdata)
return GRUB_USB_ERR_INTERNAL;
/* Allocate a queue head for the transfer queue. */
{
return GRUB_USB_ERR_INTERNAL;
}
{
if (! td)
{
/* Terminate and free. */
if (td_prev)
{
}
return GRUB_USB_ERR_INTERNAL;
}
else
{
}
}
/* Link it into the queue and terminate. Now the transaction can
take place. */
return GRUB_USB_ERR_NONE;
}
static grub_usb_err_t
{
*actual = 0;
/* Check if the transaction completed. */
{
/* Place the QH back in the free list and deallocate the associated
TDs. */
return GRUB_USB_ERR_NONE;
}
{
/* Check if the endpoint is stalled. */
/* Check if an error related to the data buffer occurred. */
/* Check if a babble error occurred. */
/* Check if a NAK occurred. */
/* Check if a timeout occurred. */
/* Check if a bitstuff error occurred. */
if (err)
{
/* Place the QH back in the free list and deallocate the associated
TDs. */
return err;
}
}
/* Fall through, no errors occurred, so the QH might be
updated. */
return GRUB_USB_ERR_WAIT;
}
static grub_usb_err_t
{
/* Place the QH back in the free list and deallocate the associated
TDs. */
return GRUB_USB_ERR_NONE;
}
static int
{
struct grub_uhci *u;
{
return 1;
}
return 0;
}
static grub_err_t
{
int reg;
unsigned int status;
if (port == 0)
else if (port == 1)
else
return grub_error (GRUB_ERR_OUT_OF_RANGE,
"UHCI Root Hub port does not exist");
if (!enable) /* We don't need reset port */
{
/* Disable the port. */
if (grub_get_time_ms () > endtime)
return GRUB_ERR_NONE;
}
/* Reset the port. */
/* Wait for the reset to complete. XXX: How long exactly? */
/* Reset bits Connect & Enable Status Change */
/* Enable the port. */
if (grub_get_time_ms () > endtime)
/* Reset recovery time */
grub_millisleep (10);
/* Read final port status */
return GRUB_ERR_NONE;
}
static grub_usb_speed_t
{
int reg;
unsigned int status;
if (port == 0)
else if (port == 1)
else
return GRUB_USB_SPEED_NONE;
/* 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
{
/* The root hub has exactly two ports. */
return 2;
}
{
.name = "uhci",
};
{
grub_uhci_inithw ();
}
{
struct grub_uhci *u;
/* Disable all UHCI controllers. */
grub_uhci_writereg16 (u, GRUB_UHCI_REG_USBCMD, 0);
/* Unregister the controller. */
}