📄 uhci.c
字号:
}
/* Really disable FSBR */
if (!uhci->fsbr && uhci->fsbrtimeout && time_after_eq(jiffies, uhci->fsbrtimeout)) {
uhci->fsbrtimeout = 0;
uhci->skel_term_qh->link = UHCI_PTR_TERM;
}
/* enter global suspend if nothing connected */
if (!uhci->is_suspended && !ports_active(uhci))
suspend_hc(uhci);
rh_init_int_timer(urb);
}
/* Root Hub INTs are polled by this timer */
static int rh_init_int_timer(struct urb *urb)
{
struct uhci *uhci = (struct uhci *)urb->dev->bus->hcpriv;
uhci->rh.interval = urb->interval;
init_timer(&uhci->rh.rh_int_timer);
uhci->rh.rh_int_timer.function = rh_int_timer_do;
uhci->rh.rh_int_timer.data = (unsigned long)urb;
uhci->rh.rh_int_timer.expires = jiffies + (HZ * (urb->interval < 30 ? 30 : urb->interval)) / 1000;
add_timer(&uhci->rh.rh_int_timer);
return 0;
}
#define OK(x) len = (x); break
#define CLR_RH_PORTSTAT(x) \
status = inw(io_addr + USBPORTSC1 + 2 * (wIndex-1)); \
status = (status & 0xfff5) & ~(x); \
outw(status, io_addr + USBPORTSC1 + 2 * (wIndex-1))
#define SET_RH_PORTSTAT(x) \
status = inw(io_addr + USBPORTSC1 + 2 * (wIndex-1)); \
status = (status & 0xfff5) | (x); \
outw(status, io_addr + USBPORTSC1 + 2 * (wIndex-1))
/* Root Hub Control Pipe */
static int rh_submit_urb(struct urb *urb)
{
struct uhci *uhci = (struct uhci *)urb->dev->bus->hcpriv;
unsigned int pipe = urb->pipe;
struct usb_ctrlrequest *cmd = (struct usb_ctrlrequest *)urb->setup_packet;
void *data = urb->transfer_buffer;
int leni = urb->transfer_buffer_length;
int len = 0;
int status = 0;
int stat = 0;
int i;
unsigned int io_addr = uhci->io_addr;
__u16 cstatus;
__u16 bmRType_bReq;
__u16 wValue;
__u16 wIndex;
__u16 wLength;
if (usb_pipetype(pipe) == PIPE_INTERRUPT) {
uhci->rh.urb = urb;
uhci->rh.send = 1;
uhci->rh.interval = urb->interval;
rh_init_int_timer(urb);
return -EINPROGRESS;
}
bmRType_bReq = cmd->bRequestType | cmd->bRequest << 8;
wValue = le16_to_cpu(cmd->wValue);
wIndex = le16_to_cpu(cmd->wIndex);
wLength = le16_to_cpu(cmd->wLength);
for (i = 0; i < 8; i++)
uhci->rh.c_p_r[i] = 0;
switch (bmRType_bReq) {
/* Request Destination:
without flags: Device,
RH_INTERFACE: interface,
RH_ENDPOINT: endpoint,
RH_CLASS means HUB here,
RH_OTHER | RH_CLASS almost ever means HUB_PORT here
*/
case RH_GET_STATUS:
*(__u16 *)data = cpu_to_le16(1);
OK(2);
case RH_GET_STATUS | RH_INTERFACE:
*(__u16 *)data = cpu_to_le16(0);
OK(2);
case RH_GET_STATUS | RH_ENDPOINT:
*(__u16 *)data = cpu_to_le16(0);
OK(2);
case RH_GET_STATUS | RH_CLASS:
*(__u32 *)data = cpu_to_le32(0);
OK(4); /* hub power */
case RH_GET_STATUS | RH_OTHER | RH_CLASS:
status = inw(io_addr + USBPORTSC1 + 2 * (wIndex - 1));
cstatus = ((status & USBPORTSC_CSC) >> (1 - 0)) |
((status & USBPORTSC_PEC) >> (3 - 1)) |
(uhci->rh.c_p_r[wIndex - 1] << (0 + 4));
status = (status & USBPORTSC_CCS) |
((status & USBPORTSC_PE) >> (2 - 1)) |
((status & USBPORTSC_SUSP) >> (12 - 2)) |
((status & USBPORTSC_PR) >> (9 - 4)) |
(1 << 8) | /* power on */
((status & USBPORTSC_LSDA) << (-8 + 9));
*(__u16 *)data = cpu_to_le16(status);
*(__u16 *)(data + 2) = cpu_to_le16(cstatus);
OK(4);
case RH_CLEAR_FEATURE | RH_ENDPOINT:
switch (wValue) {
case RH_ENDPOINT_STALL:
OK(0);
}
break;
case RH_CLEAR_FEATURE | RH_CLASS:
switch (wValue) {
case RH_C_HUB_OVER_CURRENT:
OK(0); /* hub power over current */
}
break;
case RH_CLEAR_FEATURE | RH_OTHER | RH_CLASS:
switch (wValue) {
case RH_PORT_ENABLE:
CLR_RH_PORTSTAT(USBPORTSC_PE);
OK(0);
case RH_PORT_SUSPEND:
CLR_RH_PORTSTAT(USBPORTSC_SUSP);
OK(0);
case RH_PORT_POWER:
OK(0); /* port power */
case RH_C_PORT_CONNECTION:
SET_RH_PORTSTAT(USBPORTSC_CSC);
OK(0);
case RH_C_PORT_ENABLE:
SET_RH_PORTSTAT(USBPORTSC_PEC);
OK(0);
case RH_C_PORT_SUSPEND:
/*** WR_RH_PORTSTAT(RH_PS_PSSC); */
OK(0);
case RH_C_PORT_OVER_CURRENT:
OK(0); /* port power over current */
case RH_C_PORT_RESET:
uhci->rh.c_p_r[wIndex - 1] = 0;
OK(0);
}
break;
case RH_SET_FEATURE | RH_OTHER | RH_CLASS:
switch (wValue) {
case RH_PORT_SUSPEND:
SET_RH_PORTSTAT(USBPORTSC_SUSP);
OK(0);
case RH_PORT_RESET:
SET_RH_PORTSTAT(USBPORTSC_PR);
mdelay(50); /* USB v1.1 7.1.7.3 */
uhci->rh.c_p_r[wIndex - 1] = 1;
CLR_RH_PORTSTAT(USBPORTSC_PR);
udelay(10);
SET_RH_PORTSTAT(USBPORTSC_PE);
mdelay(10);
SET_RH_PORTSTAT(0xa);
OK(0);
case RH_PORT_POWER:
OK(0); /* port power ** */
case RH_PORT_ENABLE:
SET_RH_PORTSTAT(USBPORTSC_PE);
OK(0);
}
break;
case RH_SET_ADDRESS:
uhci->rh.devnum = wValue;
OK(0);
case RH_GET_DESCRIPTOR:
switch ((wValue & 0xff00) >> 8) {
case 0x01: /* device descriptor */
len = min_t(unsigned int, leni,
min_t(unsigned int,
sizeof(root_hub_dev_des), wLength));
memcpy(data, root_hub_dev_des, len);
OK(len);
case 0x02: /* configuration descriptor */
len = min_t(unsigned int, leni,
min_t(unsigned int,
sizeof(root_hub_config_des), wLength));
memcpy (data, root_hub_config_des, len);
OK(len);
case 0x03: /* string descriptors */
len = usb_root_hub_string (wValue & 0xff,
uhci->io_addr, "UHCI-alt",
data, wLength);
if (len > 0) {
OK(min_t(int, leni, len));
} else
stat = -EPIPE;
}
break;
case RH_GET_DESCRIPTOR | RH_CLASS:
root_hub_hub_des[2] = uhci->rh.numports;
len = min_t(unsigned int, leni,
min_t(unsigned int, sizeof(root_hub_hub_des), wLength));
memcpy(data, root_hub_hub_des, len);
OK(len);
case RH_GET_CONFIGURATION:
*(__u8 *)data = 0x01;
OK(1);
case RH_SET_CONFIGURATION:
OK(0);
case RH_GET_INTERFACE | RH_INTERFACE:
*(__u8 *)data = 0x00;
OK(1);
case RH_SET_INTERFACE | RH_INTERFACE:
OK(0);
default:
stat = -EPIPE;
}
urb->actual_length = len;
return stat;
}
/*
* MUST be called with urb->lock acquired
*/
static int rh_unlink_urb(struct urb *urb)
{
struct uhci *uhci = (struct uhci *)urb->dev->bus->hcpriv;
if (uhci->rh.urb == urb) {
urb->status = -ENOENT;
uhci->rh.send = 0;
uhci->rh.urb = NULL;
del_timer(&uhci->rh.rh_int_timer);
}
return 0;
}
static void uhci_free_pending_qhs(struct uhci *uhci)
{
struct list_head *tmp, *head;
unsigned long flags;
spin_lock_irqsave(&uhci->qh_remove_list_lock, flags);
head = &uhci->qh_remove_list;
tmp = head->next;
while (tmp != head) {
struct uhci_qh *qh = list_entry(tmp, struct uhci_qh, remove_list);
tmp = tmp->next;
list_del_init(&qh->remove_list);
uhci_free_qh(uhci, qh);
}
spin_unlock_irqrestore(&uhci->qh_remove_list_lock, flags);
}
static void uhci_call_completion(struct urb *urb)
{
struct urb_priv *urbp;
struct usb_device *dev = urb->dev;
struct uhci *uhci = (struct uhci *)dev->bus->hcpriv;
int killed, resubmit_interrupt, status;
unsigned long flags;
spin_lock_irqsave(&urb->lock, flags);
urbp = (struct urb_priv *)urb->hcpriv;
if (!urbp || !urb->dev) {
spin_unlock_irqrestore(&urb->lock, flags);
return;
}
killed = (urb->status == -ENOENT || urb->status == -ECONNABORTED ||
urb->status == -ECONNRESET);
resubmit_interrupt = (usb_pipetype(urb->pipe) == PIPE_INTERRUPT &&
urb->interval);
if (urbp->transfer_buffer_dma_handle)
pci_dma_sync_single(uhci->dev, urbp->transfer_buffer_dma_handle,
urb->transfer_buffer_length, usb_pipein(urb->pipe) ?
PCI_DMA_FROMDEVICE : PCI_DMA_TODEVICE);
if (urbp->setup_packet_dma_handle)
pci_dma_sync_single(uhci->dev, urbp->setup_packet_dma_handle,
sizeof(struct usb_ctrlrequest), PCI_DMA_TODEVICE);
status = urbp->status;
if (!resubmit_interrupt || killed)
/* We don't need urb_priv anymore */
uhci_destroy_urb_priv(urb);
if (!killed)
urb->status = status;
urb->dev = NULL;
spin_unlock_irqrestore(&urb->lock, flags);
if (urb->complete)
urb->complete(urb);
if (resubmit_interrupt)
/* Recheck the status. The completion handler may have */
/* unlinked the resubmitting interrupt URB */
killed = (urb->status == -ENOENT ||
urb->status == -ECONNABORTED ||
urb->status == -ECONNRESET);
if (resubmit_interrupt && !killed) {
urb->dev = dev;
uhci_reset_interrupt(urb);
} else {
/* We decrement the usage count after we're done */
/* with everything */
usb_put_dev(dev);
usb_put_urb(urb);
}
}
static void uhci_finish_completion(struct uhci *uhci)
{
struct list_head *tmp, *head;
unsigned long flags;
spin_lock_irqsave(&uhci->complete_list_lock, flags);
head = &uhci->complete_list;
tmp = head->next;
while (tmp != head) {
struct urb_priv *urbp = list_entry(tmp, struct urb_priv, complete_list);
struct urb *urb = urbp->urb;
list_del_init(&urbp->complete_list);
spin_unlock_irqrestore(&uhci->complete_list_lock, flags);
uhci_call_completion(urb);
spin_lock_irqsave(&uhci->complete_list_lock, flags);
head = &uhci->complete_list;
tmp = head->next;
}
spin_unlock_irqrestore(&uhci->complete_list_lock, flags);
}
static void uhci_remove_pending_qhs(struct uhci *uhci)
{
struct list_head *tmp, *head;
unsigned long flags;
spin_lock_irqsave(&uhci->urb_remove_list_lock, flags);
head = &uhci->urb_remove_list;
tmp = head->next;
while (tmp != head) {
struct urb *urb = list_entry(tmp, struct urb, urb_list);
struct urb_priv *urbp = (struct urb_priv *)urb->hcpriv;
tmp = tmp->next;
list_del_init(&urb->urb_list);
urbp->status = urb->status = -ECONNRESET;
uhci_add_complete(urb);
}
spin_unlock_irqrestore(&uhci->urb_remove_list_lock, flags);
}
static void uhci_interrupt(int irq, void *__uhci, struct pt_regs *regs)
{
struct uhci *uhci = __uhci;
unsigned int io_addr = uhci->io_addr;
unsigned short status;
struct list_head *tmp, *head;
/*
* Read the interrupt status, and write it back to clear the
* interrupt cause
*/
status = inw(io_addr + USBSTS);
if (!status) /* shared interrupt, not mine */
return;
outw(status, io_addr + USBSTS); /* Clear it */
if (status & ~(USBSTS_USBINT | USBSTS_ERROR | USBSTS_RD)) {
if (status & USBSTS_HSE)
err("%x: host system error, PCI problems?", io_addr);
if (status & USBSTS_HCPE)
err("%x: host controller process error. something bad happened", io_addr);
if ((status & USBSTS_HCH) && !uhci->is_suspended) {
err("%x: host controller halted. very bad", io_addr);
/* FIXME: Reset the controller, fix the offending TD */
}
}
if (status & USBSTS_RD)
wakeup_hc(uhci);
uhci_free_pending_qhs(uhci);
uhci_remove_pending_qhs(uhci);
uhci_clear_next_interrupt(uhci);
/* Walk the list of pending URB's to see which ones completed */
spin_lock(&uhci->urb_list_lock);
head = &uhci->urb_list;
tmp = head->next;
while (tmp != head) {
struct urb *urb = list_entry(tmp, struct urb, urb_list);
tmp = tmp->next;
/* Checks the status and does all of the magic necessary */
uhci_transfer_result(uhci, urb);
}
spin_unlock(&uhci->urb_list_lock);
uhci_finish_completion(uhci);
}
static void reset_hc(struct uhci *uhci)
{
unsigned int io_addr = uhci->io_addr;
/* Global reset for 50ms */
outw(USBCMD_GRESET, io_addr + USBCMD);
wait_ms(50);
outw(0, io_addr + USBCMD);
wait_ms(10);
}
static void suspend_hc(struct uhci *uhci)
{
unsigned int io_addr = uhci->io_addr;
dbg("%x: suspend_hc", io_addr);
outw(USBCMD_EGSM, io_addr + USBCMD);
uhci->is_suspended = 1;
}
static void wakeup_hc(struct uhci *uhci)
{
unsigned int io_addr = uhci->io_addr;
unsigned int status;
dbg("%x: wakeup_hc", io_addr);
outw(0, io_addr + USBCMD);
/* wait for EOP to be sent */
status = inw(io_addr + USBCMD);
while (status & USBCMD_FGR)
status = inw(io_addr + USBCMD);
uhci->is_suspended = 0;
/* Run and mark it configured with a 64-byte max packet */
outw(USBCMD_RS | USBCMD_CF | USBCMD_MAXP, io_addr + USBCMD);
}
static int ports_active(struct uhci *uhci)
{
unsigned int io_addr = uhci->io_addr;
int connection = 0;
int i;
for (i = 0; i < uhci->rh.numports; i++)
connection |= (inw(io_addr + USBPORTSC1 + i * 2) & 0x1);
return connection;
}
static void start_hc(struct uhci *uhci)
{
unsigned int io_addr = uhci->io_addr;
int timeout = 1000;
/*
* Reset the HC - this will force us to get a
* new notification of any already connected
* ports due to the virtual disconnect that it
* implies.
*/
outw(USBCMD_HCRESET, io_addr + USBCMD);
while (inw(io_addr + USBCMD) & USBCMD_HCRESET) {
if (!--timeout) {
printk(KERN_ERR "uhci: USBCMD_HCRESET timed out!\n");
break;
}
}
/* Turn on all interrupts */
outw(USBINTR_TIMEOUT | USBINTR_RESUME | USBINTR_IOC | USBINTR_SP,
io_addr + USBINTR);
/* Start at frame 0 */
outw(0, io_addr + USBFRNUM);
outl(uhci->fl->dma_handle, io_addr + USBFLBASEADD);
/* Run and mark it configured with
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -