blob: db8c4b900e60ce6836ad282011b55639b4eb6486 [file] [log] [blame]
// Copyright 2016 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Standard Includes
#include <assert.h>
#include <limits.h>
#include <stdlib.h>
#include <string.h>
#include <threads.h>
// DDK includes
#include <ddk/binding.h>
#include <ddk/debug.h>
#include <ddk/device.h>
#include <ddk/protocol/platform-defs.h>
#include <ddk/protocol/platform-device.h>
#include <ddk/protocol/usb-bus.h>
#include <ddk/protocol/usb-hci.h>
#include <ddk/protocol/usb.h>
// Zircon USB includes
#include <zircon/hw/usb-hub.h>
#include <zircon/hw/usb.h>
#include <sync/completion.h>
#include <dwc2/usb_dwc_regs.h>
#include <zircon/process.h>
#define NUM_HOST_CHANNELS 8
#define PAGE_MASK_4K (0xFFF)
#define USB_PAGE_START (USB_BASE & (~PAGE_MASK_4K))
#define USB_PAGE_SIZE (0x1000)
#define PAGE_REG_DELTA (USB_BASE - USB_PAGE_START)
#define MMIO_INDEX 0
#define IRQ_INDEX 0
// This is how many free requests we'll hang onto in our free request cache.
#define FREE_REQ_CACHE_THRESHOLD 1024
#define MAX_DEVICE_COUNT 65
#define ROOT_HUB_DEVICE_ID (MAX_DEVICE_COUNT - 1)
static volatile struct dwc_regs* regs = NULL;
#define DIV_ROUND_UP(n, d) (((n) + (d)-1) / (d))
#define IS_WORD_ALIGNED(ptr) ((ulong)(ptr) % sizeof(ulong) == 0)
// Log every 512th frame Overrun.
#define FRAME_OVERRUN_THRESHOLD 512
static uint32_t debug_frame_overrun_counter = 0;
typedef struct dwc_usb_device dwc_usb_device_t;
typedef enum dwc_endpoint_direction {
DWC_EP_OUT = 0,
DWC_EP_IN = 1,
} dwc_endpoint_direction_t;
typedef enum dwc_usb_data_toggle {
DWC_TOGGLE_DATA0 = 0,
DWC_TOGGLE_DATA1 = 2,
DWC_TOGGLE_DATA2 = 1,
DWC_TOGGLE_SETUP = 3,
} dwc_usb_data_toggle_t;
typedef enum dwc_ctrl_phase {
CTRL_PHASE_SETUP = 1,
CTRL_PHASE_DATA = 2,
CTRL_PHASE_STATUS = 3,
} dwc_ctrl_phase_t;
typedef struct dwc_usb_transfer_request {
list_node_t node;
dwc_ctrl_phase_t ctrl_phase;
usb_request_t* setup_req;
size_t bytes_transferred;
dwc_usb_data_toggle_t next_data_toggle;
bool complete_split;
// Number of packets queued for transfer before programming the channel.
uint32_t packets_queued;
// Number of bytes queued for transfer before programming the channel.
uint32_t bytes_queued;
// Total number of bytes in this transaction.
uint32_t total_bytes_queued;
bool short_attempt;
usb_request_t* usb_req;
uint32_t cspit_retries;
// DEBUG
uint32_t request_id;
} dwc_usb_transfer_request_t;
typedef struct dwc_usb_device {
mtx_t devmtx;
usb_speed_t speed;
uint32_t hub_address;
int port;
uint32_t device_id;
list_node_t endpoints;
} dwc_usb_device_t;
typedef struct dwc_usb {
zx_device_t* zxdev;
usb_bus_interface_t bus;
zx_handle_t irq_handle;
thrd_t irq_thread;
zx_device_t* parent;
// Pertaining to root hub transactions.
mtx_t rh_req_mtx;
completion_t rh_req_completion;
list_node_t rh_req_head;
// Pertaining to a free list of request structures.
mtx_t free_req_mtx;
list_node_t free_reqs;
size_t free_req_count; // Number of free requests on this list.
// List of devices attached to this controller.
dwc_usb_device_t usb_devices[MAX_DEVICE_COUNT];
// Pertaining to requests scheduled against the mock root hub.
mtx_t rh_status_mtx;
dwc_usb_transfer_request_t* rh_intr_req;
usb_port_status_t root_port_status;
// Pertaining to the availability of channels on this device.
mtx_t free_channel_mtx;
completion_t free_channel_completion;
uint8_t free_channels;
uint32_t next_device_address;
// Assign a new request ID to each request so that we know when it's scheduled
// and when it's executed.
uint32_t DBG_reqid;
union dwc_host_channel_interrupts channel_interrupts[NUM_HOST_CHANNELS];
completion_t channel_complete[NUM_HOST_CHANNELS];
// Pertaining to threads waiting to schedule a packet on the next start of
// frame on this device.
mtx_t sof_waiters_mtx;
uint n_sof_waiters;
completion_t sof_waiters[NUM_HOST_CHANNELS];
// Pool of free requests to reuse.
usb_request_pool_t free_usb_reqs;
} dwc_usb_t;
typedef struct dwc_usb_endpoint {
list_node_t node;
uint8_t ep_address;
mtx_t pending_request_mtx;
list_node_t pending_requests; // List of requests pending for this endpoint.
// Pointer to the device that owns this endpoint.
dwc_usb_device_t* parent;
usb_endpoint_descriptor_t desc;
thrd_t request_scheduler_thread;
completion_t request_pending_completion;
} dwc_usb_endpoint_t;
typedef struct dwc_usb_scheduler_thread_ctx {
dwc_usb_endpoint_t* ep;
dwc_usb_t* dwc;
} dwc_usb_scheduler_thread_ctx_t;
#define ALL_CHANNELS_FREE 0xff
static uint acquire_channel_blocking(dwc_usb_t* dwc);
static void release_channel(uint ch, dwc_usb_t* dwc);
#define MANUFACTURER_STRING 1
#define PRODUCT_STRING_2 2
static const uint8_t dwc_language_list[] =
{4, /* bLength */ USB_DT_STRING, 0x09, 0x04, /* language ID */};
static const uint8_t dwc_manufacturer_string[] = // "Zircon"
{18, /* bLength */ USB_DT_STRING, 'M', 0, 'a', 0, 'g', 0, 'e', 0, 'n', 0, 't', 0, 'a', 0, 0, 0};
static const uint8_t dwc_product_string_2[] = // "USB 2.0 Root Hub"
{
36, /* bLength */ USB_DT_STRING, 'U', 0, 'S', 0, 'B', 0, ' ', 0, '2', 0, '.', 0, '0', 0, ' ', 0,
'R', 0, 'o', 0, 'o', 0, 't', 0, ' ', 0, 'H', 0, 'u', 0, 'b', 0, 0, 0,
};
static const uint8_t* dwc_rh_string_table[] = {
dwc_language_list,
dwc_manufacturer_string,
dwc_product_string_2,
};
// device descriptor for USB 2.0 root hub
static const usb_device_descriptor_t dwc_rh_descriptor = {
.bLength = sizeof(usb_device_descriptor_t),
.bDescriptorType = USB_DT_DEVICE,
.bcdUSB = htole16(0x0200),
.bDeviceClass = USB_CLASS_HUB,
.bDeviceSubClass = 0,
.bDeviceProtocol = 1, // Single TT
.bMaxPacketSize0 = 64,
.idVendor = htole16(0x18D1),
.idProduct = htole16(0xA002),
.bcdDevice = htole16(0x0100),
.iManufacturer = MANUFACTURER_STRING,
.iProduct = PRODUCT_STRING_2,
.iSerialNumber = 0,
.bNumConfigurations = 1,
};
// we are currently using the same configuration descriptors for both USB 2.0 and 3.0 root hubs
// this is not actually correct, but our usb-hub driver isn't sophisticated enough to notice
static const struct {
usb_configuration_descriptor_t config;
usb_interface_descriptor_t intf;
usb_endpoint_descriptor_t endp;
} dwc_rh_config_descriptor = {
.config = {
.bLength = sizeof(usb_configuration_descriptor_t),
.bDescriptorType = USB_DT_CONFIG,
.wTotalLength = htole16(sizeof(dwc_rh_config_descriptor)),
.bNumInterfaces = 1,
.bConfigurationValue = 1,
.iConfiguration = 0,
.bmAttributes = 0xE0, // self powered
.bMaxPower = 0,
},
.intf = {
.bLength = sizeof(usb_interface_descriptor_t),
.bDescriptorType = USB_DT_INTERFACE,
.bInterfaceNumber = 0,
.bAlternateSetting = 0,
.bNumEndpoints = 1,
.bInterfaceClass = USB_CLASS_HUB,
.bInterfaceSubClass = 0,
.bInterfaceProtocol = 0,
.iInterface = 0,
},
.endp = {
.bLength = sizeof(usb_endpoint_descriptor_t),
.bDescriptorType = USB_DT_ENDPOINT,
.bEndpointAddress = USB_ENDPOINT_IN | 1,
.bmAttributes = USB_ENDPOINT_INTERRUPT,
.wMaxPacketSize = htole16(4),
.bInterval = 12,
},
};
static int endpoint_request_scheduler_thread(void* arg);
static inline bool is_roothub_request(dwc_usb_transfer_request_t* req) {
usb_request_t* usb_req = req->usb_req;
return usb_req->header.device_id == ROOT_HUB_DEVICE_ID;
}
static inline bool is_control_request(dwc_usb_transfer_request_t* req) {
usb_request_t* usb_req = req->usb_req;
return usb_req->header.ep_address == 0;
}
// Completes the usb request associated with a request then cleans up the request.
static void complete_request(
dwc_usb_transfer_request_t* req,
zx_status_t status,
size_t length,
dwc_usb_t* dwc) {
if (req->setup_req) {
usb_request_release(req->setup_req);
}
zxlogf(TRACE, "dwc-usb: complete request. id = %u, status = %d, "
"length = %lu\n", req->request_id, status, length);
usb_request_t* usb_req = req->usb_req;
// Invalidate caches over this region since the DMA engine may have moved
// data below us.
if (status == ZX_OK) {
usb_request_cacheop(usb_req, USB_REQUEST_CACHE_INVALIDATE, 0, length);
}
usb_request_complete(usb_req, status, length);
// Put this back on the free list of requests, but make sure the free list
// doesn't get too long.
mtx_lock(&dwc->free_req_mtx);
if (dwc->free_req_count >= FREE_REQ_CACHE_THRESHOLD) {
// There are already too many requests on the free request list, just
// throw this one away.
free(req);
} else {
list_add_tail(&dwc->free_reqs, &req->node);
dwc->free_req_count++;
}
mtx_unlock(&dwc->free_req_mtx);
}
static void dwc_complete_root_port_status_req(dwc_usb_t* dwc) {
mtx_lock(&dwc->rh_status_mtx);
if (dwc->root_port_status.wPortChange) {
if (dwc->rh_intr_req && dwc->rh_intr_req->usb_req) {
usb_request_t* usb_req = dwc->rh_intr_req->usb_req;
uint16_t val = 0x2;
usb_request_copyto(usb_req, (void*)&val, sizeof(val), 0);
complete_request(dwc->rh_intr_req, ZX_OK, sizeof(val), dwc);
dwc->rh_intr_req = NULL;
}
}
mtx_unlock(&dwc->rh_status_mtx);
}
static void dwc_reset_host_port(void) {
union dwc_host_port_ctrlstatus hw_status = regs->host_port_ctrlstatus;
hw_status.enabled = 0;
hw_status.connected_changed = 0;
hw_status.enabled_changed = 0;
hw_status.overcurrent_changed = 0;
hw_status.reset = 1;
regs->host_port_ctrlstatus = hw_status;
// Spec defines that we must wait this long for a host port reset to settle
// in.
zx_nanosleep(zx_deadline_after(ZX_MSEC(60)));
hw_status.reset = 0;
regs->host_port_ctrlstatus = hw_status;
}
static void dwc_host_port_power_on(void) {
union dwc_host_port_ctrlstatus hw_status = regs->host_port_ctrlstatus;
hw_status.enabled = 0;
hw_status.connected_changed = 0;
hw_status.enabled_changed = 0;
hw_status.overcurrent_changed = 0;
hw_status.powered = 1;
regs->host_port_ctrlstatus = hw_status;
}
static zx_status_t usb_dwc_softreset_core(void) {
while (!(regs->core_reset & DWC_AHB_MASTER_IDLE))
;
regs->core_reset = DWC_SOFT_RESET;
while (regs->core_reset & DWC_SOFT_RESET)
;
return ZX_OK;
}
static zx_status_t usb_dwc_setupcontroller(void) {
const uint32_t rx_words = 1024;
const uint32_t tx_words = 1024;
const uint32_t ptx_words = 1024;
regs->rx_fifo_size = rx_words;
regs->nonperiodic_tx_fifo_size = (tx_words << 16) | rx_words;
regs->host_periodic_tx_fifo_size = (ptx_words << 16) | (rx_words + tx_words);
regs->ahb_configuration |= DWC_AHB_DMA_ENABLE | BCM_DWC_AHB_AXI_WAIT;
union dwc_core_interrupts core_interrupt_mask;
regs->core_interrupt_mask.val = 0;
regs->core_interrupts.val = 0xffffffff;
core_interrupt_mask.val = 0;
core_interrupt_mask.host_channel_intr = 1;
core_interrupt_mask.port_intr = 1;
regs->core_interrupt_mask = core_interrupt_mask;
regs->ahb_configuration |= DWC_AHB_INTERRUPT_ENABLE;
return ZX_OK;
}
// Queue a transaction on the DWC root hub.
static void dwc_usb_request_queue_rh(dwc_usb_t* dwc,
dwc_usb_transfer_request_t* req) {
mtx_lock(&dwc->rh_req_mtx);
list_add_tail(&dwc->rh_req_head, &req->node);
mtx_unlock(&dwc->rh_req_mtx);
// Signal to the processor thread to wake up and process this request.
completion_signal(&dwc->rh_req_completion);
}
// Queue a transaction on external peripherals using the DWC host channels.
static void dwc_usb_request_queue_hw(dwc_usb_t* dwc,
dwc_usb_transfer_request_t* req) {
// Find the Device/Endpoint where this transaction is to be scheduled.
usb_request_t* usb_req = req->usb_req;
uint32_t device_id = usb_req->header.device_id;
uint8_t ep_address = usb_req->header.ep_address;
zxlogf(TRACE, "dwc-usb: queue usb req hw. dev_id = %u, ep = %u, req_id = %u, "
"length = 0x%lx\n", device_id, ep_address, req->request_id,
usb_req->header.length);
assert(device_id < MAX_DEVICE_COUNT);
dwc_usb_device_t* target_device = &dwc->usb_devices[device_id];
assert(target_device);
// Find the endpoint where this transaction should be scheduled.
dwc_usb_endpoint_t* target_endpoint = NULL;
dwc_usb_endpoint_t* ep_iter = NULL;
list_for_every_entry (&target_device->endpoints, ep_iter, dwc_usb_endpoint_t, node) {
if (ep_iter->ep_address == ep_address) {
target_endpoint = ep_iter;
break;
}
}
assert(target_endpoint);
if (ep_address == 0) {
req->ctrl_phase = CTRL_PHASE_SETUP;
}
// Writeback any items pending on the cache. We don't want these to be
// flushed during a DMA op.
usb_request_cacheop(usb_req, USB_REQUEST_CACHE_CLEAN, 0, usb_req->header.length);
// Append this transaction to the end of the Device/Endpoint's pending
// transaction queue.
mtx_lock(&target_endpoint->pending_request_mtx);
list_add_tail(&target_endpoint->pending_requests, &req->node);
mtx_unlock(&target_endpoint->pending_request_mtx);
// Signal the Device/Endpoint to begin the transaction.
completion_signal(&target_endpoint->request_pending_completion);
}
// Tries to take a request from the list of free request objects. If none are
// available, a new one is allocated
static dwc_usb_transfer_request_t* get_free_request(dwc_usb_t* dwc) {
dwc_usb_transfer_request_t* result = NULL;
mtx_lock(&dwc->free_req_mtx);
if (list_is_empty(&dwc->free_reqs)) {
// No more free requests, allocate a new one.
// Make sure the free request count is consistent with the list.
assert(dwc->free_req_count == 0);
result = calloc(1, sizeof(*result));
} else {
// Take a request from the free list.
result = list_remove_head_type(&dwc->free_reqs,
dwc_usb_transfer_request_t, node);
memset(result, 0, sizeof(*result));
dwc->free_req_count--;
}
mtx_unlock(&dwc->free_req_mtx);
return result;
}
static void do_dwc_usb_request_queue(dwc_usb_t* dwc, usb_request_t* usb_req) {
// Once a usb request enters the low-level DWC stack, it is always encapsulated
// by a dwc_usb_transfer_request_t.
dwc_usb_transfer_request_t* req = get_free_request(dwc);
if (!req) {
// If we can't allocate memory for the request, complete the usb request with
// a failure.
usb_request_complete(usb_req, ZX_ERR_NO_MEMORY, 0);
return;
}
// Initialize the request.
req->usb_req = usb_req;
req->request_id = dwc->DBG_reqid++;
if (is_roothub_request(req)) {
dwc_usb_request_queue_rh(dwc, req);
} else {
dwc_usb_request_queue_hw(dwc, req);
}
}
size_t dwc_get_max_transfer_size(void* ctx, uint32_t device_id, uint8_t ep_address) {
// Transfers limited to a single page until scatter/gather support is implemented
return PAGE_SIZE;
}
static zx_status_t dwc_cancel_all(void* ctx, uint32_t device_id, uint8_t ep_address) {
return ZX_ERR_NOT_SUPPORTED;
}
static void dwc_request_queue(void* ctx, usb_request_t* usb_req) {
dwc_usb_t* usb_dwc = ctx;
usb_header_t* header = &usb_req->header;
if (usb_req->header.length > dwc_get_max_transfer_size(usb_dwc->zxdev, header->device_id,
header->ep_address)) {
usb_request_complete(usb_req, ZX_ERR_INVALID_ARGS, 0);
} else {
dwc_usb_t* dwc = ctx;
do_dwc_usb_request_queue(dwc, usb_req);
}
}
static void dwc_unbind(void* ctx) {
zxlogf(ERROR, "dwc_usb: dwc_unbind not implemented\n");
}
static void dwc_release(void* ctx) {
zxlogf(ERROR, "dwc_usb: dwc_release not implemented\n");
}
static zx_protocol_device_t dwc_device_proto = {
.version = DEVICE_OPS_VERSION,
.unbind = dwc_unbind,
.release = dwc_release,
};
static void dwc_set_bus_interface(void* ctx, usb_bus_interface_t* bus) {
dwc_usb_t* dwc = ctx;
if (bus) {
memcpy(&dwc->bus, bus, sizeof(dwc->bus));
usb_bus_add_device(&dwc->bus, ROOT_HUB_DEVICE_ID, 0, USB_SPEED_HIGH);
} else {
memset(&dwc->bus, 0, sizeof(dwc->bus));
}
}
static size_t dwc_get_max_device_count(void* ctx) {
return MAX_DEVICE_COUNT;
}
static zx_status_t dwc_enable_ep(void* _ctx, uint32_t device_id,
usb_endpoint_descriptor_t* ep_desc,
usb_ss_ep_comp_descriptor_t* ss_comp_desc,
bool enable) {
zxlogf(TRACE, "dwc_usb: enable_ep. dev_id = %u, ep = %u\n", device_id,
ep_desc->bEndpointAddress);
dwc_usb_t* dwc = _ctx;
if (device_id == ROOT_HUB_DEVICE_ID) {
// Nothing to be done for root hub.
return ZX_OK;
}
// Disabling endpoints not supported at this time.
assert(enable);
dwc_usb_device_t* dev = &dwc->usb_devices[device_id];
// Create a new endpoint.
dwc_usb_endpoint_t* ep = calloc(1, sizeof(*ep));
ep->ep_address = ep_desc->bEndpointAddress;
list_initialize(&ep->pending_requests);
ep->parent = dev;
memcpy(&ep->desc, ep_desc, sizeof(*ep_desc));
ep->request_pending_completion = COMPLETION_INIT;
dwc_usb_scheduler_thread_ctx_t* ctx = malloc(sizeof(*ctx));
ctx->ep = ep;
ctx->dwc = dwc;
thrd_create(
&ep->request_scheduler_thread,
endpoint_request_scheduler_thread,
(void*)ctx);
mtx_lock(&dev->devmtx);
list_add_tail(&dev->endpoints, &ep->node);
mtx_unlock(&dev->devmtx);
return ZX_OK;
}
static uint64_t dwc_get_frame(void* ctx) {
zxlogf(ERROR, "dwc_usb: dwc_get_frame not implemented\n");
return ZX_OK;
}
zx_status_t dwc_config_hub(void* ctx, uint32_t device_id, usb_speed_t speed, bool multi_tt,
usb_hub_descriptor_t* descriptor) {
// Not sure if DWC controller has to take any specific action here.
return ZX_OK;
}
static void usb_control_complete(usb_request_t* usb_req, void* cookie) {
completion_signal((completion_t*)cookie);
}
zx_status_t dwc_hub_device_added(void* _ctx, uint32_t hub_address, int port,
usb_speed_t speed) {
// Since a new device was just added it has a device address of 0 on the
// bus until it is enumerated.
zxlogf(INFO, "dwc_usb: hub device added, hub = %u, port = %d, speed = %d\n",
hub_address, port, speed);
dwc_usb_t* dwc = _ctx;
dwc_usb_device_t* new_device = &dwc->usb_devices[0];
dwc_usb_endpoint_t* ep0 = NULL;
mtx_lock(&new_device->devmtx);
new_device->hub_address = hub_address;
new_device->port = port;
new_device->speed = speed;
// Find endpoint 0 on the default device (it should be the only endpoint);
dwc_usb_endpoint_t* ep_iter = NULL;
list_for_every_entry (&new_device->endpoints, ep_iter, dwc_usb_endpoint_t, node) {
if (ep_iter->ep_address == 0) {
ep0 = ep_iter;
break;
}
}
mtx_unlock(&new_device->devmtx);
assert(ep0);
// Since we don't know the Max Packet Size for the control endpoint of this
// device yet, we set it to 8, which all devices are guaranteed to support.
ep0->desc.wMaxPacketSize = 8;
usb_request_t* get_desc = usb_request_pool_get(&dwc->free_usb_reqs, 64);
if (get_desc == NULL) {
zx_status_t status = usb_request_alloc(&get_desc, 64, 0);
assert(status == ZX_OK);
}
completion_t completion = COMPLETION_INIT;
get_desc->complete_cb = usb_control_complete;
get_desc->cookie = &completion;
get_desc->header.length = 8;
get_desc->header.device_id = 0;
get_desc->setup.bmRequestType = USB_ENDPOINT_IN;
get_desc->setup.bRequest = USB_REQ_GET_DESCRIPTOR;
get_desc->setup.wValue = (USB_DT_DEVICE << 8);
get_desc->setup.wIndex = 0;
get_desc->setup.wLength = 8;
dwc_request_queue(dwc, get_desc);
completion_wait(&completion, ZX_TIME_INFINITE);
usb_device_descriptor_t short_descriptor;
usb_request_copyfrom(get_desc, &short_descriptor, get_desc->response.actual, 0);
// Update the Max Packet Size of the control endpoint.
ep0->desc.wMaxPacketSize = short_descriptor.bMaxPacketSize0;
// Set the Device ID of the newly added device.
usb_request_t* set_addr = usb_request_pool_get(&dwc->free_usb_reqs, 64);
if (set_addr == NULL) {
zx_status_t status = usb_request_alloc(&set_addr, 64, 0);
assert(status == ZX_OK);
}
completion_reset(&completion);
set_addr->complete_cb = usb_control_complete;
set_addr->cookie = &completion;
set_addr->header.length = 0;
set_addr->header.device_id = 0;
set_addr->setup.bmRequestType = USB_ENDPOINT_OUT;
set_addr->setup.bRequest = USB_REQ_SET_ADDRESS;
set_addr->setup.wValue = dwc->next_device_address;
set_addr->setup.wIndex = 0;
set_addr->setup.wLength = 0;
dwc_request_queue(dwc, set_addr);
completion_wait(&completion, ZX_TIME_INFINITE);
zx_nanosleep(zx_deadline_after(ZX_MSEC(10)));
usb_request_pool_add(&dwc->free_usb_reqs, set_addr);
usb_request_pool_add(&dwc->free_usb_reqs, get_desc);
mtx_lock(&dwc->usb_devices[dwc->next_device_address].devmtx);
dwc->usb_devices[dwc->next_device_address].speed = speed;
dwc->usb_devices[dwc->next_device_address].hub_address = hub_address;
dwc->usb_devices[dwc->next_device_address].port = port;
dwc->usb_devices[dwc->next_device_address].device_id = dwc->next_device_address;
list_initialize(&dwc->usb_devices[dwc->next_device_address].endpoints);
dwc_usb_endpoint_t* ctrl_endpoint = calloc(1, sizeof(*ctrl_endpoint));
ctrl_endpoint->ep_address = 0;
list_initialize(&ctrl_endpoint->pending_requests);
ctrl_endpoint->parent = &dwc->usb_devices[dwc->next_device_address];
ctrl_endpoint->desc.bLength = sizeof(ctrl_endpoint->desc);
ctrl_endpoint->desc.bDescriptorType = USB_DT_ENDPOINT;
ctrl_endpoint->desc.bEndpointAddress = 0;
ctrl_endpoint->desc.bmAttributes = (USB_ENDPOINT_CONTROL);
ctrl_endpoint->desc.wMaxPacketSize = short_descriptor.bMaxPacketSize0;
ctrl_endpoint->desc.bInterval = 0;
ctrl_endpoint->request_pending_completion = COMPLETION_INIT;
list_add_tail(&dwc->usb_devices[dwc->next_device_address].endpoints, &ctrl_endpoint->node);
dwc_usb_scheduler_thread_ctx_t* ctx = malloc(sizeof(*ctx));
ctx->ep = ctrl_endpoint;
ctx->dwc = dwc;
thrd_create(
&ctrl_endpoint->request_scheduler_thread,
endpoint_request_scheduler_thread,
(void*)ctx);
mtx_unlock(&dwc->usb_devices[dwc->next_device_address].devmtx);
usb_bus_add_device(&dwc->bus, dwc->next_device_address, hub_address, speed);
dwc->next_device_address++;
return ZX_OK;
}
zx_status_t dwc_hub_device_removed(void* ctx, uint32_t hub_address, int port) {
zxlogf(ERROR, "dwc_usb: dwc_hub_device_removed not implemented\n");
return ZX_OK;
}
zx_status_t dwc_reset_endpoint(void* ctx, uint32_t device_id, uint8_t ep_address) {
return ZX_ERR_NOT_SUPPORTED;
}
static usb_hci_protocol_ops_t dwc_hci_protocol = {
.request_queue = dwc_request_queue,
.set_bus_interface = dwc_set_bus_interface,
.get_max_device_count = dwc_get_max_device_count,
.enable_endpoint = dwc_enable_ep,
.get_current_frame = dwc_get_frame,
.configure_hub = dwc_config_hub,
.hub_device_added = dwc_hub_device_added,
.hub_device_removed = dwc_hub_device_removed,
.reset_endpoint = dwc_reset_endpoint,
.get_max_transfer_size = dwc_get_max_transfer_size,
.cancel_all = dwc_cancel_all,
};
static void dwc_handle_channel_irq(uint32_t channel, dwc_usb_t* dwc) {
// Save the interrupt state of this channel.
volatile struct dwc_host_channel* chanptr = &regs->host_channels[channel];
dwc->channel_interrupts[channel] = chanptr->interrupts;
// Clear the interrupt state of this channel.
chanptr->interrupt_mask.val = 0;
chanptr->interrupts.val = 0xffffffff;
// Signal to the waiter that this channel is ready.
completion_signal(&dwc->channel_complete[channel]);
}
static void dwc_handle_irq(dwc_usb_t* dwc) {
union dwc_core_interrupts interrupts = regs->core_interrupts;
if (interrupts.port_intr) {
// Clear the interrupt.
union dwc_host_port_ctrlstatus hw_status = regs->host_port_ctrlstatus;
mtx_lock(&dwc->rh_status_mtx);
dwc->root_port_status.wPortChange = 0;
dwc->root_port_status.wPortStatus = 0;
// This device only has one port.
if (hw_status.connected)
dwc->root_port_status.wPortStatus |= USB_PORT_CONNECTION;
if (hw_status.enabled)
dwc->root_port_status.wPortStatus |= USB_PORT_ENABLE;
if (hw_status.suspended)
dwc->root_port_status.wPortStatus |= USB_PORT_SUSPEND;
if (hw_status.overcurrent)
dwc->root_port_status.wPortStatus |= USB_PORT_OVER_CURRENT;
if (hw_status.reset)
dwc->root_port_status.wPortStatus |= USB_PORT_RESET;
if (hw_status.speed == 2) {
dwc->root_port_status.wPortStatus |= USB_PORT_LOW_SPEED;
} else if (hw_status.speed == 0) {
dwc->root_port_status.wPortStatus |= USB_PORT_HIGH_SPEED;
}
if (hw_status.connected_changed)
dwc->root_port_status.wPortChange |= USB_C_PORT_CONNECTION;
if (hw_status.enabled_changed)
dwc->root_port_status.wPortChange |= USB_C_PORT_ENABLE;
if (hw_status.overcurrent_changed)
dwc->root_port_status.wPortChange |= USB_C_PORT_OVER_CURRENT;
mtx_unlock(&dwc->rh_status_mtx);
// Clear the interrupt.
hw_status.enabled = 0;
regs->host_port_ctrlstatus = hw_status;
dwc_complete_root_port_status_req(dwc);
}
if (interrupts.sof_intr) {
if ((regs->host_frame_number & 0x7) != 6) {
for (size_t i = 0; i < NUM_HOST_CHANNELS; i++) {
completion_signal(&dwc->sof_waiters[i]);
}
}
}
if (interrupts.host_channel_intr) {
uint32_t chintr = regs->host_channels_interrupt;
for (uint32_t ch = 0; ch < NUM_HOST_CHANNELS; ch++) {
if ((1 << ch) & chintr) {
dwc_handle_channel_irq(ch, dwc);
}
}
}
}
// Thread to handle interrupts.
static int dwc_irq_thread(void* arg) {
dwc_usb_t* dwc = (dwc_usb_t*)arg;
while (1) {
zx_status_t wait_res;
wait_res = zx_interrupt_wait(dwc->irq_handle);
if (wait_res != ZX_OK)
zxlogf(ERROR, "dwc_usb: irq wait failed, retcode = %d\n", wait_res);
dwc_handle_irq(dwc);
zx_interrupt_complete(dwc->irq_handle);
}
zxlogf(INFO, "dwc_usb: irq thread finished\n");
return 0;
}
static zx_status_t dwc_host_port_set_feature(uint16_t feature) {
if (feature == USB_FEATURE_PORT_POWER) {
dwc_host_port_power_on();
return ZX_OK;
} else if (feature == USB_FEATURE_PORT_RESET) {
dwc_reset_host_port();
return ZX_OK;
}
return ZX_ERR_NOT_SUPPORTED;
}
static void dwc_root_hub_get_descriptor(dwc_usb_transfer_request_t* req,
dwc_usb_t* dwc) {
usb_request_t* usb_req = req->usb_req;
usb_setup_t* setup = &usb_req->setup;
uint16_t value = le16toh(setup->wValue);
uint16_t index = le16toh(setup->wIndex);
uint16_t length = le16toh(setup->wLength);
uint8_t desc_type = value >> 8;
if (desc_type == USB_DT_DEVICE && index == 0) {
if (length > sizeof(usb_device_descriptor_t))
length = sizeof(usb_device_descriptor_t);
usb_request_copyto(usb_req, &dwc_rh_descriptor, length, 0);
complete_request(req, ZX_OK, length, dwc);
} else if (desc_type == USB_DT_CONFIG && index == 0) {
usb_configuration_descriptor_t* config_desc =
(usb_configuration_descriptor_t*)&dwc_rh_config_descriptor;
uint16_t desc_length = le16toh(config_desc->wTotalLength);
if (length > desc_length)
length = desc_length;
usb_request_copyto(usb_req, &dwc_rh_config_descriptor, length, 0);
complete_request(req, ZX_OK, length, dwc);
} else if (value >> 8 == USB_DT_STRING) {
uint8_t string_index = value & 0xFF;
if (string_index < countof(dwc_rh_string_table)) {
const uint8_t* string = dwc_rh_string_table[string_index];
if (length > string[0])
length = string[0];
usb_request_copyto(usb_req, string, length, 0);
complete_request(req, ZX_OK, length, dwc);
} else {
complete_request(req, ZX_ERR_NOT_SUPPORTED, 0, dwc);
}
}
}
static void dwc_process_root_hub_std_req(dwc_usb_transfer_request_t* req,
dwc_usb_t* dwc) {
usb_request_t* usb_req = req->usb_req;
usb_setup_t* setup = &usb_req->setup;
uint8_t request = setup->bRequest;
if (request == USB_REQ_SET_ADDRESS) {
complete_request(req, ZX_OK, 0, dwc);
} else if (request == USB_REQ_GET_DESCRIPTOR) {
dwc_root_hub_get_descriptor(req, dwc);
} else if (request == USB_REQ_SET_CONFIGURATION) {
complete_request(req, ZX_OK, 0, dwc);
} else {
complete_request(req, ZX_ERR_NOT_SUPPORTED, 0, dwc);
}
}
static void dwc_process_root_hub_class_req(dwc_usb_transfer_request_t* req,
dwc_usb_t* dwc) {
usb_request_t* usb_req = req->usb_req;
usb_setup_t* setup = &usb_req->setup;
uint8_t request = setup->bRequest;
uint16_t value = le16toh(setup->wValue);
uint16_t index = le16toh(setup->wIndex);
uint16_t length = le16toh(setup->wLength);
if (request == USB_REQ_GET_DESCRIPTOR) {
if (value == USB_HUB_DESC_TYPE << 8 && index == 0) {
usb_hub_descriptor_t desc;
memset(&desc, 0, sizeof(desc));
desc.bDescLength = sizeof(desc);
desc.bDescriptorType = value >> 8;
desc.bNbrPorts = 1;
desc.bPowerOn2PwrGood = 0;
if (length > sizeof(desc))
length = sizeof(desc);
usb_request_copyto(usb_req, &desc, length, 0);
complete_request(req, ZX_OK, length, dwc);
return;
}
} else if (request == USB_REQ_SET_FEATURE) {
zx_status_t res = dwc_host_port_set_feature(value);
complete_request(req, res, 0, dwc);
} else if (request == USB_REQ_CLEAR_FEATURE) {
mtx_lock(&dwc->rh_status_mtx);
uint16_t* change_bits = &(dwc->root_port_status.wPortChange);
switch (value) {
case USB_FEATURE_C_PORT_CONNECTION:
*change_bits &= ~USB_C_PORT_CONNECTION;
break;
case USB_FEATURE_C_PORT_ENABLE:
*change_bits &= ~USB_C_PORT_ENABLE;
break;
case USB_FEATURE_C_PORT_SUSPEND:
*change_bits &= ~USB_PORT_SUSPEND;
break;
case USB_FEATURE_C_PORT_OVER_CURRENT:
*change_bits &= ~USB_C_PORT_OVER_CURRENT;
break;
case USB_FEATURE_C_PORT_RESET:
*change_bits &= ~USB_C_PORT_RESET;
break;
}
mtx_unlock(&dwc->rh_status_mtx);
complete_request(req, ZX_OK, 0, dwc);
} else if (request == USB_REQ_GET_STATUS) {
size_t length = usb_req->header.length;
if (length > sizeof(dwc->root_port_status)) {
length = sizeof(dwc->root_port_status);
}
mtx_lock(&dwc->rh_status_mtx);
usb_request_copyto(usb_req, &dwc->root_port_status, length, 0);
mtx_unlock(&dwc->rh_status_mtx);
complete_request(req, ZX_OK, length, dwc);
} else {
complete_request(req, ZX_ERR_NOT_SUPPORTED, 0, dwc);
}
}
static void dwc_process_root_hub_ctrl_req(dwc_usb_transfer_request_t* req, dwc_usb_t* dwc) {
usb_request_t* usb_req = req->usb_req;
usb_setup_t* setup = &usb_req->setup;
if ((setup->bmRequestType & USB_TYPE_MASK) == USB_TYPE_STANDARD) {
dwc_process_root_hub_std_req(req, dwc);
} else if ((setup->bmRequestType & USB_TYPE_MASK) == USB_TYPE_CLASS) {
dwc_process_root_hub_class_req(req, dwc);
} else {
// Some unknown request type?
assert(false);
}
}
static void dwc_process_root_hub_request(dwc_usb_t* dwc,
dwc_usb_transfer_request_t* req) {
assert(req);
if (is_control_request(req)) {
dwc_process_root_hub_ctrl_req(req, dwc);
} else {
mtx_lock(&dwc->rh_status_mtx);
dwc->rh_intr_req = req;
mtx_unlock(&dwc->rh_status_mtx);
dwc_complete_root_port_status_req(dwc);
}
}
// Thread to handle queues transactions on the root hub.
static int dwc_root_hub_req_worker(void* arg) {
dwc_usb_t* dwc = (dwc_usb_t*)arg;
dwc->rh_req_completion = COMPLETION_INIT;
while (true) {
completion_wait(&dwc->rh_req_completion, ZX_TIME_INFINITE);
mtx_lock(&dwc->rh_req_mtx);
dwc_usb_transfer_request_t* req =
list_remove_head_type(&dwc->rh_req_head,
dwc_usb_transfer_request_t, node);
if (list_is_empty(&dwc->rh_req_head)) {
completion_reset(&dwc->rh_req_completion);
}
mtx_unlock(&dwc->rh_req_mtx);
dwc_process_root_hub_request(dwc, req);
}
return -1;
}
static uint acquire_channel_blocking(dwc_usb_t* dwc) {
int next_channel = -1;
while (true) {
mtx_lock(&dwc->free_channel_mtx);
// A quick sanity check. We should never mark a channel that doesn't
// exist on the system as free.
assert((dwc->free_channels & ALL_CHANNELS_FREE) == dwc->free_channels);
// Is there at least one channel that's free?
next_channel = -1;
if (dwc->free_channels) {
next_channel = __builtin_ctz(dwc->free_channels);
// Mark the bit in the free_channel bitfield = 0, meaning the
// channel is in use.
dwc->free_channels &= (ALL_CHANNELS_FREE ^ (1 << next_channel));
}
if (next_channel == -1) {
completion_reset(&dwc->free_channel_completion);
}
mtx_unlock(&dwc->free_channel_mtx);
if (next_channel >= 0) {
return next_channel;
}
// We couldn't find a free channel, wait for somebody to tell us to
// wake up and attempt to acquire a channel again.
completion_wait(&dwc->free_channel_completion, ZX_TIME_INFINITE);
}
__UNREACHABLE;
}
static void release_channel(uint ch, dwc_usb_t* dwc) {
assert(ch < DWC_NUM_CHANNELS);
mtx_lock(&dwc->free_channel_mtx);
dwc->free_channels |= (1 << ch);
mtx_unlock(&dwc->free_channel_mtx);
completion_signal(&dwc->free_channel_completion);
}
static void dwc_start_transaction(uint8_t chan,
dwc_usb_transfer_request_t* req) {
volatile struct dwc_host_channel* chanptr = &regs->host_channels[chan];
union dwc_host_channel_split_control split_control;
union dwc_host_channel_characteristics characteristics;
union dwc_host_channel_interrupts interrupt_mask;
chanptr->interrupt_mask.val = 0;
chanptr->interrupts.val = 0xffffffff;
split_control = chanptr->split_control;
split_control.complete_split = req->complete_split;
chanptr->split_control = split_control;
uint next_frame = (regs->host_frame_number & 0xffff) + 1;
if (!split_control.complete_split) {
req->cspit_retries = 0;
}
characteristics = chanptr->characteristics;
characteristics.odd_frame = next_frame & 1;
characteristics.channel_enable = 1;
chanptr->characteristics = characteristics;
interrupt_mask.val = 0;
interrupt_mask.channel_halted = 1;
chanptr->interrupt_mask = interrupt_mask;
regs->host_channels_interrupt_mask |= 1 << chan;
}
static union dwc_host_channel_interrupts dwc_await_channel_complete(uint32_t channel, dwc_usb_t* dwc) {
completion_wait(&dwc->channel_complete[channel], ZX_TIME_INFINITE);
completion_reset(&dwc->channel_complete[channel]);
return dwc->channel_interrupts[channel];
}
static void dwc_start_transfer(uint8_t chan, dwc_usb_transfer_request_t* req,
dwc_usb_endpoint_t* ep) {
volatile struct dwc_host_channel* chanptr;
union dwc_host_channel_characteristics characteristics;
union dwc_host_channel_split_control split_control;
union dwc_host_channel_transfer transfer;
void* data = NULL;
dwc_usb_device_t* dev = ep->parent;
usb_request_t* usb_req = req->usb_req;
chanptr = &regs->host_channels[chan];
characteristics.val = 0;
split_control.val = 0;
transfer.val = 0;
req->short_attempt = false;
characteristics.max_packet_size = ep->desc.wMaxPacketSize;
characteristics.endpoint_number = ep->ep_address;
characteristics.endpoint_type = usb_ep_type(&ep->desc);
characteristics.device_address = dev->device_id;
characteristics.packets_per_frame = 1;
if (ep->parent->speed == USB_SPEED_HIGH) {
characteristics.packets_per_frame +=
((ep->desc.wMaxPacketSize >> 11) & 0x3);
}
// Certain characteristics must be special cased for Control Endpoints.
if (usb_ep_type(&ep->desc) == USB_ENDPOINT_CONTROL) {
switch (req->ctrl_phase) {
case CTRL_PHASE_SETUP:
assert(req->setup_req);
characteristics.endpoint_direction = DWC_EP_OUT;
usb_request_physmap(req->setup_req);
data = (void*)usb_request_phys(req->setup_req);
// Quick sanity check to make sure that we're actually tying to
// transfer the correct number of bytes.
assert(req->setup_req->header.length == sizeof(usb_setup_t));
transfer.size = req->setup_req->header.length;
transfer.packet_id = DWC_TOGGLE_SETUP;
break;
case CTRL_PHASE_DATA:
characteristics.endpoint_direction =
usb_req->setup.bmRequestType >> 7;
usb_request_physmap(usb_req);
data = ((void*)usb_request_phys(usb_req)) + req->bytes_transferred;
transfer.size = usb_req->header.length - req->bytes_transferred;
usb_request_cacheop(usb_req, USB_REQUEST_CACHE_CLEAN_INVALIDATE, 0, transfer.size);
if (req->bytes_transferred == 0) {
transfer.packet_id = DWC_TOGGLE_DATA1;
} else {
transfer.packet_id = req->next_data_toggle;
}
break;
case CTRL_PHASE_STATUS:
// If there was no DATA phase, the status transaction is IN to the
// host. If there was a DATA phase, the status phase is in the
// opposite direction of the DATA phase.
if (usb_req->setup.wLength == 0) {
characteristics.endpoint_direction = DWC_EP_IN;
} else if ((usb_req->setup.bmRequestType >> 7) == DWC_EP_OUT) {
characteristics.endpoint_direction = DWC_EP_IN;
} else {
characteristics.endpoint_direction = DWC_EP_OUT;
}
data = NULL;
transfer.size = 0;
transfer.packet_id = DWC_TOGGLE_DATA1;
break;
}
} else {
characteristics.endpoint_direction =
(ep->ep_address & USB_ENDPOINT_DIR_MASK) >> 7;
usb_request_physmap(usb_req);
data = ((void*)usb_request_phys(usb_req)) + req->bytes_transferred;
transfer.size = usb_req->header.length - req->bytes_transferred;
transfer.packet_id = req->next_data_toggle;
}
if (dev->speed != USB_SPEED_HIGH) {
split_control.port_address = dev->port;
split_control.hub_address = dev->hub_address;
split_control.split_enable = 1;
if (transfer.size > characteristics.max_packet_size) {
transfer.size = characteristics.max_packet_size;
req->short_attempt = true;
}
if (dev->speed == USB_SPEED_LOW)
characteristics.low_speed = 1;
}
assert(IS_WORD_ALIGNED(data));
data = data ? data : (void*)0xffffff00;
//TODO(gkalsi) what to do here?
// data += BCM_SDRAM_BUS_ADDR_BASE;
chanptr->dma_address = (uint32_t)(((uintptr_t)data) & 0xffffffff);
assert(IS_WORD_ALIGNED(chanptr->dma_address));
transfer.packet_count =
DIV_ROUND_UP(transfer.size, characteristics.max_packet_size);
if (transfer.packet_count == 0) {
transfer.packet_count = 1;
}
req->bytes_queued = transfer.size;
req->total_bytes_queued = transfer.size;
req->packets_queued = transfer.packet_count;
zxlogf(TRACE, "dwc_usb: programming request, req_id = 0x%x, "
"channel = %u\n", req->request_id, chan);
chanptr->characteristics = characteristics;
chanptr->split_control = split_control;
chanptr->transfer = transfer;
dwc_start_transaction(chan, req);
}
static void await_sof_if_necessary(uint channel, dwc_usb_transfer_request_t* req,
dwc_usb_endpoint_t* ep, dwc_usb_t* dwc) {
if (usb_ep_type(&ep->desc) == USB_ENDPOINT_INTERRUPT &&
!req->complete_split && ep->parent->speed != USB_SPEED_HIGH) {
mtx_lock(&dwc->sof_waiters_mtx);
if (dwc->n_sof_waiters == 0) {
// If we're the first sof-waiter, enable the SOF interrupt.
union dwc_core_interrupts core_interrupt_mask =
regs->core_interrupt_mask;
core_interrupt_mask.sof_intr = 1;
regs->core_interrupt_mask = core_interrupt_mask;
}
dwc->n_sof_waiters++;
mtx_unlock(&dwc->sof_waiters_mtx);
// Block until we get a sof interrupt.
completion_reset(&dwc->sof_waiters[channel]);
completion_wait(&dwc->sof_waiters[channel], ZX_TIME_INFINITE);
mtx_lock(&dwc->sof_waiters_mtx);
dwc->n_sof_waiters--;
if (dwc->n_sof_waiters == 0) {
// If we're the last sof waiter, turn off the sof interrupt.
union dwc_core_interrupts core_interrupt_mask =
regs->core_interrupt_mask;
core_interrupt_mask.sof_intr = 0;
regs->core_interrupt_mask = core_interrupt_mask;
}
mtx_unlock(&dwc->sof_waiters_mtx);
}
}
static bool handle_normal_channel_halted(uint channel,
dwc_usb_transfer_request_t* req,
dwc_usb_endpoint_t* ep,
union dwc_host_channel_interrupts interrupts,
dwc_usb_t* dwc) {
volatile struct dwc_host_channel* chanptr = &regs->host_channels[channel];
uint32_t packets_remaining = chanptr->transfer.packet_count;
uint32_t packets_transferred = req->packets_queued - packets_remaining;
usb_request_t* usb_req = req->usb_req;
if (packets_transferred != 0) {
uint32_t bytes_transferred = 0;
union dwc_host_channel_characteristics characteristics =
chanptr->characteristics;
uint32_t max_packet_size = characteristics.max_packet_size;
bool is_dir_in = characteristics.endpoint_direction == 1;
if (is_dir_in) {
bytes_transferred = req->bytes_queued - chanptr->transfer.size;
} else {
if (packets_transferred > 1) {
bytes_transferred += max_packet_size * (packets_transferred - 1);
}
if (packets_remaining == 0 &&
(req->total_bytes_queued % max_packet_size != 0 ||
req->total_bytes_queued == 0)) {
bytes_transferred += req->total_bytes_queued;
} else {
bytes_transferred += max_packet_size;
}
}
req->packets_queued -= packets_transferred;
req->bytes_queued -= bytes_transferred;
req->bytes_transferred += bytes_transferred;
if ((req->packets_queued == 0) ||
((is_dir_in) &&
(bytes_transferred < packets_transferred * max_packet_size))) {
if (!interrupts.transfer_completed) {
zxlogf(ERROR, "dwc_usb: xfer failed, irq = 0x%x\n",
interrupts.val);
release_channel(channel, dwc);
complete_request(req, ZX_ERR_IO, 0, dwc);
return true;
}
if (req->short_attempt && req->bytes_queued == 0 &&
(usb_ep_type(&ep->desc) != USB_ENDPOINT_INTERRUPT)) {
req->complete_split = false;
req->next_data_toggle = chanptr->transfer.packet_id;
// Requeue the request, don't release the channel.
mtx_lock(&ep->pending_request_mtx);
list_add_head(&ep->pending_requests, &req->node);
mtx_unlock(&ep->pending_request_mtx);
completion_signal(&ep->request_pending_completion);
return true;
}
if ((usb_ep_type(&ep->desc) == USB_ENDPOINT_CONTROL) &&
(req->ctrl_phase < CTRL_PHASE_STATUS)) {
req->complete_split = false;
if (req->ctrl_phase == CTRL_PHASE_SETUP) {
req->bytes_transferred = 0;
req->next_data_toggle = DWC_TOGGLE_DATA1;
}
req->ctrl_phase++;
// If there's no DATA phase, advance directly to STATUS phase.
if (req->ctrl_phase == CTRL_PHASE_DATA && usb_req->header.length == 0) {
req->ctrl_phase++;
}
mtx_lock(&ep->pending_request_mtx);
list_add_head(&ep->pending_requests, &req->node);
mtx_unlock(&ep->pending_request_mtx);
completion_signal(&ep->request_pending_completion);
return true;
}
release_channel(channel, dwc);
complete_request(req, ZX_OK, req->bytes_transferred, dwc);
return true;
} else {
if (chanptr->split_control.split_enable) {
req->complete_split = !req->complete_split;
}
// Restart the transaction.
dwc_start_transaction(channel, req);
return false;
}
} else {
if (interrupts.ack_response_received &&
chanptr->split_control.split_enable && !req->complete_split) {
req->complete_split = true;
dwc_start_transaction(channel, req);
return false;
} else {
release_channel(channel, dwc);
complete_request(req, ZX_ERR_IO, 0, dwc);
return true;
}
}
}
static bool handle_channel_halted_interrupt(uint channel,
dwc_usb_transfer_request_t* req,
dwc_usb_endpoint_t* ep,
union dwc_host_channel_interrupts interrupts,
dwc_usb_t* dwc) {
volatile struct dwc_host_channel* chanptr = &regs->host_channels[channel];
if (interrupts.stall_response_received || interrupts.ahb_error ||
interrupts.transaction_error || interrupts.babble_error ||
interrupts.excess_transaction_error || interrupts.frame_list_rollover ||
(interrupts.nyet_response_received && !req->complete_split) ||
(interrupts.data_toggle_error &&
chanptr->characteristics.endpoint_direction == 0)) {
// There was an error on the bus.
if (!interrupts.stall_response_received) {
// It's totally okay for the EP to return stall so don't log it.
zxlogf(ERROR, "dwc_usb: xfer failed, irq = 0x%x\n",
interrupts.val);
}
// Release the channel used for this transaction.
release_channel(channel, dwc);
// Complete the request with a failure.
complete_request(req, ZX_ERR_IO, 0, dwc);
return true;
} else if (interrupts.frame_overrun) {
if (++debug_frame_overrun_counter == FRAME_OVERRUN_THRESHOLD) {
debug_frame_overrun_counter = 0;
// A little coarse since we only log every nth frame overrun.
zxlogf(INFO, "dwc_usb: requeued %d frame overruns, last one on "
"ep = %u, devid = %u\n", FRAME_OVERRUN_THRESHOLD,
ep->ep_address, ep->parent->device_id);
}
release_channel(channel, dwc);
mtx_lock(&ep->pending_request_mtx);
list_add_head(&ep->pending_requests, &req->node);
mtx_unlock(&ep->pending_request_mtx);
completion_signal(&ep->request_pending_completion);
return true;
} else if (interrupts.nak_response_received) {
// Wait a defined period of time
uint8_t bInterval = ep->desc.bInterval;
zx_duration_t sleep_ns;
req->next_data_toggle = chanptr->transfer.packet_id;
if (usb_ep_type(&ep->desc) != USB_ENDPOINT_CONTROL) {
release_channel(channel, dwc);
} else {
// Only release the channel if we're in the SETUP phase. The later
// phases assume that the channel is already held when they retry.
if (req->ctrl_phase == CTRL_PHASE_SETUP) {
release_channel(channel, dwc);
}
}
if (ep->parent->speed == USB_SPEED_HIGH) {
sleep_ns = (1 << (bInterval - 1)) * 125000;
} else {
sleep_ns = ZX_MSEC(bInterval);
}
if (!sleep_ns) {
sleep_ns = ZX_MSEC(1);
}
zx_nanosleep(zx_deadline_after(sleep_ns));
await_sof_if_necessary(channel, req, ep, dwc);
req->complete_split = false;
// Requeue the transfer and signal the endpoint.
mtx_lock(&ep->pending_request_mtx);
list_add_head(&ep->pending_requests, &req->node);
mtx_unlock(&ep->pending_request_mtx);
completion_signal(&ep->request_pending_completion);
return true;
} else if (interrupts.nyet_response_received) {
if (++req->cspit_retries >= 8) {
req->complete_split = false;
}
// Wait half a microframe to retry a NYET, otherwise wait for the start
// of the next frame.
if (usb_ep_type(&ep->desc) != USB_ENDPOINT_INTERRUPT) {
zx_nanosleep(zx_deadline_after(62500));
}
await_sof_if_necessary(channel, req, ep, dwc);
zxlogf(TRACE, "dwc_usb: requeue nyet on ep = %u, devid = %u\n",
ep->ep_address, ep->parent->device_id);
dwc_start_transaction(channel, req);
return false;
} else {
// Channel halted normally.
return handle_normal_channel_halted(channel, req, ep, interrupts, dwc);
}
}
// There is one instance of this thread per Device Endpoint.
// It is responsbile for managing requests on an endpoint.
static int endpoint_request_scheduler_thread(void* arg) {
assert(arg);
dwc_usb_scheduler_thread_ctx_t* ctx = (dwc_usb_scheduler_thread_ctx_t*)arg;
dwc_usb_endpoint_t* self = ctx->ep;
dwc_usb_t* dwc = ctx->dwc;
// No need for this anymore.
free(ctx);
dwc_usb_data_toggle_t next_data_toggle = 0;
uint channel = NUM_HOST_CHANNELS + 1;
while (true) {
zx_status_t res =
completion_wait(&self->request_pending_completion, ZX_TIME_INFINITE);
if (res != ZX_OK) {
zxlogf(ERROR, "dwc_usb: completion wait failed, retcode = %d, "
"device_id = %u, ep = %u\n", res, self->parent->device_id,
self->ep_address);
break;
}
// Attempt to take a request from the pending request queue.
dwc_usb_transfer_request_t* req = NULL;
mtx_lock(&self->pending_request_mtx);
req = list_remove_head_type(&self->pending_requests,
dwc_usb_transfer_request_t, node);
if (list_is_empty(&self->pending_requests)) {
completion_reset(&self->request_pending_completion);
}
mtx_unlock(&self->pending_request_mtx);
assert(req);
// Start this transfer.
if (usb_ep_type(&self->desc) == USB_ENDPOINT_CONTROL) {
switch (req->ctrl_phase) {
case CTRL_PHASE_SETUP:
// We're going to use a single channel for all three phases
// of the request, so we're going to acquire one here and
// hold onto it until the transaction is complete.
channel = acquire_channel_blocking(dwc);
// Allocate a usb request for the SETUP packet.
req->setup_req = usb_request_pool_get(&dwc->free_usb_reqs, sizeof(usb_setup_t));
if (req->setup_req == NULL) {
zx_status_t status =
usb_request_alloc(&req->setup_req, sizeof(usb_setup_t), 0);
assert(status == ZX_OK);
}
usb_request_t* setup_req = req->setup_req;
// Copy the setup data into the setup usb request.
usb_request_copyto(setup_req, &setup_req->setup, sizeof(usb_setup_t), 0);
usb_request_cacheop(setup_req, USB_REQUEST_CACHE_CLEAN, 0, sizeof(usb_setup_t));
setup_req->header.length = sizeof(usb_setup_t);
// Perform the SETUP phase of the control transfer.
dwc_start_transfer(channel, req, self);
break;
case CTRL_PHASE_DATA:
// The DATA phase doesn't care how many bytes the SETUP
// phase transferred.
dwc_start_transfer(channel, req, self);
break;
case CTRL_PHASE_STATUS:
dwc_start_transfer(channel, req, self);
break;
}
} else if (usb_ep_type(&self->desc) == USB_ENDPOINT_ISOCHRONOUS) {
zxlogf(ERROR, "dwc_usb: isochronous endpoints not implemented\n");
return -1;
} else if (usb_ep_type(&self->desc) == USB_ENDPOINT_BULK) {
req->next_data_toggle = next_data_toggle;
channel = acquire_channel_blocking(dwc);
dwc_start_transfer(channel, req, self);
} else if (usb_ep_type(&self->desc) == USB_ENDPOINT_INTERRUPT) {
req->next_data_toggle = next_data_toggle;
channel = acquire_channel_blocking(dwc);
await_sof_if_necessary(channel, req, self, dwc);
dwc_start_transfer(channel, req, self);
}
// Wait for an interrupt on this channel.
while (true) {
union dwc_host_channel_interrupts interrupts =
dwc_await_channel_complete(channel, dwc);
volatile struct dwc_host_channel* chanptr = &regs->host_channels[channel];
next_data_toggle = chanptr->transfer.packet_id;
if (handle_channel_halted_interrupt(channel, req, self, interrupts, dwc))
break;
}
}
return -1;
}
static zx_status_t create_default_device(dwc_usb_t* dwc) {
zx_status_t retval = ZX_OK;
dwc_usb_device_t* default_device = &dwc->usb_devices[0];
mtx_lock(&default_device->devmtx);
default_device->speed = USB_SPEED_HIGH;
default_device->hub_address = 0;
default_device->port = 0;
default_device->device_id = 0;
list_initialize(&default_device->endpoints);
// Create a control endpoint for the default device.
dwc_usb_endpoint_t* ep0 = calloc(1, sizeof(*ep0));
if (!ep0) {
retval = ZX_ERR_NO_MEMORY;
}
ep0->ep_address = 0;
list_initialize(&ep0->pending_requests);
ep0->parent = default_device;
ep0->desc.bLength = sizeof(ep0->desc);
ep0->desc.bDescriptorType = USB_DT_ENDPOINT;
ep0->desc.bEndpointAddress = 0; // Control endpoints have a size of 8;
ep0->desc.bmAttributes = (USB_ENDPOINT_CONTROL);
ep0->desc.wMaxPacketSize = 8;
ep0->desc.bInterval = 0; // Ignored for ctrl endpoints.
ep0->request_pending_completion = COMPLETION_INIT;
list_add_tail(&default_device->endpoints, &ep0->node);
dwc_usb_scheduler_thread_ctx_t* ctx = malloc(sizeof(*ctx));
ctx->ep = ep0;
ctx->dwc = dwc;
// Start the request processor thread.
thrd_create(
&ep0->request_scheduler_thread,
endpoint_request_scheduler_thread,
(void*)ctx);
mtx_unlock(&default_device->devmtx);
return retval;
}
// Bind is the entry point for this driver.
static zx_status_t usb_dwc_bind(void* ctx, zx_device_t* dev, void** cookie) {
zxlogf(TRACE, "usb_dwc: bind dev = %p\n", dev);
dwc_usb_t* usb_dwc = NULL;
platform_device_protocol_t proto;
zx_status_t st = device_get_protocol(dev, ZX_PROTOCOL_PLATFORM_DEV, &proto);
if (st != ZX_OK) {
return st;
}
// Allocate a new device object for the bus.
usb_dwc = calloc(1, sizeof(*usb_dwc));
if (!usb_dwc) {
zxlogf(ERROR, "usb_dwc: bind failed to allocate usb_dwc struct\n");
return ZX_ERR_NO_MEMORY;
}
usb_dwc->free_channel_completion = COMPLETION_INIT;
usb_dwc->free_channels = ALL_CHANNELS_FREE;
usb_dwc->next_device_address = 1;
usb_dwc->DBG_reqid = 0x1;
// Carve out some address space for this device.
size_t mmio_size;
zx_handle_t mmio_handle = ZX_HANDLE_INVALID;
st = pdev_map_mmio(&proto, MMIO_INDEX, ZX_CACHE_POLICY_UNCACHED_DEVICE, (void **)&regs,
&mmio_size, &mmio_handle);
if (st != ZX_OK) {
zxlogf(ERROR, "usb_dwc: bind failed to pdev_map_mmio.\n");
goto error_return;
}
// Create an IRQ Handle for this device.
st = pdev_map_interrupt(&proto, IRQ_INDEX, &usb_dwc->irq_handle);
if (st != ZX_OK) {
zxlogf(ERROR, "usb_dwc: bind failed to map usb irq.\n");
goto error_return;
}
usb_dwc->parent = dev;
list_initialize(&usb_dwc->rh_req_head);
// Initialize the free list.
mtx_lock(&usb_dwc->free_req_mtx);
list_initialize(&usb_dwc->free_reqs);
mtx_unlock(&usb_dwc->free_req_mtx);
// TODO(gkalsi):
// The BCM Mailbox Driver currently turns on USB power but it should be
// done here instead.
if ((st = usb_dwc_softreset_core()) != ZX_OK) {
zxlogf(ERROR, "usb_dwc: failed to reset core.\n");
goto error_return;
}
if ((st = usb_dwc_setupcontroller()) != ZX_OK) {
zxlogf(ERROR, "usb_dwc: failed setup controller.\n");
goto error_return;
}
// Initialize all the channel completions.
for (size_t i = 0; i < NUM_HOST_CHANNELS; i++) {
usb_dwc->channel_complete[i] = COMPLETION_INIT;
usb_dwc->sof_waiters[i] = COMPLETION_INIT;
}
// We create a mock device at device_id = 0 for enumeration purposes.
// Any new device that connects to the bus is assigned this ID until we
// set its address.
if ((st = create_default_device(usb_dwc)) != ZX_OK) {
zxlogf(ERROR, "usb_dwc: failed to create default device. "
"retcode = %d\n", st);
goto error_return;
}
device_add_args_t args = {
.version = DEVICE_ADD_ARGS_VERSION,
.name = "dwc2",
.ctx = usb_dwc,
.ops = &dwc_device_proto,
.proto_id = ZX_PROTOCOL_USB_HCI,
.proto_ops = &dwc_hci_protocol,
};
if ((st = device_add(dev, &args, &usb_dwc->zxdev)) != ZX_OK) {
free(usb_dwc);
return st;
}
// Thread that responds to requests for the root hub.
thrd_t root_hub_req_worker;
thrd_create_with_name(&root_hub_req_worker, dwc_root_hub_req_worker,
usb_dwc, "dwc_root_hub_req_worker");
thrd_detach(root_hub_req_worker);
thrd_t irq_thread;
thrd_create_with_name(&irq_thread, dwc_irq_thread, usb_dwc,
"dwc_irq_thread");
thrd_detach(irq_thread);
zxlogf(TRACE, "usb_dwc: bind success!\n");
return ZX_OK;
error_return:
if (regs) {
zx_vmar_unmap(zx_vmar_root_self(), (uintptr_t)regs, mmio_size);
}
zx_handle_close(mmio_handle);
if (usb_dwc) {
zx_handle_close(usb_dwc->irq_handle);
free(usb_dwc);
}
return st;
}
static zx_driver_ops_t usb_dwc_driver_ops = {
.version = DRIVER_OPS_VERSION,
.bind = usb_dwc_bind,
};
// The formatter does not play nice with these macros.
// clang-format off
ZIRCON_DRIVER_BEGIN(dwc2, usb_dwc_driver_ops, "zircon", "0.1", 3)
BI_ABORT_IF(NE, BIND_PLATFORM_DEV_VID, PDEV_VID_GENERIC),
BI_ABORT_IF(NE, BIND_PLATFORM_DEV_PID, PDEV_PID_GENERIC),
BI_MATCH_IF(EQ, BIND_PLATFORM_DEV_DID, PDEV_DID_USB_DWC2),
ZIRCON_DRIVER_END(dwc2)
// clang-format on