X-Git-Url: https://pintos-os.org/cgi-bin/gitweb.cgi?a=blobdiff_plain;f=src%2Fdevices%2Fusb_uhci.c;fp=src%2Fdevices%2Fusb_uhci.c;h=1e5a64a152040fe2d546746e4a03b5ace3873996;hb=b4e9c266d656c6b595cc57920a34937776acc300;hp=0000000000000000000000000000000000000000;hpb=6ffbc2b68c34c2d1e42d5f6bcd8f2b94b82d05d7;p=pintos-anon diff --git a/src/devices/usb_uhci.c b/src/devices/usb_uhci.c new file mode 100644 index 0000000..1e5a64a --- /dev/null +++ b/src/devices/usb_uhci.c @@ -0,0 +1,1263 @@ +/** + * Universal Host Controller Interface driver + * TODO: + * Stall timeouts + * Better (any) root hub handling + */ + +#include +#include +#include +#include +#include "threads/pte.h" +#include "threads/malloc.h" +#include "threads/palloc.h" +#include "threads/synch.h" +#include "threads/interrupt.h" +#include "threads/thread.h" +#include "devices/pci.h" +#include "devices/usb.h" +#include "devices/timer.h" + +#define UHCI_MAX_PORTS 8 + +#define FRAME_LIST_ENTRIES 1024 +#define TD_ENTRIES (4096/32) /* number of entries allocated */ +#define QH_ENTRIES (4096/16) + +/* uhci pci registers */ +#define UHCI_REG_USBCMD 0x00 /* Command */ +#define UHCI_REG_USBSTS 0x02 /* Status */ +#define UHCI_REG_USBINTR 0x04 /* interrupt enable */ +#define UHCI_REG_FRNUM 0x06 /* frame number */ +#define UHCI_REG_FLBASEADD 0x08 /* frame list base address */ +#define UHCI_REG_SOFMOD 0x0c /* start of frame modify */ +#define UHCI_REG_PORTSC1 0x10 /* port 1 status/control */ +#define UHCI_REG_PORTSC2 0x12 /* port 2 status/control */ +#define UHCI_REGSZ 0x20 /* register iospace size */ + +/* in PCI config space for some reason */ +#define UHCI_REG_LEGSUP 0xC0 + + +/* command register */ +#define USB_CMD_MAX_PACKET (1 << 7) +#define USB_CMD_CONFIGURE (1 << 6) +#define USB_CMD_FGR (1 << 4) /* force global resume */ +#define USB_CMD_EGSM (1 << 3) /* global suspend mode */ +#define USB_CMD_GRESET (1 << 2) /* global reset */ +#define USB_CMD_HCRESET (1 << 1) /* host controller reset */ +#define USB_CMD_RS (1 << 0) /* run/stop */ + +/* status register */ +#define USB_STATUS_HALTED (1 << 5) +#define USB_STATUS_PROCESSERR (1 << 4) +#define USB_STATUS_HOSTERR (1 << 3) +#define USB_STATUS_RESUME (1 << 2) +#define USB_STATUS_INTERR (1 << 1) +#define USB_STATUS_USBINT (1 << 0) + +/* interrupt enable register */ +#define USB_INTR_SHORT (1 << 3) /* enable short packets */ +#define USB_INTR_IOC (1 << 2) /* interrupt on complete */ +#define USB_INTR_RESUME (1 << 1) /* resume interrupt enable */ +#define USB_INTR_TIMEOUT (1 << 0) /* timeout int enable */ + +/* port control register */ +#define USB_PORT_SUSPEND (1 << 12) +#define USB_PORT_RESET (1 << 9) +#define USB_PORT_LOWSPEED (1 << 8) +#define USB_PORT_RESUMED (1 << 6) /* resume detected */ +#define USB_PORT_CHANGE (1 << 3) /* enable change */ +#define USB_PORT_ENABLE (1 << 2) /* enable the port */ +#define USB_PORT_CONNECTCHG (1 << 1) /* connect status changed */ +#define USB_PORT_CONNECTSTATUS (1 << 0) /* device is connected */ + +#define ptr_to_flp(x) (((uintptr_t)x) >> 4) +#define flp_to_ptr(x) (uintptr_t)(((uintptr_t)x) << 4) + +/* frame structures */ +#pragma pack(1) +struct frame_list_ptr +{ + uint32_t terminate:1; + uint32_t qh_select:1; + uint32_t depth_select:1; /* only for TD */ + uint32_t resv:1; /* zero */ + uint32_t flp:28; /* frame list pointer */ +}; + +struct td_token +{ + uint32_t pid:8; /* packet id */ + uint32_t dev_addr:7; /* device address */ + uint32_t end_point:4; + uint32_t data_toggle:1; + uint32_t resv:1; + uint32_t maxlen:11; /* maximum pkt length */ +}; + +struct td_control +{ + uint32_t actual_len:11; + uint32_t resv1:5; + + /* status information */ + uint32_t resv2:1; + uint32_t bitstuff:1; + uint32_t timeout:1; + uint32_t nak:1; + uint32_t babble:1; + uint32_t buffer_error:1; + uint32_t stalled:1; + uint32_t active:1; + + /* config data */ + uint32_t ioc:1; /* issue int on complete */ + uint32_t ios:1; /* isochronous select */ + uint32_t ls:1; /* low speed device */ + uint32_t error_limit:2; /* errors before interrupt */ + uint32_t spd:1; /* short packet detect */ + uint32_t resv3:2; +}; + +#define TD_FL_ASYNC 1 +#define TD_FL_USED 0x80000000 + +struct tx_descriptor +{ + struct frame_list_ptr flp; + struct td_control control; + struct td_token token; + uint32_t buf_ptr; + + + struct frame_list_ptr *head; /* for fast removal */ + uint32_t flags; +}; + +struct queue_head +{ + struct frame_list_ptr qhlp; /* queue head link pointer */ + struct frame_list_ptr qelp; /* queue elem link pointer */ +}; +#pragma pack() + +struct uhci_info +{ + struct pci_dev *dev; + struct pci_io *io; /* pci io space */ + struct lock lock; + struct frame_list_ptr *frame_list; /* page aligned frame list */ + + struct tx_descriptor *td_pool; + struct bitmap *td_used; + struct queue_head *qh_pool; + struct bitmap *qh_used; + + uint8_t num_ports; + uint8_t attached_ports; + + int timeouts; /* number of timeouts */ + + struct semaphore td_sem; + struct list devices; /* devices on host */ + struct list waiting; /* threads waiting */ +}; + +struct usb_wait +{ + struct tx_descriptor *td; + struct uhci_dev_info *ud; + struct semaphore sem; + struct list_elem peers; +}; + +struct uhci_dev_info +{ + struct uhci_info *ui; /* owner */ + bool low_speed; /* whether device is low speed */ + int dev_addr; /* device address */ + int errors; /* aggregate errors */ + struct list_elem peers; /* next dev on host */ + struct lock lock; + struct queue_head *qh; +}; + +struct uhci_eop_info +{ + struct uhci_dev_info *ud; + int eop; + int maxpkt; /* max packet size */ + int toggle; /* data toggle bit for bulk transfers */ +}; + +#define uhci_lock(x) lock_acquire(&(x)->lock) +#define uhci_unlock(x) lock_release(&(x)->lock) +#define dev_lock(x) lock_acquire(&(x)->lock) +#define dev_unlock(x) lock_release(&(x)->lock) + + +static int token_to_pid (int token); + +static int uhci_tx_pkt (host_eop_info eop, int token, void *pkt, + int min_sz, int max_sz, int *in_sz, bool wait); + +static int uhci_detect_change (host_info); + + +static int uhci_tx_pkt_now (struct uhci_eop_info *ue, int token, void *pkt, + int sz); +static int uhci_tx_pkt_wait (struct uhci_eop_info *ue, int token, void *pkt, + int max_sz, int *in_sz); +static int uhci_tx_pkt_bulk (struct uhci_eop_info *ue, int token, void *buf, + int sz, int *tx); + + +static int uhci_process_completed (struct uhci_info *ui); + +static struct tx_descriptor *uhci_acquire_td (struct uhci_info *); +static void uhci_release_td (struct uhci_info *, struct tx_descriptor *); +static void uhci_remove_qh (struct uhci_info *ui, struct queue_head *qh); + + +static void qh_free (struct uhci_info *ui, struct queue_head *qh); +static struct queue_head *qh_alloc (struct uhci_info *ui); + +static struct uhci_info *uhci_create_info (struct pci_io *io); +static void uhci_destroy_info (struct uhci_info *ui); +static host_eop_info uhci_create_eop (host_dev_info hd, int eop, int maxpkt); +static void uhci_remove_eop (host_eop_info hei); +static host_dev_info uhci_create_chan (host_info hi, int dev_addr, int ver); +static void uhci_destroy_chan (host_dev_info); +static void uhci_modify_chan (host_dev_info, int dev_addr, int ver); + +static struct tx_descriptor *td_from_pool (struct uhci_info *ui, int idx); + +static int check_and_flip_change (struct uhci_info *ui, int reg); +static void uhci_stop (struct uhci_info *ui); +static void uhci_run (struct uhci_info *ui); +static void uhci_stop_unlocked (struct uhci_info *ui); +static void uhci_run_unlocked (struct uhci_info *ui); +#define uhci_is_stopped(x) (pci_reg_read16((x)->io, UHCI_REG_USBSTS) \ + & USB_STATUS_HALTED) +#define uhci_port_enabled(x, y) (pci_reg_read16((x)->io, (y)) & USB_PORT_ENABLE) +static void uhci_add_td_to_qh (struct queue_head *qh, + struct tx_descriptor *td); +static void uhci_remove_error_td (struct tx_descriptor *td); +static void uhci_setup_td (struct tx_descriptor *td, int dev_addr, int token, + int eop, void *pkt, int sz, int toggle, bool ls); +static int uhci_enable_port (struct uhci_info *ui, int port); +static void uhci_irq (void *uhci_data); +static void uhci_detect_ports (struct uhci_info *ui); + +static int uhci_remove_stalled (struct uhci_info *ui); +static void uhci_stall_watchdog (struct uhci_info *ui); + +static void dump_all_qh (struct uhci_info *ui); +static void dump_qh (struct queue_head *qh); + +static void dump_regs (struct uhci_info *ui); + +void uhci_init (void); + + +static struct usb_host uhci_host = { + .name = "UHCI", + .tx_pkt = uhci_tx_pkt, + .detect_change = uhci_detect_change, + .create_dev_channel = uhci_create_chan, + .remove_dev_channel = uhci_destroy_chan, + .modify_dev_channel = uhci_modify_chan, + .create_eop = uhci_create_eop, + .remove_eop = uhci_remove_eop +}; + +void +uhci_init (void) +{ + struct pci_dev *pd; + int dev_num; + + dev_num = 0; + while ((pd = pci_get_dev_by_class (PCI_MAJOR_SERIALBUS, PCI_MINOR_USB, + PCI_USB_IFACE_UHCI, dev_num)) != NULL) + { + struct pci_io *io; + struct uhci_info *ui; + uint8_t sof; + int i; + + dev_num++; + + /* find IO space */ + io = NULL; + while ((io = pci_io_enum (pd, io)) != NULL) + { + if (pci_io_size (io) == UHCI_REGSZ) + break; + } + + /* not found, next PCI */ + if (io == NULL) + continue; + + ui = uhci_create_info (io); + ui->dev = pd; + + sof = pci_reg_read8 (ui->io, UHCI_REG_SOFMOD); + uhci_detect_ports (ui); + + /* reset devices */ + pci_reg_write16 (ui->io, UHCI_REG_USBCMD, USB_CMD_GRESET); + timer_msleep (50); + pci_reg_write16 (ui->io, UHCI_REG_USBCMD, 0); + timer_msleep (1); + + /* reset controller */ + pci_reg_write16 (ui->io, UHCI_REG_USBCMD, USB_CMD_HCRESET); + timer_msleep (1); + if (pci_reg_read16 (ui->io, UHCI_REG_USBCMD) & USB_CMD_HCRESET) + { + printf ("UHCI: Reset timed out\n"); + uhci_destroy_info (ui); + continue; + } + pci_reg_write16 (ui->io, UHCI_REG_USBINTR, 0); + pci_reg_write16 (ui->io, UHCI_REG_USBCMD, 0); + + for (i = 0; i < ui->num_ports; i++) + pci_reg_write16 (ui->io, UHCI_REG_PORTSC1 + i * 2, 0); + + timer_msleep (100); + printf ("UHCI: Enabling %d root ports\n", ui->num_ports); + ui->attached_ports = 0; + for (i = 0; i < ui->num_ports; i++) + ui->attached_ports += uhci_enable_port (ui, i); + + pci_reg_write8 (ui->io, UHCI_REG_SOFMOD, sof); + pci_reg_write16 (ui->io, UHCI_REG_FRNUM, 0); + pci_reg_write32 (ui->io, UHCI_REG_FLBASEADD, vtop (ui->frame_list)); + pci_reg_write16 (ui->io, UHCI_REG_USBINTR, + USB_INTR_SHORT | USB_INTR_IOC | + USB_INTR_TIMEOUT | USB_INTR_RESUME); + + /* deactivate SMM junk, only enable IRQ */ + pci_write_config16 (ui->dev, UHCI_REG_LEGSUP, 0x2000); + + uhci_lock (ui); + uhci_run (ui); + uhci_unlock (ui); + + pci_register_irq (pd, uhci_irq, ui); + + usb_register_host (&uhci_host, ui); + } +} + +#define UHCI_PORT_TIMEOUT 1000 +static int +uhci_enable_port (struct uhci_info *ui, int port) +{ + uint16_t status; + int count; + + /* individual ports must be reset for QEMU to go into USB_STATE_DEFAULT */ + pci_reg_write16 (ui->io, UHCI_REG_PORTSC1 + port * 2, USB_PORT_RESET); + timer_msleep (50); + + /* ACK disconnect from reset so we can see if port is connected */ + pci_reg_write16 (ui->io, UHCI_REG_PORTSC1 + port * 2, + USB_PORT_ENABLE | USB_PORT_CONNECTCHG); + timer_msleep (10); + status = pci_reg_read16 (ui->io, UHCI_REG_PORTSC1 + port * 2); + if (!(status & USB_PORT_CONNECTSTATUS)) + { + return 0; + } + + /* ACK CONNECTCHG status so port can be enabled */ + pci_reg_write16 (ui->io, UHCI_REG_PORTSC1 + port * 2, + USB_PORT_ENABLE | USB_PORT_CONNECTCHG); + for (count = 0; count < UHCI_PORT_TIMEOUT; count++) + { + if (uhci_port_enabled (ui, UHCI_REG_PORTSC1 + port * 2)) + { + pci_reg_write16 (ui->io, UHCI_REG_PORTSC1 + port * 2, + USB_PORT_ENABLE | USB_PORT_CONNECTCHG); + return 1; + } + } + + printf ("UHCI: Port %d enable timed out\n", port); + dump_regs (ui); + + return 0; +} + + +static void +dump_regs (struct uhci_info *ui) +{ + int regs[] = { 0, 2, 4, 6, 8, 0xc, 0x10, 0x12 }; + int sz[] = { 2, 2, 2, 2, 4, 2, 2, 2 }; + char *name[] = + { "cmd", "sts", "intr", "frnum", "base", "sofmod", "portsc1", "portsc2" }; + int i; + printf ("UHCI registers:\n"); + for (i = 0; i < 8; i++) + { + printf ("%s: %x\n", name[i], (sz[i] == 2) ? + pci_reg_read16 (ui->io, regs[i]) : + pci_reg_read32 (ui->io, regs[i])); + + } +} + + +static void +uhci_destroy_info (struct uhci_info *ui) +{ + palloc_free_page (ui->frame_list); + palloc_free_page (ui->td_pool); + palloc_free_page (ui->qh_pool); + bitmap_destroy (ui->qh_used); + bitmap_destroy (ui->td_used); + free (ui); +} + +static struct uhci_info * +uhci_create_info (struct pci_io *io) +{ + struct uhci_info *ui; + int i; + + ui = malloc (sizeof (struct uhci_info)); + + ui->io = io; + lock_init (&ui->lock); + + /* create an empty schedule */ + ui->frame_list = palloc_get_page (PAL_ASSERT | PAL_NOCACHE); + memset (ui->frame_list, 0, PGSIZE); + for (i = 0; i < FRAME_LIST_ENTRIES; i++) + ui->frame_list[i].terminate = 1; + + /* permit 3 timeouts */ + ui->timeouts = 3; +// thread_create ("uhci watchdog", PRI_MIN, +// (thread_func *) uhci_stall_watchdog, ui); + ui->td_pool = palloc_get_page (PAL_ASSERT | PAL_NOCACHE); + ui->td_used = bitmap_create (TD_ENTRIES); + ui->qh_pool = palloc_get_page (PAL_ASSERT | PAL_NOCACHE); + ui->qh_used = bitmap_create (QH_ENTRIES); + sema_init (&ui->td_sem, TD_ENTRIES); + + list_init (&ui->devices); + list_init (&ui->waiting); + + return ui; +} + + +static int +uhci_tx_pkt (host_eop_info hei, int token, void *pkt, int min_sz, + int max_sz, int *in_sz, bool wait) +{ + struct uhci_eop_info *ue; + struct uhci_dev_info *ud; + + ASSERT (min_sz <= max_sz); + + /* can't have page overlap */ + if (pkt != NULL) + { + ASSERT (max_sz > 0); + ASSERT (pg_no (pkt + max_sz - 1) == pg_no (pkt)); + } + + ue = hei; + ud = ue->ud; + + /* don't bother if ports are down */ + if (ud->ui->attached_ports == 0) + { + return USB_HOST_ERR_NODEV; + } + + /* setup token acts to synchronize data toggle */ + if (token == USB_TOKEN_SETUP) + ue->toggle = 0; + + if (min_sz != 0) + { + return uhci_tx_pkt_bulk (ue, token, pkt, max_sz, in_sz); + } + else + { + if (wait == false) + { + if (in_sz != NULL) + *in_sz = max_sz; + return uhci_tx_pkt_now (ue, token, pkt, max_sz); + } + else + { + return uhci_tx_pkt_wait (ue, token, pkt, max_sz, in_sz); + } + } + + return 0; +} + +static int +uhci_tx_pkt_bulk (struct uhci_eop_info *ue, int token, void *buf, + int sz, int *tx) +{ + /* XXX this can be made to use async packets */ + int bytes = 0; + int txed = 0; + + /* send data in max_pkt sized chunks */ + while (bytes < sz) + { + int to_tx, pkt_txed; + int left; + int err; + bool wait_on_pkt; + + left = sz - bytes; + to_tx = (left > ue->maxpkt) ? ue->maxpkt : left; + wait_on_pkt = (left <= to_tx) ? true : false; + + pkt_txed = 0; + err = uhci_tx_pkt (ue, token, buf + bytes, 0, to_tx, &pkt_txed, + wait_on_pkt); + if (err) + { + if (tx != NULL) + *tx = txed; + return err; + } + txed += pkt_txed; + bytes += pkt_txed; + } + + if (tx != NULL) + *tx = txed; + + return USB_HOST_ERR_NONE; +} + +static int +token_to_pid (int token) +{ + switch (token) + { + case USB_TOKEN_SETUP: + return USB_PID_SETUP; + case USB_TOKEN_IN: + return USB_PID_IN; + case USB_TOKEN_OUT: + return USB_PID_OUT; + default: + PANIC ("Unknown USB token\n"); + } +} + +static void +uhci_setup_td (struct tx_descriptor *td, int dev_addr, int token, + int eop, void *pkt, int sz, int toggle, bool ls) +{ + td->buf_ptr = (sz == 0) ? 0 : vtop (pkt); + + td->token.pid = token_to_pid (token); + td->token.dev_addr = dev_addr; + td->token.end_point = eop; + td->token.data_toggle = toggle; + td->token.maxlen = sz - 1; +// td->control.ls = ls; + + td->control.actual_len = 0; + td->control.active = 1; + td->flp.qh_select = 0; + td->flp.depth_select = 0; + + /* kill packet if too many errors */ + td->control.error_limit = 3; +} + +static int +uhci_tx_pkt_now (struct uhci_eop_info *ue, int token, void *pkt, int sz) +{ + struct tx_descriptor *td; + struct uhci_dev_info *ud; + + ud = ue->ud; + + uhci_lock (ud->ui); + + td = uhci_acquire_td (ud->ui); + memset (td, 0, sizeof (struct tx_descriptor)); + uhci_setup_td (td, ud->dev_addr, token, ue->eop, pkt, sz, ue->toggle, + ud->low_speed); + td->control.ioc = 1; + + uhci_stop (ud->ui); + + uhci_add_td_to_qh (ud->qh, td); + td->flags = TD_FL_ASYNC | TD_FL_USED; + + uhci_run (ud->ui); + uhci_unlock (ud->ui); + + ue->toggle ^= 1; + return USB_HOST_ERR_NONE; +} + +static int +uhci_tx_pkt_wait (struct uhci_eop_info *ue, int token, void *pkt, + int max_sz, int *in_sz) +{ + enum intr_level old_lvl; + struct tx_descriptor *td; + struct usb_wait w; + int err; + struct uhci_dev_info *ud; + + ud = ue->ud; + + uhci_lock (ud->ui); + + td = uhci_acquire_td (ud->ui); + memset (td, 0, sizeof (struct tx_descriptor)); + + uhci_setup_td (td, ud->dev_addr, token, ue->eop, pkt, max_sz, ue->toggle, + ud->low_speed); + td->control.ioc = 1; + + w.td = td; + w.ud = ud; + sema_init (&w.sem, 0); + + uhci_stop (ud->ui); + + /* put into device's queue and add to waiting packet list */ + uhci_add_td_to_qh (ud->qh, td); + td->flags = TD_FL_USED; + + list_push_back (&ud->ui->waiting, &w.peers); + + /* reactivate controller and wait */ + old_lvl = intr_disable (); + uhci_run (ud->ui); + uhci_unlock (ud->ui); + sema_down (&w.sem); + intr_set_level (old_lvl); + + if (in_sz != NULL) + { + if (w.td->control.actual_len == 0x7ff) + *in_sz = 0; + else + *in_sz = w.td->control.actual_len + 1; + } + + if (w.td->control.bitstuff) + err = USB_HOST_ERR_BITSTUFF; + else if (w.td->control.timeout) + err = USB_HOST_ERR_TIMEOUT; + else if (w.td->control.nak) + err = USB_HOST_ERR_NAK; + else if (w.td->control.babble) + err = USB_HOST_ERR_BABBLE; + else if (w.td->control.buffer_error) + err = USB_HOST_ERR_BUFFER; + else if (w.td->control.stalled) + err = USB_HOST_ERR_STALL; + else + { + err = USB_HOST_ERR_NONE; + ue->toggle ^= 1; + } + + uhci_release_td (ud->ui, td); + + return err; +} + +static void +uhci_add_td_to_qh (struct queue_head *qh, struct tx_descriptor *td) +{ + struct frame_list_ptr *fp; + + ASSERT (td != NULL); + + td->head = &qh->qelp; + if (qh->qelp.terminate == 1) + { + /* queue is empty */ + td->flp.terminate = 1; + barrier (); + td->flp.flp = 0; + qh->qelp.flp = ptr_to_flp (vtop (td)); + qh->qelp.terminate = 0; + } + else + { + /* find the last element in the queue */ + fp = ptov (flp_to_ptr (qh->qelp.flp)); + ASSERT (qh->qelp.terminate == 0); + while (!fp->terminate) + { + fp = ptov (flp_to_ptr (fp->flp)); + } + + /* set TD to terminated ptr */ + td->flp = *fp; + + fp->qh_select = 0; + fp->depth_select = 0; + fp->flp = ptr_to_flp (vtop (td)); + barrier (); + fp->terminate = 0; + } +} + +static void +uhci_irq (void *uhci_data) +{ + struct uhci_info *ui; + uint16_t status; + + ui = uhci_data; + status = pci_reg_read16 (ui->io, UHCI_REG_USBSTS); + if (status & USB_STATUS_PROCESSERR) + { + dump_all_qh (ui); + dump_regs (ui); + PANIC ("UHCI: Malformed schedule"); + } + else if (status & USB_STATUS_HOSTERR) + { + dump_all_qh (ui); + dump_regs (ui); + PANIC ("UHCI: Host system error"); + } + else if (status & USB_STATUS_INTERR) + { + /* errors */ + pci_reg_write16 (ui->io, UHCI_REG_USBSTS, USB_STATUS_INTERR); + } + + if (status & USB_STATUS_USBINT) + { + /* turn off interrupt */ + uhci_stop_unlocked (ui); + pci_reg_write16 (ui->io, UHCI_REG_USBSTS, USB_STATUS_USBINT); + uhci_process_completed (ui); + uhci_run_unlocked (ui); + } +} + +static int +uhci_process_completed (struct uhci_info *ui) +{ + struct list_elem *li; + int completed = 0; + size_t start = 0; + + li = list_begin (&ui->waiting); + while (li != list_end (&ui->waiting)) + { + struct usb_wait *uw; + struct list_elem *next; + + next = list_next (li); + uw = list_entry (li, struct usb_wait, peers); + + if (!uw->td->control.active) + { + list_remove (li); + if (uw->td->control.error_limit == 0 || uw->td->control.stalled) + { + uhci_remove_error_td (uw->td); + } + uw->td->flags = 0; + sema_up (&uw->sem); + completed++; + } + li = next; + } + + /* must be a completed async TD.. */ + /* is this too time consuming? I hope not */ + if (completed != 0) + return completed; + + while (start < TD_ENTRIES) + { + struct tx_descriptor *td; + + start = bitmap_scan (ui->td_used, start, 1, true); + if (start == BITMAP_ERROR) + break; + + td = td_from_pool (ui, start); + + if (!td->control.active && (td->flags & TD_FL_ASYNC) && + (td->flags & TD_FL_USED)) + { + if (td->control.error_limit == 0 || td->control.stalled) + { + uhci_remove_error_td (td); + } + uhci_release_td (ui, td); + completed++; + } + start++; + } + + return completed; +} + +static void +uhci_remove_error_td (struct tx_descriptor *td) +{ + struct frame_list_ptr *fp; + uint32_t td_flp; + + ASSERT (td->head != NULL); + + fp = td->head; + td_flp = ptr_to_flp (vtop (td)); + while (fp->flp != td_flp) + { + ASSERT (fp->terminate == 0); + fp = ptov (flp_to_ptr (fp->flp)); + } + *fp = td->flp; +} + +static int +uhci_detect_change (host_info hi) +{ + struct uhci_info *ui; + int change; + int i; + + ui = hi; + change = 0; + uhci_lock (ui); + for (i = 0; i < ui->num_ports; i++) + { + change = check_and_flip_change (ui, i); + if (change != 0) + break; + } + uhci_unlock (ui); + + return change; +} + +static int +check_and_flip_change (struct uhci_info *ui, int port) +{ + int val; + int reg; + + reg = UHCI_REG_PORTSC1 + port * 2; + val = pci_reg_read16 (ui->io, reg); + if (val & USB_PORT_CHANGE) + { + pci_reg_write16 (ui->io, reg, val & ~(USB_PORT_CHANGE)); + return 1; + } + + return 0; +} + +static host_dev_info +uhci_create_chan (host_info hi, int dev_addr, int ver) +{ + struct uhci_info *ui; + struct uhci_dev_info *ud; + int i; + + ASSERT (dev_addr <= 127 && dev_addr >= 0); + + ui = hi; + + ud = malloc (sizeof (struct uhci_dev_info)); + ud->dev_addr = dev_addr; + ud->low_speed = (ver == USB_VERSION_1_0) ? true : false; + + ud->errors = 0; + ud->ui = ui; + lock_init (&ud->lock); + + uhci_lock (ui); + + ud->qh = qh_alloc (ud->ui); + + /* queue data */ + memset (ud->qh, 0, sizeof (*ud->qh)); + ud->qh->qelp.terminate = 1; + barrier (); + ud->qh->qelp.flp = 0; + ud->qh->qelp.qh_select = 0; + ud->qh->qhlp.qh_select = 1; + + uhci_stop (ui); + + /* add to queues in frame list */ + ud->qh->qhlp.flp = ui->frame_list[0].flp; + ud->qh->qhlp.terminate = ui->frame_list[0].terminate; + for (i = 0; i < FRAME_LIST_ENTRIES; i++) + { + ui->frame_list[i].flp = ptr_to_flp (vtop (ud->qh)); + ui->frame_list[i].qh_select = 1; + ui->frame_list[i].terminate = 0; + } + + /* add to device list */ + list_push_back (&ui->devices, &ud->peers); + + uhci_run (ui); + uhci_unlock (ui); + + return ud; +} + +static void +uhci_destroy_chan (host_dev_info hd) +{ + struct uhci_dev_info *ud; + struct list_elem *li; + + ud = hd; + uhci_lock (ud->ui); + + uhci_stop (ud->ui); + + uhci_remove_qh (ud->ui, ud->qh); + + /* wake up all waiting */ + li = list_begin (&ud->ui->waiting); + while (li != list_end (&ud->ui->waiting)) + { + struct usb_wait *w; + w = list_entry (li, struct usb_wait, peers); + if (w->ud == ud) + { + sema_up (&w->sem); + list_remove (li); + } + li = list_next (li); + } + + list_remove (&ud->peers); + + uhci_run (ud->ui); + + qh_free (ud->ui, ud->qh); + + uhci_unlock (ud->ui); + + free (ud); +} + +/** + * Remove a queue from the UHCI schedule + */ +static void +uhci_remove_qh (struct uhci_info *ui, struct queue_head *qh) +{ + uintptr_t qh_flp; + + ASSERT (lock_held_by_current_thread (&ui->lock)); + ASSERT (uhci_is_stopped (ui)); + ASSERT (qh != NULL); + + qh_flp = ptr_to_flp (vtop (qh)); + /* remove from host queue */ + if (ui->frame_list[0].flp == qh_flp) + { + int i; + /* up top */ + for (i = 0; i < FRAME_LIST_ENTRIES; i++) + { + ui->frame_list[i] = qh->qhlp; + } + } + else + { + /* in the middle */ + struct frame_list_ptr *fp; + struct frame_list_ptr *prev; + + fp = ptov (flp_to_ptr (ui->frame_list[0].flp)); + ASSERT (!fp->terminate); + do + { + prev = fp; + fp = ptov (flp_to_ptr (fp->flp)); + } + while (!fp->terminate && fp->flp != qh_flp); + *prev = qh->qhlp; + } +} + +/** + * Put UHCI into stop state + * Wait until status register reflects setting + */ +static void +uhci_stop (struct uhci_info *ui) +{ + ASSERT (intr_get_level () != INTR_OFF); + ASSERT (lock_held_by_current_thread (&ui->lock)); + + uhci_stop_unlocked (ui); +} + +static void +uhci_stop_unlocked (struct uhci_info *ui) +{ + uint16_t cmd; + int i; + + cmd = pci_reg_read16 (ui->io, UHCI_REG_USBCMD); + cmd = cmd & ~USB_CMD_RS; + + pci_reg_write16 (ui->io, UHCI_REG_USBCMD, cmd); + + /* wait for execution schedule to finish up */ + for (i = 0; i < 1000; i++) + { + if (uhci_is_stopped (ui)) + return; + } + + PANIC ("UHCI: Controller did not halt\n"); + +} + +static void +uhci_run_unlocked (struct uhci_info *ui) +{ + uint16_t cmd; + cmd = pci_reg_read16 (ui->io, UHCI_REG_USBCMD); + cmd = cmd | USB_CMD_RS | USB_CMD_MAX_PACKET; + pci_reg_write16 (ui->io, UHCI_REG_USBCMD, cmd); +} + +/** + * Put UHCI into 'Run' State + */ +static void +uhci_run (struct uhci_info *ui) +{ + ASSERT (lock_held_by_current_thread (&ui->lock)); + uhci_run_unlocked (ui); +} + +static struct tx_descriptor * +uhci_acquire_td (struct uhci_info *ui) +{ + size_t td_idx; + struct tx_descriptor *td; + + ASSERT (lock_held_by_current_thread (&ui->lock)); + ASSERT (!uhci_is_stopped (ui)); + + sema_down (&ui->td_sem); + td_idx = bitmap_scan_and_flip (ui->td_used, 0, 1, false); + ASSERT (td_idx != BITMAP_ERROR); + td = td_from_pool (ui, td_idx); + + return td; +} + +static void +uhci_modify_chan (host_dev_info hd, int dev_addr, int ver) +{ + struct uhci_dev_info *ud; + + ud = hd; + ud->dev_addr = dev_addr; + ud->low_speed = (ver == USB_VERSION_1_0) ? true : false; +} + +static void +dump_all_qh (struct uhci_info *ui) +{ + struct list_elem *li; + + printf ("schedule: %x...", vtop (ui->frame_list)); + printf ("%x", *((uint32_t *) ui->frame_list)); + li = list_begin (&ui->devices); + while (li != list_end (&ui->devices)) + { + struct uhci_dev_info *ud; + ud = list_entry (li, struct uhci_dev_info, peers); + dump_qh (ud->qh); + li = list_next (li); + } +} + +static void +dump_qh (struct queue_head *qh) +{ + struct frame_list_ptr *fp; + printf ("qh: %p %x\n", qh, vtop (qh)); + fp = &qh->qelp; + while (!fp->terminate) + { + printf ("%x %x\n", *((uint32_t *) fp), *(uint32_t *) (fp + 1)); + fp = ptov (flp_to_ptr (fp->flp)); + } + printf ("%x %x\n\n", *(uint32_t *) fp, *(uint32_t *) (fp + 1)); +} + +static struct tx_descriptor * +td_from_pool (struct uhci_info *ui, int idx) +{ + ASSERT (idx >= 0 && idx < TD_ENTRIES); + return (((void *) ui->td_pool) + idx * 32); +} + +static void +uhci_detect_ports (struct uhci_info *ui) +{ + int i; + ui->num_ports = 0; + for (i = 0; i < UHCI_MAX_PORTS; i++) + { + uint16_t status; + status = pci_reg_read16 (ui->io, UHCI_REG_PORTSC1 + i * 2); + if (!(status & 0x0080) || status == 0xffff) + return; + ui->num_ports++; + } +} + +static void +uhci_stall_watchdog (struct uhci_info *ui) +{ + while (1) + { + int rmved; + timer_msleep (1000); + printf ("watchdog\n"); + uhci_lock (ui); + uhci_stop (ui); + rmved = uhci_remove_stalled (ui); + if (rmved > 0) + printf ("removed stalled packet in watchdog\n"); + uhci_run (ui); + uhci_unlock (ui); + } +} + +static int +uhci_remove_stalled (struct uhci_info *ui) +{ + struct list_elem *li; + int rmved; + + rmved = 0; + li = list_begin (&ui->waiting); + + intr_disable (); + + while (li != list_end (&ui->waiting)) + { + struct usb_wait *uw; + struct list_elem *next; + uint32_t ctrl; + + next = list_next (li); + uw = list_entry (li, struct usb_wait, peers); + + if ((!uw->td->control.active && uw->td->control.stalled) || + (uw->td->control.nak)) + { + memcpy (&ctrl, &uw->td->control, 4); + printf ("CTRL: %x\n", ctrl); + list_remove (li); + uhci_remove_error_td (uw->td); + sema_up (&uw->sem); + rmved++; + } + li = next; + } + + intr_enable (); + + return rmved; +} + +static void +uhci_release_td (struct uhci_info *ui, struct tx_descriptor *td) +{ + int ofs = (uintptr_t) td - (uintptr_t) ui->td_pool; + int entry = ofs / 32; + + ASSERT (entry < TD_ENTRIES); + + td->flags = 0; + bitmap_reset (ui->td_used, entry); + sema_up (&ui->td_sem); +} + +static host_eop_info +uhci_create_eop (host_dev_info hd, int eop, int maxpkt) +{ + struct uhci_dev_info *ud; + struct uhci_eop_info *e; + + ud = hd; + + e = malloc (sizeof (struct uhci_eop_info)); + e->eop = eop; + e->ud = ud; + e->maxpkt = maxpkt; + e->toggle = 0; + + return e; +} + +static void +uhci_remove_eop (host_eop_info hei) +{ + free (hei); +} + +static struct queue_head * +qh_alloc (struct uhci_info *ui) +{ + size_t qh_idx; + struct queue_head *qh; + + ASSERT (lock_held_by_current_thread (&ui->lock)); + + qh_idx = bitmap_scan_and_flip (ui->qh_used, 0, 1, false); + if (qh_idx == BITMAP_ERROR) + { + PANIC ("UHCI: Too many queue heads in use-- runaway USB stack?\n"); + } + qh = (void *) (((intptr_t) ui->qh_pool) + qh_idx * 16); + + return qh; +} + +static void +qh_free (struct uhci_info *ui, struct queue_head *qh) +{ + size_t entry; + ASSERT (lock_held_by_current_thread (&ui->lock)); + + entry = ((intptr_t) qh - (intptr_t) ui->qh_pool) / 16; + bitmap_reset (ui->qh_used, entry); +}