blob: c7dc88f29e35deeac1c48060c6f2584d714af253 [file] [log] [blame]
/*
* Copyright 2017, Data61
* Commonwealth Scientific and Industrial Research Organisation (CSIRO)
* ABN 41 687 119 230.
*
* This software may be distributed and modified according to the terms of
* the BSD 2-Clause license. Note that NO WARRANTY is provided.
* See "LICENSE_BSD2.txt" for details.
*
* @TAG(DATA61_BSD)
*/
/**
* @brief EHCI asynchronous schedule implementation.
*/
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <usb/drivers/usbhub.h>
#include "ehci.h"
#include "../services.h"
enum usb_xact_status qtd_get_status(volatile struct TD* qtd)
{
uint32_t t = qtd->token;
if (t & TDTOK_SACTIVE) {
/* Note that we have already returned an error code
* if this TD is still pending due to an error in
* a previous TD */
return XACTSTAT_PENDING;
} else if (t & TDTOK_SHALTED) {
if (t & TDTOK_SXACTERR) {
return XACTSTAT_ERROR;
} else if (t & TDTOK_ERROR) {
return XACTSTAT_HOSTERROR;
}
ZF_LOGF("EHCI: Unknown QTD error code 0x%x\n", t);
return XACTSTAT_HOSTERROR;
} else {
return XACTSTAT_SUCCESS;
}
}
enum usb_xact_status qhn_get_status(struct QHn * qhn)
{
struct TDn *tdn;
tdn = qhn->tdns;
while (tdn) {
enum usb_xact_status stat;
stat = qtd_get_status(tdn->td);
if (stat != XACTSTAT_SUCCESS) {
return stat;
}
tdn = tdn->next;
}
/* All TDs complete, return the status of the QH */
return qtd_get_status(&qhn->qh->td_overlay);
}
/****************************
**** Queue manipulation ****
****************************/
/*
* TODO: The current data structure assumes one xact per TD, which means the
* length of an xact can not exceed 20KB.
*/
struct TDn*
qtd_alloc(struct ehci_host *edev, enum usb_speed speed, struct endpoint *ep,
struct xact *xact, int nxact, usb_cb_t cb, void *token)
{
struct TDn *head_tdn = NULL, *prev_tdn, *tdn = NULL;
int buf_filled, cnt, total_bytes = 0;
int xact_stage = 0;
if (!xact || nxact <=0) {
ZF_LOGF("Invalid arguments\n");
}
prev_tdn = NULL;
for (int i = 0; i < nxact; i++) {
tdn = calloc(1, sizeof(struct TDn));
if (!tdn) {
ZF_LOGE("Out of memory");
return NULL;
}
/* Allocate TD overlay */
tdn->td = ps_dma_alloc_pinned(edev->dman, sizeof(*tdn->td), 32, 0,
PS_MEM_NORMAL, &tdn->ptd);
if (!tdn->td) {
ZF_LOGE("Out of DMA memory");
return NULL;
}
memset((void*)tdn->td, 0, sizeof(*tdn->td));
/* Fill in the TD */
if (prev_tdn) {
prev_tdn->td->next = tdn->ptd;
} else {
head_tdn = tdn;
}
tdn->td->alt = TDLP_INVALID;
/* The Control endpoint manages its own data toggle */
if (ep->type == EP_CONTROL) {
if (ep->max_pkt & (ep->max_pkt + total_bytes - 1)) {
tdn->td->token = TDTOK_DT;
}
}
tdn->td->token |= TDTOK_BYTES(xact[i].len);
tdn->td->token |= TDTOK_C_ERR(0x3); //Maximize retries
switch (xact[i].type) {
case PID_SETUP:
tdn->td->token |= TDTOK_PID_SETUP;
xact_stage |= TDTOK_PID_SETUP;
break;
case PID_IN:
tdn->td->token |= TDTOK_PID_IN;
xact_stage |= TDTOK_PID_IN;
break;
case PID_OUT:
tdn->td->token |= TDTOK_PID_OUT;
xact_stage |= TDTOK_PID_OUT;
break;
default:
ZF_LOGF("Invalid PID!\n");
break;
}
tdn->td->token |= TDTOK_SHALTED;
/* Ping control */
if (speed == USBSPEED_HIGH && xact[i].type == PID_OUT) {
tdn->td->token |= TDTOK_PINGSTATE;
}
/* Fill in the buffer */
cnt = 0;
tdn->td->buf[cnt] = xact[i].paddr; //First buffer has offset
buf_filled = 0x1000 - (xact[i].paddr & 0xFFF);
/* All following buffers are page aligned */
while (buf_filled < xact[i].len) {
cnt++;
tdn->td->buf[cnt] = (xact[i].paddr + 0x1000 * cnt) & ~0xFFF;
buf_filled += 0x1000;
}
/* We only have 5 page-sized buffers */
if (cnt > 4) {
ZF_LOGF("Too many buffers\n");
}
/* Total data transferred */
total_bytes += xact[i].len;
if (prev_tdn) {
prev_tdn->next = tdn;
}
prev_tdn = tdn;
}
/*
* Zero length packet
* XXX: It is unclear that the exact condition of when the zero length
* packet is required. The following implementation is partially based
* on observation. According to USB 2.0 spec(5.5.3), a zero length
* packet shouldn't be needed under some of the situations below, but
* apparently, some devices don't always follow the spec.
*/
if (((xact_stage & TDTOK_PID_OUT) && !(total_bytes % ep->max_pkt)) ||
ep->type == EP_CONTROL) {
/* Allocate TD for the zero length packet */
tdn = calloc(1, sizeof(struct TDn));
/* Allocate TD overlay */
tdn->td = ps_dma_alloc_pinned(edev->dman, sizeof(*tdn->td),
32, 0, PS_MEM_NORMAL, &tdn->ptd);
if (!tdn->td) {
ZF_LOGF("Out of DMA memory\n");
}
memset((void*)tdn->td, 0, sizeof(*tdn->td));
/* Fill in the TD */
tdn->td->alt = TDLP_INVALID;
tdn->td->token = TDTOK_C_ERR(0x3) | TDTOK_SHALTED;
if (xact_stage & TDTOK_PID_SETUP) {
/* Flip the PID, if there is no data stage, then IN */
if (xact_stage & TDTOK_PID_IN) {
tdn->td->token |= TDTOK_PID_OUT;
} else {
tdn->td->token |= TDTOK_PID_IN;
}
/* Force DATA1 */
tdn->td->token |= TDTOK_DT;
} else {
/* Bulk out transfer */
tdn->td->token |= TDTOK_PID_OUT;
/* XXX: Flip the data toggle? */
if (!(prev_tdn->td->token & TDTOK_DT)) {
tdn->td->token |= TDTOK_DT;
}
}
/* Add to the list */
prev_tdn->td->next = tdn->ptd;
prev_tdn->next = tdn;
}
/* Send IRQ when finished processing the last TD */
tdn->td->token |= TDTOK_IOC; //TODO: Maybe disable IRQ when cb == NULL
tdn->cb = cb;
tdn->token = token;
/* Mark the last TD as terminate TD */
tdn->td->next |= TDLP_INVALID;
return head_tdn;
}
/*
* Allocate generic queue head for both periodic and asynchronous schedule
* Note that the link pointer and reclamation flag bit are set when inserting
* the queue head to asynchronous schedule.
*
* XXX: For some unknown reason, we cannot initialize the link pointer and head
* type here, it seems that the register can be only written once. So we'll fill
* the register when adding the queue head to the schedule.
*/
struct QHn*
qhn_alloc(struct ehci_host *edev, uint8_t address, uint8_t hub_addr,
uint8_t hub_port, enum usb_speed speed, struct endpoint *ep)
{
struct QHn *qhn;
volatile struct QH *qh;
qhn = calloc(1, sizeof(struct QHn));
if (!qhn) {
ZF_LOGF("Out of memory\n");
}
/* Allocate queue head */
qhn->qh = ps_dma_alloc_pinned(edev->dman, sizeof(*qh), 32, 0,
PS_MEM_NORMAL, &qhn->pqh);
if (!qhn->qh) {
ZF_LOGF("Out of DMA memory\n");
}
memset((void*)qhn->qh, 0, sizeof(*qh));
/* Fill in the queue head */
qh = qhn->qh;
qh->qhlptr = QHLP_INVALID;
/* epc0 */
switch (speed) {
case USBSPEED_HIGH:
qh->epc[0] = QHEPC0_HSPEED;
break;
case USBSPEED_FULL:
qh->epc[0] = QHEPC0_FSPEED;
break;
case USBSPEED_LOW :
qh->epc[0] = QHEPC0_LSPEED;
break;
default:
ZF_LOGF("Invalid speed\n");
}
qh->epc[0] |= QHEPC0_MAXPKTLEN(ep->max_pkt) | QHEPC0_ADDR(address) |
QHEPC0_EP(ep->num);
/*
* Nak counter must NOT be used for interrupt endpoint
* EHCI spec chapter 4.9(Nak "Not Used" mode)
*/
if (ep->type == EP_INTERRUPT) {
qh->epc[0] |= QHEPC0_NAKCNT_RL(0);
} else {
qh->epc[0] |= QHEPC0_NAKCNT_RL(0x8);
}
/* Control endpoint manages its own data toggle */
if (ep->type == EP_CONTROL) {
qh->epc[0] |= QHEPC0_DTC;
/* For full/low speed control endpoint */
if (speed != USBSPEED_HIGH) {
qh->epc[0] |= QHEPC0_C;
}
}
/* epc1 */
qh->epc[1] = QHEPC1_MULT(1);
if (speed != USBSPEED_HIGH) {
qh->epc[1] |= QHEPC1_HUB_ADDR(hub_addr) | QHEPC1_PORT(hub_port);
}
/* TODO: Check CMASK and SMASK */
if ((ep->type == EP_INTERRUPT || ep->type == EP_ISOCHRONOUS) &&
speed != USBSPEED_HIGH) {
qh->epc[1] |= QHEPC1_UFRAME_CMASK(0x1C);
}
if (ep->type == EP_INTERRUPT) {
qh->epc[1] |= QHEPC1_UFRAME_SMASK(1);
}
qh->td_overlay.next = TDLP_INVALID;
qh->td_overlay.alt = TDLP_INVALID;
return qhn;
}
void
qhn_update(struct QHn *qhn, uint8_t address, struct endpoint *ep)
{
uint32_t epc0;
if (!qhn || !ep) {
ZF_LOGF("Invalid arguments\n");
}
/*
* We only care about the control endpoint, because all other
* endpoints' info is extracted from the endpoint descriptor, and by the
* time the core driver reads the descriptors, the device's address
* should have settled already.
*/
if (ep->type != EP_CONTROL) {
return;
}
/* Update maximum packet size */
epc0 = qhn->qh->epc[0];
if (unlikely(QHEPC0_GET_MAXPKT(epc0) != ep->max_pkt)) {
epc0 &= ~QHEPC0_MAXPKT_MASK;
epc0 |= QHEPC0_MAXPKTLEN(ep->max_pkt);
}
/* Update device address */
if (unlikely(QHEPC0_GET_ADDR(epc0) != address)) {
epc0 &= ~QHEPC0_ADDR_MASK;
epc0 |= QHEPC0_ADDR(address);
}
qhn->qh->epc[0] = epc0;
}
void
qtd_enqueue(struct ehci_host *edev, struct QHn *qhn, struct TDn *tdn)
{
struct TDn *last_tdn;
if (!qhn || !tdn) {
ZF_LOGF("Invalid arguments\n");
}
/* If the queue is empty, point the TD overlay to the first TD */
if (!qhn->tdns) {
qhn->qh->td_overlay.next = tdn->ptd;
qhn->tdns = tdn;
} else {
ps_mutex_lock(edev->sync, qhn->lock);
/* Find the last TD */
last_tdn = qhn->tdns;
while (last_tdn->next) {
last_tdn = last_tdn->next;
}
if (qhn->qh->td_cur == last_tdn->ptd &&
qhn->qh->td_overlay.next == TDLP_INVALID) {
qhn->qh->td_overlay.next = tdn->ptd;
}
/* Add new TD to the queue and update the termination bit */
last_tdn->next = tdn;
last_tdn->td->next = tdn->ptd & ~TDLP_INVALID;
ps_mutex_unlock(edev->sync, qhn->lock);
}
/* Enable all TDs */
while (tdn) {
tdn->td->token &= ~TDTOK_SHALTED;
tdn->td->token |= TDTOK_SACTIVE;
tdn = tdn->next;
}
/* Make sure the controller sees all the active TDs */
dsb();
}
void qhn_destroy(ps_dma_man_t* dman, struct QHn* qhn)
{
struct TDn *tdn, *tmp;
tdn = qhn->tdns;
while (tdn) {
tmp = tdn;
tdn = tdn->next;
if (tmp->cb) {
tmp->cb(tmp->token, XACTSTAT_CANCELLED, 0);
}
ps_dma_free_pinned(dman, (void*)tmp->td, sizeof(struct TD));
usb_free(tmp);
}
ps_dma_free_pinned(dman, (void*)qhn->qh, sizeof(struct QH));
usb_free(qhn);
}
void ehci_async_complete(struct ehci_host *edev)
{
struct QHn *qhn;
struct TDn *tdn, *head, *tmp;
int sum;
qhn = edev->alist_tail;
/* Nothing to do if the queue is empty */
if (!qhn) {
return;
}
do {
ps_mutex_lock(edev->sync, qhn->lock);
tdn = qhn->tdns;
sum = 0;
head = tdn;
while (tdn != NULL &&
qtd_get_status(tdn->td) == XACTSTAT_SUCCESS) {
sum += TDTOK_GET_BYTES(tdn->td->token);
if (tdn->td->token & TDTOK_IOC) {
if (tdn->cb) {
tdn->cb(tdn->token, XACTSTAT_SUCCESS, sum);
sum = 0;
}
qhn->tdns = tdn->next;
/*
* Update the QH if we are about to dequeue the
* "previous" last TD in the queue. This happens
* when the last TD gets partially processed
* while we enqueue new TDs.
*/
if (qhn->tdns &&
qhn->qh->td_cur == tdn->ptd &&
qhn->qh->td_overlay.next == TDLP_INVALID) {
qhn->qh->td_overlay.next = tdn->next->ptd;
dsb();
}
/* Free */
while (head != tdn->next) {
tmp = head;
head = head->next;
ps_dma_free_pinned(edev->dman,
(void*)tmp->td,
sizeof(struct TD));
usb_free(tmp);
}
}
tdn = tdn->next;
}
ps_mutex_unlock(edev->sync, qhn->lock);
qhn = qhn->next;
} while (qhn != edev->alist_tail);
}
void ehci_add_qhn_async(struct ehci_host *edev, struct QHn *qhn)
{
/* Add new queue head to async queue */
if (edev->alist_tail) {
/* Update the hardware queue */
qhn->qh->qhlptr = edev->alist_tail->qh->qhlptr;
edev->alist_tail->qh->qhlptr = qhn->pqh | QHLP_TYPE_QH;
/* Update the Software queue */
qhn->next = edev->alist_tail->next;
edev->alist_tail->next = qhn;
edev->alist_tail = qhn;
} else {
edev->alist_tail = qhn;
edev->alist_tail->next = qhn;
qhn->qh->qhlptr = qhn->pqh | QHLP_TYPE_QH;
}
dsb();
}
void ehci_del_qhn_async(struct ehci_host *edev, struct QHn *qhn)
{
struct TDn *tdn;
struct QHn *prev;
/*
* The EHCI spec(section 4.8.2) gives the instructions of removing a QH
* from the active async queue. However, the instructions have been
* proved racy and impractical. See the following link for more
* information,
*
* https://marc.info/?l=linux-usb&m=144554811526940&w=2
*
* This implementation combines part of the spec instructions and
* Linux's double IAA cycle approach. Hope it would minimize the chances
* of trigging serious problems. Good Luck!
*/
/*
* Deactivate all active TDs
*
* This is the racy part of the EHCI spec. The host may have already in
* the middle of processing the TD overlay when we change the token.
* But we've got no choice otherwise the host would follow the TD list
* and would cause further errors.
*/
tdn = qhn->tdns;
while (tdn) {
if (tdn->td->token & TDTOK_SACTIVE) {
tdn->td->token &= ~TDTOK_SACTIVE;
tdn->td->token |= TDTOK_SHALTED;
}
tdn = tdn->next;
}
dsb();
/* Select another queue head to set its H-bit */
if ((qhn->qh->epc[0] & QHEPC0_H) && qhn->next) {
qhn->next->qh->epc[0] |= QHEPC0_H;
}
/* Remove the queue head from async list */
prev = edev->alist_tail;
while (prev->next != qhn) {
prev = prev->next;
}
prev->qh->qhlptr = qhn->qh->qhlptr;
prev->next = qhn->next;
if (edev->alist_tail == qhn) {
edev->alist_tail = qhn->next;
}
/* Put the queue head to the recycle queue */
if (edev->db_pending) {
prev = edev->db_pending;
while (prev->next != NULL) {
prev = prev->next;
}
prev->next = qhn;
} else {
edev->db_pending = qhn;
}
qhn->next = NULL;
qhn->was_cancelled = 1;
/* Ring the doorbell */
edev->op_regs->usbcmd |= EHCICMD_ASYNC_DB;
}
int ehci_wait_for_completion(struct TDn *tdn)
{
int status;
int cnt, sum = 0;
while (tdn) {
cnt = 3000;
do {
status = tdn->td->token & 0xFF;
if (status == 0 || status == 1) {
sum += TDTOK_GET_BYTES(tdn->td->token);
break;
}
if (cnt <= 0) {
ZF_LOGF("Timeout(%p, %p)\n", tdn->td,
(void*)tdn->ptd);
return -1;
}
ps_mdelay(1);
cnt--;
} while (status);
tdn = tdn->next;
}
return sum;
}
/* TODO: Is it okay to use alist_tail and remove qhn */
void ehci_schedule_async(struct ehci_host* edev, struct QHn* qhn)
{
/* Make sure we are safe to write to the register */
while (((edev->op_regs->usbsts & EHCISTS_ASYNC_EN) >> 15)
^ ((edev->op_regs->usbcmd & EHCICMD_ASYNC_EN) >> 5));
/* Enable the async scheduling */
if (!(edev->op_regs->usbsts & EHCISTS_ASYNC_EN)) {
qhn->qh->epc[0] |= QHEPC0_H;
edev->op_regs->asynclistaddr = qhn->pqh;
edev->op_regs->usbcmd |= EHCICMD_ASYNC_EN;
while (edev->op_regs->usbsts & EHCISTS_ASYNC_EN) break;
}
}
void check_doorbell(struct ehci_host* edev)
{
int again = 0;
struct QHn *qhn, *tmp, *tail = NULL;
qhn = edev->db_pending;
edev->db_pending = NULL;
while (qhn) {
tmp = qhn;
qhn = qhn->next;
/* Two IAA cycles have passed, safe to remove */
if (tmp->was_cancelled > 1) {
qhn_destroy(edev->dman, tmp);
} else {
tmp->was_cancelled++;
if (!edev->db_pending) {
edev->db_pending = tmp;
} else {
tail->next = tmp;
}
tail = tmp;
again = 1;
}
}
if (again) {
edev->op_regs->usbcmd |= EHCICMD_ASYNC_DB;
}
}