Comparar commits
3 Commits
| Autor | SHA1 | Data | |
|---|---|---|---|
| 129f0eadf2 | |||
| 2cd57373c3 | |||
| 1ac867dc05 |
@@ -0,0 +1,634 @@
|
||||
// Copyright 2017 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.
|
||||
|
||||
#include <assert.h>
|
||||
#include <inttypes.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <threads.h>
|
||||
|
||||
#include <ddk/binding.h>
|
||||
#include <ddk/debug.h>
|
||||
#include <ddk/device.h>
|
||||
#include <ddk/driver.h>
|
||||
#include <ddk/iotxn.h>
|
||||
#include <ddk/protocol/ethernet.h>
|
||||
#include <ddk/protocol/usb-function.h>
|
||||
#include <magenta/listnode.h>
|
||||
#include <magenta/process.h>
|
||||
#include <magenta/syscalls.h>
|
||||
#include <magenta/device/usb-device.h>
|
||||
#include <magenta/hw/usb-cdc.h>
|
||||
|
||||
#define BULK_TXN_SIZE 2048
|
||||
#define BULK_TX_COUNT 16
|
||||
#define BULK_RX_COUNT 16
|
||||
|
||||
#define BULK_MAX_PACKET 512 // FIXME(voydanoff) USB 3.0 support
|
||||
#define INTR_MAX_PACKET 16
|
||||
|
||||
#define CDC_BITRATE 1000000000 // say we are gigabit
|
||||
|
||||
typedef struct {
|
||||
mx_device_t* mxdev;
|
||||
usb_function_protocol_t function;
|
||||
|
||||
list_node_t bulk_out_txns;
|
||||
list_node_t bulk_in_txns;
|
||||
|
||||
// Device attributes
|
||||
uint8_t mac_addr[ETH_MAC_SIZE];
|
||||
uint16_t mtu;
|
||||
|
||||
mtx_t ethmac_mutex;
|
||||
ethmac_ifc_t* ethmac_ifc;
|
||||
void* ethmac_cookie;
|
||||
bool online;
|
||||
|
||||
mtx_t tx_mutex;
|
||||
mtx_t rx_mutex;
|
||||
|
||||
uint8_t bulk_out_addr;
|
||||
uint8_t bulk_in_addr;
|
||||
uint8_t intr_addr;
|
||||
uint16_t bulk_max_packet;
|
||||
} usb_cdc_t;
|
||||
|
||||
static struct {
|
||||
usb_interface_descriptor_t comm_intf;
|
||||
usb_cs_header_interface_descriptor_t cdc_header;
|
||||
usb_cs_union_interface_descriptor_1_t cdc_union;
|
||||
usb_cs_ethernet_interface_descriptor_t cdc_eth;
|
||||
usb_endpoint_descriptor_t intr_ep;
|
||||
usb_interface_descriptor_t cdc_intf_0;
|
||||
usb_interface_descriptor_t cdc_intf_1;
|
||||
usb_endpoint_descriptor_t bulk_out_ep;
|
||||
usb_endpoint_descriptor_t bulk_in_ep;
|
||||
} descriptors = {
|
||||
.comm_intf = {
|
||||
.bLength = sizeof(usb_interface_descriptor_t),
|
||||
.bDescriptorType = USB_DT_INTERFACE,
|
||||
// .bInterfaceNumber set later
|
||||
.bAlternateSetting = 0,
|
||||
.bNumEndpoints = 1,
|
||||
.bInterfaceClass = USB_CLASS_COMM,
|
||||
.bInterfaceSubClass = USB_CDC_SUBCLASS_ETHERNET,
|
||||
.bInterfaceProtocol = 0,
|
||||
.iInterface = 0,
|
||||
},
|
||||
.cdc_header = {
|
||||
.bLength = sizeof(usb_cs_header_interface_descriptor_t),
|
||||
.bDescriptorType = USB_DT_CS_INTERFACE,
|
||||
.bDescriptorSubType = USB_CDC_DST_HEADER,
|
||||
.bcdCDC = 0x110,
|
||||
},
|
||||
.cdc_union = {
|
||||
.bLength = sizeof(usb_cs_union_interface_descriptor_1_t),
|
||||
.bDescriptorType = USB_DT_CS_INTERFACE,
|
||||
.bDescriptorSubType = USB_CDC_DST_UNION,
|
||||
// .bControlInterface set later
|
||||
// .bSubordinateInterface set later
|
||||
},
|
||||
.cdc_eth = {
|
||||
.bLength = sizeof(usb_cs_ethernet_interface_descriptor_t),
|
||||
.bDescriptorType = USB_DT_CS_INTERFACE,
|
||||
.bDescriptorSubType = USB_CDC_DST_ETHERNET,
|
||||
// .iMACAddress filled in later
|
||||
.bmEthernetStatistics = 0,
|
||||
.wMaxSegmentSize = 1514,
|
||||
.wNumberMCFilters = 0,
|
||||
.bNumberPowerFilters = 0,
|
||||
},
|
||||
.intr_ep = {
|
||||
.bLength = sizeof(usb_endpoint_descriptor_t),
|
||||
.bDescriptorType = USB_DT_ENDPOINT,
|
||||
// .bEndpointAddress set later
|
||||
.bmAttributes = USB_ENDPOINT_INTERRUPT,
|
||||
.wMaxPacketSize = htole16(INTR_MAX_PACKET),
|
||||
.bInterval = 8,
|
||||
},
|
||||
.cdc_intf_0 = {
|
||||
.bLength = sizeof(usb_interface_descriptor_t),
|
||||
.bDescriptorType = USB_DT_INTERFACE,
|
||||
// .bInterfaceNumber set later
|
||||
.bAlternateSetting = 0,
|
||||
.bNumEndpoints = 0,
|
||||
.bInterfaceClass = USB_CLASS_CDC,
|
||||
.bInterfaceSubClass = 0,
|
||||
.bInterfaceProtocol = 0,
|
||||
.iInterface = 0,
|
||||
},
|
||||
.cdc_intf_1 = {
|
||||
.bLength = sizeof(usb_interface_descriptor_t),
|
||||
.bDescriptorType = USB_DT_INTERFACE,
|
||||
// .bInterfaceNumber set later
|
||||
.bAlternateSetting = 1,
|
||||
.bNumEndpoints = 2,
|
||||
.bInterfaceClass = USB_CLASS_CDC,
|
||||
.bInterfaceSubClass = 0,
|
||||
.bInterfaceProtocol = 0,
|
||||
.iInterface = 0,
|
||||
},
|
||||
.bulk_out_ep = {
|
||||
.bLength = sizeof(usb_endpoint_descriptor_t),
|
||||
.bDescriptorType = USB_DT_ENDPOINT,
|
||||
// .bEndpointAddress set later
|
||||
.bmAttributes = USB_ENDPOINT_BULK,
|
||||
.wMaxPacketSize = htole16(BULK_MAX_PACKET),
|
||||
.bInterval = 0,
|
||||
},
|
||||
.bulk_in_ep = {
|
||||
.bLength = sizeof(usb_endpoint_descriptor_t),
|
||||
.bDescriptorType = USB_DT_ENDPOINT,
|
||||
// .bEndpointAddress set later
|
||||
.bmAttributes = USB_ENDPOINT_BULK,
|
||||
.wMaxPacketSize = htole16(BULK_MAX_PACKET),
|
||||
.bInterval = 0,
|
||||
},
|
||||
};
|
||||
|
||||
static mx_status_t cdc_generate_mac_address(usb_cdc_t* cdc) {
|
||||
size_t actual;
|
||||
mx_status_t status = mx_cprng_draw(cdc->mac_addr, sizeof(cdc->mac_addr), &actual);
|
||||
if (status != MX_OK) {
|
||||
dprintf(ERROR, "cdc_generate_mac_address: mx_cprng_draw failed\n");
|
||||
return status;
|
||||
}
|
||||
|
||||
// set most significant byte so we are using a locally managed address
|
||||
// TODO(voydanoff) add a way to configure a real MAC address here
|
||||
cdc->mac_addr[0] = 0x02;
|
||||
char buffer[sizeof(cdc->mac_addr) * 3];
|
||||
snprintf(buffer, sizeof(buffer), "%02X%02X%02X%02X%02X%02X",
|
||||
cdc->mac_addr[0], cdc->mac_addr[1], cdc->mac_addr[2],
|
||||
cdc->mac_addr[3], cdc->mac_addr[4], cdc->mac_addr[5]);
|
||||
|
||||
return usb_function_alloc_string_desc(&cdc->function, buffer, &descriptors.cdc_eth.iMACAddress);
|
||||
}
|
||||
|
||||
static mx_status_t cdc_ethmac_query(void* ctx, uint32_t options, ethmac_info_t* info) {
|
||||
dprintf(TRACE, "%s: cdc_ethmac_query\n");
|
||||
usb_cdc_t* cdc = ctx;
|
||||
|
||||
// No options are supported
|
||||
if (options) {
|
||||
dprintf(ERROR, "cdc_ethmac_query: unexpected options (0x%"PRIx32") to ethmac_query\n",
|
||||
options);
|
||||
return MX_ERR_INVALID_ARGS;
|
||||
}
|
||||
|
||||
memset(info, 0, sizeof(*info));
|
||||
info->mtu = cdc->mtu;
|
||||
memcpy(info->mac, cdc->mac_addr, sizeof(cdc->mac_addr));
|
||||
|
||||
return MX_OK;
|
||||
}
|
||||
|
||||
static void cdc_ethmac_stop(void* cookie) {
|
||||
dprintf(TRACE, "%s: cdc_ethmac_stop\n");
|
||||
usb_cdc_t* cdc = cookie;
|
||||
mtx_lock(&cdc->ethmac_mutex);
|
||||
cdc->ethmac_ifc = NULL;
|
||||
mtx_unlock(&cdc->ethmac_mutex);
|
||||
}
|
||||
|
||||
static mx_status_t cdc_ethmac_start(void* ctx_cookie, ethmac_ifc_t* ifc, void* ethmac_cookie) {
|
||||
dprintf(TRACE, "%s: cdc_ethmac_start\n");
|
||||
usb_cdc_t* cdc = ctx_cookie;
|
||||
mx_status_t status = MX_OK;
|
||||
|
||||
mtx_lock(&cdc->ethmac_mutex);
|
||||
if (cdc->ethmac_ifc) {
|
||||
status = MX_ERR_ALREADY_BOUND;
|
||||
} else {
|
||||
cdc->ethmac_ifc = ifc;
|
||||
cdc->ethmac_cookie = ethmac_cookie;
|
||||
cdc->ethmac_ifc->status(ethmac_cookie, cdc->online ? ETH_STATUS_ONLINE : 0);
|
||||
}
|
||||
mtx_unlock(&cdc->ethmac_mutex);
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
static void cdc_ethmac_send(void* cookie, uint32_t options, void* data, size_t length) {
|
||||
usb_cdc_t* cdc = cookie;
|
||||
uint8_t* byte_data = data;
|
||||
|
||||
if (!cdc->online || length > cdc->mtu || length == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
dprintf(LTRACE, "ethmac_send: sending %d bytes\n", length);
|
||||
|
||||
mtx_lock(&cdc->tx_mutex);
|
||||
|
||||
// Make sure that we can get all of the tx buffers we need to use
|
||||
iotxn_t* tx_req = list_remove_head_type(&cdc->bulk_in_txns, iotxn_t, node);
|
||||
if (tx_req == NULL) {
|
||||
dprintf(LINFO, "ethmac_send: no free write txns, dropping packet\n");
|
||||
mtx_unlock(&cdc->tx_mutex);
|
||||
return;
|
||||
}
|
||||
|
||||
// As per the CDC-ECM spec, we need to send a zero-length packet to signify the end of
|
||||
// transmission when the endpoint max packet size is a factor of the total transmission size.
|
||||
iotxn_t* zlp_txn = NULL;
|
||||
if (length % cdc->bulk_max_packet == 0) {
|
||||
zlp_txn = list_remove_head_type(&cdc->bulk_in_txns, iotxn_t, node);
|
||||
if (zlp_txn == NULL) {
|
||||
dprintf(LINFO, "cdc_ethmac_send: no free write txns, dropping packet\n");
|
||||
list_add_tail(&cdc->bulk_in_txns, &tx_req->node);
|
||||
mtx_unlock(&cdc->tx_mutex);
|
||||
return;
|
||||
}
|
||||
zlp_txn->length = 0;
|
||||
}
|
||||
|
||||
// Send data
|
||||
tx_req->length = length;
|
||||
ssize_t bytes_copied = iotxn_copyto(tx_req, byte_data, tx_req->length, 0);
|
||||
if (bytes_copied < 0) {
|
||||
dprintf(LERROR, "cdc_ethmac_send: failed to copy data into send txn (error %zd)\n",
|
||||
bytes_copied);
|
||||
list_add_tail(&cdc->bulk_in_txns, &tx_req->node);
|
||||
if (zlp_txn) {
|
||||
list_add_tail(&cdc->bulk_in_txns, &zlp_txn->node);
|
||||
}
|
||||
mtx_unlock(&cdc->tx_mutex);
|
||||
return;
|
||||
}
|
||||
|
||||
// unlock before queueing txns to avoid potential deadlocks
|
||||
mtx_unlock(&cdc->tx_mutex);
|
||||
|
||||
usb_function_queue(&cdc->function, tx_req, cdc->bulk_in_addr);
|
||||
// Send zero-length terminal packet, if needed
|
||||
if (zlp_txn) {
|
||||
usb_function_queue(&cdc->function, zlp_txn, cdc->bulk_in_addr);
|
||||
}
|
||||
}
|
||||
|
||||
static ethmac_protocol_ops_t ethmac_ops = {
|
||||
.query = cdc_ethmac_query,
|
||||
.stop = cdc_ethmac_stop,
|
||||
.start = cdc_ethmac_start,
|
||||
.send = cdc_ethmac_send,
|
||||
};
|
||||
|
||||
static void cdc_intr_complete(iotxn_t* txn, void* cookie) {
|
||||
dprintf(TRACE, "cdc_intr_complete %d %ld\n", txn->status, txn->actual);
|
||||
iotxn_release(txn);
|
||||
}
|
||||
|
||||
static mx_status_t cdc_alloc_interrupt_txn(usb_cdc_t* cdc, iotxn_t** out_txn) {
|
||||
iotxn_t* txn;
|
||||
mx_status_t status = iotxn_alloc(&txn, 0, INTR_MAX_PACKET);
|
||||
if (status != MX_OK) {
|
||||
dprintf(ERROR, "cdc_alloc_interrupt_txn iotxn_alloc failed %d\n", status);
|
||||
return status;
|
||||
}
|
||||
txn->complete_cb = cdc_intr_complete;
|
||||
txn->cookie = cdc;
|
||||
*out_txn = txn;
|
||||
return MX_OK;
|
||||
}
|
||||
|
||||
// sends network connection and speed change notifications on the interrupt endpoint
|
||||
// we only do this once per USB connect, so instead of pooling iotxns we just allocate
|
||||
// them here and release them when they complete.
|
||||
static mx_status_t cdc_send_notifications(usb_cdc_t* cdc) {
|
||||
iotxn_t* txn;
|
||||
mx_status_t status;
|
||||
|
||||
usb_cdc_notification_t network_notification = {
|
||||
.bmRequestType = USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE,
|
||||
.bNotification = USB_CDC_NC_NETWORK_CONNECTION,
|
||||
.wValue = 1, // online
|
||||
.wIndex = descriptors.cdc_intf_0.bInterfaceNumber,
|
||||
.wLength = 0,
|
||||
};
|
||||
|
||||
usb_cdc_speed_change_notification_t speed_notification = {
|
||||
.notification = {
|
||||
.bmRequestType = USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE,
|
||||
.bNotification = USB_CDC_NC_CONNECTION_SPEED_CHANGE,
|
||||
.wValue = 0,
|
||||
.wIndex = descriptors.cdc_intf_0.bInterfaceNumber,
|
||||
.wLength = 0,
|
||||
},
|
||||
.downlink_br = CDC_BITRATE,
|
||||
.uplink_br = CDC_BITRATE,
|
||||
};
|
||||
|
||||
status = cdc_alloc_interrupt_txn(cdc, &txn);
|
||||
if (status != MX_OK) return status;
|
||||
iotxn_copyto(txn, &network_notification, sizeof(network_notification), 0);
|
||||
txn->length = sizeof(network_notification);
|
||||
usb_function_queue(&cdc->function, txn, cdc->intr_addr);
|
||||
|
||||
status = cdc_alloc_interrupt_txn(cdc, &txn);
|
||||
if (status != MX_OK) return status;
|
||||
iotxn_copyto(txn, &speed_notification, sizeof(speed_notification), 0);
|
||||
txn->length = sizeof(speed_notification);
|
||||
usb_function_queue(&cdc->function, txn, cdc->intr_addr);
|
||||
|
||||
return MX_OK;
|
||||
}
|
||||
|
||||
static void cdc_rx_complete(iotxn_t* txn, void* cookie) {
|
||||
usb_cdc_t* cdc = cookie;
|
||||
|
||||
dprintf(LTRACE, "cdc_rx_complete %d %ld\n", txn->status, txn->actual);
|
||||
|
||||
if (txn->status == MX_ERR_IO_NOT_PRESENT) {
|
||||
mtx_lock(&cdc->rx_mutex);
|
||||
list_add_head(&cdc->bulk_out_txns, &txn->node);
|
||||
mtx_unlock(&cdc->rx_mutex);
|
||||
return;
|
||||
}
|
||||
if (txn->status != MX_OK) {
|
||||
dprintf(ERROR, "cdc_rx_complete: usb_read_complete called with status %d\n", txn->status);
|
||||
}
|
||||
|
||||
if (txn->status == MX_OK) {
|
||||
mtx_lock(&cdc->ethmac_mutex);
|
||||
if (cdc->ethmac_ifc) {
|
||||
uint8_t* data = NULL;
|
||||
iotxn_mmap(txn, (void*)&data);
|
||||
cdc->ethmac_ifc->recv(cdc->ethmac_cookie, data, txn->actual, 0);
|
||||
}
|
||||
mtx_unlock(&cdc->ethmac_mutex);
|
||||
}
|
||||
|
||||
usb_function_queue(&cdc->function, txn, cdc->bulk_out_addr);
|
||||
}
|
||||
|
||||
static void cdc_tx_complete(iotxn_t* txn, void* cookie) {
|
||||
usb_cdc_t* cdc = cookie;
|
||||
|
||||
dprintf(LTRACE, "cdc_tx_complete %d %ld\n", txn->status, txn->actual);
|
||||
|
||||
mtx_lock(&cdc->tx_mutex);
|
||||
list_add_tail(&cdc->bulk_in_txns, &txn->node);
|
||||
mtx_unlock(&cdc->tx_mutex);
|
||||
}
|
||||
|
||||
static const usb_descriptor_header_t* cdc_get_descriptors(void* ctx, size_t* out_length) {
|
||||
*out_length = sizeof(descriptors);
|
||||
return (const usb_descriptor_header_t *)&descriptors;
|
||||
}
|
||||
|
||||
static mx_status_t cdc_control(void* ctx, const usb_setup_t* setup, void* buffer,
|
||||
size_t length, size_t* out_actual) {
|
||||
*out_actual = 0;
|
||||
|
||||
dprintf(TRACE, "cdc_control\n");
|
||||
|
||||
// USB_CDC_SET_ETHERNET_PACKET_FILTER is the only control request required by the spec
|
||||
if (setup->bmRequestType == (USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE) &&
|
||||
setup->bRequest == USB_CDC_SET_ETHERNET_PACKET_FILTER) {
|
||||
dprintf(TRACE, "USB_CDC_SET_ETHERNET_PACKET_FILTER\n");
|
||||
// TODO(voydanoff) implement the requested packet filtering
|
||||
return MX_OK;
|
||||
}
|
||||
|
||||
return MX_ERR_NOT_SUPPORTED;
|
||||
}
|
||||
|
||||
static mx_status_t cdc_set_configured(void* ctx, bool configured, usb_speed_t speed) {
|
||||
dprintf(TRACE, "cdc_set_configured %d %d\n", configured, speed);
|
||||
usb_cdc_t* cdc = ctx;
|
||||
mx_status_t status;
|
||||
|
||||
mtx_lock(&cdc->ethmac_mutex);
|
||||
cdc->online = false;
|
||||
if (cdc->ethmac_ifc) {
|
||||
cdc->ethmac_ifc->status(cdc->ethmac_cookie, 0);
|
||||
}
|
||||
mtx_unlock(&cdc->ethmac_mutex);
|
||||
|
||||
if (configured) {
|
||||
if ((status = usb_function_config_ep(&cdc->function, &descriptors.intr_ep, NULL)) != MX_OK) {
|
||||
dprintf(ERROR, "cdc_set_configured: usb_function_config_ep failed\n");
|
||||
return status;
|
||||
}
|
||||
} else {
|
||||
usb_function_disable_ep(&cdc->function, cdc->bulk_out_addr);
|
||||
usb_function_disable_ep(&cdc->function, cdc->bulk_in_addr);
|
||||
usb_function_disable_ep(&cdc->function, cdc->intr_addr);
|
||||
}
|
||||
|
||||
return MX_OK;
|
||||
}
|
||||
|
||||
static mx_status_t cdc_set_interface(void* ctx, unsigned interface, unsigned alt_setting) {
|
||||
dprintf(TRACE, "cdc_set_interface %d %d\n", interface, alt_setting);
|
||||
usb_cdc_t* cdc = ctx;
|
||||
mx_status_t status;
|
||||
|
||||
if (interface != descriptors.cdc_intf_0.bInterfaceNumber || alt_setting > 1) {
|
||||
return MX_ERR_INVALID_ARGS;
|
||||
}
|
||||
|
||||
// TODO(voydanoff) fullspeed and superspeed support
|
||||
if (alt_setting) {
|
||||
if ((status = usb_function_config_ep(&cdc->function, &descriptors.bulk_out_ep, NULL))
|
||||
!= MX_OK ||
|
||||
(status = usb_function_config_ep(&cdc->function, &descriptors.bulk_in_ep, NULL))
|
||||
!= MX_OK) {
|
||||
dprintf(ERROR, "cdc_set_interface: usb_function_config_ep failed\n");
|
||||
}
|
||||
} else {
|
||||
if ((status = usb_function_disable_ep(&cdc->function, cdc->bulk_out_addr)) != MX_OK ||
|
||||
(status = usb_function_disable_ep(&cdc->function, cdc->bulk_in_addr)) != MX_OK) {
|
||||
dprintf(ERROR, "cdc_set_interface: usb_function_disable_ep failed\n");
|
||||
}
|
||||
}
|
||||
|
||||
bool online = false;
|
||||
if (alt_setting && status == MX_OK) {
|
||||
online = true;
|
||||
|
||||
// queue our OUT txns
|
||||
mtx_lock(&cdc->rx_mutex);
|
||||
iotxn_t* txn;
|
||||
while ((txn = list_remove_head_type(&cdc->bulk_out_txns, iotxn_t, node)) != NULL) {
|
||||
usb_function_queue(&cdc->function, txn, cdc->bulk_out_addr);
|
||||
}
|
||||
mtx_unlock(&cdc->rx_mutex);
|
||||
|
||||
// send status notifications on interrupt endpoint
|
||||
status = cdc_send_notifications(cdc);
|
||||
}
|
||||
|
||||
mtx_lock(&cdc->ethmac_mutex);
|
||||
cdc->online = online;
|
||||
if (cdc->ethmac_ifc) {
|
||||
cdc->ethmac_ifc->status(cdc->ethmac_cookie, online ? ETH_STATUS_ONLINE : 0);
|
||||
}
|
||||
mtx_unlock(&cdc->ethmac_mutex);
|
||||
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
usb_function_interface_ops_t device_ops = {
|
||||
.get_descriptors = cdc_get_descriptors,
|
||||
.control = cdc_control,
|
||||
.set_configured = cdc_set_configured,
|
||||
.set_interface = cdc_set_interface,
|
||||
};
|
||||
|
||||
static void usb_cdc_unbind(void* ctx) {
|
||||
dprintf(TRACE, "usb_cdc_unbind\n");
|
||||
usb_cdc_t* cdc = ctx;
|
||||
device_remove(cdc->mxdev);
|
||||
}
|
||||
|
||||
static void usb_cdc_release(void* ctx) {
|
||||
dprintf(TRACE, "usb_cdc_release\n");
|
||||
usb_cdc_t* cdc = ctx;
|
||||
iotxn_t* txn;
|
||||
|
||||
while ((txn = list_remove_head_type(&cdc->bulk_out_txns, iotxn_t, node)) != NULL) {
|
||||
iotxn_release(txn);
|
||||
}
|
||||
while ((txn = list_remove_head_type(&cdc->bulk_in_txns, iotxn_t, node)) != NULL) {
|
||||
iotxn_release(txn);
|
||||
}
|
||||
|
||||
free(cdc);
|
||||
}
|
||||
|
||||
static mx_protocol_device_t usb_cdc_proto = {
|
||||
.version = DEVICE_OPS_VERSION,
|
||||
.unbind = usb_cdc_unbind,
|
||||
.release = usb_cdc_release,
|
||||
};
|
||||
|
||||
mx_status_t usb_cdc_bind(void* ctx, mx_device_t* parent, void** cookie) {
|
||||
dprintf(INFO, "usb_cdc_bind\n");
|
||||
|
||||
usb_cdc_t* cdc = calloc(1, sizeof(usb_cdc_t));
|
||||
if (!cdc) {
|
||||
return MX_ERR_NO_MEMORY;
|
||||
}
|
||||
|
||||
mx_status_t status = device_get_protocol(parent, MX_PROTOCOL_USB_FUNCTION, &cdc->function);
|
||||
if (status != MX_OK) {
|
||||
goto fail;
|
||||
}
|
||||
|
||||
list_initialize(&cdc->bulk_out_txns);
|
||||
list_initialize(&cdc->bulk_in_txns);
|
||||
mtx_init(&cdc->ethmac_mutex, mtx_plain);
|
||||
mtx_init(&cdc->tx_mutex, mtx_plain);
|
||||
|
||||
cdc->bulk_max_packet = BULK_MAX_PACKET; // FIXME(voydanoff) USB 3.0 support
|
||||
cdc->mtu = 1514;
|
||||
|
||||
status = usb_function_alloc_interface(&cdc->function, &descriptors.comm_intf.bInterfaceNumber);
|
||||
if (status != MX_OK) {
|
||||
dprintf(ERROR, "usb_cdc_bind: usb_function_alloc_interface failed\n");
|
||||
goto fail;
|
||||
}
|
||||
status = usb_function_alloc_interface(&cdc->function, &descriptors.cdc_intf_0.bInterfaceNumber);
|
||||
if (status != MX_OK) {
|
||||
dprintf(ERROR, "usb_cdc_bind: usb_function_alloc_interface failed\n");
|
||||
goto fail;
|
||||
}
|
||||
descriptors.cdc_intf_1.bInterfaceNumber = descriptors.cdc_intf_0.bInterfaceNumber;
|
||||
descriptors.cdc_union.bControlInterface = descriptors.comm_intf.bInterfaceNumber;
|
||||
descriptors.cdc_union.bSubordinateInterface = descriptors.cdc_intf_0.bInterfaceNumber;
|
||||
|
||||
status = usb_function_alloc_ep(&cdc->function, USB_DIR_OUT, &cdc->bulk_out_addr);
|
||||
if (status != MX_OK) {
|
||||
dprintf(ERROR, "usb_cdc_bind: usb_function_alloc_ep failed\n");
|
||||
goto fail;
|
||||
}
|
||||
status = usb_function_alloc_ep(&cdc->function, USB_DIR_IN, &cdc->bulk_in_addr);
|
||||
if (status != MX_OK) {
|
||||
dprintf(ERROR, "usb_cdc_bind: usb_function_alloc_ep failed\n");
|
||||
goto fail;
|
||||
}
|
||||
status = usb_function_alloc_ep(&cdc->function, USB_DIR_IN, &cdc->intr_addr);
|
||||
if (status != MX_OK) {
|
||||
dprintf(ERROR, "usb_cdc_bind: usb_function_alloc_ep failed\n");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
descriptors.bulk_out_ep.bEndpointAddress = cdc->bulk_out_addr;
|
||||
descriptors.bulk_in_ep.bEndpointAddress = cdc->bulk_in_addr;
|
||||
descriptors.intr_ep.bEndpointAddress = cdc->intr_addr;
|
||||
|
||||
status = cdc_generate_mac_address(cdc);
|
||||
if (status != MX_OK) {
|
||||
goto fail;
|
||||
}
|
||||
|
||||
// allocate bulk out iotxns
|
||||
iotxn_t* txn;
|
||||
for (int i = 0; i < BULK_TX_COUNT; i++) {
|
||||
status = iotxn_alloc(&txn, 0, BULK_TXN_SIZE);
|
||||
if (status != MX_OK) {
|
||||
goto fail;
|
||||
}
|
||||
txn->length = BULK_TXN_SIZE;
|
||||
txn->complete_cb = cdc_rx_complete;
|
||||
txn->cookie = cdc;
|
||||
list_add_head(&cdc->bulk_out_txns, &txn->node);
|
||||
}
|
||||
// allocate bulk in iotxns
|
||||
for (int i = 0; i < BULK_RX_COUNT; i++) {
|
||||
status = iotxn_alloc(&txn, 0, BULK_TXN_SIZE);
|
||||
if (status != MX_OK) {
|
||||
goto fail;
|
||||
}
|
||||
txn->complete_cb = cdc_tx_complete;
|
||||
txn->cookie = cdc;
|
||||
list_add_head(&cdc->bulk_in_txns, &txn->node);
|
||||
}
|
||||
|
||||
device_add_args_t args = {
|
||||
.version = DEVICE_ADD_ARGS_VERSION,
|
||||
.name = "cdc-eth-function",
|
||||
.ctx = cdc,
|
||||
.ops = &usb_cdc_proto,
|
||||
.proto_id = MX_PROTOCOL_ETHERMAC,
|
||||
.proto_ops = ðmac_ops,
|
||||
};
|
||||
|
||||
status = device_add(parent, &args, &cdc->mxdev);
|
||||
if (status != MX_OK) {
|
||||
dprintf(ERROR, "usb_device_bind add_device failed %d\n", status);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
usb_function_interface_t intf = {
|
||||
.ops = &device_ops,
|
||||
.ctx = cdc,
|
||||
};
|
||||
usb_function_register(&cdc->function, &intf);
|
||||
|
||||
return MX_OK;
|
||||
|
||||
fail:
|
||||
usb_cdc_release(cdc);
|
||||
return status;
|
||||
}
|
||||
|
||||
static mx_driver_ops_t usb_cdc_ops = {
|
||||
.version = DRIVER_OPS_VERSION,
|
||||
.bind = usb_cdc_bind,
|
||||
};
|
||||
|
||||
// clang-format off
|
||||
MAGENTA_DRIVER_BEGIN(usb_cdc, usb_cdc_ops, "magenta", "0.1", 4)
|
||||
BI_ABORT_IF(NE, BIND_PROTOCOL, MX_PROTOCOL_USB_FUNCTION),
|
||||
BI_ABORT_IF(NE, BIND_USB_CLASS, USB_CLASS_COMM),
|
||||
BI_ABORT_IF(NE, BIND_USB_SUBCLASS, USB_CDC_SUBCLASS_ETHERNET),
|
||||
BI_MATCH_IF(EQ, BIND_USB_PROTOCOL, 0),
|
||||
MAGENTA_DRIVER_END(usb_cdc)
|
||||
@@ -0,0 +1,18 @@
|
||||
# Copyright 2017 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.
|
||||
|
||||
LOCAL_DIR := $(GET_LOCAL_DIR)
|
||||
|
||||
MODULE := $(LOCAL_DIR)
|
||||
|
||||
MODULE_TYPE := driver
|
||||
|
||||
MODULE_SRCS := \
|
||||
$(LOCAL_DIR)/cdc-eth-function.c \
|
||||
|
||||
MODULE_STATIC_LIBS := system/ulib/ddk system/ulib/sync
|
||||
|
||||
MODULE_LIBS := system/ulib/driver system/ulib/magenta system/ulib/c
|
||||
|
||||
include make/module.mk
|
||||
@@ -0,0 +1,71 @@
|
||||
// Copyright 2017 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.
|
||||
|
||||
#include "dwc3.h"
|
||||
#include "dwc3-regs.h"
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
static void dwc3_ep_cmd(dwc3_t* dwc, unsigned ep_num, uint32_t command, uint32_t param0,
|
||||
uint32_t param1, uint32_t param2, uint32_t flags) {
|
||||
volatile void* mmio = dwc3_mmio(dwc);
|
||||
|
||||
mtx_lock(&dwc->lock);
|
||||
|
||||
DWC3_WRITE32(mmio + DEPCMDPAR0(ep_num), param0);
|
||||
DWC3_WRITE32(mmio + DEPCMDPAR1(ep_num), param1);
|
||||
DWC3_WRITE32(mmio + DEPCMDPAR2(ep_num), param2);
|
||||
|
||||
command |= (DEPCMD_CMDACT | flags);
|
||||
volatile void* depcmd = mmio + DEPCMD(ep_num);
|
||||
DWC3_WRITE32(depcmd, command | DEPCMD_CMDACT);
|
||||
|
||||
if ((flags & DEPCMD_CMDIOC) == 0) {
|
||||
dwc3_wait_bits(depcmd, DEPCMD_CMDACT, 0);
|
||||
}
|
||||
mtx_unlock(&dwc->lock);
|
||||
}
|
||||
|
||||
void dwc3_cmd_start_new_config(dwc3_t* dwc, unsigned ep_num, unsigned rsrc_id) {
|
||||
dwc3_ep_cmd(dwc, ep_num, DEPSTARTCFG | DEPCMD_RESOURCE_INDEX(rsrc_id), 0, 0, 0, 0);
|
||||
}
|
||||
|
||||
void dwc3_cmd_ep_set_config(dwc3_t* dwc, unsigned ep_num, unsigned ep_type,
|
||||
unsigned max_packet_size, unsigned interval, bool modify) {
|
||||
// fifo number is zero for OUT endpoints and EP0_IN
|
||||
unsigned fifo_num = (EP_OUT(ep_num) || ep_num == EP0_IN ? 0 : ep_num >> 1);
|
||||
uint32_t param0 = DEPCFG_FIFO_NUM(fifo_num) | DEPCFG_MAX_PACKET_SIZE(max_packet_size)
|
||||
| DEPCFG_EP_TYPE(ep_type);
|
||||
if (modify) {
|
||||
param0 |= DEPCFG_ACTION_MODIFY;
|
||||
} else {
|
||||
param0 |= DEPCFG_ACTION_INITIALIZE;
|
||||
}
|
||||
uint32_t param1 = DEPCFG_EP_NUMBER(ep_num) | DEPCFG_INTERVAL(interval) |
|
||||
DEPCFG_XFER_NOT_READY_EN | DEPCFG_XFER_COMPLETE_EN | DEPCFG_INTR_NUM(0);
|
||||
dwc3_ep_cmd(dwc, ep_num, DEPCFG, param0, param1, 0, 0);
|
||||
}
|
||||
|
||||
void dwc3_cmd_ep_transfer_config(dwc3_t* dwc, unsigned ep_num) {
|
||||
dwc3_ep_cmd(dwc, ep_num, DEPXFERCFG, 1, 0, 0, 0);
|
||||
}
|
||||
|
||||
void dwc3_cmd_ep_start_transfer(dwc3_t* dwc, unsigned ep_num, mx_paddr_t trb_phys) {
|
||||
dwc3_ep_cmd(dwc, ep_num, DEPSTRTXFER, (uint32_t)(trb_phys >> 32),
|
||||
(uint32_t)trb_phys, 0, DEPCMD_CMDIOC);
|
||||
}
|
||||
|
||||
void dwc3_cmd_ep_end_transfer(dwc3_t* dwc, unsigned ep_num) {
|
||||
unsigned rsrc_id = dwc->eps[ep_num].rsrc_id;
|
||||
dwc3_ep_cmd(dwc, ep_num, DEPENDXFER, 0, 0, 0,
|
||||
DEPCMD_RESOURCE_INDEX(rsrc_id) | DEPCMD_CMDIOC | DEPCMD_HIPRI_FORCERM);
|
||||
}
|
||||
|
||||
void dwc3_cmd_ep_set_stall(dwc3_t* dwc, unsigned ep_num) {
|
||||
dwc3_ep_cmd(dwc, ep_num, DEPSSTALL, 0, 0, 0, DEPCMD_CMDIOC);
|
||||
}
|
||||
|
||||
void dwc3_cmd_ep_clear_stall(dwc3_t* dwc, unsigned ep_num) {
|
||||
dwc3_ep_cmd(dwc, ep_num, DEPCSTALL, 0, 0, 0, DEPCMD_CMDIOC);
|
||||
}
|
||||
@@ -0,0 +1,350 @@
|
||||
// Copyright 2017 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.
|
||||
|
||||
#include <ddk/debug.h>
|
||||
#include <magenta/assert.h>
|
||||
|
||||
#include "dwc3.h"
|
||||
#include "dwc3-regs.h"
|
||||
#include "dwc3-types.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#define EP_FIFO_SIZE PAGE_SIZE
|
||||
|
||||
static mx_paddr_t dwc3_ep_trb_phys(dwc3_endpoint_t* ep, dwc3_trb_t* trb) {
|
||||
return io_buffer_phys(&ep->fifo.buffer) + ((void *)trb - (void *)ep->fifo.first);
|
||||
}
|
||||
|
||||
static void dwc3_enable_ep(dwc3_t* dwc, unsigned ep_num, bool enable) {
|
||||
volatile void* reg = dwc3_mmio(dwc) + DALEPENA;
|
||||
|
||||
mtx_lock(&dwc->lock);
|
||||
|
||||
uint32_t temp = DWC3_READ32(reg);
|
||||
uint32_t bit = 1 << ep_num;
|
||||
|
||||
if (enable) {
|
||||
temp |= bit;
|
||||
} else {
|
||||
temp &= ~bit;
|
||||
}
|
||||
DWC3_WRITE32(reg, temp);
|
||||
|
||||
mtx_unlock(&dwc->lock);
|
||||
}
|
||||
|
||||
mx_status_t dwc3_ep_fifo_init(dwc3_t* dwc, unsigned ep_num) {
|
||||
MX_DEBUG_ASSERT(ep_num < countof(dwc->eps));
|
||||
dwc3_endpoint_t* ep = &dwc->eps[ep_num];
|
||||
dwc3_fifo_t* fifo = &ep->fifo;
|
||||
|
||||
mx_status_t status = io_buffer_init(&fifo->buffer, EP_FIFO_SIZE, IO_BUFFER_RW);
|
||||
if (status != MX_OK) {
|
||||
return status;
|
||||
}
|
||||
|
||||
fifo->first = io_buffer_virt(&fifo->buffer);
|
||||
fifo->next = fifo->first;
|
||||
fifo->current = NULL;
|
||||
fifo->last = (void *)fifo->first + EP_FIFO_SIZE - sizeof(dwc3_trb_t);
|
||||
|
||||
// set up link TRB pointing back to the start of the fifo
|
||||
dwc3_trb_t* trb = fifo->last;
|
||||
mx_paddr_t trb_phys = io_buffer_phys(&fifo->buffer);
|
||||
trb->ptr_low = (uint32_t)trb_phys;
|
||||
trb->ptr_high = (uint32_t)(trb_phys >> 32);
|
||||
trb->status = 0;
|
||||
trb->control = TRB_TRBCTL_LINK | TRB_HWO;
|
||||
io_buffer_cache_op(&ep->fifo.buffer, MX_VMO_OP_CACHE_CLEAN,
|
||||
(trb - ep->fifo.first) * sizeof(*trb), sizeof(*trb));
|
||||
|
||||
return MX_OK;
|
||||
}
|
||||
|
||||
void dwc3_ep_fifo_release(dwc3_t* dwc, unsigned ep_num) {
|
||||
MX_DEBUG_ASSERT(ep_num < countof(dwc->eps));
|
||||
dwc3_endpoint_t* ep = &dwc->eps[ep_num];
|
||||
|
||||
io_buffer_release(&ep->fifo.buffer);
|
||||
}
|
||||
|
||||
void dwc3_ep_start_transfer(dwc3_t* dwc, unsigned ep_num, unsigned type, mx_paddr_t buffer,
|
||||
size_t length) {
|
||||
dprintf(LTRACE, "dwc3_ep_start_transfer ep %u type %u length %zu\n", ep_num, type, length);
|
||||
|
||||
// special case: EP0_OUT and EP0_IN use the same fifo
|
||||
dwc3_endpoint_t* ep = (ep_num == EP0_IN ? &dwc->eps[EP0_OUT] : &dwc->eps[ep_num]);
|
||||
|
||||
dwc3_trb_t* trb = ep->fifo.next++;
|
||||
if (ep->fifo.next == ep->fifo.last) {
|
||||
ep->fifo.next = ep->fifo.first;
|
||||
}
|
||||
if (ep->fifo.current == NULL) {
|
||||
ep->fifo.current = trb;
|
||||
}
|
||||
|
||||
trb->ptr_low = (uint32_t)buffer;
|
||||
trb->ptr_high = (uint32_t)(buffer >> 32);
|
||||
trb->status = TRB_BUFSIZ(length);
|
||||
trb->control = type | TRB_LST | TRB_IOC | TRB_HWO;
|
||||
io_buffer_cache_op(&ep->fifo.buffer, MX_VMO_OP_CACHE_CLEAN,
|
||||
(trb - ep->fifo.first) * sizeof(*trb), sizeof(*trb));
|
||||
|
||||
dwc3_cmd_ep_start_transfer(dwc, ep_num, dwc3_ep_trb_phys(ep, trb));
|
||||
}
|
||||
|
||||
static void dwc3_ep_queue_next_locked(dwc3_t* dwc, dwc3_endpoint_t* ep) {
|
||||
iotxn_t* txn;
|
||||
|
||||
if (ep->current_txn == NULL && ep->got_not_ready &&
|
||||
(txn = list_remove_head_type(&ep->queued_txns, iotxn_t, node)) != NULL) {
|
||||
ep->current_txn = txn;
|
||||
ep->got_not_ready = false;
|
||||
if (EP_IN(ep->ep_num)) {
|
||||
iotxn_cacheop(txn, IOTXN_CACHE_CLEAN, 0, txn->length);
|
||||
}
|
||||
|
||||
// TODO(voydanoff) scatter/gather support
|
||||
iotxn_physmap(txn);
|
||||
mx_paddr_t phys = iotxn_phys(txn);
|
||||
dwc3_ep_start_transfer(dwc, ep->ep_num, TRB_TRBCTL_NORMAL, phys, txn->length);
|
||||
}
|
||||
}
|
||||
|
||||
mx_status_t dwc3_ep_config(dwc3_t* dwc, usb_endpoint_descriptor_t* ep_desc,
|
||||
usb_ss_ep_comp_descriptor_t* ss_comp_desc) {
|
||||
// convert address to index in range 0 - 31
|
||||
// low bit is IN/OUT
|
||||
unsigned ep_num = dwc3_ep_num(ep_desc->bEndpointAddress);
|
||||
if (ep_num < 2) {
|
||||
// index 0 and 1 are for endpoint zero
|
||||
return MX_ERR_INVALID_ARGS;
|
||||
}
|
||||
|
||||
unsigned ep_type = usb_ep_type(ep_desc);
|
||||
if (ep_type == USB_ENDPOINT_ISOCHRONOUS) {
|
||||
dprintf(ERROR, "dwc3_ep_config: isochronous endpoints are not supported\n");
|
||||
return MX_ERR_NOT_SUPPORTED;
|
||||
}
|
||||
|
||||
dwc3_endpoint_t* ep = &dwc->eps[ep_num];
|
||||
|
||||
mtx_lock(&ep->lock);
|
||||
mx_status_t status = dwc3_ep_fifo_init(dwc, ep_num);
|
||||
if (status != MX_OK) {
|
||||
dprintf(ERROR, "dwc3_config_ep: dwc3_ep_fifo_init failed %d\n", status);
|
||||
mtx_unlock(&ep->lock);
|
||||
return status;
|
||||
}
|
||||
ep->max_packet_size = usb_ep_max_packet(ep_desc);
|
||||
ep->type = ep_type;
|
||||
ep->interval = ep_desc->bInterval;
|
||||
// TODO(voydanoff) USB3 support
|
||||
|
||||
ep->enabled = true;
|
||||
|
||||
if (dwc->configured) {
|
||||
dwc3_ep_queue_next_locked(dwc, ep);
|
||||
}
|
||||
|
||||
mtx_unlock(&ep->lock);
|
||||
|
||||
return MX_OK;
|
||||
}
|
||||
|
||||
mx_status_t dwc3_ep_disable(dwc3_t* dwc, uint8_t ep_addr) {
|
||||
// convert address to index in range 0 - 31
|
||||
// low bit is IN/OUT
|
||||
unsigned ep_num = dwc3_ep_num(ep_addr);
|
||||
if (ep_num < 2) {
|
||||
// index 0 and 1 are for endpoint zero
|
||||
return MX_ERR_INVALID_ARGS;
|
||||
}
|
||||
|
||||
dwc3_endpoint_t* ep = &dwc->eps[ep_num];
|
||||
mtx_lock(&ep->lock);
|
||||
dwc3_ep_fifo_release(dwc, ep_num);
|
||||
ep->enabled = false;
|
||||
mtx_unlock(&ep->lock);
|
||||
|
||||
return MX_OK;
|
||||
}
|
||||
|
||||
void dwc3_ep_queue(dwc3_t* dwc, unsigned ep_num, iotxn_t* txn) {
|
||||
dwc3_endpoint_t* ep = &dwc->eps[ep_num];
|
||||
|
||||
// OUT transactions must have length > 0 and multiple of max packet size
|
||||
if (EP_OUT(ep_num)) {
|
||||
if (txn->length == 0 || txn->length % ep->max_packet_size != 0) {
|
||||
dprintf(ERROR, "dwc3_ep_queue: OUT transfers must be multiple of max packet size\n");
|
||||
iotxn_complete(txn, MX_ERR_INVALID_ARGS, 0);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
mtx_lock(&ep->lock);
|
||||
|
||||
if (!ep->enabled) {
|
||||
mtx_unlock(&ep->lock);
|
||||
iotxn_complete(txn, MX_ERR_BAD_STATE, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
list_add_tail(&ep->queued_txns, &txn->node);
|
||||
|
||||
if (dwc->configured) {
|
||||
dwc3_ep_queue_next_locked(dwc, ep);
|
||||
}
|
||||
|
||||
mtx_unlock(&ep->lock);
|
||||
}
|
||||
|
||||
void dwc3_ep_set_config(dwc3_t* dwc, unsigned ep_num, bool enable) {
|
||||
dprintf(TRACE, "dwc3_ep_set_config %u\n", ep_num);
|
||||
|
||||
dwc3_endpoint_t* ep = &dwc->eps[ep_num];
|
||||
|
||||
if (enable) {
|
||||
dwc3_cmd_ep_set_config(dwc, ep_num, ep->type, ep->max_packet_size, ep->interval, false);
|
||||
dwc3_cmd_ep_transfer_config(dwc, ep_num);
|
||||
dwc3_enable_ep(dwc, ep_num, true);
|
||||
} else {
|
||||
dwc3_enable_ep(dwc, ep_num, false);
|
||||
}
|
||||
}
|
||||
|
||||
void dwc3_start_eps(dwc3_t* dwc) {
|
||||
dprintf(TRACE, "dwc3_start_eps\n");
|
||||
|
||||
dwc3_cmd_ep_set_config(dwc, EP0_IN, USB_ENDPOINT_CONTROL, dwc->eps[EP0_IN].max_packet_size, 0,
|
||||
true);
|
||||
dwc3_cmd_start_new_config(dwc, EP0_OUT, 2);
|
||||
|
||||
for (unsigned ep_num = 2; ep_num < countof(dwc->eps); ep_num++) {
|
||||
dwc3_endpoint_t* ep = &dwc->eps[ep_num];
|
||||
if (ep->enabled) {
|
||||
dwc3_ep_set_config(dwc, ep_num, true);
|
||||
|
||||
mtx_lock(&ep->lock);
|
||||
dwc3_ep_queue_next_locked(dwc, ep);
|
||||
mtx_unlock(&ep->lock);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void dwc_ep_read_trb(dwc3_endpoint_t* ep, dwc3_trb_t* trb, dwc3_trb_t* out_trb) {
|
||||
if (trb >= ep->fifo.first && trb < ep->fifo.last) {
|
||||
io_buffer_cache_op(&ep->fifo.buffer, MX_VMO_OP_CACHE_INVALIDATE,
|
||||
(trb - ep->fifo.first) * sizeof(*trb), sizeof(*trb));
|
||||
memcpy((void *)out_trb, (void *)trb, sizeof(*trb));
|
||||
} else {
|
||||
dprintf(ERROR, "dwc_ep_read_trb: bad trb\n");
|
||||
}
|
||||
}
|
||||
|
||||
void dwc3_ep_xfer_started(dwc3_t* dwc, unsigned ep_num, unsigned rsrc_id) {
|
||||
dwc3_endpoint_t* ep = &dwc->eps[ep_num];
|
||||
mtx_lock(&ep->lock);
|
||||
ep->rsrc_id = rsrc_id;
|
||||
mtx_unlock(&ep->lock);
|
||||
}
|
||||
|
||||
void dwc3_ep_xfer_not_ready(dwc3_t* dwc, unsigned ep_num, unsigned stage) {
|
||||
dprintf(LTRACE, "dwc3_ep_xfer_not_ready ep %u state %d\n", ep_num, dwc->ep0_state);
|
||||
|
||||
if (ep_num == EP0_OUT || ep_num == EP0_IN) {
|
||||
dwc3_ep0_xfer_not_ready(dwc, ep_num, stage);
|
||||
} else {
|
||||
dwc3_endpoint_t* ep = &dwc->eps[ep_num];
|
||||
|
||||
mtx_lock(&ep->lock);
|
||||
ep->got_not_ready = true;
|
||||
dwc3_ep_queue_next_locked(dwc, ep);
|
||||
mtx_unlock(&ep->lock);
|
||||
}
|
||||
}
|
||||
|
||||
void dwc3_ep_xfer_complete(dwc3_t* dwc, unsigned ep_num) {
|
||||
dprintf(LTRACE, "dwc3_ep_xfer_complete ep %u state %d\n", ep_num, dwc->ep0_state);
|
||||
|
||||
if (ep_num >= countof(dwc->eps)) {
|
||||
dprintf(ERROR, "dwc3_ep_xfer_complete: bad ep_num %u\n", ep_num);
|
||||
return;
|
||||
}
|
||||
|
||||
if (ep_num == EP0_OUT || ep_num == EP0_IN) {
|
||||
dwc3_ep0_xfer_complete(dwc, ep_num);
|
||||
} else {
|
||||
dwc3_endpoint_t* ep = &dwc->eps[ep_num];
|
||||
|
||||
mtx_lock(&ep->lock);
|
||||
iotxn_t* txn = ep->current_txn;
|
||||
ep->current_txn = NULL;
|
||||
|
||||
if (txn) {
|
||||
dwc3_trb_t trb;
|
||||
dwc_ep_read_trb(ep, ep->fifo.current, &trb);
|
||||
ep->fifo.current = NULL;
|
||||
if (trb.control & TRB_HWO) {
|
||||
dprintf(ERROR, "TRB_HWO still set in dwc3_ep_xfer_complete\n");
|
||||
}
|
||||
|
||||
mx_off_t actual = txn->length - TRB_BUFSIZ(trb.status);
|
||||
// dwc3_ep_queue_next_locked(dwc, ep);
|
||||
|
||||
mtx_unlock(&ep->lock);
|
||||
|
||||
if (EP_OUT(ep_num)) {
|
||||
iotxn_cacheop(txn, MX_VMO_OP_CACHE_INVALIDATE, 0, actual);
|
||||
}
|
||||
iotxn_complete(txn, MX_OK, actual);
|
||||
} else {
|
||||
mtx_unlock(&ep->lock);
|
||||
dprintf(ERROR, "dwc3_ep_xfer_complete: no iotxn found to complete!\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mx_status_t dwc3_ep_set_stall(dwc3_t* dwc, unsigned ep_num, bool stall) {
|
||||
if (ep_num >= countof(dwc->eps)) {
|
||||
return MX_ERR_INVALID_ARGS;
|
||||
}
|
||||
|
||||
dwc3_endpoint_t* ep = &dwc->eps[ep_num];
|
||||
mtx_lock(&ep->lock);
|
||||
|
||||
if (!ep->enabled) {
|
||||
mtx_unlock(&ep->lock);
|
||||
return MX_ERR_BAD_STATE;
|
||||
}
|
||||
if (stall && !ep->stalled) {
|
||||
dwc3_cmd_ep_set_stall(dwc, ep_num);
|
||||
} else if (!stall && ep->stalled) {
|
||||
dwc3_cmd_ep_clear_stall(dwc, ep_num);
|
||||
}
|
||||
ep->stalled = stall;
|
||||
mtx_unlock(&ep->lock);
|
||||
|
||||
return MX_OK;
|
||||
}
|
||||
|
||||
void dwc3_ep_end_transfers(dwc3_t* dwc, unsigned ep_num, mx_status_t reason) {
|
||||
dwc3_endpoint_t* ep = &dwc->eps[ep_num];
|
||||
mtx_lock(&ep->lock);
|
||||
|
||||
if (ep->current_txn) {
|
||||
dwc3_cmd_ep_end_transfer(dwc, ep_num);
|
||||
iotxn_complete(ep->current_txn, reason, 0);
|
||||
ep->current_txn = NULL;
|
||||
}
|
||||
|
||||
iotxn_t* txn;
|
||||
while ((txn = list_remove_head_type(&ep->queued_txns, iotxn_t, node)) != NULL) {
|
||||
iotxn_complete(txn, reason, 0);
|
||||
}
|
||||
|
||||
mtx_unlock(&ep->lock);
|
||||
}
|
||||
@@ -0,0 +1,212 @@
|
||||
// Copyright 2017 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.
|
||||
|
||||
#include <ddk/debug.h>
|
||||
|
||||
#include "dwc3.h"
|
||||
#include "dwc3-regs.h"
|
||||
#include "dwc3-types.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#define EP0_LOCK(dwc) (&(dwc)->eps[EP0_OUT].lock)
|
||||
|
||||
static void dwc3_queue_setup_locked(dwc3_t* dwc) {
|
||||
dwc3_ep_start_transfer(dwc, EP0_OUT, TRB_TRBCTL_SETUP, io_buffer_phys(&dwc->ep0_buffer),
|
||||
sizeof(usb_setup_t));
|
||||
dwc->ep0_state = EP0_STATE_SETUP;
|
||||
}
|
||||
|
||||
mx_status_t dwc3_ep0_init(dwc3_t* dwc) {
|
||||
// fifo only needed for physical endpoint 0
|
||||
mx_status_t status = dwc3_ep_fifo_init(dwc, EP0_OUT);
|
||||
if (status != MX_OK) {
|
||||
return status;
|
||||
}
|
||||
|
||||
for (unsigned i = EP0_OUT; i <= EP0_IN; i++) {
|
||||
dwc3_endpoint_t* ep = &dwc->eps[i];
|
||||
ep->enabled = false;
|
||||
ep->max_packet_size = EP0_MAX_PACKET_SIZE;
|
||||
ep->type = USB_ENDPOINT_CONTROL;
|
||||
ep->interval = 0;
|
||||
}
|
||||
|
||||
return MX_OK;
|
||||
}
|
||||
|
||||
void dwc3_ep0_reset(dwc3_t* dwc) {
|
||||
mtx_lock(EP0_LOCK(dwc));
|
||||
dwc3_cmd_ep_end_transfer(dwc, EP0_OUT);
|
||||
dwc->ep0_state = EP0_STATE_NONE;
|
||||
mtx_unlock(EP0_LOCK(dwc));
|
||||
}
|
||||
|
||||
void dwc3_ep0_start(dwc3_t* dwc) {
|
||||
mtx_lock(EP0_LOCK(dwc));
|
||||
dwc3_cmd_start_new_config(dwc, EP0_OUT, 0);
|
||||
dwc3_ep_set_config(dwc, EP0_OUT, true);
|
||||
dwc3_ep_set_config(dwc, EP0_IN, true);
|
||||
|
||||
dwc3_queue_setup_locked(dwc);
|
||||
mtx_unlock(EP0_LOCK(dwc));
|
||||
}
|
||||
|
||||
static mx_status_t dwc3_handle_setup(dwc3_t* dwc, usb_setup_t* setup, void* buffer, size_t length,
|
||||
size_t* out_actual) {
|
||||
mx_status_t status;
|
||||
|
||||
if (setup->bmRequestType == (USB_DIR_OUT | USB_TYPE_STANDARD | USB_RECIP_DEVICE)) {
|
||||
// handle some special setup requests in this driver
|
||||
switch (setup->bRequest) {
|
||||
case USB_REQ_SET_ADDRESS:
|
||||
dprintf(TRACE, "SET_ADDRESS %d\n", setup->wValue);
|
||||
dwc3_set_address(dwc, setup->wValue);
|
||||
*out_actual = 0;
|
||||
return MX_OK;
|
||||
case USB_REQ_SET_CONFIGURATION:
|
||||
dprintf(TRACE, "SET_CONFIGURATION %d\n", setup->wValue);
|
||||
dwc3_reset_configuration(dwc);
|
||||
dwc->configured = false;
|
||||
status = usb_dci_control(&dwc->dci_intf, setup, buffer, length, out_actual);
|
||||
if (status == MX_OK && setup->wValue) {
|
||||
dwc->configured = true;
|
||||
dwc3_start_eps(dwc);
|
||||
}
|
||||
return status;
|
||||
default:
|
||||
// fall through to usb_dci_control()
|
||||
break;
|
||||
}
|
||||
} else if (setup->bmRequestType == (USB_DIR_OUT | USB_TYPE_STANDARD | USB_RECIP_INTERFACE) &&
|
||||
setup->bRequest == USB_REQ_SET_INTERFACE) {
|
||||
dprintf(TRACE, "SET_INTERFACE %d\n", setup->wValue);
|
||||
dwc3_reset_configuration(dwc);
|
||||
dwc->configured = false;
|
||||
status = usb_dci_control(&dwc->dci_intf, setup, buffer, length, out_actual);
|
||||
if (status == MX_OK) {
|
||||
dwc->configured = true;
|
||||
dwc3_start_eps(dwc);
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
return usb_dci_control(&dwc->dci_intf, setup, buffer, length, out_actual);
|
||||
}
|
||||
|
||||
void dwc3_ep0_xfer_not_ready(dwc3_t* dwc, unsigned ep_num, unsigned stage) {
|
||||
mtx_lock(EP0_LOCK(dwc));
|
||||
|
||||
switch (dwc->ep0_state) {
|
||||
case EP0_STATE_SETUP:
|
||||
if (stage == DEPEVT_XFER_NOT_READY_STAGE_DATA ||
|
||||
stage == DEPEVT_XFER_NOT_READY_STAGE_STATUS) {
|
||||
// Stall if we receive xfer not ready data/status while waiting for setup to complete
|
||||
dwc3_cmd_ep_set_stall(dwc, EP0_OUT);
|
||||
dwc3_queue_setup_locked(dwc);
|
||||
}
|
||||
break;
|
||||
case EP0_STATE_DATA_OUT:
|
||||
if (ep_num == EP0_IN && stage == DEPEVT_XFER_NOT_READY_STAGE_DATA) {
|
||||
// end transfer and stall if we receive xfer not ready in the opposite direction
|
||||
dwc3_cmd_ep_end_transfer(dwc, EP0_OUT);
|
||||
dwc3_cmd_ep_set_stall(dwc, EP0_OUT);
|
||||
dwc3_queue_setup_locked(dwc);
|
||||
}
|
||||
break;
|
||||
case EP0_STATE_DATA_IN:
|
||||
if (ep_num == EP0_OUT && stage == DEPEVT_XFER_NOT_READY_STAGE_DATA) {
|
||||
// end transfer and stall if we receive xfer not ready in the opposite direction
|
||||
dwc3_cmd_ep_end_transfer(dwc, EP0_IN);
|
||||
dwc3_cmd_ep_set_stall(dwc, EP0_OUT);
|
||||
dwc3_queue_setup_locked(dwc);
|
||||
}
|
||||
break;
|
||||
case EP0_STATE_WAIT_NRDY_OUT:
|
||||
if (ep_num == EP0_OUT) {
|
||||
if (dwc->cur_setup.wLength > 0) {
|
||||
dwc3_ep_start_transfer(dwc, EP0_OUT, TRB_TRBCTL_STATUS_3, 0, 0);
|
||||
} else {
|
||||
dwc3_ep_start_transfer(dwc, EP0_OUT, TRB_TRBCTL_STATUS_2, 0, 0);
|
||||
}
|
||||
dwc->ep0_state = EP0_STATE_STATUS;
|
||||
}
|
||||
break;
|
||||
case EP0_STATE_WAIT_NRDY_IN:
|
||||
if (ep_num == EP0_IN) {
|
||||
if (dwc->cur_setup.wLength > 0) {
|
||||
dwc3_ep_start_transfer(dwc, EP0_IN, TRB_TRBCTL_STATUS_3, 0, 0);
|
||||
} else {
|
||||
dwc3_ep_start_transfer(dwc, EP0_IN, TRB_TRBCTL_STATUS_2, 0, 0);
|
||||
}
|
||||
dwc->ep0_state = EP0_STATE_STATUS;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
dprintf(ERROR, "dwc3_ep0_xfer_not_ready unhandled state %u\n", dwc->ep0_state);
|
||||
break;
|
||||
}
|
||||
|
||||
mtx_unlock(EP0_LOCK(dwc));
|
||||
}
|
||||
|
||||
void dwc3_ep0_xfer_complete(dwc3_t* dwc, unsigned ep_num) {
|
||||
mtx_lock(EP0_LOCK(dwc));
|
||||
|
||||
switch (dwc->ep0_state) {
|
||||
case EP0_STATE_SETUP: {
|
||||
usb_setup_t* setup = &dwc->cur_setup;
|
||||
|
||||
io_buffer_cache_op(&dwc->ep0_buffer, MX_VMO_OP_CACHE_INVALIDATE, 0, sizeof(*setup));
|
||||
memcpy(setup, io_buffer_virt(&dwc->ep0_buffer), sizeof(*setup));
|
||||
|
||||
dprintf(TRACE, "got setup: type: 0x%02X req: %d value: %d index: %d length: %d\n",
|
||||
setup->bmRequestType, setup->bRequest, setup->wValue, setup->wIndex,
|
||||
setup->wLength);
|
||||
|
||||
bool is_out = ((setup->bmRequestType & USB_DIR_MASK) == USB_DIR_OUT);
|
||||
if (setup->wLength > 0 && is_out) {
|
||||
// queue a read for the data phase
|
||||
dwc3_ep_start_transfer(dwc, EP0_OUT, TRB_TRBCTL_CONTROL_DATA,
|
||||
io_buffer_phys(&dwc->ep0_buffer), setup->wLength);
|
||||
dwc->ep0_state = EP0_STATE_DATA_OUT;
|
||||
} else {
|
||||
size_t actual;
|
||||
mx_status_t status = dwc3_handle_setup(dwc, setup, io_buffer_virt(&dwc->ep0_buffer),
|
||||
dwc->ep0_buffer.size, &actual);
|
||||
dprintf(TRACE, "dwc3_handle_setup returned %d actual %zu\n", status, actual);
|
||||
if (status != MX_OK) {
|
||||
dwc3_cmd_ep_set_stall(dwc, EP0_OUT);
|
||||
dwc3_queue_setup_locked(dwc);
|
||||
break;
|
||||
}
|
||||
|
||||
if (setup->wLength > 0) {
|
||||
// queue a write for the data phase
|
||||
io_buffer_cache_op(&dwc->ep0_buffer, MX_VMO_OP_CACHE_CLEAN, 0, actual);
|
||||
dwc3_ep_start_transfer(dwc, EP0_IN, TRB_TRBCTL_CONTROL_DATA,
|
||||
io_buffer_phys(&dwc->ep0_buffer), actual);
|
||||
dwc->ep0_state = EP0_STATE_DATA_IN;
|
||||
} else {
|
||||
dwc->ep0_state = EP0_STATE_WAIT_NRDY_IN;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case EP0_STATE_DATA_OUT:
|
||||
dwc->ep0_state = EP0_STATE_WAIT_NRDY_IN;
|
||||
break;
|
||||
case EP0_STATE_DATA_IN:
|
||||
dwc->ep0_state = EP0_STATE_WAIT_NRDY_OUT;
|
||||
break;
|
||||
case EP0_STATE_STATUS:
|
||||
dwc3_queue_setup_locked(dwc);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
mtx_unlock(EP0_LOCK(dwc));
|
||||
}
|
||||
@@ -0,0 +1,236 @@
|
||||
// Copyright 2017 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.
|
||||
|
||||
#include <ddk/debug.h>
|
||||
#include <hw/arch_ops.h>
|
||||
|
||||
#include "dwc3.h"
|
||||
#include "dwc3-regs.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <unistd.h>
|
||||
|
||||
static void dwc_handle_ep_event(dwc3_t* dwc, uint32_t event) {
|
||||
uint32_t type = DEPEVT_TYPE(event);
|
||||
uint32_t ep_num = DEPEVT_PHYS_EP(event);
|
||||
uint32_t status = DEPEVT_STATUS(event);
|
||||
|
||||
switch (type) {
|
||||
case DEPEVT_XFER_COMPLETE:
|
||||
dwc3_ep_xfer_complete(dwc, ep_num);
|
||||
break;
|
||||
case DEPEVT_XFER_IN_PROGRESS:
|
||||
dprintf(TRACE, "DEPEVT_XFER_IN_PROGRESS ep_num: %u status %u\n", ep_num, status);
|
||||
break;
|
||||
case DEPEVT_XFER_NOT_READY:
|
||||
dwc3_ep_xfer_not_ready(dwc, ep_num, DEPEVT_XFER_NOT_READY_STAGE(event));
|
||||
break;
|
||||
case DEPEVT_STREAM_EVT:
|
||||
dprintf(TRACE, "DEPEVT_STREAM_EVT ep_num: %u status %u\n", ep_num, status);
|
||||
break;
|
||||
case DEPEVT_CMD_CMPLT: {
|
||||
unsigned cmd_type = DEPEVT_CMD_CMPLT_CMD_TYPE(event);
|
||||
unsigned rsrc_id = DEPEVT_CMD_CMPLT_RSRC_ID(event);
|
||||
if (cmd_type == DEPSTRTXFER) {
|
||||
dwc3_ep_xfer_started(dwc, ep_num, rsrc_id);
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
dprintf(ERROR, "dwc_handle_ep_event: unknown event type %u\n", type);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void dwc_handle_event(dwc3_t* dwc, uint32_t event) {
|
||||
dprintf(LTRACE, "dwc_handle_event %08X\n", event);
|
||||
if (!(event & DEPEVT_NON_EP)) {
|
||||
dwc_handle_ep_event(dwc, event);
|
||||
return;
|
||||
}
|
||||
|
||||
uint32_t type = DEVT_TYPE(event);
|
||||
uint32_t info = DEVT_INFO(event);
|
||||
|
||||
switch (type) {
|
||||
case DEVT_DISCONNECT:
|
||||
dprintf(TRACE, "DEVT_DISCONNECT\n");
|
||||
break;
|
||||
case DEVT_USB_RESET:
|
||||
dprintf(TRACE, "DEVT_USB_RESET\n");
|
||||
dwc3_usb_reset(dwc);
|
||||
break;
|
||||
case DEVT_CONNECTION_DONE:
|
||||
dprintf(TRACE, "DEVT_CONNECTION_DONE\n");
|
||||
dwc3_connection_done(dwc);
|
||||
break;
|
||||
case DEVT_LINK_STATE_CHANGE:
|
||||
dprintf(TRACE, "DEVT_LINK_STATE_CHANGE: ");
|
||||
switch (info) {
|
||||
case DSTS_USBLNKST_U0 | DEVT_LINK_STATE_CHANGE_SS:
|
||||
dprintf(TRACE, "DSTS_USBLNKST_U0\n");
|
||||
break;
|
||||
case DSTS_USBLNKST_U1 | DEVT_LINK_STATE_CHANGE_SS:
|
||||
dprintf(TRACE, "DSTS_USBLNKST_U1\n");
|
||||
break;
|
||||
case DSTS_USBLNKST_U2 | DEVT_LINK_STATE_CHANGE_SS:
|
||||
dprintf(TRACE, "DSTS_USBLNKST_U2\n");
|
||||
break;
|
||||
case DSTS_USBLNKST_U3 | DEVT_LINK_STATE_CHANGE_SS:
|
||||
dprintf(TRACE, "DSTS_USBLNKST_U3\n");
|
||||
break;
|
||||
case DSTS_USBLNKST_ESS_DIS | DEVT_LINK_STATE_CHANGE_SS:
|
||||
dprintf(TRACE, "DSTS_USBLNKST_ESS_DIS\n");
|
||||
break;
|
||||
case DSTS_USBLNKST_RX_DET | DEVT_LINK_STATE_CHANGE_SS:
|
||||
dprintf(TRACE, "DSTS_USBLNKST_RX_DET\n");
|
||||
break;
|
||||
case DSTS_USBLNKST_ESS_INACT | DEVT_LINK_STATE_CHANGE_SS:
|
||||
dprintf(TRACE, "DSTS_USBLNKST_ESS_INACT\n");
|
||||
break;
|
||||
case DSTS_USBLNKST_POLL | DEVT_LINK_STATE_CHANGE_SS:
|
||||
dprintf(TRACE, "DSTS_USBLNKST_POLL\n");
|
||||
break;
|
||||
case DSTS_USBLNKST_RECOV | DEVT_LINK_STATE_CHANGE_SS:
|
||||
dprintf(TRACE, "DSTS_USBLNKST_RECOV\n");
|
||||
break;
|
||||
case DSTS_USBLNKST_HRESET | DEVT_LINK_STATE_CHANGE_SS:
|
||||
dprintf(TRACE, "DSTS_USBLNKST_HRESET\n");
|
||||
break;
|
||||
case DSTS_USBLNKST_CMPLY | DEVT_LINK_STATE_CHANGE_SS:
|
||||
dprintf(TRACE, "DSTS_USBLNKST_CMPLY\n");
|
||||
break;
|
||||
case DSTS_USBLNKST_LPBK | DEVT_LINK_STATE_CHANGE_SS:
|
||||
dprintf(TRACE, "DSTS_USBLNKST_LPBK\n");
|
||||
break;
|
||||
case DSTS_USBLNKST_RESUME_RESET | DEVT_LINK_STATE_CHANGE_SS:
|
||||
dprintf(TRACE, "DSTS_USBLNKST_RESUME_RESET\n");
|
||||
break;
|
||||
case DSTS_USBLNKST_ON:
|
||||
dprintf(TRACE, "DSTS_USBLNKST_ON\n");
|
||||
break;
|
||||
case DSTS_USBLNKST_SLEEP:
|
||||
dprintf(TRACE, "DSTS_USBLNKST_SLEEP\n");
|
||||
break;
|
||||
case DSTS_USBLNKST_SUSPEND:
|
||||
dprintf(TRACE, "DSTS_USBLNKST_SUSPEND\n");
|
||||
break;
|
||||
case DSTS_USBLNKST_DISCONNECTED:
|
||||
dprintf(TRACE, "DSTS_USBLNKST_DISCONNECTED\n");
|
||||
break;
|
||||
case DSTS_USBLNKST_EARLY_SUSPEND:
|
||||
dprintf(TRACE, "DSTS_USBLNKST_EARLY_SUSPEND\n");
|
||||
break;
|
||||
case DSTS_USBLNKST_RESET:
|
||||
dprintf(TRACE, "DSTS_USBLNKST_RESET\n");
|
||||
break;
|
||||
case DSTS_USBLNKST_RESUME:
|
||||
dprintf(TRACE, "DSTS_USBLNKST_RESUME\n");
|
||||
break;
|
||||
default:
|
||||
dprintf(ERROR, "unknown state %d\n", info);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case DEVT_REMOTE_WAKEUP:
|
||||
dprintf(TRACE, "DEVT_REMOTE_WAKEUP\n");
|
||||
break;
|
||||
case DEVT_HIBERNATE_REQUEST:
|
||||
dprintf(TRACE, "DEVT_HIBERNATE_REQUEST\n");
|
||||
break;
|
||||
case DEVT_SUSPEND_ENTRY:
|
||||
dprintf(TRACE, "DEVT_SUSPEND_ENTRY\n");
|
||||
//TODO(voydanoff) is this the best way to detect disconnect?
|
||||
dwc3_disconnected(dwc);
|
||||
break;
|
||||
case DEVT_SOF:
|
||||
dprintf(TRACE, "DEVT_SOF\n");
|
||||
break;
|
||||
case DEVT_ERRATIC_ERROR:
|
||||
dprintf(TRACE, "DEVT_ERRATIC_ERROR\n");
|
||||
break;
|
||||
case DEVT_COMMAND_COMPLETE:
|
||||
dprintf(TRACE, "DEVT_COMMAND_COMPLETE\n");
|
||||
break;
|
||||
case DEVT_EVENT_BUF_OVERFLOW:
|
||||
dprintf(TRACE, "DEVT_EVENT_BUF_OVERFLOW\n");
|
||||
break;
|
||||
case DEVT_VENDOR_TEST_LMP:
|
||||
dprintf(TRACE, "DEVT_VENDOR_TEST_LMP\n");
|
||||
break;
|
||||
case DEVT_STOPPED_DISCONNECT:
|
||||
dprintf(TRACE, "DEVT_STOPPED_DISCONNECT\n");
|
||||
break;
|
||||
case DEVT_L1_RESUME_DETECT:
|
||||
dprintf(TRACE, "DEVT_L1_RESUME_DETECT\n");
|
||||
break;
|
||||
case DEVT_LDM_RESPONSE:
|
||||
dprintf(TRACE, "DEVT_LDM_RESPONSE\n");
|
||||
break;
|
||||
default:
|
||||
dprintf(ERROR, "dwc_handle_event: unknown event type %u\n", type);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static int dwc_irq_thread(void* arg) {
|
||||
dwc3_t* dwc = arg;
|
||||
volatile void* mmio = dwc3_mmio(dwc);
|
||||
|
||||
dprintf(TRACE, "dwc_irq_thread start\n");
|
||||
|
||||
uint32_t* ring_start = io_buffer_virt(&dwc->event_buffer);
|
||||
uint32_t* ring_end = (void *)ring_start + EVENT_BUFFER_SIZE;
|
||||
volatile uint32_t* ring_cur = ring_start;
|
||||
|
||||
while (1) {
|
||||
mx_status_t status = mx_interrupt_wait(dwc->irq_handle);
|
||||
mx_interrupt_complete(dwc->irq_handle);
|
||||
if (status != MX_OK) {
|
||||
dprintf(ERROR, "mx_interrupt_wait returned %d\n", status);
|
||||
break;
|
||||
}
|
||||
|
||||
// read number of new bytes in the event buffer
|
||||
uint32_t event_count;
|
||||
while ((event_count = DWC3_READ32(mmio + GEVNTCOUNT(0)) & GEVNTCOUNT_EVNTCOUNT_MASK) > 0) {
|
||||
// invalidate cache so we can read fresh events
|
||||
io_buffer_cache_op(&dwc->event_buffer, MX_VMO_OP_CACHE_INVALIDATE, 0,
|
||||
EVENT_BUFFER_SIZE);
|
||||
|
||||
for (unsigned i = 0; i < event_count; i += sizeof(uint32_t)) {
|
||||
uint32_t event = *ring_cur++;
|
||||
if (ring_cur == ring_end) {
|
||||
ring_cur = ring_start;
|
||||
}
|
||||
dwc_handle_event(dwc, event);
|
||||
}
|
||||
|
||||
// acknowledge the events we have processed
|
||||
DWC3_WRITE32(mmio + GEVNTCOUNT(0), event_count);
|
||||
}
|
||||
}
|
||||
|
||||
dprintf(TRACE, "dwc_irq_thread done\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
void dwc3_events_start(dwc3_t* dwc) {
|
||||
volatile void* mmio = dwc3_mmio(dwc);
|
||||
|
||||
// set event buffer pointer and size
|
||||
// keep interrupts masked until we are ready
|
||||
mx_paddr_t paddr = io_buffer_phys(&dwc->event_buffer);
|
||||
DWC3_WRITE32(mmio + GEVNTADRLO(0), (uint32_t)paddr);
|
||||
DWC3_WRITE32(mmio + GEVNTADRHI(0), (uint32_t)(paddr >> 32));
|
||||
DWC3_WRITE32(mmio + GEVNTSIZ(0), EVENT_BUFFER_SIZE | GEVNTSIZ_EVNTINTRPTMASK);
|
||||
DWC3_WRITE32(mmio + GEVNTCOUNT(0), 0);
|
||||
|
||||
// enable events
|
||||
uint32_t event_mask = DEVTEN_USBRSTEVTEN | DEVTEN_CONNECTDONEEVTEN | DEVTEN_DISSCONNEVTEN |
|
||||
DEVTEN_L1SUSPEN | DEVTEN_U3_L2_SUSP_EN;
|
||||
DWC3_WRITE32(mmio + DEVTEN, event_mask);
|
||||
|
||||
thrd_create_with_name(&dwc->irq_thread, dwc_irq_thread, dwc, "dwc_irq_thread");
|
||||
}
|
||||
@@ -0,0 +1,488 @@
|
||||
// Copyright 2017 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.
|
||||
|
||||
#pragma once
|
||||
|
||||
// clang-format off
|
||||
|
||||
#include <hw/reg.h>
|
||||
|
||||
#define DWC3_READ32(a) readl(a)
|
||||
#define DWC3_WRITE32(a, v) writel(v, a)
|
||||
#define DWC3_READ64(a) readll(a)
|
||||
#define DWC3_WRITE64(a, v) writell(v, a)
|
||||
|
||||
#define DWC3_MASK(start, count) (((1 << (count)) - 1) << (start))
|
||||
#define DWC3_GET_BITS32(src, start, count) ((DWC3_READ32(src) & DWC3_MASK(start, count)) >> (start))
|
||||
#define DWC3_SET_BITS32(dest, start, count, value) \
|
||||
DWC3_WRITE32(dest, (DWC3_READ32(dest) & ~DWC3_MASK(start, count)) | \
|
||||
(((value) << (start)) & DWC3_MASK(start, count)))
|
||||
|
||||
// XHCI register offsets
|
||||
#define CAPLENGTH 0x0000 // Host Controller Operational Registers
|
||||
#define CAPLENGTH_HCIVERSION_START 16
|
||||
#define CAPLENGTH_HCIVERSION_BITS 16
|
||||
#define CAPLENGTH_CAPLENGTH_START 0
|
||||
#define CAPLENGTH_CAPLENGTH_BITS 8
|
||||
|
||||
#define HCSPARAMS1 0x0004 // Structural Parameters 1 Register
|
||||
#define HCSPARAMS1_MAXPORTS_START 24
|
||||
#define HCSPARAMS1_MAXPORTS_BITS 8
|
||||
#define HCSPARAMS1_MAXINTRS_START 8
|
||||
#define HCSPARAMS1_MAXINTRS_BITS 11
|
||||
#define HCSPARAMS1_MAXSLOTS_START 0
|
||||
#define HCSPARAMS1_MAXSLOTS_BITS 8
|
||||
|
||||
#define HCSPARAMS2 0x0008 // Structural Parameters 2 Register
|
||||
#define HCSPARAMS2_MAXSCRATCHPADBUFS_START 27
|
||||
#define HCSPARAMS2_MAXSCRATCHPADBUFS_BITS 5
|
||||
#define HCSPARAMS2_SPR (1 << 26)
|
||||
#define HCSPARAMS2_MAXSCRATCHPADBUFS_HI_START 21
|
||||
#define HCSPARAMS2_MAXSCRATCHPADBUFS_HI_BITS 5
|
||||
#define HCSPARAMS2_ERSTMAX_START 4
|
||||
#define HCSPARAMS2_ERSTMAX_BITS 4
|
||||
#define HCSPARAMS2_IST_START 0
|
||||
#define HCSPARAMS2_IST_BITS 4
|
||||
|
||||
#define HCSPARAMS3 0x000c // Structural Parameters 3 Register
|
||||
#define HCSPARAMS3_U2_DEVICE_EXIT_LAT_START 16
|
||||
#define HCSPARAMS3_U2_DEVICE_EXIT_LAT_BITS 16
|
||||
#define HCSPARAMS3_U1_DEVICE_EXIT_LAT_START 0
|
||||
#define HCSPARAMS3_U1_DEVICE_EXIT_LAT_BITS 8
|
||||
|
||||
#define HCCPARAMS1 0x0010 // Capability Parameters 1 Register
|
||||
#define HCCPARAMS1_XECP_START 16
|
||||
#define HCCPARAMS1_XECP_BITS 16
|
||||
#define HCCPARAMS1_MAXPSASIZE_START 12
|
||||
#define HCCPARAMS1_MAXPSASIZE_BITS 4
|
||||
#define HCCPARAMS1_CFC (1 << 11)
|
||||
#define HCCPARAMS1_SEC (1 << 10)
|
||||
#define HCCPARAMS1_SPC (1 << 9)
|
||||
#define HCCPARAMS1_PAE (1 << 8)
|
||||
#define HCCPARAMS1_NSS (1 << 7)
|
||||
#define HCCPARAMS1_LTC (1 << 6)
|
||||
#define HCCPARAMS1_LHRC (1 << 5)
|
||||
#define HCCPARAMS1_PIND (1 << 4)
|
||||
#define HCCPARAMS1_PPC (1 << 3)
|
||||
#define HCCPARAMS1_CSZ (1 << 2)
|
||||
#define HCCPARAMS1_BNC (1 << 1)
|
||||
#define HCCPARAMS1_AC64 (1 << 0)
|
||||
|
||||
#define DBOFF 0x0014 // Doorbell Offset Register
|
||||
#define RTSOFF 0x0018 // Runtime Register Space Offset Register
|
||||
|
||||
#define HCCPARAMS2 0x001c // Host Controller Capability Parameters 2
|
||||
#define HCCPARAMS2_ETC (1 << 6)
|
||||
#define HCCPARAMS2_CIC (1 << 5)
|
||||
#define HCCPARAMS2_LEC (1 << 4)
|
||||
#define HCCPARAMS2_CTC (1 << 3)
|
||||
#define HCCPARAMS2_FSC (1 << 2)
|
||||
#define HCCPARAMS2_CMC (1 << 1)
|
||||
#define HCCPARAMS2_U3C (1 << 0)
|
||||
|
||||
// Global register offsets
|
||||
#define GSBUSCFG0 0xc100 // Global SoC Bus Configuration Register 0
|
||||
#define GSBUSCFG1 0xc104 // Global SoC Bus Configuration Register 1
|
||||
#define GTXTHRCFG 0xc108 // Global Tx Threshold Control Register
|
||||
#define GRXTHRCFG 0xc10c // Global Rx Threshold Control Register
|
||||
|
||||
#define GCTL 0xc110 // Global Core Control Register
|
||||
#define GCTL_PWRDNSCALE(n) (((n) & 0x1fff) << 19)
|
||||
#define GCTL_PWRDNSCALE_START 19
|
||||
#define GCTL_PWRDNSCALE_BITS 13
|
||||
#define GCTL_MASTERFILTBYPASS (1 << 18)
|
||||
#define GCTL_BYPSSETADDR (1 << 17)
|
||||
#define GCTL_U2RSTECN (1 << 16)
|
||||
#define GCTL_FRMSCLDWN_START 14
|
||||
#define GCTL_FRMSCLDWN_BITS 2
|
||||
#define GCTL_PRTCAPDIR_START 12
|
||||
#define GCTL_PRTCAPDIR_BITS 2
|
||||
#define GCTL_PRTCAPDIR_HOST (1 << GCTL_PRTCAPDIR_START)
|
||||
#define GCTL_PRTCAPDIR_DEVICE (2 << GCTL_PRTCAPDIR_START)
|
||||
#define GCTL_PRTCAPDIR_OTG (3 << GCTL_PRTCAPDIR_START)
|
||||
#define GCTL_PRTCAPDIR_MASK (3 << GCTL_PRTCAPDIR_START)
|
||||
#define GCTL_CORESOFTRESET (1 << 11)
|
||||
#define GCTL_U1_U2_TIMER_SCALE (1 << 9)
|
||||
#define GCTL_DEBUGATTACH (1 << 8)
|
||||
#define GCTL_SCALEDOWN_START 4
|
||||
#define GCTL_SCALEDOWN_BITS 2
|
||||
#define GCTL_DISSCRAMBLE (1 << 3)
|
||||
#define GCTL_U2EXIT_LFPS (1 << 2)
|
||||
#define GCTL_GBL_HIBERNATION_EN (1 << 1)
|
||||
#define GCTL_DSBLCLKGTNG (1 << 0)
|
||||
|
||||
#define GPMSTS 0xc114 // Global Power Management Status Register
|
||||
#define GSTS 0xc118 // Global Status Register
|
||||
#define GSTS_CBELT_START 18
|
||||
#define GSTS_CBELT_BITS 4
|
||||
#define GSTS_CBELT(s) (((s) >> GSTS_CBELT_START) & ((1 << GSTS_CBELT_START) - 1))
|
||||
#define GSTS_SSIC_IP (1 << 11)
|
||||
#define GSTS_OTG_IP (1 << 10)
|
||||
#define GSTS_BC_IP (1 << 9)
|
||||
#define GSTS_ADP_IP (1 << 8)
|
||||
#define GSTS_HOST_IP (1 << 7)
|
||||
#define GSTS_DEVICE_IP (1 << 6)
|
||||
#define GSTS_CSR_TIMEOUT (1 << 5)
|
||||
#define GSTS_BUSERRADDRVLD (1 << 4)
|
||||
#define GSTS_CURMOD_START 0
|
||||
#define GSTS_CURMOD_BITS 2
|
||||
#define GSTS_CURMOD(s) (((s) >> GSTS_CURMOD_START) & ((1 << GSTS_CURMOD_BITS) - 1))
|
||||
|
||||
|
||||
|
||||
#define GUCTL1 0xc11c // Global User Control Register 1
|
||||
#define USB31_IP_NAME 0xc120 // IP NAME REGISTER
|
||||
#define GGPIO 0xc124 // Global General Purpose Input/Output Register
|
||||
#define GUID 0xc128 // Global User ID Register
|
||||
#define GUCTL 0xc12c // Global User Control Register
|
||||
#define GBUSERRADDR 0xc130 // Global Soc Bus Error Address Register
|
||||
#define GBUSERRADDRLO 0xc130 // Global Soc Bus Error Address Register - Low
|
||||
#define GBUSERRADDRHI 0xc134 // Global Soc Bus Error Address Register - High
|
||||
#define GPRTBIMAP 0xc138 // Global SS Port to Bus Instance Mapping Register - Low
|
||||
#define GPRTBIMAPHI 0xc13c // Global SS Port to Bus Instance Mapping Register - High
|
||||
|
||||
#define GHWPARAMS0 0xc140 // Global Hardware Parameters Register 0
|
||||
#define GHWPARAMS0_AWIDTH_START 24
|
||||
#define GHWPARAMS0_AWIDTH_BITS 8
|
||||
#define GHWPARAMS0_AWIDTH(p) (((p) >> GHWPARAMS0_AWIDTH_START) & \
|
||||
((1 << GHWPARAMS0_AWIDTH_BITS) - 1))
|
||||
#define GHWPARAMS0_SDWIDTH_START 16
|
||||
#define GHWPARAMS0_SDWIDTH_BITS 8
|
||||
#define GHWPARAMS0_SDWIDTH(p) (((p) >> GHWPARAMS0_SDWIDTH_START) & \
|
||||
((1 << GHWPARAMS0_SDWIDTH_BITS) - 1))
|
||||
#define GHWPARAMS0_MDWIDTH_START 8
|
||||
#define GHWPARAMS0_MDWIDTH_BITS 8
|
||||
#define GHWPARAMS0_MDWIDTH(p) (((p) >> GHWPARAMS0_MDWIDTH_START) & \
|
||||
((1 << GHWPARAMS0_MDWIDTH_BITS) - 1))
|
||||
#define GHWPARAMS0_SBUS_TYPE_START 6
|
||||
#define GHWPARAMS0_SBUS_TYPE_BITS 2
|
||||
#define GHWPARAMS0_SBUS_TYPE(p) (((p) >> GHWPARAMS0_SBUS_TYPE_START) & \
|
||||
((1 << GHWPARAMS0_SBUS_TYPE_BITS) - 1))
|
||||
#define GHWPARAMS0_MBUS_TYPE_START 3
|
||||
#define GHWPARAMS0_MBUS_TYPE_BITS 3
|
||||
#define GHWPARAMS0_MBUS_TYPE(p) (((p) >> GHWPARAMS0_MBUS_TYPE_START) & \
|
||||
((1 << GHWPARAMS0_MBUS_TYPE_BITS) - 1))
|
||||
#define GHWPARAMS0_MODE_START 0
|
||||
#define GHWPARAMS0_MODE_BITS 3
|
||||
#define GHWPARAMS0_MODE(p) (((p) >> GHWPARAMS0_MODE_START) & \
|
||||
((1 << GHWPARAMS0_MODE_BITS) - 1))
|
||||
|
||||
#define GHWPARAMS1 0xc144 // Global Hardware Parameters Register 1
|
||||
#define GHWPARAMS2 0xc148 // Global Hardware Parameters Register 2
|
||||
#define GHWPARAMS3 0xc14c // Global Hardware Parameters Register 3
|
||||
#define GHWPARAMS4 0xc150 // Global Hardware Parameters Register 4
|
||||
#define GHWPARAMS5 0xc154 // Global Hardware Parameters Register 5
|
||||
#define GHWPARAMS6 0xc158 // Global Hardware Parameters Register 6
|
||||
#define GHWPARAMS7 0xc15c // Global Hardware Parameters Register 7
|
||||
#define GDBGFIFOSPACE 0xc160 // Global Debug Queue/FIFO Space Available Register
|
||||
#define GBMUCTL 0xc164 // Global BMU Control Register
|
||||
#define GDBGBMU 0xc16c // Global Debug BMU Register
|
||||
#define GDBGLSPMUX_HST 0xc170 // Global Debug LSP MUX Register in host mode
|
||||
#define GDBGLSPMUX_DEV 0xc170 // Global Debug LSP MUX Register
|
||||
#define GDBGLSP 0xc174 // Global Debug LSP Register
|
||||
#define GDBGEPINFO0 0xc178 // Global Debug Endpoint Information Register 0
|
||||
#define GDBGEPINFO1 0xc17c // Global Debug Endpoint Information Register 1
|
||||
#define GPRTBIMAP_HS 0xc180 // Global High-Speed Port to Bus Instance Mapping Register
|
||||
#define GPRTBIMAP_HSLO 0xc180 // Global High-Speed Port to Bus Instance Mapping Register - Low
|
||||
#define GPRTBIMAP_HSHI 0xc184 // Global High-Speed Port to Bus Instance Mapping Register - High
|
||||
#define GPRTBIMAP_FS 0xc188 // Global Full/Low-Speed Port to Bus Instance Mapping Register
|
||||
#define GPRTBIMAP_FSLO 0xc188 // Global Full/Low-Speed Port to Bus Instance Mapping Register - Low
|
||||
#define GPRTBIMAP_FSHI 0xc18c // Global Full/Low-Speed Port to Bus Instance Mapping Register - High
|
||||
#define GHMSOCBWOR 0xc190 // Global Host Mode SoC Bandwidth Override Register
|
||||
#define GERRINJCTL_1 0xc194 // Global Error Injection 1 Control Register
|
||||
#define GERRINJCTL_2 0xc194 // Global Error Injection 2 Control Register
|
||||
#define USB31_VER_NUMBER 0xc1a0 // USB31 IP VERSION NUMBER
|
||||
#define USB31_VER_TYPE 0xc1a4 // USB31 IP VERSION TYPE
|
||||
#define GSYSBLKWINCTRL 0xc1b0 // System Bus Blocking Window Control
|
||||
//#defineGUSB3RMMICTL(n) varies
|
||||
#define GUSB2PHYCFG(n) (0xc200 + 4 * (n)) // Global USB2 PHY Configuration Register
|
||||
#define GUSB2PHYCFG_PHYSOFTRST (1 << 31)
|
||||
#define GUSB2PHYCFG_ULPI_LPM_WITH_OPMODE_CHK (1 << 29)
|
||||
#define GUSB2PHYCFG_HSIC_CON_WIDTH_ADJ(n) (((n) & 0x3) << 27)
|
||||
#define GUSB2PHYCFG_INV_SEL_HSIC (1 << 26)
|
||||
#define GUSB2PHYCFG_LSTRD(n) (((n) & 0x7) << 22)
|
||||
#define GUSB2PHYCFG_LSIPD(n) (((n) & 0x7) << 19)
|
||||
#define GUSB2PHYCFG_ULPIEXTVBUSINDICATOR (1 << 18)
|
||||
#define GUSB2PHYCFG_ULPIEXTVBUSDRV (1 << 17)
|
||||
#define GUSB2PHYCFG_ULPIAUTORES (1 << 15)
|
||||
#define GUSB2PHYCFG_USBTRDTIM(n) (((n) & 0xf) << 10)
|
||||
#define GUSB2PHYCFG_USBTRDTIM_MASK (0xf << 10)
|
||||
#define GUSB2PHYCFG_XCVRDLY (1 << 9)
|
||||
#define GUSB2PHYCFG_ENBLSLPM (1 << 8)
|
||||
#define GUSB2PHYCFG_PHYSEL (1 << 7)
|
||||
#define GUSB2PHYCFG_SUSPENDUSB20 (1 << 6)
|
||||
#define GUSB2PHYCFG_FSINTF (1 << 5)
|
||||
#define GUSB2PHYCFG_ULPI_UTMI_SEL (1 << 4)
|
||||
#define GUSB2PHYCFG_PHYIF (1 << 3)
|
||||
#define GUSB2PHYCFG_TOUTCAL(n) (((n) & 0x7) << 0)
|
||||
|
||||
#define GUSB2I2CCTL(n) (0xc240 + 4 * (n)) // Reserved Register
|
||||
#define GUSB2PHYACC_UTMI(n) (0xc280 + 4 * (n)) // Global USB 2.0 UTMI PHY Vendor Control Register
|
||||
#define GUSB2PHYACC_ULPI(n) (0xc280 + 4 * (n)) // Global USB 2.0 UTMI PHY Vendor Control Register
|
||||
|
||||
#define GUSB3PIPECTL(n) (0xc2c0 + 4 * (n)) // Global USB 3.1 PIPE Control Register
|
||||
#define GUSB3PIPECTL_PHY_SOFT_RST (1 << 31) // USB3 PHY Soft Reset
|
||||
#define GUSB3PIPECTL_HST_PRT_CMPL (1 << 30)
|
||||
#define GUSB3PIPECTL_DIS_RX_DET_P3 (1 << 28)
|
||||
#define GUSB3PIPECTL_UX_EXIT_IN_PX (1 << 27)
|
||||
#define GUSB3PIPECTL_PING_ENHANCE_EN (1 << 26)
|
||||
#define GUSB3PIPECTL_U1U2_EXIT_FAIL_TO_RECOV (1 << 25)
|
||||
#define GUSB3PIPECTL_REQUEST_P1P2P3 (1 << 24)
|
||||
#define GUSB3PIPECTL_START_RX_DET_U3_RX_DET (1 << 23)
|
||||
#define GUSB3PIPECTL_DIS_RX_DET_U3_RX_DET (1 << 22)
|
||||
#define GUSB3PIPECTL_DELAY_P1P2P3(n) (((n) & 0x7) << 19)
|
||||
#define GUSB3PIPECTL_DELAYP1TRANS (1 << 18)
|
||||
#define GUSB3PIPECTL_SUSPENDENABLE (1 << 17)
|
||||
#define GUSB3PIPECTL_DATWIDTH(n) (((n) & 0x3) << 15)
|
||||
#define GUSB3PIPECTL_ABORT_RX_DET_IN_U2 (1 << 14)
|
||||
#define GUSB3PIPECTL_SKIP_RX_DET (1 << 13)
|
||||
#define GUSB3PIPECTL_LFPS_P0_ALGN (1 << 12)
|
||||
#define GUSB3PIPECTL_P3P2_TRAN_OK (1 << 11)
|
||||
#define GUSB3PIPECTL_P3_EX_SIG_P3 (1 << 10)
|
||||
#define GUSB3PIPECTL_LFPSFILTER (1 << 9)
|
||||
#define GUSB3PIPECTL_RX_DETECT_TO_POLLING_LFPS_CONTROL (1 << 8)
|
||||
#define GUSB3PIPECTL_SSIC_EN (1 << 7)
|
||||
#define GUSB3PIPECTL_TX_SWING (1 << 6)
|
||||
#define GUSB3PIPECTL_TX_MARGIN(n) (((n) & 0x7) << 3)
|
||||
#define GUSB3PIPECTL_SS_TX_DE_EMPHASIS(n) (((n) & 0x3) << 1)
|
||||
#define GUSB3PIPECTL_ELASTIC_BUFFER_MODE (1 << 0)
|
||||
|
||||
#define GTXFIFOSIZ(n) (0xc300 + 0x7c * (n)) // Global Transmit FIFO Size Register
|
||||
#define GRXFIFOSIZ(n) (0xc380 + 0x7c * (n)) // Global Receive FIFO Size Register
|
||||
#define GEVNTADR(n) (0xc400 + 0x10 * (n)) // Global Event Buffer Address Register
|
||||
#define GEVNTADRLO(n) (0xc400 + 0x10 * (n)) // Global Event Buffer Address Register - Low
|
||||
#define GEVNTADRHI(n) (0xc404 + 0x10 * (n)) // Global Event Buffer Address Register - High
|
||||
|
||||
#define GEVNTSIZ(n) (0xc408 + 0x10 * (n)) // Global Event Buffer Size Register
|
||||
#define GEVNTSIZ_EVNTINTRPTMASK (1 << 31) // Event Interrupt Mask
|
||||
|
||||
#define GEVNTCOUNT(n) (0xc40c + 0x10 * (n)) // Global Event Buffer Size Register
|
||||
#define GEVNTCOUNT_EVNT_HANDLER_BUSY (1 << 31) // Event Handler Busy
|
||||
#define GEVNTCOUNT_EVNTCOUNT_MASK 0xffff // Mask for Event Count
|
||||
|
||||
#define GHWPARAMS8 0xc600 // Global Hardware Parameters Register 8
|
||||
#define GSMACCTL 0xc604 // Global SMAC CONTROL REGISTER
|
||||
#define GUCTL2 0xc608 // Global User Control Register 2
|
||||
#define GUCTL3 0xc60c // Global User Control Register 3
|
||||
#define GTXFIFOPRIDEV 0xc610 // Global Device TXFIFO DMA Priority Register
|
||||
#define GTXFIFOPRIHST 0xc618 // Global Host TXFIFO DMA Priority Register
|
||||
#define GRXFIFOPRIHST 0xc61c // Global Host RXFIFO DMA Priority Register
|
||||
#define GFIFOPRIDBC 0xc620 // Global Host Debug Capability DMA Priority Register
|
||||
#define GDMAHLRATIO 0xc624 // Global Host FIFO DMA High-Low Priority Ratio Register
|
||||
#define GOSTDDMA_ASYNC 0xc628 // Global Number of Async Outstanding DMA Register
|
||||
#define GOSTDDMA_PRD 0xc62c // Global Number of Periodic Outstanding DMA Register
|
||||
#define GFLADJ 0xc630 // Global Frame Length Adjustment Register
|
||||
#define GUSB2RHBCTL(n) (0xc640 + 4 * (n)) // Global USB2 PHY Configuration Register
|
||||
|
||||
// Device mode register offsets
|
||||
#define DCFG 0xc700 // Device Configuration Register
|
||||
#define DCFG_STOP_ON_DISCONNECT (1 << 24)
|
||||
#define DCFG_IGN_STRM_PP (1 << 23)
|
||||
#define DCFG_LPMCAP (1 << 22)
|
||||
#define DCFG_NUMP_START 17
|
||||
#define DCFG_NUMP_BITS 5
|
||||
#define DCFG_INTRNUM_START 12
|
||||
#define DCFG_INTRNUM_BITS 5
|
||||
#define DCFG_DEVADDR_START 3
|
||||
#define DCFG_DEVADDR_BITS 7
|
||||
#define DCFG_DEVSPD_START 0
|
||||
#define DCFG_DEVSPD_BITS 3
|
||||
#define DCFG_DEVSPD_HIGH 0
|
||||
#define DCFG_DEVSPD_FULL 1
|
||||
#define DCFG_DEVSPD_LOW 2
|
||||
#define DCFG_DEVSPD_SUPER 4
|
||||
|
||||
#define DCTL 0xc704 // Device Control Register
|
||||
#define DCTL_RUN_STOP (1 << 31)
|
||||
#define DCTL_CSFTRST (1 << 30)
|
||||
#define DCFG_HIRDTHRES_START 24
|
||||
#define DCFG_HIRDTHRES_BITS 5
|
||||
#define DCFG_LPM_NYET_THRES_START 20
|
||||
#define DCFG_LPM_NYET_THRES_BITS 4
|
||||
#define DCTL_KEEP_CONNECT (1 << 19)
|
||||
#define DCTL_L1_HIBERNATION_EN (1 << 18)
|
||||
#define DCTL_CRS (1 << 17)
|
||||
#define DCTL_CSS (1 << 16)
|
||||
#define DCTL_INITU2ENA (1 << 12)
|
||||
#define DCTL_ACCEPTU2ENA (1 << 11)
|
||||
#define DCTL_INITU1ENA (1 << 10)
|
||||
#define DCTL_ACCEPTU1ENA (1 << 9)
|
||||
#define DCTL_ACCEPTU1ENA (1 << 9)
|
||||
#define DCFG_ULSTCHNGREQ_START 5
|
||||
#define DCFG_ULSTCHNGREQ_BITS 4
|
||||
#define DCFG_TSTCTL_START 1
|
||||
#define DCFG_TSTCTL_BITS 4
|
||||
|
||||
#define DEVTEN 0xc708 // Device Event Enable Register
|
||||
#define DEVTEN_LDMEVTEN (1 << 15)
|
||||
#define DEVTEN_L1WKUPEVTEN (1 << 14)
|
||||
#define DEVTEN_STOP_ON_DISCONNECT_EN (1 << 13)
|
||||
#define DEVTEN_VENDEVTSTRCVDEN (1 << 12)
|
||||
#define DEVTEN_ERRTICERREVTEN (1 << 9)
|
||||
#define DEVTEN_L1SUSPEN (1 << 8)
|
||||
#define DEVTEN_SOFTEVTEN (1 << 7)
|
||||
#define DEVTEN_U3_L2_SUSP_EN (1 << 6)
|
||||
#define DEVTEN_HIBERNATION_REQ_EVT_EN (1 << 5)
|
||||
#define DEVTEN_WKUPEVTEN (1 << 4)
|
||||
#define DEVTEN_ULSTCNGEN (1 << 3)
|
||||
#define DEVTEN_CONNECTDONEEVTEN (1 << 2)
|
||||
#define DEVTEN_USBRSTEVTEN (1 << 1)
|
||||
#define DEVTEN_DISSCONNEVTEN (1 << 0)
|
||||
|
||||
#define DSTS 0xc70c // Device Status Register
|
||||
#define DSTS_DCNRD (1 << 29)
|
||||
#define DSTS_SRE (1 << 28)
|
||||
#define DSTS_RSS (1 << 25)
|
||||
#define DSTS_SSS (1 << 24)
|
||||
#define DSTS_COREIDLE (1 << 23)
|
||||
#define DSTS_DEVCTRLHLT (1 << 22)
|
||||
#define DSTS_USBLNKST_START 18
|
||||
#define DSTS_USBLNKST_BITS 4
|
||||
#define DSTS_USBLNKST(s) (((s) >> DSTS_USBLNKST_START) & \
|
||||
((1 << DSTS_USBLNKST_BITS) - 1))
|
||||
#define DSTS_RXFIFOEMPTY (1 << 17)
|
||||
#define DSTS_SOFFN_START 3
|
||||
#define DSTS_SOFFN_BITS 14
|
||||
#define DSTS_SOFFN(s) (((s) >> DSTS_SOFFN_START) & \
|
||||
((1 << DSTS_SOFFN_BITS) - 1))
|
||||
#define DSTS_CONNECTSPD_START 0
|
||||
#define DSTS_CONNECTSPD_BITS 3
|
||||
#define DSTS_CONNECTSPD(s) (((s) >> DSTS_CONNECTSPD_START) & \
|
||||
((1 << DSTS_CONNECTSPD_BITS) - 1))
|
||||
|
||||
// DSTS link state in SS node
|
||||
#define DSTS_USBLNKST_U0 0x0
|
||||
#define DSTS_USBLNKST_U1 0x1
|
||||
#define DSTS_USBLNKST_U2 0x2
|
||||
#define DSTS_USBLNKST_U3 0x3
|
||||
#define DSTS_USBLNKST_ESS_DIS 0x4
|
||||
#define DSTS_USBLNKST_RX_DET 0x5
|
||||
#define DSTS_USBLNKST_ESS_INACT 0x6
|
||||
#define DSTS_USBLNKST_POLL 0x7
|
||||
#define DSTS_USBLNKST_RECOV 0x8
|
||||
#define DSTS_USBLNKST_HRESET 0x9
|
||||
#define DSTS_USBLNKST_CMPLY 0xa
|
||||
#define DSTS_USBLNKST_LPBK 0xb
|
||||
#define DSTS_USBLNKST_RESUME_RESET 0xf
|
||||
|
||||
// DSTS link state in HS/FS/LS node
|
||||
#define DSTS_USBLNKST_ON 0x0
|
||||
#define DSTS_USBLNKST_SLEEP 0x2
|
||||
#define DSTS_USBLNKST_SUSPEND 0x3
|
||||
#define DSTS_USBLNKST_DISCONNECTED 0x4
|
||||
#define DSTS_USBLNKST_EARLY_SUSPEND 0x5
|
||||
#define DSTS_USBLNKST_RESET 0xe
|
||||
#define DSTS_USBLNKST_RESUME 0xf
|
||||
|
||||
// DSTS connection speed
|
||||
#define DSTS_CONNECTSPD_HIGH 0
|
||||
#define DSTS_CONNECTSPD_FULL 1
|
||||
#define DSTS_CONNECTSPD_SUPER 4
|
||||
#define DSTS_CONNECTSPD_ENHANCED_SUPER 5
|
||||
|
||||
#define DGCMDPAR 0xc710 // Device Generic Command Parameter Register
|
||||
|
||||
#define DGCMD 0xc714 // Device Generic Command Register
|
||||
#define DGCMD_CMDSTATUS_START 12
|
||||
#define DGCMD_CMDSTATUS_BITS 4
|
||||
#define DGCMD_CMDACT (1 << 10)
|
||||
#define DGCMD_CMDIOC (1 << 8)
|
||||
#define DGCMD_CMDTYP_START 0
|
||||
#define DGCMD_CMDTYP_BITS 8
|
||||
|
||||
#define DALEPENA 0xc720 // Device Active USB Endpoint Enable Register
|
||||
#define DLDMENA 0xc724 // Device LDM Request Control Register
|
||||
|
||||
#define DEPCMDPAR2(n) (0xc800 + 0x10 * (n)) // Endpoint-n Command Parameter 2 Register
|
||||
#define DEPCMDPAR1(n) (0xc804 + 0x10 * (n)) // Endpoint-n Command Parameter 1 Register
|
||||
#define DEPCMDPAR0(n) (0xc808 + 0x10 * (n)) // Endpoint-n Command Parameter 0 Register
|
||||
|
||||
#define DEPCMD(n) (0xc80c + 0x10 * (n)) // Endpoint-n Command Parameter 0 Register
|
||||
#define DEPCMD_COMMANDPARAM_START 16 // Command Parameters
|
||||
#define DEPCMD_COMMANDPARAM_BITS 16
|
||||
#define DEPCMD_CMDSTATUS_START 12 // Command Completion Status
|
||||
#define DEPCMD_CMDSTATUS_BITS 4
|
||||
#define DEPCMD_HIPRI_FORCERM (1 << 11) // HighPriority/ForceRM
|
||||
#define DEPCMD_CMDACT (1 << 10) // Command Active
|
||||
#define DEPCMD_CMDIOC (1 << 8) // Command Interrupt on Complete
|
||||
#define DEPCMD_CMDTYP(n) (((n) & 0xf) >> 0)
|
||||
|
||||
// Command Types for DEPCMD
|
||||
#define DEPCFG 1 // Set Endpoint Configuration
|
||||
#define DEPXFERCFG 2 // Set Endpoint Transfer Resource Configuration
|
||||
#define DEPGETSTATE 3 // Get EndpointState
|
||||
#define DEPSSTALL 4 // Set Stall
|
||||
#define DEPCSTALL 5 // Clear Stall
|
||||
#define DEPSTRTXFER 6 // Start Transfer
|
||||
#define DEPUPDXFER 7 // Update Transfer
|
||||
#define DEPENDXFER 8 // End Transfer
|
||||
#define DEPSTARTCFG 9 // Start New Configuration
|
||||
|
||||
#define DEPCMD_RESOURCE_INDEX(n) (((n) & 0x7f) << 16)
|
||||
|
||||
// DEPCFG Params 0
|
||||
#define DEPCFG_ACTION_INITIALIZE (0 << 30)
|
||||
#define DEPCFG_ACTION_RESTORE (1 << 30)
|
||||
#define DEPCFG_ACTION_MODIFY (2 << 30)
|
||||
#define DEPCFG_BURST_SIZE(n) ((((n) - 1) & 0xf) << 22)
|
||||
#define DEPCFG_FIFO_NUM(n) (((n) & 0x1f) << 17)
|
||||
#define DEPCFG_INTERNAL_RETRY (1 << 15)
|
||||
#define DEPCFG_MAX_PACKET_SIZE(n) (((n) & 0x7ff) << 3)
|
||||
#define DEPCFG_EP_TYPE(n) (((n) & 0x3) << 1)
|
||||
|
||||
// DEPCFG Params 1
|
||||
#define DEPCFG_FIFO_BASED (1 << 31)
|
||||
#define DEPCFG_EP_NUMBER(n) (((n) & 0x1f) << 25)
|
||||
#define DEPCFG_STREAM_CAPABLE (1 << 24)
|
||||
#define DEPCFG_INTERVAL(n) (((n) & 0xff) << 16)
|
||||
#define DEPCFG_EBC (1 << 15) // External Buffer Control
|
||||
#define DEPCFG_EBC_NO_WRITE_BACK (1 << 14) // Don't write back HWO bit to the TRB descriptor
|
||||
#define DEPCFG_STREAM_EVT_EN (1 << 13)
|
||||
#define DEPCFG_XFER_NOT_READY_EN (1 << 10)
|
||||
#define DEPCFG_XFER_IN_PROGRESS_EN (1 << 9)
|
||||
#define DEPCFG_XFER_COMPLETE_EN (1 << 8)
|
||||
#define DEPCFG_INTR_NUM(n) (((n) & 0x1f) << 0)
|
||||
|
||||
// DEPXFERCFG Params 0
|
||||
#define DEPXFERCFG_NUM_XFER_RES(n) (((n) & 0xff) << 0)
|
||||
|
||||
#define DEV_IMOD(n) (0xca00 + 4 * (n)) // Device Interrupt Moderation Register
|
||||
|
||||
// BC register offsets
|
||||
#define BCFG 0xcc30 // BC Configuration Register
|
||||
#define BCEVT 0xcc38 // BC Event Register
|
||||
#define BCEVTEN 0xcc3c // BC Event Enable Register
|
||||
|
||||
// Link register offsets
|
||||
#define LU1LFPSRXTIM(n) (0xd000 + 0x80 * (n)) // U1_LFPS_RX_TIMER_REG
|
||||
#define LU1LFPSTXTIM(n) (0xd004 + 0x80 * (n)) // U1 LFPS TX TIMER REGISTER
|
||||
#define LU2LFPSRXTIM(n) (0xd008 + 0x80 * (n)) // U1 LFPS RX TIMER REGISTER
|
||||
#define LU2LFPSTXTIM(n) (0xd00c + 0x80 * (n)) // U2 LFPS TX TIMER REG REGISTER
|
||||
#define LU3LFPSRXTIM(n) (0xd010 + 0x80 * (n)) // U3 LFPS RX TIMER REGS REGISTER
|
||||
#define LU3LFPSTXTIM(n) (0xd014 + 0x80 * (n)) // U3 LFPS TX TIMER REGS REGISTER
|
||||
#define LPINGLFPSTIM(n) (0xd018 + 0x80 * (n)) // PING LFPS TIMER REGISTER
|
||||
#define LPOLLLFPSTXTIM(n) (0xd01c + 0x80 * (n)) // POLL LFPS TX TIMER REGISTER
|
||||
#define LSKIPFREQ(n) (0xd020 + 0x80 * (n)) // SKIP FREQUENCY REGISTER
|
||||
#define LLUCTL(n) (0xd024 + 0x80 * (n)) // TX TS1 COUNT REGISTER
|
||||
#define LPTMDPDELAY(n) (0xd028 + 0x80 * (n)) // PTM DATAPATH DELAY REGISTER
|
||||
#define LSCDTIM1(n) (0xd02c + 0x80 * (n)) // SCD TIMER 1 REGISTER
|
||||
#define LSCDTIM2(n) (0xd030 + 0x80 * (n)) // SCD TIMER 2 REGISTER
|
||||
#define LSCDTIM3(n) (0xd034 + 0x80 * (n)) // SCD TIMER 3 REGISTER
|
||||
#define LSCDTIM4(n) (0xd038 + 0x80 * (n)) // SCD TIMER 4 REGISTER
|
||||
#define LLPBMTIM1(n) (0xd03c + 0x80 * (n)) // LPBM TIMER 1 REGISTER
|
||||
#define LLPBMTIM2(n) (0xd040 + 0x80 * (n)) // LPBM TIMER 2 REGISTER
|
||||
#define LLPBMTXTIM(n) (0xd044 + 0x80 * (n)) // LPBM TX TIMER REGISTER
|
||||
#define LLINKERRINJ(n) (0xd048 + 0x80 * (n)) // LINK ERROR TYPE INJECT REGISTER
|
||||
#define LLINKERRINJEN(n) (0xd04c + 0x80 * (n)) // LINK ERROR INJECT ENABLE REGISTER
|
||||
#define GDBGLTSSM(n) (0xd050 + 0x80 * (n)) // Global Debug LTSSM Register
|
||||
#define GDBGLNMCC(n) (0xd054 + 0x80 * (n)) // Global Debug LNMCC Register
|
||||
#define LLINKDBGCTRL(n) (0xd058 + 0x80 * (n)) // LINK DEBUG CONTROL REGISTER
|
||||
#define LLINKDBGCNTTRIG(n) (0xd05c + 0x80 * (n)) // LINK DEBUG COUNT TRIGGER REGISTER
|
||||
#define LCSR_TX_DEEMPH(n) (0xd060 + 0x80 * (n)) // LCSR_TX_DEEMPH REGISTER
|
||||
#define LCSR_TX_DEEMPH_1(n) (0xd064 + 0x80 * (n)) // LCSR_TX_DEEMPH_1 REGISTER
|
||||
#define LCSR_TX_DEEMPH_2(n) (0xd068 + 0x80 * (n)) // LCSR_TX_DEEMPH_2 REGISTER
|
||||
#define LCSR_TX_DEEMPH_3(n) (0xd06c + 0x80 * (n)) // LCSR_TX_DEEMPH_3 REGISTER
|
||||
#define LCSRPTMDEBUG1(n) (0xd070 + 0x80 * (n)) // LCSRPTMDEBUG1 REGISTER
|
||||
#define LCSRPTMDEBUG2(n) (0xd074 + 0x80 * (n)) // LCSRPTMDELAY2 REGISTER
|
||||
|
||||
@@ -0,0 +1,122 @@
|
||||
// Copyright 2017 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.
|
||||
|
||||
#pragma once
|
||||
|
||||
// Transfer Request Block
|
||||
typedef volatile struct {
|
||||
uint32_t ptr_low;
|
||||
uint32_t ptr_high;
|
||||
uint32_t status;
|
||||
uint32_t control;
|
||||
} __PACKED dwc3_trb_t;
|
||||
|
||||
// TRB status fields
|
||||
#define TRB_BUFSIZ_START 0 // Buffer Size
|
||||
#define TRB_BUFSIZ_BITS 24
|
||||
#define TRB_BUFSIZ(n) (((n) & 0xffffff) << 0)
|
||||
#define TRB_PCM1_START 24 // Packet Count M1
|
||||
#define TRB_PCM1_BITS 2
|
||||
#define TRB_SPR (1 << 26) // Short Packet Received
|
||||
#define TRB_TRBSTS_START 28 // TRB Status
|
||||
#define TRB_TRBSTS_BITS 4
|
||||
|
||||
// TRB control fields
|
||||
#define TRB_HWO (1 << 0) // Hardware Owner of Descriptor
|
||||
#define TRB_LST (1 << 1) // Last TRB
|
||||
#define TRB_CHN (1 << 2) // Chain Buffers
|
||||
#define TRB_CSP (1 << 3) // Continue on Short Packet
|
||||
#define TRB_TRBCTL_START 4
|
||||
#define TRB_TRBCTL_BITS 5
|
||||
#define TRB_TRBCTL(c) ((c) & (((1 << TRB_TRBCTL_BITS) - 1) << TRB_TRBCTL_START))
|
||||
#define TRB_TRBCTL_NORMAL (1 << TRB_TRBCTL_START)
|
||||
#define TRB_TRBCTL_SETUP (2 << TRB_TRBCTL_START)
|
||||
#define TRB_TRBCTL_STATUS_2 (3 << TRB_TRBCTL_START)
|
||||
#define TRB_TRBCTL_STATUS_3 (4 << TRB_TRBCTL_START)
|
||||
#define TRB_TRBCTL_CONTROL_DATA (5 << TRB_TRBCTL_START)
|
||||
#define TRB_TRBCTL_ISOCH_FIRST (6 << TRB_TRBCTL_START)
|
||||
#define TRB_TRBCTL_ISOCH (7 << TRB_TRBCTL_START)
|
||||
#define TRB_TRBCTL_LINK (8 << TRB_TRBCTL_START)
|
||||
#define TRB_ISP (1 << 10) // Interrupt on Short Packet
|
||||
#define TRB_IMI (1 << 10) // Interrupt on Missed ISOC
|
||||
#define TRB_IOC (1 << 11) // Interrupt on Complete
|
||||
#define TRB_STREAM_ID_START 14 // Stream ID
|
||||
#define TRB_STREAM_ID_BITS 16
|
||||
#define TRB_SOF_NUM_START 14 // SOF Number
|
||||
#define TRB_SOF_NUM_BITS 16
|
||||
|
||||
// DEPEVT (endpoint specific)
|
||||
#define DEPEVT_PARAMS_START 16 // Event Parameters
|
||||
#define DEPEVT_PARAMS_BITS 16
|
||||
#define DEPEVT_STATUS_START 12 // Event Status
|
||||
#define DEPEVT_STATUS_BITS 4
|
||||
#define DEPEVT_TYPE_START 6 // Event Type
|
||||
#define DEPEVT_TYPE_BITS 4
|
||||
#define DEPEVT_PHYS_EP_START 1
|
||||
#define DEPEVT_PHYS_EP_BITS 5
|
||||
#define DEPEVT_NON_EP (1 << 0) // Event is not endpoint specific
|
||||
|
||||
#define DEPEVT_PARAMS(e) (((e) >> DEPEVT_PARAMS_START) & ((1 << DEPEVT_PARAMS_BITS) - 1))
|
||||
#define DEPEVT_STATUS(e) (((e) >> DEPEVT_STATUS_START) & ((1 << DEPEVT_STATUS_BITS) - 1))
|
||||
#define DEPEVT_TYPE(e) (((e) >> DEPEVT_TYPE_START) & ((1 << DEPEVT_TYPE_BITS) - 1))
|
||||
#define DEPEVT_PHYS_EP(e) (((e) >> DEPEVT_PHYS_EP_START) & \
|
||||
((1 << DEPEVT_PHYS_EP_BITS) - 1))
|
||||
|
||||
// event parameters for DEPEVT_CMD_CMPLT
|
||||
#define DEPEVT_CMD_CMPLT_CMD_TYPE_START 24
|
||||
#define DEPEVT_CMD_CMPLT_CMD_TYPE_BITS 4
|
||||
#define DEPEVT_CMD_CMPLT_CMD_TYPE(e) (((e) >> DEPEVT_CMD_CMPLT_CMD_TYPE_START) & \
|
||||
((1 << DEPEVT_CMD_CMPLT_CMD_TYPE_BITS) - 1))
|
||||
#define DEPEVT_CMD_CMPLT_RSRC_ID_START 16
|
||||
#define DEPEVT_CMD_CMPLT_RSRC_ID_BITS 7
|
||||
#define DEPEVT_CMD_CMPLT_RSRC_ID(e) (((e) >> DEPEVT_CMD_CMPLT_RSRC_ID_START) & \
|
||||
((1 << DEPEVT_CMD_CMPLT_RSRC_ID_BITS) - 1))
|
||||
|
||||
// event parameters for DEPEVT_XFER_NOT_READY
|
||||
#define DEPEVT_XFER_NOT_READY_REASON (1 << 15)
|
||||
#define DEPEVT_XFER_NOT_READY_STAGE_START 12
|
||||
#define DEPEVT_XFER_NOT_READY_STAGE_BITS 2
|
||||
#define DEPEVT_XFER_NOT_READY_STAGE(e) (((e) >> DEPEVT_XFER_NOT_READY_STAGE_START) & \
|
||||
((1 << DEPEVT_XFER_NOT_READY_STAGE_BITS) - 1))
|
||||
#define DEPEVT_XFER_NOT_READY_STAGE_DATA 1
|
||||
#define DEPEVT_XFER_NOT_READY_STAGE_STATUS 2
|
||||
|
||||
|
||||
// DEPEVT event types
|
||||
#define DEPEVT_XFER_COMPLETE 1
|
||||
#define DEPEVT_XFER_IN_PROGRESS 2
|
||||
#define DEPEVT_XFER_NOT_READY 3
|
||||
#define DEPEVT_STREAM_EVT 6
|
||||
#define DEPEVT_CMD_CMPLT 7
|
||||
|
||||
// DEVT (device specific)
|
||||
#define DEVT_INFO_START 16 // Event Information Bits
|
||||
#define DEVT_INFO_BITS 16
|
||||
#define DEVT_TYPE_START 8 // Event type
|
||||
#define DEVT_TYPE_BITS 7
|
||||
#define DEVT_NON_EP (1 << 0) // Event is not endpoint specific
|
||||
|
||||
#define DEVT_INFO(e) (((e) >> DEVT_INFO_START) & ((1 << DEVT_INFO_BITS) - 1))
|
||||
#define DEVT_TYPE(e) (((e) >> DEVT_TYPE_START) & ((1 << DEVT_TYPE_BITS) - 1))
|
||||
|
||||
// DEVT event types
|
||||
#define DEVT_DISCONNECT 0
|
||||
#define DEVT_USB_RESET 1
|
||||
#define DEVT_CONNECTION_DONE 2
|
||||
#define DEVT_LINK_STATE_CHANGE 3
|
||||
#define DEVT_REMOTE_WAKEUP 4
|
||||
#define DEVT_HIBERNATE_REQUEST 5
|
||||
#define DEVT_SUSPEND_ENTRY 6
|
||||
#define DEVT_SOF 7
|
||||
#define DEVT_ERRATIC_ERROR 9
|
||||
#define DEVT_COMMAND_COMPLETE 10
|
||||
#define DEVT_EVENT_BUF_OVERFLOW 11
|
||||
#define DEVT_VENDOR_TEST_LMP 12
|
||||
#define DEVT_STOPPED_DISCONNECT 13
|
||||
#define DEVT_L1_RESUME_DETECT 14
|
||||
#define DEVT_LDM_RESPONSE 15
|
||||
|
||||
// for DEVT_LINK_STATE_CHANGE
|
||||
#define DEVT_LINK_STATE_CHANGE_SS (1 << 4) // Set if link is super speed
|
||||
#define DEVT_LINK_STATE_CHANGE_STATE(s) ((s) & 0xf) // Same as DSTS state
|
||||
@@ -0,0 +1,403 @@
|
||||
// Copyright 2017 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.
|
||||
|
||||
#include <ddk/binding.h>
|
||||
#include <ddk/debug.h>
|
||||
#include <ddk/protocol/platform-devices.h>
|
||||
#include <ddk/protocol/usb-function.h>
|
||||
#include <hw/reg.h>
|
||||
#include <pretty/hexdump.h>
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "dwc3.h"
|
||||
#include "dwc3-regs.h"
|
||||
#include "dwc3-types.h"
|
||||
|
||||
// MMIO indices
|
||||
enum {
|
||||
MMIO_USB3OTG,
|
||||
};
|
||||
|
||||
// IRQ indices
|
||||
enum {
|
||||
IRQ_USB3,
|
||||
};
|
||||
|
||||
void dwc3_wait_bits(volatile uint32_t* ptr, uint32_t bits, uint32_t expected) {
|
||||
uint32_t value = DWC3_READ32(ptr);
|
||||
while ((value & bits) != expected) {
|
||||
usleep(1000);
|
||||
value = DWC3_READ32(ptr);
|
||||
}
|
||||
}
|
||||
|
||||
void dwc3_print_status(dwc3_t* dwc) {
|
||||
volatile void* mmio = dwc3_mmio(dwc);
|
||||
uint32_t status = DWC3_READ32(mmio + DSTS);
|
||||
dprintf(TRACE, "DSTS: ");
|
||||
dprintf(TRACE, "USBLNKST: %d ", DSTS_USBLNKST(status));
|
||||
dprintf(TRACE, "SOFFN: %d ", DSTS_SOFFN(status));
|
||||
dprintf(TRACE, "CONNECTSPD: %d ", DSTS_CONNECTSPD(status));
|
||||
if (status & DSTS_DCNRD) dprintf(TRACE, "DCNRD ");
|
||||
if (status & DSTS_SRE) dprintf(TRACE, "SRE ");
|
||||
if (status & DSTS_RSS) dprintf(TRACE, "RSS ");
|
||||
if (status & DSTS_SSS) dprintf(TRACE, "SSS ");
|
||||
if (status & DSTS_COREIDLE) dprintf(TRACE, "COREIDLE ");
|
||||
if (status & DSTS_DEVCTRLHLT) dprintf(TRACE, "DEVCTRLHLT ");
|
||||
if (status & DSTS_RXFIFOEMPTY) dprintf(TRACE, "RXFIFOEMPTY ");
|
||||
dprintf(TRACE, "\n");
|
||||
|
||||
status = DWC3_READ32(mmio + GSTS);
|
||||
dprintf(TRACE, "GSTS: ");
|
||||
dprintf(TRACE, "CBELT: %d ", GSTS_CBELT(status));
|
||||
dprintf(TRACE, "CURMOD: %d ", GSTS_CURMOD(status));
|
||||
if (status & GSTS_SSIC_IP) dprintf(TRACE, "SSIC_IP ");
|
||||
if (status & GSTS_OTG_IP) dprintf(TRACE, "OTG_IP ");
|
||||
if (status & GSTS_BC_IP) dprintf(TRACE, "BC_IP ");
|
||||
if (status & GSTS_ADP_IP) dprintf(TRACE, "ADP_IP ");
|
||||
if (status & GSTS_HOST_IP) dprintf(TRACE, "HOST_IP ");
|
||||
if (status & GSTS_DEVICE_IP) dprintf(TRACE, "DEVICE_IP ");
|
||||
if (status & GSTS_CSR_TIMEOUT) dprintf(TRACE, "CSR_TIMEOUT ");
|
||||
if (status & GSTS_BUSERRADDRVLD) dprintf(TRACE, "BUSERRADDRVLD ");
|
||||
dprintf(TRACE, "\n");
|
||||
}
|
||||
|
||||
static mx_status_t dwc3_start(dwc3_t* dwc) {
|
||||
volatile void* mmio = dwc3_mmio(dwc);
|
||||
uint32_t temp;
|
||||
|
||||
mtx_lock(&dwc->lock);
|
||||
|
||||
temp = DWC3_READ32(mmio + DCTL);
|
||||
temp &= ~DCTL_RUN_STOP;
|
||||
temp |= DCTL_CSFTRST;
|
||||
DWC3_WRITE32(mmio + DCTL, temp);
|
||||
dwc3_wait_bits(mmio + DCTL, DCTL_CSFTRST, 0);
|
||||
|
||||
// configure and enable PHYs
|
||||
temp = DWC3_READ32(mmio + GUSB2PHYCFG(0));
|
||||
temp &= ~(GUSB2PHYCFG_USBTRDTIM_MASK | GUSB2PHYCFG_SUSPENDUSB20);
|
||||
temp |= GUSB2PHYCFG_USBTRDTIM(9);
|
||||
DWC3_WRITE32(mmio + GUSB2PHYCFG(0), temp);
|
||||
|
||||
temp = DWC3_READ32(mmio + GUSB3PIPECTL(0));
|
||||
temp &= ~(GUSB3PIPECTL_DELAYP1TRANS | GUSB3PIPECTL_SUSPENDENABLE);
|
||||
temp |= GUSB3PIPECTL_LFPSFILTER | GUSB3PIPECTL_SS_TX_DE_EMPHASIS(1);
|
||||
DWC3_WRITE32(mmio + GUSB3PIPECTL(0), temp);
|
||||
|
||||
// configure for device mode
|
||||
DWC3_WRITE32(mmio + GCTL, GCTL_U2EXIT_LFPS | GCTL_PRTCAPDIR_DEVICE | GCTL_U2RSTECN |
|
||||
GCTL_PWRDNSCALE(2));
|
||||
|
||||
temp = DWC3_READ32(mmio + DCFG);
|
||||
uint32_t nump = 16;
|
||||
uint32_t max_speed = DCFG_DEVSPD_SUPER;
|
||||
temp &= ~DWC3_MASK(DCFG_NUMP_START, DCFG_NUMP_BITS);
|
||||
temp |= nump << DCFG_NUMP_START;
|
||||
temp &= ~DWC3_MASK(DCFG_DEVSPD_START, DCFG_DEVSPD_BITS);
|
||||
temp |= max_speed << DCFG_DEVSPD_START;
|
||||
temp &= ~DWC3_MASK(DCFG_DEVADDR_START, DCFG_DEVADDR_BITS); // clear address
|
||||
DWC3_WRITE32(mmio + DCFG, temp);
|
||||
|
||||
dwc3_events_start(dwc);
|
||||
mtx_unlock(&dwc->lock);
|
||||
|
||||
dwc3_ep0_start(dwc);
|
||||
|
||||
mtx_lock(&dwc->lock);
|
||||
|
||||
// start the controller
|
||||
DWC3_WRITE32(mmio + DCTL, DCTL_RUN_STOP);
|
||||
|
||||
temp = DWC3_READ32(mmio + DCTL);
|
||||
temp |= DCTL_RUN_STOP;
|
||||
DWC3_WRITE32(mmio + DCTL, temp);
|
||||
|
||||
mtx_unlock(&dwc->lock);
|
||||
|
||||
return MX_OK;
|
||||
}
|
||||
|
||||
void dwc3_usb_reset(dwc3_t* dwc) {
|
||||
dprintf(INFO, "dwc3_usb_reset\n");
|
||||
|
||||
dwc3_ep0_reset(dwc);
|
||||
|
||||
for (unsigned i = 2; i < countof(dwc->eps); i++) {
|
||||
dwc3_ep_end_transfers(dwc, i, MX_ERR_IO_NOT_PRESENT);
|
||||
dwc3_ep_set_stall(dwc, i, false);
|
||||
}
|
||||
|
||||
dwc3_set_address(dwc, 0);
|
||||
dwc3_ep0_start(dwc);
|
||||
usb_dci_set_connected(&dwc->dci_intf, true);
|
||||
}
|
||||
|
||||
void dwc3_disconnected(dwc3_t* dwc) {
|
||||
dprintf(INFO, "dwc3_disconnected\n");
|
||||
|
||||
dwc3_cmd_ep_end_transfer(dwc, EP0_OUT);
|
||||
dwc->ep0_state = EP0_STATE_NONE;
|
||||
|
||||
if (dwc->dci_intf.ops) {
|
||||
usb_dci_set_connected(&dwc->dci_intf, false);
|
||||
}
|
||||
|
||||
for (unsigned i = 2; i < countof(dwc->eps); i++) {
|
||||
dwc3_ep_end_transfers(dwc, i, MX_ERR_IO_NOT_PRESENT);
|
||||
dwc3_ep_set_stall(dwc, i, false);
|
||||
}
|
||||
}
|
||||
|
||||
void dwc3_connection_done(dwc3_t* dwc) {
|
||||
volatile void* mmio = dwc3_mmio(dwc);
|
||||
|
||||
mtx_lock(&dwc->lock);
|
||||
uint32_t status = DWC3_READ32(mmio + DSTS);
|
||||
uint32_t speed = DSTS_CONNECTSPD(status);
|
||||
unsigned ep0_max_packet = 0;
|
||||
|
||||
switch (speed) {
|
||||
case DSTS_CONNECTSPD_HIGH:
|
||||
dwc->speed = USB_SPEED_HIGH;
|
||||
ep0_max_packet = 64;
|
||||
break;
|
||||
case DSTS_CONNECTSPD_FULL:
|
||||
dwc->speed = USB_SPEED_FULL;
|
||||
ep0_max_packet = 64;
|
||||
break;
|
||||
case DSTS_CONNECTSPD_SUPER:
|
||||
case DSTS_CONNECTSPD_ENHANCED_SUPER:
|
||||
dwc->speed = USB_SPEED_SUPER;
|
||||
ep0_max_packet = 512;
|
||||
break;
|
||||
default:
|
||||
dprintf(ERROR, "dwc3_connection_done: unsupported speed %u\n", speed);
|
||||
dwc->speed = USB_SPEED_UNDEFINED;
|
||||
break;
|
||||
}
|
||||
|
||||
mtx_unlock(&dwc->lock);
|
||||
|
||||
if (ep0_max_packet) {
|
||||
dwc->eps[EP0_OUT].max_packet_size = ep0_max_packet;
|
||||
dwc->eps[EP0_IN].max_packet_size = ep0_max_packet;
|
||||
dwc3_cmd_ep_set_config(dwc, EP0_OUT, USB_ENDPOINT_CONTROL, ep0_max_packet, 0, true);
|
||||
dwc3_cmd_ep_set_config(dwc, EP0_IN, USB_ENDPOINT_CONTROL, ep0_max_packet, 0, true);
|
||||
}
|
||||
|
||||
usb_dci_set_speed(&dwc->dci_intf, dwc->speed);
|
||||
}
|
||||
|
||||
void dwc3_set_address(dwc3_t* dwc, unsigned address) {
|
||||
volatile void* mmio = dwc3_mmio(dwc);
|
||||
mtx_lock(&dwc->lock);
|
||||
DWC3_SET_BITS32(mmio + DCFG, DCFG_DEVADDR_START, DCFG_DEVADDR_BITS, address);
|
||||
mtx_unlock(&dwc->lock);
|
||||
}
|
||||
|
||||
void dwc3_reset_configuration(dwc3_t* dwc) {
|
||||
volatile void* mmio = dwc3_mmio(dwc);
|
||||
|
||||
mtx_lock(&dwc->lock);
|
||||
|
||||
// disable all endpoints except EP0_OUT and EP0_IN
|
||||
DWC3_WRITE32(mmio + DALEPENA, (1 << EP0_OUT) | (1 << EP0_IN));
|
||||
|
||||
mtx_unlock(&dwc->lock);
|
||||
|
||||
for (unsigned i = 2; i < countof(dwc->eps); i++) {
|
||||
dwc3_ep_end_transfers(dwc, i, MX_ERR_IO_NOT_PRESENT);
|
||||
dwc3_ep_set_stall(dwc, i, false);
|
||||
}
|
||||
}
|
||||
|
||||
static mx_status_t dwc3_set_interface(void* ctx, usb_dci_interface_t* dci_intf) {
|
||||
dwc3_t* dwc = ctx;
|
||||
memcpy(&dwc->dci_intf, dci_intf, sizeof(dwc->dci_intf));
|
||||
return MX_OK;
|
||||
}
|
||||
|
||||
static mx_status_t dwc3_config_ep(void* ctx, usb_endpoint_descriptor_t* ep_desc,
|
||||
usb_ss_ep_comp_descriptor_t* ss_comp_desc) {
|
||||
dwc3_t* dwc = ctx;
|
||||
return dwc3_ep_config(dwc, ep_desc, ss_comp_desc);
|
||||
}
|
||||
|
||||
static mx_status_t dwc3_disable_ep(void* ctx, uint8_t ep_addr) {
|
||||
dwc3_t* dwc = ctx;
|
||||
return dwc3_ep_disable(dwc, ep_addr);
|
||||
}
|
||||
|
||||
static mx_status_t dwc_set_enabled(void* ctx, bool enabled) {
|
||||
dwc3_t* dwc = ctx;
|
||||
|
||||
if (enabled) {
|
||||
return dwc3_start(dwc);
|
||||
} else {
|
||||
// TODO(voydanoff) more cleanup to do here?
|
||||
dwc3_disconnected(dwc);
|
||||
return MX_OK;
|
||||
}
|
||||
}
|
||||
|
||||
static mx_status_t dwc3_set_stall(void* ctx, uint8_t ep_address) {
|
||||
dwc3_t* dwc = ctx;
|
||||
return dwc3_ep_set_stall(dwc, dwc3_ep_num(ep_address), true);
|
||||
}
|
||||
|
||||
static mx_status_t dwc3_clear_stall(void* ctx, uint8_t ep_address) {
|
||||
dwc3_t* dwc = ctx;
|
||||
return dwc3_ep_set_stall(dwc, dwc3_ep_num(ep_address), false);
|
||||
}
|
||||
|
||||
usb_dci_protocol_ops_t dwc_dci_protocol = {
|
||||
.set_interface = dwc3_set_interface,
|
||||
.config_ep = dwc3_config_ep,
|
||||
.disable_ep = dwc3_disable_ep,
|
||||
.set_enabled = dwc_set_enabled,
|
||||
.ep_set_stall = dwc3_set_stall,
|
||||
.ep_clear_stall = dwc3_clear_stall,
|
||||
};
|
||||
|
||||
static void dwc3_unbind(void* ctx) {
|
||||
dwc3_t* dwc = ctx;
|
||||
|
||||
mx_interrupt_signal(dwc->irq_handle);
|
||||
thrd_join(dwc->irq_thread, NULL);
|
||||
device_remove(dwc->mxdev);
|
||||
}
|
||||
|
||||
static void dwc3_iotxn_queue(void* ctx, iotxn_t* txn) {
|
||||
dwc3_t* dwc = ctx;
|
||||
|
||||
if (txn->protocol != MX_PROTOCOL_USB_FUNCTION) {
|
||||
iotxn_complete(txn, MX_ERR_NOT_SUPPORTED, 0);
|
||||
return;
|
||||
}
|
||||
usb_function_protocol_data_t* data = iotxn_pdata(txn, usb_function_protocol_data_t);
|
||||
dprintf(LTRACE, "dwc3_iotxn_queue ep: %u\n", data->ep_address);
|
||||
unsigned ep_num = dwc3_ep_num(data->ep_address);
|
||||
if (ep_num < 2 || ep_num >= countof(dwc->eps)) {
|
||||
dprintf(ERROR, "dwc3_iotxn_queue: bad ep address 0x%02X\n", data->ep_address);
|
||||
iotxn_complete(txn, MX_ERR_INVALID_ARGS, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
dwc3_ep_queue(dwc, ep_num, txn);
|
||||
}
|
||||
|
||||
static void dwc3_release(void* ctx) {
|
||||
dwc3_t* dwc = ctx;
|
||||
|
||||
for (unsigned i = 0; i < countof(dwc->eps); i++) {
|
||||
dwc3_ep_fifo_release(dwc, i);
|
||||
}
|
||||
io_buffer_release(&dwc->event_buffer);
|
||||
io_buffer_release(&dwc->ep0_buffer);
|
||||
|
||||
pdev_mmio_buffer_release(&dwc->mmio);
|
||||
mx_handle_close(dwc->irq_handle);
|
||||
free(dwc);
|
||||
}
|
||||
|
||||
static mx_protocol_device_t dwc3_device_proto = {
|
||||
.version = DEVICE_OPS_VERSION,
|
||||
.iotxn_queue = dwc3_iotxn_queue,
|
||||
.release = dwc3_release,
|
||||
};
|
||||
|
||||
static mx_status_t dwc3_bind(void* ctx, mx_device_t* dev, void** cookie) {
|
||||
dprintf(INFO, "dwc3_bind\n");
|
||||
|
||||
dwc3_t* dwc = calloc(1, sizeof(dwc3_t));
|
||||
if (!dwc) {
|
||||
return MX_ERR_NO_MEMORY;
|
||||
}
|
||||
|
||||
platform_device_protocol_t pdev;
|
||||
mx_status_t status = device_get_protocol(dev, MX_PROTOCOL_PLATFORM_DEV, &pdev);
|
||||
if (status != MX_OK) {
|
||||
goto fail;
|
||||
}
|
||||
|
||||
mtx_init(&dwc->lock, mtx_plain);
|
||||
for (unsigned i = 0; i < countof(dwc->eps); i++) {
|
||||
dwc3_endpoint_t* ep = &dwc->eps[i];
|
||||
ep->ep_num = i;
|
||||
mtx_init(&ep->lock, mtx_plain);
|
||||
list_initialize(&ep->queued_txns);
|
||||
}
|
||||
|
||||
status = pdev_map_mmio_buffer(&pdev, MMIO_USB3OTG, MX_CACHE_POLICY_UNCACHED_DEVICE,
|
||||
&dwc->mmio);
|
||||
if (status != MX_OK) {
|
||||
dprintf(ERROR, "dwc3_bind: pdev_map_mmio_buffer failed\n");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
status = pdev_map_interrupt(&pdev, IRQ_USB3, &dwc->irq_handle);
|
||||
if (status != MX_OK) {
|
||||
dprintf(ERROR, "dwc3_bind: pdev_map_interrupt failed\n");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
status = io_buffer_init(&dwc->event_buffer, EVENT_BUFFER_SIZE, IO_BUFFER_RO);
|
||||
if (status != MX_OK) {
|
||||
dprintf(ERROR, "dwc3_bind: io_buffer_init failed\n");
|
||||
goto fail;
|
||||
}
|
||||
io_buffer_cache_op(&dwc->event_buffer, MX_VMO_OP_CACHE_CLEAN, 0, EVENT_BUFFER_SIZE);
|
||||
|
||||
status = io_buffer_init(&dwc->ep0_buffer, 65536, IO_BUFFER_RW);
|
||||
if (status != MX_OK) {
|
||||
dprintf(ERROR, "dwc3_bind: io_buffer_init failed\n");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
status = dwc3_ep0_init(dwc);
|
||||
if (status != MX_OK) {
|
||||
dprintf(ERROR, "dwc3_bind: dwc3_ep_init failed\n");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
device_add_args_t args = {
|
||||
.version = DEVICE_ADD_ARGS_VERSION,
|
||||
.name = "dwc3",
|
||||
.ctx = dwc,
|
||||
.ops = &dwc3_device_proto,
|
||||
.proto_id = MX_PROTOCOL_USB_DCI,
|
||||
.proto_ops = &dwc_dci_protocol,
|
||||
};
|
||||
|
||||
status = device_add(dev, &args, &dwc->mxdev);
|
||||
if (status != MX_OK) {
|
||||
goto fail;
|
||||
}
|
||||
|
||||
return MX_OK;
|
||||
|
||||
fail:
|
||||
dprintf(ERROR, "dwc3_bind failed %d\n", status);
|
||||
dwc3_release(dwc);
|
||||
return status;
|
||||
}
|
||||
|
||||
static mx_driver_ops_t dwc3_driver_ops = {
|
||||
.version = DRIVER_OPS_VERSION,
|
||||
.bind = dwc3_bind,
|
||||
};
|
||||
|
||||
// The formatter does not play nice with these macros.
|
||||
// clang-format off
|
||||
MAGENTA_DRIVER_BEGIN(dwc3, dwc3_driver_ops, "magenta", "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_DWC3),
|
||||
MAGENTA_DRIVER_END(dwc3)
|
||||
// clang-format on
|
||||
@@ -0,0 +1,153 @@
|
||||
// Copyright 2017 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.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <ddk/device.h>
|
||||
#include <ddk/io-buffer.h>
|
||||
#include <ddk/protocol/platform-device.h>
|
||||
#include <ddk/protocol/usb-dci.h>
|
||||
#include <magenta/listnode.h>
|
||||
#include <magenta/types.h>
|
||||
#include <magenta/hw/usb.h>
|
||||
|
||||
#include <threads.h>
|
||||
|
||||
#include "dwc3-types.h"
|
||||
|
||||
// physical endpoint numbers for ep0
|
||||
#define EP0_OUT 0
|
||||
#define EP0_IN 1
|
||||
#define EP0_FIFO EP0_OUT
|
||||
|
||||
#define EP_OUT(ep_num) (((ep_num) & 1) == 0)
|
||||
#define EP_IN(ep_num) (((ep_num) & 1) == 1)
|
||||
|
||||
#define EVENT_BUFFER_SIZE PAGE_SIZE
|
||||
#define EP0_MAX_PACKET_SIZE 512
|
||||
#define DWC3_MAX_EPS 32
|
||||
|
||||
// converts a USB endpoint address to 0 - 31 index
|
||||
#define dwc3_ep_num(addr) ((((addr) & 0xF) << 1) | !!((addr) & USB_DIR_IN))
|
||||
|
||||
typedef enum {
|
||||
EP0_STATE_NONE,
|
||||
EP0_STATE_SETUP, // Queued setup phase
|
||||
EP0_STATE_DATA_OUT, // Queued data on EP0_OUT
|
||||
EP0_STATE_DATA_IN, // Queued data on EP0_IN
|
||||
EP0_STATE_WAIT_NRDY_OUT, // Waiting for not-ready on EP0_OUT
|
||||
EP0_STATE_WAIT_NRDY_IN, // Waiting for not-ready on EP0_IN
|
||||
EP0_STATE_STATUS, // Waiting for status to complete
|
||||
} dwc3_ep0_state;
|
||||
|
||||
typedef struct {
|
||||
io_buffer_t buffer;
|
||||
dwc3_trb_t* first; // first TRB in the fifo
|
||||
dwc3_trb_t* next; // next free TRB in the fifo
|
||||
dwc3_trb_t* current; // TRB for currently pending transaction
|
||||
dwc3_trb_t* last; // last TRB in the fifo (link TRB)
|
||||
} dwc3_fifo_t;
|
||||
|
||||
typedef struct {
|
||||
dwc3_fifo_t fifo;
|
||||
list_node_t queued_txns; // iotxns waiting to be processed
|
||||
iotxn_t* current_txn; // iotxn currently being processed
|
||||
unsigned rsrc_id; // resource ID for current_txn
|
||||
|
||||
// Used for synchronizing endpoint state
|
||||
// and ep specific hardware registers
|
||||
// This should be acquired before dwc3_t.lock
|
||||
// if acquiring both locks.
|
||||
mtx_t lock;
|
||||
|
||||
uint16_t max_packet_size;
|
||||
uint8_t ep_num;
|
||||
bool enabled;
|
||||
uint8_t type; // control, bulk, interrupt or isochronous
|
||||
uint8_t interval;
|
||||
// TODO(voydanoff) USB 3 specific stuff here
|
||||
|
||||
bool got_not_ready;
|
||||
bool stalled;
|
||||
} dwc3_endpoint_t;
|
||||
|
||||
typedef struct {
|
||||
mx_device_t* mxdev;
|
||||
usb_dci_interface_t dci_intf;
|
||||
pdev_mmio_buffer_t mmio;
|
||||
|
||||
// event stuff
|
||||
io_buffer_t event_buffer;
|
||||
mx_handle_t irq_handle;
|
||||
thrd_t irq_thread;
|
||||
|
||||
dwc3_endpoint_t eps[DWC3_MAX_EPS];
|
||||
|
||||
// connection state
|
||||
usb_speed_t speed;
|
||||
|
||||
// ep0 stuff
|
||||
usb_setup_t cur_setup; // current setup request
|
||||
io_buffer_t ep0_buffer;
|
||||
dwc3_ep0_state ep0_state;
|
||||
|
||||
// Used for synchronizing global state
|
||||
// and non ep specific hardware registers.
|
||||
// dwc3_endpoint_t.lock should be acquired first
|
||||
// if acquiring both locks.
|
||||
mtx_t lock;
|
||||
bool configured;
|
||||
} dwc3_t;
|
||||
|
||||
static inline volatile void* dwc3_mmio(dwc3_t* dwc) {
|
||||
return dwc->mmio.vaddr;
|
||||
}
|
||||
|
||||
void dwc3_usb_reset(dwc3_t* dwc);
|
||||
void dwc3_disconnected(dwc3_t* dwc);
|
||||
void dwc3_connection_done(dwc3_t* dwc);
|
||||
void dwc3_set_address(dwc3_t* dwc, unsigned address);
|
||||
void dwc3_reset_configuration(dwc3_t* dwc);
|
||||
|
||||
// Commands
|
||||
void dwc3_cmd_start_new_config(dwc3_t* dwc, unsigned ep_num, unsigned resource_index);
|
||||
void dwc3_cmd_ep_set_config(dwc3_t* dwc, unsigned ep_num, unsigned ep_type,
|
||||
unsigned max_packet_size, unsigned interval, bool modify);
|
||||
void dwc3_cmd_ep_transfer_config(dwc3_t* dwc, unsigned ep_num);
|
||||
void dwc3_cmd_ep_start_transfer(dwc3_t* dwc, unsigned ep_num, mx_paddr_t trb_phys);
|
||||
void dwc3_cmd_ep_end_transfer(dwc3_t* dwc, unsigned ep_num);
|
||||
void dwc3_cmd_ep_set_stall(dwc3_t* dwc, unsigned ep_num);
|
||||
void dwc3_cmd_ep_clear_stall(dwc3_t* dwc, unsigned ep_num);
|
||||
|
||||
// Endpoints
|
||||
mx_status_t dwc3_ep_fifo_init(dwc3_t* dwc, unsigned ep_num);
|
||||
void dwc3_ep_fifo_release(dwc3_t* dwc, unsigned ep_num);
|
||||
mx_status_t dwc3_ep_config(dwc3_t* dwc, usb_endpoint_descriptor_t* ep_desc,
|
||||
usb_ss_ep_comp_descriptor_t* ss_comp_desc);
|
||||
void dwc3_ep_set_config(dwc3_t* dwc, unsigned ep_num, bool enable);
|
||||
mx_status_t dwc3_ep_disable(dwc3_t* dwc, uint8_t ep_addr);
|
||||
void dwc3_start_eps(dwc3_t* dwc);
|
||||
void dwc3_ep_queue(dwc3_t* dwc, unsigned ep_num, iotxn_t* txn);
|
||||
void dwc3_ep_start_transfer(dwc3_t* dwc, unsigned ep_num, unsigned type, mx_paddr_t buffer,
|
||||
size_t length);
|
||||
void dwc3_ep_xfer_started(dwc3_t* dwc, unsigned ep_num, unsigned rsrc_id);
|
||||
void dwc3_ep_xfer_complete(dwc3_t* dwc, unsigned ep_num);
|
||||
void dwc3_ep_xfer_not_ready(dwc3_t* dwc, unsigned ep_num, unsigned stage);
|
||||
mx_status_t dwc3_ep_set_stall(dwc3_t* dwc, unsigned ep_num, bool stall);
|
||||
void dwc3_ep_end_transfers(dwc3_t* dwc, unsigned ep_num, mx_status_t reason);
|
||||
|
||||
// Endpoint 0
|
||||
mx_status_t dwc3_ep0_init(dwc3_t* dwc);
|
||||
void dwc3_ep0_reset(dwc3_t* dwc);
|
||||
void dwc3_ep0_start(dwc3_t* dwc);
|
||||
void dwc3_ep0_xfer_not_ready(dwc3_t* dwc, unsigned ep_num, unsigned stage);
|
||||
void dwc3_ep0_xfer_complete(dwc3_t* dwc, unsigned ep_num);
|
||||
|
||||
|
||||
// Events
|
||||
void dwc3_events_start(dwc3_t* dwc);
|
||||
|
||||
// Utils
|
||||
void dwc3_wait_bits(volatile uint32_t* ptr, uint32_t bits, uint32_t expected);
|
||||
void dwc3_print_status(dwc3_t* dwc);
|
||||
@@ -0,0 +1,22 @@
|
||||
# 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.
|
||||
|
||||
LOCAL_DIR := $(GET_LOCAL_DIR)
|
||||
|
||||
MODULE := $(LOCAL_DIR)
|
||||
|
||||
MODULE_TYPE := driver
|
||||
|
||||
MODULE_SRCS += \
|
||||
$(LOCAL_DIR)/dwc3.c \
|
||||
$(LOCAL_DIR)/dwc3-commands.c \
|
||||
$(LOCAL_DIR)/dwc3-endpoints.c \
|
||||
$(LOCAL_DIR)/dwc3-ep0.c \
|
||||
$(LOCAL_DIR)/dwc3-events.c
|
||||
|
||||
MODULE_STATIC_LIBS := system/ulib/ddk system/ulib/sync system/ulib/pretty
|
||||
|
||||
MODULE_LIBS := system/ulib/driver system/ulib/magenta system/ulib/c
|
||||
|
||||
include make/module.mk
|
||||
@@ -5,6 +5,7 @@
|
||||
#include <ddk/binding.h>
|
||||
#include <ddk/debug.h>
|
||||
#include <ddk/driver.h>
|
||||
#include <ddk/protocol/platform-devices.h>
|
||||
#include <ddk/protocol/usb-hci.h>
|
||||
#include <ddk/protocol/usb.h>
|
||||
|
||||
@@ -26,6 +27,9 @@
|
||||
#define DEFAULT_PRIORITY 16
|
||||
#define HIGH_PRIORITY 24
|
||||
|
||||
#define PDEV_MMIO_INDEX 0
|
||||
#define PDEV_IRQ_INDEX 0
|
||||
|
||||
mx_status_t xhci_add_device(xhci_t* xhci, int slot_id, int hub_address, int speed) {
|
||||
dprintf(TRACE, "xhci_add_new_device\n");
|
||||
|
||||
@@ -264,19 +268,17 @@ error_return:
|
||||
return status;
|
||||
}
|
||||
|
||||
static mx_status_t usb_xhci_bind(void* ctx, mx_device_t* dev, void** cookie) {
|
||||
static mx_status_t usb_xhci_bind_common(mx_device_t* parent, pci_protocol_t* pci) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
static mx_status_t usb_xhci_bind_pci(mx_device_t* parent, pci_protocol_t* pci) {
|
||||
mx_handle_t mmio_handle = MX_HANDLE_INVALID;
|
||||
mx_handle_t cfg_handle = MX_HANDLE_INVALID;
|
||||
xhci_t* xhci = NULL;
|
||||
uint32_t num_irq_handles_initialized = 0;
|
||||
mx_status_t status;
|
||||
|
||||
pci_protocol_t pci;
|
||||
if (device_get_protocol(dev, MX_PROTOCOL_PCI, &pci)) {
|
||||
status = MX_ERR_NOT_SUPPORTED;
|
||||
goto error_return;
|
||||
}
|
||||
|
||||
xhci = calloc(1, sizeof(xhci_t));
|
||||
if (!xhci) {
|
||||
status = MX_ERR_NO_MEMORY;
|
||||
@@ -289,7 +291,7 @@ static mx_status_t usb_xhci_bind(void* ctx, mx_device_t* dev, void** cookie) {
|
||||
* eXtensible Host Controller Interface revision 1.1, section 5, xhci
|
||||
* should only use BARs 0 and 1. 0 for 32 bit addressing, and 0+1 for 64 bit addressing.
|
||||
*/
|
||||
status = pci_map_resource(&pci, PCI_RESOURCE_BAR_0, MX_CACHE_POLICY_UNCACHED_DEVICE,
|
||||
status = pci_map_resource(pci, PCI_RESOURCE_BAR_0, MX_CACHE_POLICY_UNCACHED_DEVICE,
|
||||
&mmio, &mmio_len, &mmio_handle);
|
||||
if (status != MX_OK) {
|
||||
dprintf(ERROR, "usb_xhci_bind could not find bar\n");
|
||||
@@ -298,7 +300,7 @@ static mx_status_t usb_xhci_bind(void* ctx, mx_device_t* dev, void** cookie) {
|
||||
}
|
||||
|
||||
uint32_t irq_cnt = 0;
|
||||
status = pci_query_irq_mode_caps(&pci, MX_PCIE_IRQ_MODE_MSI, &irq_cnt);
|
||||
status = pci_query_irq_mode_caps(pci, MX_PCIE_IRQ_MODE_MSI, &irq_cnt);
|
||||
if (status != MX_OK) {
|
||||
dprintf(ERROR, "pci_query_irq_mode_caps failed %d\n", status);
|
||||
goto error_return;
|
||||
@@ -306,9 +308,9 @@ static mx_status_t usb_xhci_bind(void* ctx, mx_device_t* dev, void** cookie) {
|
||||
xhci_num_interrupts_init(xhci, mmio, irq_cnt);
|
||||
|
||||
// select our IRQ mode
|
||||
status = pci_set_irq_mode(&pci, MX_PCIE_IRQ_MODE_MSI, xhci->num_interrupts);
|
||||
status = pci_set_irq_mode(pci, MX_PCIE_IRQ_MODE_MSI, xhci->num_interrupts);
|
||||
if (status < 0) {
|
||||
mx_status_t status_legacy = pci_set_irq_mode(&pci, MX_PCIE_IRQ_MODE_LEGACY, 1);
|
||||
mx_status_t status_legacy = pci_set_irq_mode(pci, MX_PCIE_IRQ_MODE_LEGACY, 1);
|
||||
|
||||
if (status_legacy < 0) {
|
||||
dprintf(ERROR, "usb_xhci_bind Failed to set IRQ mode to either MSI "
|
||||
@@ -323,7 +325,7 @@ static mx_status_t usb_xhci_bind(void* ctx, mx_device_t* dev, void** cookie) {
|
||||
|
||||
for (uint32_t i = 0; i < xhci->num_interrupts; i++) {
|
||||
// register for interrupts
|
||||
status = pci_map_interrupt(&pci, i, &xhci->irq_handles[i]);
|
||||
status = pci_map_interrupt(pci, i, &xhci->irq_handles[i]);
|
||||
if (status != MX_OK) {
|
||||
dprintf(ERROR, "usb_xhci_bind map_interrupt failed %d\n", status);
|
||||
goto error_return;
|
||||
@@ -334,9 +336,9 @@ static mx_status_t usb_xhci_bind(void* ctx, mx_device_t* dev, void** cookie) {
|
||||
xhci->cfg_handle = cfg_handle;
|
||||
|
||||
// stash this here for the startup thread to call device_add() with
|
||||
xhci->parent = dev;
|
||||
xhci->parent = parent;
|
||||
// used for enabling bus mastering
|
||||
memcpy(&xhci->pci, &pci, sizeof(pci_protocol_t));
|
||||
memcpy(&xhci->pci, pci, sizeof(pci_protocol_t));
|
||||
|
||||
status = xhci_init(xhci, mmio);
|
||||
if (status != MX_OK) {
|
||||
@@ -350,18 +352,80 @@ static mx_status_t usb_xhci_bind(void* ctx, mx_device_t* dev, void** cookie) {
|
||||
return MX_OK;
|
||||
|
||||
error_return:
|
||||
if (xhci) {
|
||||
free(xhci);
|
||||
}
|
||||
free(xhci);
|
||||
for (uint32_t i = 0; i < num_irq_handles_initialized; i++) {
|
||||
mx_handle_close(xhci->irq_handles[i]);
|
||||
}
|
||||
if (mmio_handle != MX_HANDLE_INVALID) {
|
||||
mx_handle_close(mmio_handle);
|
||||
mx_handle_close(mmio_handle);
|
||||
mx_handle_close(cfg_handle);
|
||||
return status;
|
||||
}
|
||||
|
||||
|
||||
static mx_status_t usb_xhci_bind_pdev(mx_device_t* parent, platform_device_protocol_t* pdev) {
|
||||
mx_handle_t mmio_handle = MX_HANDLE_INVALID;
|
||||
mx_handle_t irq_handle = MX_HANDLE_INVALID;
|
||||
xhci_t* xhci = NULL;
|
||||
mx_status_t status;
|
||||
|
||||
xhci = calloc(1, sizeof(xhci_t));
|
||||
if (!xhci) {
|
||||
status = MX_ERR_NO_MEMORY;
|
||||
goto error_return;
|
||||
}
|
||||
if (cfg_handle != MX_HANDLE_INVALID) {
|
||||
mx_handle_close(cfg_handle);
|
||||
|
||||
void* mmio;
|
||||
uint64_t mmio_len;
|
||||
status = pdev_map_mmio(pdev, PDEV_MMIO_INDEX, MX_CACHE_POLICY_UNCACHED_DEVICE,
|
||||
&mmio, &mmio_len, &mmio_handle);
|
||||
if (status != MX_OK) {
|
||||
dprintf(ERROR, "usb_xhci_bind_pdev: pdev_map_mmio failed\n");
|
||||
goto error_return;
|
||||
}
|
||||
|
||||
status = pdev_map_interrupt(pdev, PDEV_IRQ_INDEX, &irq_handle);
|
||||
if (status != MX_OK) {
|
||||
dprintf(ERROR, "usb_xhci_bind_pdev: pdev_map_interrupt failed\n");
|
||||
goto error_return;
|
||||
}
|
||||
|
||||
xhci->mmio_handle = mmio_handle;
|
||||
xhci->irq_handles[0] = irq_handle;
|
||||
xhci->num_interrupts = 1;
|
||||
|
||||
// stash this here for the startup thread to call device_add() with
|
||||
xhci->parent = parent;
|
||||
|
||||
status = xhci_init(xhci, mmio);
|
||||
if (status != MX_OK) {
|
||||
goto error_return;
|
||||
}
|
||||
|
||||
thrd_t thread;
|
||||
thrd_create_with_name(&thread, xhci_start_thread, xhci, "xhci_start_thread");
|
||||
thrd_detach(thread);
|
||||
|
||||
return MX_OK;
|
||||
|
||||
error_return:
|
||||
free(xhci);
|
||||
mx_handle_close(mmio_handle);
|
||||
mx_handle_close(irq_handle);
|
||||
return status;
|
||||
}
|
||||
|
||||
static mx_status_t usb_xhci_bind(void* ctx, mx_device_t* parent, void** cookie) {
|
||||
pci_protocol_t pci;
|
||||
platform_device_protocol_t pdev;
|
||||
mx_status_t status;
|
||||
|
||||
if ((status = device_get_protocol(parent, MX_PROTOCOL_PCI, &pci)) == MX_OK) {
|
||||
return usb_xhci_bind_pci(parent, &pci);
|
||||
}
|
||||
if ((status = device_get_protocol(parent, MX_PROTOCOL_PLATFORM_DEV, &pdev)) == MX_OK) {
|
||||
return usb_xhci_bind_pdev(parent, &pdev);
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
@@ -371,9 +435,16 @@ static mx_driver_ops_t xhci_driver_ops = {
|
||||
};
|
||||
|
||||
// clang-format off
|
||||
MAGENTA_DRIVER_BEGIN(usb_xhci, xhci_driver_ops, "magenta", "0.1", 4)
|
||||
BI_ABORT_IF(NE, BIND_PROTOCOL, MX_PROTOCOL_PCI),
|
||||
MAGENTA_DRIVER_BEGIN(usb_xhci, xhci_driver_ops, "magenta", "0.1", 8)
|
||||
// PCI binding support
|
||||
BI_GOTO_IF(NE, BIND_PROTOCOL, MX_PROTOCOL_PCI, 0),
|
||||
BI_ABORT_IF(NE, BIND_PCI_CLASS, 0x0C),
|
||||
BI_ABORT_IF(NE, BIND_PCI_SUBCLASS, 0x03),
|
||||
BI_MATCH_IF(EQ, BIND_PCI_INTERFACE, 0x30),
|
||||
|
||||
// platform bus binding support
|
||||
BI_LABEL(0),
|
||||
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_XHCI),
|
||||
MAGENTA_DRIVER_END(usb_xhci)
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
|
||||
#include <ddk/device.h>
|
||||
#include <ddk/protocol/pci.h>
|
||||
#include <ddk/protocol/platform-device.h>
|
||||
#include <ddk/protocol/usb-bus.h>
|
||||
|
||||
#include "xhci-hw.h"
|
||||
@@ -99,12 +100,17 @@ struct xhci {
|
||||
#define INTERRUPTER_COUNT 2
|
||||
mx_handle_t irq_handles[INTERRUPTER_COUNT];
|
||||
mx_handle_t mmio_handle;
|
||||
mx_handle_t cfg_handle;
|
||||
thrd_t irq_thread;
|
||||
|
||||
// used by the start thread
|
||||
mx_device_t* parent;
|
||||
|
||||
// PCI support
|
||||
pci_protocol_t pci;
|
||||
mx_handle_t cfg_handle;
|
||||
|
||||
// platform device support
|
||||
platform_device_protocol_t* pdev;
|
||||
|
||||
// MMIO data structures
|
||||
xhci_cap_regs_t* cap_regs;
|
||||
|
||||
@@ -52,10 +52,17 @@
|
||||
#define USB_CDC_DST_OBEX_SERVICE_ID 0x19
|
||||
#define USB_CDC_DST_NCM 0x1A
|
||||
|
||||
/* CDC Class-Specific Notificaiton Codes */
|
||||
/* CDC Class-Specific Notification Codes */
|
||||
#define USB_CDC_NC_NETWORK_CONNECTION 0x00
|
||||
#define USB_CDC_NC_CONNECTION_SPEED_CHANGE 0x2A
|
||||
|
||||
/* CDC Ethernet Class-Specific Request Codes */
|
||||
#define USB_CDC_SET_ETHERNET_MULTICAST_FILTERS 0x40
|
||||
#define USB_CDC_SET_ETHERNET_PM_PATTERN_FILTER 0x41
|
||||
#define USB_CDC_GET_ETHERNET_PM_PATTERN_FILTER 0x42
|
||||
#define USB_CDC_SET_ETHERNET_PACKET_FILTER 0x43
|
||||
#define USB_CDC_GET_ETHERNET_STATISTIC 0x44
|
||||
|
||||
typedef struct {
|
||||
uint8_t bLength;
|
||||
uint8_t bDescriptorType; // USB_DT_CS_INTERFACE
|
||||
@@ -86,6 +93,15 @@ typedef struct {
|
||||
uint8_t bSubordinateInterface[];
|
||||
} __attribute__ ((packed)) usb_cs_union_interface_descriptor_t;
|
||||
|
||||
// fixed size version of usb_cs_union_interface_descriptor_t
|
||||
typedef struct {
|
||||
uint8_t bLength;
|
||||
uint8_t bDescriptorType; // USB_DT_CS_INTERFACE
|
||||
uint8_t bDescriptorSubType; // USB_CDC_DST_UNION
|
||||
uint8_t bControlInterface;
|
||||
uint8_t bSubordinateInterface;
|
||||
} __attribute__ ((packed)) usb_cs_union_interface_descriptor_1_t;
|
||||
|
||||
typedef struct {
|
||||
uint8_t bLength;
|
||||
uint8_t bDescriptorType; // USB_DT_CS_INTERFACE
|
||||
@@ -105,4 +121,10 @@ typedef struct {
|
||||
uint16_t wLength;
|
||||
} __attribute__ ((packed)) usb_cdc_notification_t;
|
||||
|
||||
typedef struct {
|
||||
usb_cdc_notification_t notification;
|
||||
uint32_t downlink_br;
|
||||
uint32_t uplink_br;
|
||||
} __attribute__ ((packed)) usb_cdc_speed_change_notification_t;
|
||||
|
||||
__BEGIN_CDECLS;
|
||||
|
||||
Referência em uma Nova Issue
Bloquear um usuário