blob: 705d08a86740d5970345eaf9343b3f39ab8f0e59 [file] [log] [blame]
// 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 "rndis_host.h"
#include <fuchsia/hardware/ethernet/c/banjo.h>
#include <fuchsia/hardware/usb/c/banjo.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <threads.h>
#include <zircon/hw/usb.h>
#include <zircon/hw/usb/cdc.h>
#include <zircon/listnode.h>
#include <ddk/debug.h>
#include <ddk/device.h>
#include <ddk/driver.h>
#include <fbl/alloc_checker.h>
#include <fbl/auto_lock.h>
#include <usb/usb-request.h>
#include <usb/usb.h>
#include "src/connectivity/ethernet/drivers/rndis-host/rndishost_bind.h"
#define READ_REQ_COUNT 8
#define WRITE_REQ_COUNT 4
#define ETH_HEADER_SIZE 4
#define ETHERNET_MAX_TRANSMIT_DELAY 100
#define ETHERNET_MAX_RECV_DELAY 100
#define ETHERNET_TRANSMIT_DELAY 10
#define ETHERNET_RECV_DELAY 10
#define ETHERNET_INITIAL_TRANSMIT_DELAY 0
#define ETHERNET_INITIAL_RECV_DELAY 0
static bool command_succeeded(const void* buf, uint32_t type, size_t length) {
const auto* header = static_cast<const rndis_header_complete*>(buf);
if (header->msg_type != type) {
zxlogf(TRACE, "Bad type: Actual: %x, Expected: %x.", header->msg_type, type);
return false;
}
if (header->msg_length != length) {
zxlogf(TRACE, "Bad length: Actual: %u, Expected: %zu.", header->msg_length, length);
return false;
}
if (header->status != RNDIS_STATUS_SUCCESS) {
zxlogf(TRACE, "Bad status: %x.", header->status);
return false;
}
return true;
}
template <typename T>
bool command_succeeded(const T* buf, uint32_t type) {
return command_succeeded(buf, type, sizeof(*buf));
}
zx_status_t RndisHost::SendControlCommand(void* command) {
rndis_header* header = static_cast<rndis_header*>(command);
header->request_id = next_request_id_++;
return usb_.ControlOut(USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE,
USB_CDC_SEND_ENCAPSULATED_COMMAND, 0, control_intf_, RNDIS_CONTROL_TIMEOUT,
command, header->msg_length);
}
zx_status_t RndisHost::ReceiveControlMessage(uint32_t request_id) {
size_t len_read = 0;
zx_status_t status =
usb_.ControlIn(USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE,
USB_CDC_GET_ENCAPSULATED_RESPONSE, 0, control_intf_, RNDIS_CONTROL_TIMEOUT,
control_receive_buffer_, sizeof(control_receive_buffer_), &len_read);
if (len_read == 0) {
zxlogf(ERROR, "rndishost received a zero-length response on the control channel");
return ZX_ERR_IO_REFUSED;
}
const auto* header = reinterpret_cast<rndis_header*>(control_receive_buffer_);
if (header->request_id != request_id) {
zxlogf(ERROR, "rndishost received wrong packet ID on control channel: got %d, wanted %d",
header->request_id, request_id);
return ZX_ERR_IO_DATA_INTEGRITY;
}
return status;
}
zx_status_t RndisHost::Command(void* command) {
zx_status_t status = SendControlCommand(command);
if (status != ZX_OK) {
return status;
}
const uint32_t request_id = static_cast<rndis_header*>(command)->request_id;
return ReceiveControlMessage(request_id);
}
void RndisHost::Recv(usb_request_t* request) {
void* read_data;
zx_status_t status = usb_request_mmap(request, &read_data);
if (status != ZX_OK) {
zxlogf(ERROR, "rndishost receive: usb_request_mmap failed: %d", status);
return;
}
size_t remaining = request->response.actual;
while (remaining >= sizeof(rndis_packet_header)) {
const auto* header = static_cast<const rndis_packet_header*>(read_data);
if (header->msg_type != RNDIS_PACKET_MSG) {
zxlogf(ERROR, "rndishost receive: bad data packet type %u", header->msg_type);
return;
}
if (header->msg_length > remaining) {
zxlogf(ERROR,
"rndishost receive: bad data packet message length: %u bytes, but only %zu bytes "
"remain\n",
header->msg_length, remaining);
return;
}
if (header->msg_length < sizeof(*header)) {
zxlogf(ERROR,
"rndishost receive: bad data packet message length: %u bytes is smaller than header\n",
header->msg_length);
return;
}
if (header->data_length > header->msg_length) {
zxlogf(ERROR,
"rndishost receive: bad data packet data length: %u bytes is longer than message of "
"%u bytes\n",
header->data_length, header->msg_length);
return;
}
static_assert(sizeof(*header) >= offsetof(rndis_packet_header, data_offset));
if (header->data_offset > header->msg_length - offsetof(rndis_packet_header, data_offset)) {
zxlogf(ERROR,
"rndishost receive: bad data packet data offset: %zu + %u bytes is after message of "
"%u bytes\n",
offsetof(rndis_packet_header, data_offset), header->data_offset, header->msg_length);
return;
}
size_t total_offset = offsetof(rndis_packet_header, data_offset) + header->data_offset;
if (total_offset > header->msg_length - header->data_length) {
zxlogf(ERROR,
"rndishost receive: bad data packet: data continues after end of message (message "
"length = %u; data offset = %zu, data length = %u)\n",
header->msg_length, total_offset, header->data_length);
return;
}
ethernet_ifc_recv(&ifc_, static_cast<uint8_t*>(read_data) + total_offset, header->data_length,
0);
read_data = static_cast<uint8_t*>(read_data) + header->msg_length;
remaining -= header->msg_length;
}
}
void RndisHost::ReadComplete(usb_request_t* request) {
if (request->response.status == ZX_ERR_IO_NOT_PRESENT) {
usb_request_release(request);
return;
}
fbl::AutoLock lock(&mutex_);
if (request->response.status == ZX_ERR_IO_REFUSED) {
zxlogf(DEBUG, "rndis_read_complete usb_reset_endpoint");
usb_.ResetEndpoint(bulk_in_addr_);
} else if (request->response.status == ZX_ERR_IO_INVALID) {
zxlogf(DEBUG,
"rndis_read_complete Slowing down the requests by %d usec"
" and resetting the recv endpoint\n",
ETHERNET_RECV_DELAY);
if (rx_endpoint_delay_ < ETHERNET_MAX_RECV_DELAY) {
rx_endpoint_delay_ += ETHERNET_RECV_DELAY;
}
usb_.ResetEndpoint(bulk_in_addr_);
}
if (request->response.status == ZX_OK && ifc_.ops) {
Recv(request);
} else {
zxlogf(TRACE, "rndis read complete: bad status = %d", request->response.status);
}
// TODO: Only usb_request_queue if the device is online.
zx_nanosleep(zx_deadline_after(ZX_USEC(rx_endpoint_delay_)));
usb_request_complete_t complete = {
.callback = [](void* arg, usb_request_t* request) -> void {
static_cast<RndisHost*>(arg)->ReadComplete(request);
},
.ctx = this,
};
usb_.RequestQueue(request, &complete);
}
void RndisHost::WriteComplete(usb_request_t* request) {
if (request->response.status == ZX_ERR_IO_NOT_PRESENT) {
zxlogf(ERROR, "rndis_write_complete zx_err_io_not_present");
usb_request_release(request);
return;
}
fbl::AutoLock lock(&mutex_);
if (request->response.status == ZX_ERR_IO_REFUSED) {
zxlogf(DEBUG, "rndishost usb_reset_endpoint");
usb_.ResetEndpoint(bulk_out_addr_);
} else if (request->response.status == ZX_ERR_IO_INVALID) {
zxlogf(DEBUG,
"rndis_write_complete Slowing down the requests by %d usec"
" and resetting the transmit endpoint\n",
ETHERNET_TRANSMIT_DELAY);
if (tx_endpoint_delay_ < ETHERNET_MAX_TRANSMIT_DELAY) {
tx_endpoint_delay_ += ETHERNET_TRANSMIT_DELAY;
}
usb_.ResetEndpoint(bulk_out_addr_);
}
zx_status_t status = usb_req_list_add_tail(&free_write_reqs_, request, parent_req_size_);
ZX_DEBUG_ASSERT(status == ZX_OK);
}
RndisHost::RndisHost(zx_device_t* parent, uint8_t control_intf, uint8_t bulk_in_addr,
uint8_t bulk_out_addr, const usb::UsbDevice& usb)
: RndisHostType(parent),
usb_(usb),
mac_addr_{},
control_intf_(control_intf),
next_request_id_(0),
mtu_(0),
bulk_in_addr_(bulk_in_addr),
bulk_out_addr_(bulk_out_addr),
rx_endpoint_delay_(0),
tx_endpoint_delay_(0),
ifc_({}),
thread_started_(false),
parent_req_size_(usb.GetRequestSize()) {
list_initialize(&free_read_reqs_);
list_initialize(&free_write_reqs_);
ifc_.ops = nullptr;
}
zx_status_t RndisHost::EthernetImplQuery(uint32_t options, ethernet_info_t* info) {
if (options) {
return ZX_ERR_INVALID_ARGS;
}
memset(info, 0, sizeof(*info));
info->mtu = mtu_;
memcpy(info->mac, mac_addr_, sizeof(mac_addr_));
info->netbuf_size = sizeof(ethernet_netbuf_t);
return ZX_OK;
}
void RndisHost::EthernetImplStop() {
fbl::AutoLock lock(&mutex_);
ifc_.ops = nullptr;
}
zx_status_t RndisHost::EthernetImplStart(const ethernet_ifc_protocol_t* ifc) {
fbl::AutoLock lock(&mutex_);
if (ifc_.ops) {
return ZX_ERR_ALREADY_BOUND;
}
ifc_ = *ifc;
// TODO: Check that the device is online before sending ETHERNET_STATUS_ONLINE.
ethernet_ifc_status(&ifc_, ETHERNET_STATUS_ONLINE);
return ZX_OK;
}
void RndisHost::EthernetImplQueueTx(uint32_t options, ethernet_netbuf_t* netbuf,
ethernet_impl_queue_tx_callback completion_cb, void* cookie) {
zx_status_t status = ZX_OK;
fbl::AutoLock lock(&mutex_);
usb_request_t* req = usb_req_list_remove_head(&free_write_reqs_, parent_req_size_);
if (req == nullptr) {
zxlogf(DEBUG, "rndishost dropped a packet");
status = ZX_ERR_NO_RESOURCES;
goto done;
}
status = PrepareDataPacket(req, netbuf->data_buffer, netbuf->data_size);
if (status != ZX_OK) {
status = usb_req_list_add_tail(&free_write_reqs_, req, parent_req_size_);
ZX_DEBUG_ASSERT(status == ZX_OK);
goto done;
}
zx_nanosleep(zx_deadline_after(ZX_USEC(tx_endpoint_delay_)));
{
usb_request_complete_t complete = {
.callback = [](void* arg, usb_request_t* request) -> void {
static_cast<RndisHost*>(arg)->WriteComplete(request);
},
.ctx = this,
};
usb_.RequestQueue(req, &complete);
}
done:
lock.release();
completion_cb(cookie, status, netbuf);
}
zx_status_t RndisHost::PrepareDataPacket(usb_request_t* req, const void* data, size_t data_length) {
if (data_length > RNDIS_MAX_DATA_SIZE) {
zxlogf(ERROR, "rndishost: data packet too large (%zu bytes, maximum %u)", data_length,
RNDIS_MAX_DATA_SIZE);
return ZX_ERR_IO_OVERRUN;
}
static_assert(sizeof(rndis_packet_header) < UINT32_MAX &&
RNDIS_MAX_DATA_SIZE < UINT32_MAX - sizeof(rndis_packet_header));
rndis_packet_header header{};
header.msg_type = RNDIS_PACKET_MSG;
header.msg_length = static_cast<uint32_t>(sizeof(header) + data_length);
header.data_offset = sizeof(header) - offsetof(rndis_packet_header, data_offset);
header.data_length = static_cast<uint32_t>(data_length);
ssize_t bytes_copied = usb_request_copy_to(req, &header, sizeof(header), 0);
if (bytes_copied < 0) {
zxlogf(ERROR, "rndishost: failed to copy request header into send txn (error %zd)",
bytes_copied);
return ZX_ERR_IO;
}
if (static_cast<size_t>(bytes_copied) < sizeof(header)) {
zxlogf(ERROR,
"rndishost: failed to copy whole request header into send txn (copied %zd out of %zu "
"bytes)\n",
bytes_copied, sizeof(header));
return ZX_ERR_IO_OVERRUN;
}
bytes_copied = usb_request_copy_to(req, data, data_length, sizeof(header));
if (bytes_copied < 0) {
zxlogf(ERROR, "rndishost: failed to copy data into send txn (error %zd)", bytes_copied);
return ZX_ERR_IO;
}
if (static_cast<size_t>(bytes_copied) < data_length) {
zxlogf(ERROR,
"rndishost: failed to copy all data into send txn (copied %zd out of %zu bytes)\n",
bytes_copied, data_length);
return ZX_ERR_IO_OVERRUN;
}
req->header.length = sizeof(header) + data_length;
return ZX_OK;
}
void RndisHost::DdkUnbind(ddk::UnbindTxn txn) { txn.Reply(); }
void RndisHost::DdkRelease() {
if (thread_started_) {
thrd_join(thread_, NULL);
}
usb_request_t* txn;
while ((txn = usb_req_list_remove_head(&free_read_reqs_, parent_req_size_)) != NULL) {
usb_request_release(txn);
}
while ((txn = usb_req_list_remove_head(&free_write_reqs_, parent_req_size_)) != NULL) {
usb_request_release(txn);
}
}
zx_status_t RndisHost::EthernetImplSetParam(uint32_t param, int32_t value, const uint8_t* data,
size_t data_size) {
return ZX_ERR_NOT_SUPPORTED;
}
void RndisHost::EthernetImplGetBti(zx::bti* out_bti) {}
// Send an initialization message to the device.
zx_status_t RndisHost::InitializeDevice() {
rndis_init init{};
init.msg_type = RNDIS_INITIALIZE_MSG;
init.msg_length = sizeof(init);
init.major_version = RNDIS_MAJOR_VERSION;
init.minor_version = RNDIS_MINOR_VERSION;
init.max_xfer_size = RNDIS_MAX_XFER_SIZE;
zx_status_t status = Command(&init);
if (status != ZX_OK) {
zxlogf(ERROR, "rndishost bad status on initial message. %d", status);
return status;
}
rndis_init_complete* init_cmplt = reinterpret_cast<rndis_init_complete*>(control_receive_buffer_);
if (!command_succeeded(init_cmplt, RNDIS_INITIALIZE_CMPLT)) {
zxlogf(ERROR, "rndishost initialization failed.");
return ZX_ERR_IO;
}
zxlogf(INFO, "rndishost maximum bus transfer size: %u bytes", init_cmplt->max_xfer_size);
return ZX_OK;
}
zx_status_t RndisHost::QueryDevice(uint32_t oid, void* info_buffer_out,
size_t expected_info_buffer_length) {
rndis_query query{};
query.msg_type = RNDIS_QUERY_MSG;
query.msg_length = sizeof(query);
query.oid = oid;
query.info_buffer_length = 0;
query.info_buffer_offset = 0;
zx_status_t status = SendControlCommand(&query);
if (status != ZX_OK) {
zxlogf(ERROR, "rndishost failed to issue query: %d", status);
return status;
}
status = ReceiveControlMessage(query.request_id);
if (status != ZX_OK) {
zxlogf(ERROR, "rndishost failed to receive query response: %d", status);
return status;
}
rndis_query_complete* query_cmplt =
reinterpret_cast<rndis_query_complete*>(control_receive_buffer_);
if (!command_succeeded(control_receive_buffer_, RNDIS_QUERY_CMPLT,
sizeof(*query_cmplt) + expected_info_buffer_length)) {
return ZX_ERR_IO;
}
// info_buffer_offset and info_buffer_length determine where the query result is in the response
// buffer. Check that the length of the result matches what we expect.
if (query_cmplt->info_buffer_length != expected_info_buffer_length) {
zxlogf(ERROR, "rndishost expected info buffer of size %zu, got %u", expected_info_buffer_length,
query_cmplt->info_buffer_length);
return ZX_ERR_IO_DATA_INTEGRITY;
}
if (query_cmplt->info_buffer_offset == 0 || query_cmplt->info_buffer_length == 0) {
// Section 2.2.10 (REMOTE_NDIS_QUERY_CMPLT), p. 20 of the RNDIS specification states that if
// there is no payload, both the offset and length must be set to 0. It does not expressly
// forbid a nonempty payload with a zero offset, but we assume it is meant to be forbidden.
if (query_cmplt->info_buffer_offset != 0 || query_cmplt->info_buffer_length != 0) {
return ZX_ERR_IO_DATA_INTEGRITY;
}
// Both the offset and the length are zero. As the length equals expected_info_buffer_length, we
// were expecting an empty response to this query. (It is unclear when this might happen, but it
// is permitted.)
return ZX_OK;
}
// The offset in info_buffer_offset is given in bytes from from the beginning of request_id. Check
// that it doesn't begin outside the response buffer. This also ensures that computing the total
// offset from the start of the buffer does not overflow.
if (query_cmplt->info_buffer_offset >=
sizeof(control_receive_buffer_) - offsetof(rndis_query_complete, request_id)) {
return ZX_ERR_IO_DATA_INTEGRITY;
}
// Check that the length + offset lies within the buffer. From the previous check, we know that
// total_offset < sizeof(control_receive_buffer_), and therefore the subtraction won't underflow.
const ptrdiff_t total_offset =
offsetof(rndis_query, request_id) + query_cmplt->info_buffer_offset;
if (query_cmplt->info_buffer_length > sizeof(control_receive_buffer_) - total_offset) {
return ZX_ERR_IO_DATA_INTEGRITY;
}
if (info_buffer_out != nullptr) {
memcpy(info_buffer_out, control_receive_buffer_ + total_offset, expected_info_buffer_length);
}
return ZX_OK;
}
zx_status_t RndisHost::SetDeviceOid(uint32_t oid, const void* data, size_t data_length) {
struct Payload {
rndis_set header;
uint8_t data[RNDIS_SET_INFO_BUFFER_LENGTH];
} __PACKED;
Payload set = {};
set.header.msg_type = RNDIS_SET_MSG;
set.header.msg_length = sizeof(rndis_set);
set.header.info_buffer_length = 0;
set.header.info_buffer_offset = 0;
set.header.oid = oid;
if (data_length > 0) {
if (data_length > RNDIS_SET_INFO_BUFFER_LENGTH) {
zxlogf(ERROR, "rndishost attempted to set OID %u with size %zu bytes (maximum is %d)", oid,
data_length, RNDIS_SET_INFO_BUFFER_LENGTH);
return ZX_ERR_INVALID_ARGS;
}
// The buffer is always the same size regardless of the size of the payload.
set.header.msg_length += RNDIS_SET_INFO_BUFFER_LENGTH;
set.header.info_buffer_length = RNDIS_SET_INFO_BUFFER_LENGTH;
set.header.info_buffer_offset = offsetof(Payload, data) - offsetof(rndis_set, request_id);
memcpy(&set.data, data, data_length);
}
zx_status_t status = Command(&set);
if (status != ZX_OK) {
zxlogf(ERROR, "rndishost issuing set command failed: %d", status);
return status;
}
rndis_set_complete* set_cmplt = reinterpret_cast<rndis_set_complete*>(control_receive_buffer_);
if (!command_succeeded(set_cmplt, RNDIS_SET_CMPLT)) {
return ZX_ERR_IO;
}
return ZX_OK;
}
zx_status_t RndisHost::StartThread() {
ZX_DEBUG_ASSERT(init_txn_.has_value());
zx_status_t status = InitializeDevice();
if (status != ZX_OK) {
goto fail;
}
status = QueryDevice(OID_802_3_PERMANENT_ADDRESS, mac_addr_, sizeof(mac_addr_));
if (status != ZX_OK) {
zxlogf(ERROR, "rndishost could not obtain device physical address: %d", status);
goto fail;
}
zxlogf(INFO, "rndishost MAC address: %02x:%02x:%02x:%02x:%02x:%02x", mac_addr_[0], mac_addr_[1],
mac_addr_[2], mac_addr_[3], mac_addr_[4], mac_addr_[5]);
status = QueryDevice(OID_GEN_MAXIMUM_FRAME_SIZE, &mtu_, sizeof(mtu_));
if (status != ZX_OK) {
zxlogf(ERROR, "rndishost could not obtain maximum frame size: %d", status);
goto fail;
}
zxlogf(INFO, "rndishost maximum frame size: %u bytes", mtu_);
{
// The device's packet filter is initialized to 0, which blocks all traffic. Enable network
// traffic.
const uint32_t filter = RNDIS_PACKET_TYPE_DIRECTED | RNDIS_PACKET_TYPE_BROADCAST |
RNDIS_PACKET_TYPE_ALL_MULTICAST | RNDIS_PACKET_TYPE_PROMISCUOUS;
status = SetDeviceOid(OID_GEN_CURRENT_PACKET_FILTER, &filter, sizeof(filter));
if (status != ZX_OK) {
zxlogf(ERROR, "rndishost failed to set packet filter");
goto fail;
}
}
// Queue read requests
{
fbl::AutoLock lock(&mutex_);
usb_request_t* txn;
usb_request_complete_t complete = {
.callback = [](void* arg, usb_request_t* request) -> void {
static_cast<RndisHost*>(arg)->ReadComplete(request);
},
.ctx = this,
};
while ((txn = usb_req_list_remove_head(&free_read_reqs_, parent_req_size_)) != nullptr) {
usb_.RequestQueue(txn, &complete);
}
}
init_txn_->Reply(ZX_OK); // This will make the device visible and able to be unbound.
zxlogf(INFO, "rndishost ready");
return ZX_OK;
fail:
init_txn_->Reply(status); // This will schedule unbinding of the device.
return status;
}
zx_status_t RndisHost::AddDevice() {
zx_status_t status = ZX_OK;
uint64_t req_size = parent_req_size_ + sizeof(usb_req_internal_t);
for (int i = 0; i < READ_REQ_COUNT; i++) {
usb_request_t* req;
status = usb_request_alloc(&req, RNDIS_MAX_XFER_SIZE, bulk_in_addr_, req_size);
if (status != ZX_OK) {
return status;
}
status = usb_req_list_add_head(&free_read_reqs_, req, parent_req_size_);
ZX_DEBUG_ASSERT(status == ZX_OK);
}
for (int i = 0; i < WRITE_REQ_COUNT; i++) {
usb_request_t* req;
// TODO: Allocate based on mtu.
status = usb_request_alloc(&req, RNDIS_BUFFER_SIZE, bulk_out_addr_, req_size);
if (status != ZX_OK) {
return status;
}
status = usb_req_list_add_head(&free_write_reqs_, req, parent_req_size_);
ZX_DEBUG_ASSERT(status == ZX_OK);
}
status = DdkAdd(ddk::DeviceAddArgs("rndishost").set_proto_id(ZX_PROTOCOL_ETHERNET_IMPL));
if (status != ZX_OK) {
zxlogf(ERROR, "rndishost: failed to create device: %d", status);
return status;
}
return ZX_OK;
}
void RndisHost::DdkInit(ddk::InitTxn txn) {
init_txn_ = std::move(txn);
thread_started_ = true;
int ret = thrd_create_with_name(
&thread_, [](void* arg) -> int { return static_cast<RndisHost*>(arg)->StartThread(); }, this,
"rndishost_start_thread");
if (ret != thrd_success) {
thread_started_ = false;
return init_txn_->Reply(ZX_ERR_NO_RESOURCES);
}
// The thread will reply to |init_txn_| once it is ready to make the device visible
// and able to be unbound.
}
static zx_status_t rndishost_bind(void* ctx, zx_device_t* parent) {
usb::UsbDevice usb;
zx_status_t status = usb::UsbDevice::CreateFromDevice(parent, &usb);
if (status != ZX_OK) {
return status;
}
uint8_t bulk_in_addr = 0;
uint8_t bulk_out_addr = 0;
uint8_t intr_addr = 0;
uint8_t control_intf = 0;
{
// Find our endpoints.
// We should have two interfaces: the CDC classified interface the bulk in
// and out endpoints, and the RNDIS interface for control. The RNDIS
// interface will be classified as USB_CLASS_WIRELESS when the device is
// used for tethering.
// TODO: Figure out how to handle other RNDIS use cases.
std::optional<usb::InterfaceList> interfaces;
status = usb::InterfaceList::Create(usb, false, &interfaces);
if (status != ZX_OK) {
return status;
}
for (const usb::Interface& interface : *interfaces) {
const usb_interface_descriptor_t* intf = interface.descriptor();
if (intf->bInterfaceClass == USB_CLASS_WIRELESS) {
control_intf = intf->bInterfaceNumber;
if (intf->bNumEndpoints != 1) {
return ZX_ERR_NOT_SUPPORTED;
}
for (const auto& endp : interface.GetEndpointList()) {
if (usb_ep_direction(&endp.descriptor) == USB_ENDPOINT_IN &&
usb_ep_type(&endp.descriptor) == USB_ENDPOINT_INTERRUPT) {
intr_addr = endp.descriptor.bEndpointAddress;
}
}
} else if (intf->bInterfaceClass == USB_CLASS_CDC) {
if (intf->bNumEndpoints != 2) {
return ZX_ERR_NOT_SUPPORTED;
}
for (const auto& endp : interface.GetEndpointList()) {
if (usb_ep_direction(&endp.descriptor) == USB_ENDPOINT_OUT) {
if (usb_ep_type(&endp.descriptor) == USB_ENDPOINT_BULK) {
bulk_out_addr = endp.descriptor.bEndpointAddress;
}
} else if (usb_ep_direction(&endp.descriptor) == USB_ENDPOINT_IN) {
if (usb_ep_type(&endp.descriptor) == USB_ENDPOINT_BULK) {
bulk_in_addr = endp.descriptor.bEndpointAddress;
}
}
}
} else {
return ZX_ERR_NOT_SUPPORTED;
}
}
}
if (!bulk_in_addr || !bulk_out_addr || !intr_addr) {
zxlogf(ERROR, "rndishost couldn't find endpoints");
return ZX_ERR_NOT_SUPPORTED;
}
fbl::AllocChecker ac;
auto dev = fbl::make_unique_checked<RndisHost>(&ac, parent, control_intf, bulk_in_addr,
bulk_out_addr, usb);
if (!ac.check()) {
return ZX_ERR_NO_MEMORY;
}
status = dev->AddDevice();
if (status == ZX_OK) {
// devmgr is now in charge of the memory for dev, so we don't own it any more.
dev.release();
} else {
zxlogf(ERROR, "rndishost_bind failed: %d", status);
}
return status;
}
static zx_driver_ops_t rndis_driver_ops = []() {
zx_driver_ops_t ops{};
ops.version = DRIVER_OPS_VERSION;
ops.bind = rndishost_bind;
return ops;
}();
// TODO: Make sure we can bind to all RNDIS use cases. USB_CLASS_WIRELESS only
// covers the tethered device case.
// clang-format off
ZIRCON_DRIVER(rndishost, rndis_driver_ops, "zircon", "0.1");