[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[cdi-devel] [PATCH 2/5] ehci: Implement interrupt transfers
Signed-off-by: Max Reitz <max@xxxxxxxxxx>
---
ehci/ehci.c | 265 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++---
ehci/ehci.h | 20 +++++
ehci/main.c | 11 +--
3 files changed, 278 insertions(+), 18 deletions(-)
diff --git a/ehci/ehci.c b/ehci/ehci.c
index addec00..4b548ae 100644
--- a/ehci/ehci.c
+++ b/ehci/ehci.c
@@ -40,6 +40,13 @@
static void bios_handoff(struct cdi_pci_device *pci_dev, struct ehci *hc);
static void irq_handler(struct cdi_device *dev);
+static void init_periodic_pseudo_qhs(struct ehci *hc);
+
+
+static inline bool is_power_of_two(unsigned value)
+{
+ return !(value & (value - 1));
+}
struct cdi_device *ehci_init_device(struct cdi_bus_data *bus_data)
@@ -111,6 +118,18 @@ struct cdi_device *ehci_init_device(struct cdi_bus_data *bus_data)
}
hc->regs->periodiclistbase = hc->periodic_list_mem->paddr.items[0].start;
+ hc->periodic_qhs = calloc(1024, sizeof(*hc->periodic_qhs));
+ // 10 = |0..10| = |ld(1)..ld(1024)|
+ hc->periodic_pseudo_qhs_mem =
+ cdi_mem_alloc(11 * sizeof(hc->periodic_pseudo_qhs[0]),
+ 6
+ | CDI_MEM_PHYS_CONTIGUOUS
+ | CDI_MEM_DMA_4G
+ | CDI_MEM_NOINIT);
+ hc->periodic_pseudo_qhs = hc->periodic_pseudo_qhs_mem->vaddr;
+
+ init_periodic_pseudo_qhs(hc);
+
// Create anchor
struct cdi_mem_area *anchor_qh;
anchor_qh = cdi_mem_alloc(sizeof(ehci_fat_qh_t), 6
@@ -157,8 +176,8 @@ struct cdi_device *ehci_init_device(struct cdi_bus_data *bus_data)
cdi_register_irq(pci_dev->irq, &irq_handler, &hc->hc.dev);
- // async advance doorbell
- hc->regs->usbintr = EHCI_INT_IAA;
+ // async advance doorbell, USB interrupt
+ hc->regs->usbintr = EHCI_INT_IAA | EHCI_INT_USBINT;
// Route everything to this HC
hc->regs->configflag |= EHCI_CF_CF;
@@ -181,6 +200,10 @@ fail:
if (hc->periodic_list_mem) {
cdi_mem_free(hc->periodic_list_mem);
}
+ free(hc->periodic_qhs);
+ if (hc->periodic_pseudo_qhs_mem) {
+ cdi_mem_free(hc->periodic_pseudo_qhs_mem);
+ }
cdi_list_destroy(hc->retired_qhs);
free((void *)hc->hc.dev.name);
free(hc);
@@ -223,6 +246,8 @@ static void bios_handoff(struct cdi_pci_device *pci_dev, struct ehci *hc)
}
+static void qtd_write_back(ehci_fat_qtd_t *qtd);
+
static void irq_handler(struct cdi_device *dev)
{
struct cdi_usb_hc *cdi_hc = CDI_UPCAST(dev, struct cdi_usb_hc, dev);
@@ -231,14 +256,108 @@ static void irq_handler(struct cdi_device *dev)
uint32_t usbsts = hc->regs->usbsts;
hc->regs->usbsts = usbsts;
- // async advance doorbell
- if (!(usbsts & EHCI_STS_IAA)) {
- return;
+ if (usbsts & EHCI_STS_IAA) {
+ // async advance doorbell
+
+ ehci_fat_qh_t *rqh;
+ while ((rqh = cdi_list_pop(hc->retired_qhs))) {
+ cdi_mem_free(rqh->cdi_mem_area);
+ }
}
- ehci_fat_qh_t *rqh;
- while ((rqh = cdi_list_pop(hc->retired_qhs))) {
- cdi_mem_free(rqh->cdi_mem_area);
+ if (usbsts & EHCI_STS_USBINT) {
+ // IOC qTD retired
+
+ for (ehci_fat_qh_t *qh = hc->periodic_functional_qhs; qh;
+ qh = qh->next_periodic)
+ {
+ if (qh->qh.overlay.status & EHCI_QTDS_ACTIVE) {
+ continue;
+ }
+
+ bool error = false;
+
+ for (ehci_fat_qtd_t *qtd = qh->first_qtd; qtd; qtd = qtd->next) {
+ assert((qtd->qtd.status & EHCI_QTDS_PID_MASK) ==
+ EHCI_QTDS_PID_IN);
+
+ error = error || (qtd->qtd.status & EHCI_QTDS_HALTED);
+ if (!error) {
+ qtd_write_back(qtd);
+ }
+ qtd->qtd.buffers[0] &= ~0xfffu;
+ qtd->qtd.status &= ~0x7fu; // clear errors
+ qtd->qtd.status |= EHCI_QTDS_XFLEN(qtd->write_back_size)
+ | EHCI_QTDS_IOC
+ | EHCI_QTDS_CP(0)
+ | EHCI_QTDS_CERR(3)
+ | EHCI_QTDS_PID_IN
+ | EHCI_QTDS_ACTIVE;
+ }
+
+ if (!error) {
+ qh->cb(qh->cb_opaque);
+ }
+
+ // Reactivate the QH
+ qh->qh.overlay.next_qtd = qh->first_qtd->paddr;
+ qh->qh.overlay.alt_next_qtd = qh->first_qtd->paddr;
+ qh->qh.overlay.status &= ~0x7fu; // clear errors
+ }
+ }
+}
+
+
+static void init_periodic_pseudo_qhs(struct ehci *hc)
+{
+ for (int e = 0; e < 11; e++) {
+ ehci_fat_qh_t *qh = &hc->periodic_pseudo_qhs[e];
+ int interval = 1 << e;
+
+ memset(qh, 0, sizeof(*qh));
+
+ qh->is_pseudo_qh = true;
+
+ qh->paddr = hc->periodic_pseudo_qhs_mem->paddr.items[0].start
+ + e * sizeof(*qh);
+
+ qh->qh.ep_state[0] = EHCI_QHES0_RL(0)
+ | EHCI_QHES0_MPL(0)
+ | EHCI_QHES0_DTC
+ | EHCI_QHES0_EP(0)
+ | EHCI_QHES0_DEV(0)
+ | EHCI_QHES0_EPS_HS;
+ qh->qh.ep_state[1] = EHCI_QHES1_MULT(1)
+ | EHCI_QHES1_PORT(0)
+ | EHCI_QHES1_HUB(0)
+ | EHCI_QHES1_SCM(0xff)
+ | EHCI_QHES1_ISM(0x01);
+
+ qh->qh.overlay.next_qtd = EHCI_T;
+ qh->qh.overlay.alt_next_qtd = EHCI_T;
+ qh->qh.overlay.status = EHCI_QTDS_HALTED;
+
+
+ // EHCI specification, 4.6: "Interrupt queue heads are linked into the
+ // frame list ordered by poll rate. Longer poll rates are linked first
+ // [...]."
+ // Therefore, link from high exponents (intervals) to lower ones.
+
+ if (e > 0) {
+ qh->qh.next_qh = hc->periodic_pseudo_qhs[e - 1].paddr | EHCI_QH;
+ qh->next = &hc->periodic_pseudo_qhs[e - 1];
+ } else {
+ qh->qh.next_qh = EHCI_T;
+ }
+
+ if (e < 10) {
+ qh->previous = &hc->periodic_pseudo_qhs[e + 1];
+ }
+
+ for (int i = 0; i < 1024; i += interval) {
+ hc->periodic_list[i] = qh->paddr | EHCI_QH;
+ hc->periodic_qhs[i] = qh;
+ }
}
}
@@ -323,7 +442,28 @@ static void enter_async_qh(struct ehci *hc, ehci_fat_qh_t *qh)
}
-static void retire_qh(struct ehci *hc, ehci_fat_qh_t *qh)
+static void enter_interrupt_qh(struct ehci *hc, ehci_fat_qh_t *qh,
+ int frame_interval_shift)
+{
+ qh->next = hc->periodic_functional_qhs;
+ hc->periodic_functional_qhs = qh;
+
+ ehci_fat_qh_t *pseudo_qh = &hc->periodic_pseudo_qhs[frame_interval_shift];
+
+ qh->next = pseudo_qh->next;
+ qh->previous = pseudo_qh;
+
+ pseudo_qh->next = qh;
+ if (qh->next) {
+ qh->next->previous = qh;
+ }
+
+ qh->qh.next_qh = pseudo_qh->qh.next_qh;
+ pseudo_qh->qh.next_qh = qh->paddr | EHCI_QH;
+}
+
+
+static void retire_async_qh(struct ehci *hc, ehci_fat_qh_t *qh)
{
qh->previous->qh.next_qh = qh->qh.next_qh;
@@ -336,11 +476,29 @@ static void retire_qh(struct ehci *hc, ehci_fat_qh_t *qh)
}
-static void retire_first_qtd(ehci_fat_qh_t *qh)
+static void retire_periodic_qh(struct ehci *hc, ehci_fat_qh_t *qh)
{
- ehci_fat_qtd_t *qtd = qh->first_qtd;
+ qh->previous->qh.next_qh = qh->qh.next_qh;
- assert(qtd);
+ qh->previous->next = qh->next;
+ if (qh->next) {
+ qh->next->previous = qh->previous;
+ }
+
+ ehci_fat_qh_t **func_periodic_ptr = &hc->periodic_functional_qhs;
+ while (*func_periodic_ptr && *func_periodic_ptr != qh) {
+ func_periodic_ptr = &(*func_periodic_ptr)->next_periodic;
+ }
+ assert(*func_periodic_ptr == qh);
+ *func_periodic_ptr = qh->next_periodic;
+
+ cdi_list_push(hc->retired_qhs, qh);
+}
+
+
+static void qtd_write_back(ehci_fat_qtd_t *qtd)
+{
+ ehci_fat_qh_t *qh = qtd->qh;
uint32_t err_mask = (qh->qh.ep_state[0] & EHCI_QHES0_EPS_MASK)
== EHCI_QHES0_EPS_HS
@@ -371,6 +529,16 @@ static void retire_first_qtd(ehci_fat_qh_t *qh)
}
assert(!rem);
}
+}
+
+
+static void retire_first_qtd(ehci_fat_qh_t *qh)
+{
+ ehci_fat_qtd_t *qtd = qh->first_qtd;
+
+ assert(qtd);
+
+ qtd_write_back(qtd);
qh->first_qtd = qtd->next;
if (qh->last_qtd == qtd) {
@@ -561,6 +729,73 @@ void ehci_start_transaction(struct cdi_usb_hc *cdi_hc,
}
+void ehci_start_interrupt_transaction(struct cdi_usb_hc *cdi_hc,
+ cdi_usb_hc_transaction_t ta,
+ int interval,
+ cdi_usb_hc_interrupt_cb_t cb,
+ void *cb_opaque)
+{
+ struct ehci *hc = CDI_UPCAST(cdi_hc, struct ehci, hc);
+ ehci_fat_qh_t *qh = ta;
+
+ // Set IOC for all qTDs
+ for (ehci_fat_qtd_t *qtd = qh->first_qtd; qtd; qtd = qtd->next) {
+ qtd->qtd.status |= EHCI_QTDS_IOC;
+ }
+
+ assert(interval > 0 && is_power_of_two(interval));
+
+ assert(cb);
+ qh->cb = cb;
+ qh->cb_opaque = cb_opaque;
+
+ if (interval > 1024 * 8) {
+ interval = 1024 * 8;
+ }
+
+ // Moving the microframe index along looks like an easy way of balancing the
+ // periodic load a bit.
+ int start_microframe = hc->periodic_start_microframe;
+ hc->periodic_start_microframe = (hc->periodic_start_microframe + 1) % 8;
+
+ bool split =
+ (qh->qh.ep_state[0] & EHCI_QHES0_EPS_MASK) != EHCI_QHES0_EPS_HS;
+
+ if (split) {
+ // If split transactions start in the latter four microframes of a
+ // frame, their management is more difficult. Therefore, just limit it
+ // to the first four microframes.
+ start_microframe %= 4;
+ }
+
+ int s_mask = 0;
+
+ for (int microframe = start_microframe; microframe < start_microframe + 8;
+ microframe += interval)
+ {
+ s_mask |= 1 << (microframe % 8);
+ }
+
+ assert(!(qh->qh.ep_state[1] & EHCI_QHES1_ISM(0xff)));
+ qh->qh.ep_state[1] |= EHCI_QHES1_ISM(s_mask);
+
+ if (split) {
+ // Don't just issue complete split requests all over the place, but only
+ // when we actually should do so (3 requests starting 2 microframes
+ // after the start split request)
+ qh->qh.ep_state[1] &= ~EHCI_QHES1_SCM(0xff);
+ qh->qh.ep_state[1] |= EHCI_QHES1_SCM(7 << (start_microframe + 2));
+ }
+
+ int frame_interval_shift = __builtin_ctz(interval) - 3;
+ if (frame_interval_shift < 0) {
+ frame_interval_shift = 0;
+ }
+
+ enter_interrupt_qh(hc, qh, frame_interval_shift);
+}
+
+
void ehci_wait_transaction(struct cdi_usb_hc *cdi_hc,
cdi_usb_hc_transaction_t ta)
{
@@ -639,5 +874,9 @@ void ehci_destroy_transaction(struct cdi_usb_hc *cdi_hc,
while (qh->first_qtd) {
retire_first_qtd(qh);
}
- retire_qh(hc, qh);
+ if (qh->cb) {
+ retire_periodic_qh(hc, qh);
+ } else {
+ retire_async_qh(hc, qh);
+ }
}
diff --git a/ehci/ehci.h b/ehci/ehci.h
index fb8dd9e..4b55391 100644
--- a/ehci/ehci.h
+++ b/ehci/ehci.h
@@ -87,8 +87,14 @@ struct ehci_fat_qh {
struct cdi_mem_area *cdi_mem_area;
uintptr_t paddr;
+ cdi_usb_hc_interrupt_cb_t cb;
+ void *cb_opaque;
+
ehci_fat_qh_t *next, *previous;
+ ehci_fat_qh_t *next_periodic;
ehci_fat_qtd_t *first_qtd, *last_qtd;
+
+ bool is_pseudo_qh;
} __attribute__((aligned(64)));
static_assert(!(sizeof(struct ehci_fat_qh) % 64),
@@ -122,9 +128,17 @@ struct ehci {
struct cdi_mem_area *periodic_list_mem;
volatile uint32_t *periodic_list;
+ ehci_fat_qh_t **periodic_qhs;
+
+ struct cdi_mem_area *periodic_pseudo_qhs_mem;
+ ehci_fat_qh_t *periodic_pseudo_qhs;
+ ehci_fat_qh_t *periodic_functional_qhs;
ehci_fat_qh_t *async_start;
cdi_list_t retired_qhs;
+
+ // For a bit of load balancing
+ int periodic_start_microframe;
};
#define PCI_CLASS_SERIAL_BUS_CONTROLLER 0x0c
@@ -254,6 +268,7 @@ struct ehci {
#define EHCI_QTDS_PID_OUT (0 << 8) // PID: OUT
#define EHCI_QTDS_PID_IN (1 << 8) // PID: IN
#define EHCI_QTDS_PID_SETUP (2 << 8) // PID: SETUP
+#define EHCI_QTDS_PID_MASK (3 << 8) // PID mask
#define EHCI_QTDS_ACTIVE (1 << 7) // Status: Active
#define EHCI_QTDS_HALTED (1 << 6) // Status: Halted
#define EHCI_QTDS_BUFERR (1 << 5) // Status: Data Buffer Error
@@ -287,6 +302,11 @@ void ehci_enqueue(struct cdi_usb_hc *hc,
void ehci_start_transaction(struct cdi_usb_hc *cdi_hc,
cdi_usb_hc_transaction_t ta);
void ehci_wait_transaction(struct cdi_usb_hc *hc, cdi_usb_hc_transaction_t ta);
+void ehci_start_interrupt_transaction(struct cdi_usb_hc *hc,
+ cdi_usb_hc_transaction_t ta,
+ int interval,
+ cdi_usb_hc_interrupt_cb_t cb,
+ void *cb_opaque);
void ehci_destroy_transaction(struct cdi_usb_hc *hc,
cdi_usb_hc_transaction_t ta);
diff --git a/ehci/main.c b/ehci/main.c
index 5623819..97974ac 100644
--- a/ehci/main.c
+++ b/ehci/main.c
@@ -59,11 +59,12 @@ static struct cdi_usb_hcd ehcd = {
.port_status = ehci_rh_port_status,
},
- .create_transaction = ehci_create_transaction,
- .enqueue = ehci_enqueue,
- .start_transaction = ehci_start_transaction,
- .wait_transaction = ehci_wait_transaction,
- .destroy_transaction = ehci_destroy_transaction,
+ .create_transaction = ehci_create_transaction,
+ .enqueue = ehci_enqueue,
+ .start_transaction = ehci_start_transaction,
+ .wait_transaction = ehci_wait_transaction,
+ .start_interrupt_transaction = ehci_start_interrupt_transaction,
+ .destroy_transaction = ehci_destroy_transaction,
};
CDI_DRIVER(DRIVER_NAME, ehcd)
--
2.6.4