1// Copyright 2017 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "rndishost.h"
6
7#include <ddk/binding.h>
8#include <ddk/debug.h>
9#include <ddk/device.h>
10#include <ddk/driver.h>
11#include <ddk/protocol/ethernet.h>
12#include <ddk/protocol/usb.h>
13#include <ddk/usb/usb.h>
14#include <zircon/hw/usb-cdc.h>
15#include <zircon/hw/usb.h>
16#include <zircon/listnode.h>
17
18#include <stdio.h>
19#include <stdlib.h>
20#include <string.h>
21#include <threads.h>
22
23#define READ_REQ_COUNT 8
24#define WRITE_REQ_COUNT 4
25#define ETH_HEADER_SIZE 4
26
27#define ETHMAC_MAX_TRANSMIT_DELAY 100
28#define ETHMAC_MAX_RECV_DELAY 100
29#define ETHMAC_TRANSMIT_DELAY 10
30#define ETHMAC_RECV_DELAY 10
31#define ETHMAC_INITIAL_TRANSMIT_DELAY 0
32#define ETHMAC_INITIAL_RECV_DELAY 0
33
34typedef struct {
35    zx_device_t* zxdev;
36    zx_device_t* usb_zxdev;
37    usb_protocol_t usb;
38
39    uint8_t mac_addr[6];
40    uint8_t control_intf;
41    uint32_t request_id;
42    uint32_t mtu;
43
44    uint8_t bulk_in_addr;
45    uint8_t bulk_out_addr;
46    uint8_t intr_addr;
47
48    list_node_t free_read_reqs;
49    list_node_t free_write_reqs;
50    list_node_t free_intr_reqs;
51
52    uint64_t rx_endpoint_delay;    // wait time between 2 recv requests
53    uint64_t tx_endpoint_delay;    // wait time between 2 transmit requests
54
55    // Interface to the ethernet layer.
56    ethmac_ifc_t* ifc;
57    void* cookie;
58
59    mtx_t mutex;
60} rndishost_t;
61
62static void dump_buffer(void* buf) {
63    uint8_t* p = buf;
64    for (int i = 0; i < RNDIS_BUFFER_SIZE; i += 4) {
65        if (i != 0 && i % 24 == 0) {
66            zxlogf(DEBUG1, "\n");
67        }
68        zxlogf(DEBUG1, "%08x ", p[i] | p[i + 1] << 8 | p[i + 2] << 16 | p[i + 3] << 24);
69    }
70    zxlogf(DEBUG1, "\n");
71}
72
73static bool command_succeeded(void* buf, uint32_t type, uint32_t length) {
74    rndis_header_complete* header = buf;
75    if (header->msg_type != type) {
76        zxlogf(DEBUG1, "Bad type: Actual: %x, Expected: %x.\n",
77               header->msg_type, type);
78        return false;
79    }
80    if (header->msg_length != length) {
81        zxlogf(DEBUG1, "Bad length: Actual: %d, Expected: %d.\n",
82               header->msg_length, length);
83        return false;
84    }
85    if (header->status != RNDIS_STATUS_SUCCESS) {
86        zxlogf(DEBUG1, "Bad status: %x.\n", header->status);
87        return false;
88    }
89    return true;
90}
91
92static zx_status_t rndis_command(rndishost_t* eth, void* buf) {
93    rndis_header* header = buf;
94    uint32_t request_id = eth->request_id++;
95    header->request_id = request_id;
96
97    zx_status_t status;
98    status = usb_control(&eth->usb, USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE,
99                         USB_CDC_SEND_ENCAPSULATED_COMMAND,
100                         0, eth->control_intf, buf, header->msg_length, ZX_TIME_INFINITE, NULL);
101
102    if (status < 0) {
103        return status;
104    }
105
106    // TODO: Set a reasonable timeout on this call.
107    status = usb_control(&eth->usb, USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE,
108                         USB_CDC_GET_ENCAPSULATED_RESPONSE,
109                         0, eth->control_intf, buf, RNDIS_BUFFER_SIZE, ZX_TIME_INFINITE, NULL);
110
111    if (header->request_id != request_id) {
112        return ZX_ERR_IO_DATA_INTEGRITY;
113    }
114
115    return status;
116}
117
118static void rndis_read_complete(usb_request_t* request, void* cookie) {
119    zxlogf(TRACE, "rndis_read_complete\n");
120    rndishost_t* eth = (rndishost_t*)cookie;
121
122    if (request->response.status == ZX_ERR_IO_NOT_PRESENT) {
123        usb_req_release(&eth->usb, request);
124        return;
125    }
126
127    mtx_lock(&eth->mutex);
128    if (request->response.status == ZX_ERR_IO_REFUSED) {
129        zxlogf(TRACE, "rndis_read_complete usb_reset_endpoint\n");
130        usb_reset_endpoint(&eth->usb, eth->bulk_in_addr);
131    } else if (request->response.status == ZX_ERR_IO_INVALID) {
132        zxlogf(TRACE, "rndis_read_complete Slowing down the requests by %d usec"
133               " and resetting the recv endpoint\n", ETHMAC_RECV_DELAY);
134        if (eth->rx_endpoint_delay < ETHMAC_MAX_RECV_DELAY) {
135            eth->rx_endpoint_delay += ETHMAC_RECV_DELAY;
136        }
137        usb_reset_endpoint(&eth->usb, eth->bulk_in_addr);
138    }
139    if ((request->response.status == ZX_OK) && eth->ifc) {
140        size_t len = request->response.actual;
141
142        uint8_t* read_data;
143        zx_status_t status = usb_req_mmap(&eth->usb, request, (void*)&read_data);
144        if (status != ZX_OK) {
145            printf("usb_req_mmap failed: %d\n", status);
146            mtx_unlock(&eth->mutex);
147            return;
148        }
149
150        eth->ifc->recv(eth->cookie, read_data, len, 0);
151    }
152
153    // TODO: Only usb_request_queue if the device is online.
154    zx_nanosleep(zx_deadline_after(ZX_USEC(eth->rx_endpoint_delay)));
155    usb_request_queue(&eth->usb, request);
156
157    mtx_unlock(&eth->mutex);
158}
159
160static void rndis_write_complete(usb_request_t* request, void* cookie) {
161    rndishost_t* eth = (rndishost_t*)cookie;
162
163    if (request->response.status == ZX_ERR_IO_NOT_PRESENT) {
164        zxlogf(ERROR, "rndis_write_complete zx_err_io_not_present\n");
165        usb_req_release(&eth->usb, request);
166        return;
167    }
168
169    mtx_lock(&eth->mutex);
170    if (request->response.status == ZX_ERR_IO_REFUSED) {
171        zxlogf(TRACE, "rndishost usb_reset_endpoint\n");
172        usb_reset_endpoint(&eth->usb, eth->bulk_out_addr);
173    } else if (request->response.status == ZX_ERR_IO_INVALID) {
174        zxlogf(TRACE, "rndis_write_complete Slowing down the requests by %d usec"
175               " and resetting the transmit endpoint\n", ETHMAC_TRANSMIT_DELAY);
176        if (eth->tx_endpoint_delay < ETHMAC_MAX_TRANSMIT_DELAY) {
177            eth->tx_endpoint_delay += ETHMAC_TRANSMIT_DELAY;
178        }
179        usb_reset_endpoint(&eth->usb, eth->bulk_out_addr);
180    }
181
182    list_add_tail(&eth->free_write_reqs, &request->node);
183    mtx_unlock(&eth->mutex);
184}
185
186static void rndishost_free(rndishost_t* eth) {
187    usb_request_t* txn;
188    while ((txn = list_remove_head_type(&eth->free_read_reqs, usb_request_t, node)) != NULL) {
189        usb_req_release(&eth->usb, txn);
190    }
191    while ((txn = list_remove_head_type(&eth->free_write_reqs, usb_request_t, node)) != NULL) {
192        usb_req_release(&eth->usb, txn);
193    }
194    while ((txn = list_remove_head_type(&eth->free_intr_reqs, usb_request_t, node)) != NULL) {
195        usb_req_release(&eth->usb, txn);
196    }
197    free(eth);
198}
199
200static zx_status_t rndishost_query(void* ctx, uint32_t options, ethmac_info_t* info) {
201    zxlogf(TRACE, "rndishost_query\n");
202    rndishost_t* eth = (rndishost_t*)ctx;
203
204    zxlogf(DEBUG1, "options = %x\n", options);
205    if (options) {
206        return ZX_ERR_INVALID_ARGS;
207    }
208
209    memset(info, 0, sizeof(*info));
210    info->mtu = eth->mtu;
211    memcpy(info->mac, eth->mac_addr, sizeof(eth->mac_addr));
212
213    return ZX_OK;
214}
215
216static void rndishost_stop(void* ctx) {
217    rndishost_t* eth = (rndishost_t*)ctx;
218    mtx_lock(&eth->mutex);
219    eth->ifc = NULL;
220    mtx_unlock(&eth->mutex);
221}
222
223static zx_status_t rndishost_start(void* ctx, ethmac_ifc_t* ifc, void* cookie) {
224    rndishost_t* eth = (rndishost_t*)ctx;
225    zx_status_t status = ZX_OK;
226
227    mtx_lock(&eth->mutex);
228    if (eth->ifc) {
229        status = ZX_ERR_ALREADY_BOUND;
230    } else {
231        eth->ifc = ifc;
232        eth->cookie = cookie;
233        // TODO: Check that the device is online before sending ETH_STATUS_ONLINE.
234        eth->ifc->status(eth->cookie, ETH_STATUS_ONLINE);
235    }
236    mtx_unlock(&eth->mutex);
237
238    return status;
239}
240
241static zx_status_t rndishost_queue_tx(void* ctx, uint32_t options, ethmac_netbuf_t* netbuf) {
242    size_t length = netbuf->len;
243    rndishost_t* eth = (rndishost_t*)ctx;
244    uint8_t* byte_data = netbuf->data;
245    zx_status_t status = ZX_OK;
246
247    mtx_lock(&eth->mutex);
248
249    usb_request_t* req = list_remove_head_type(&eth->free_write_reqs, usb_request_t, node);
250    if (req == NULL) {
251        zxlogf(DEBUG1, "dropped a packet.\n");
252        status = ZX_ERR_NO_RESOURCES;
253        goto done;
254    }
255
256    // TODO: Check that length + header <= MTU
257
258    rndis_packet_header header;
259    uint8_t* header_data = (uint8_t*)&header;
260    memset(header_data, 0, sizeof(rndis_packet_header));
261    header.msg_type = RNDIS_PACKET_MSG;
262    header.msg_length = sizeof(rndis_packet_header) + length;
263    // The offset should be given from the beginning of the data_offset field.
264    // So subtract 8 bytes for msg_type and msg_length.
265    header.data_offset = sizeof(rndis_packet_header) - 8;
266    header.data_length = length;
267
268    usb_req_copy_to(&eth->usb, req, header_data, sizeof(rndis_packet_header), 0);
269    ssize_t bytes_copied = usb_req_copy_to(&eth->usb, req, byte_data, length,
270                                           sizeof(rndis_packet_header));
271    req->header.length = sizeof(rndis_packet_header) + length;
272    if (bytes_copied < 0) {
273        printf("rndishost: failed to copy data into send txn (error %zd)\n", bytes_copied);
274        list_add_tail(&eth->free_write_reqs, &req->node);
275        goto done;
276    }
277    zx_nanosleep(zx_deadline_after(ZX_USEC(eth->tx_endpoint_delay)));
278    usb_request_queue(&eth->usb, req);
279
280done:
281    mtx_unlock(&eth->mutex);
282    return status;
283}
284
285static void rndishost_unbind(void* ctx) {
286    rndishost_t* eth = (rndishost_t*)ctx;
287    device_remove(eth->zxdev);
288}
289
290static void rndishost_release(void* ctx) {
291    rndishost_t* eth = (rndishost_t*)ctx;
292    rndishost_free(eth);
293}
294
295static zx_status_t rndishost_set_param(void *ctx, uint32_t param, int32_t value, void* data) {
296    return ZX_ERR_NOT_SUPPORTED;
297}
298
299static ethmac_protocol_ops_t ethmac_ops = {
300    .query = rndishost_query,
301    .stop = rndishost_stop,
302    .start = rndishost_start,
303    .queue_tx = rndishost_queue_tx,
304    .set_param = rndishost_set_param,
305};
306
307static zx_protocol_device_t rndishost_device_proto = {
308    .version = DEVICE_OPS_VERSION,
309    .unbind = rndishost_unbind,
310    .release = rndishost_release,
311};
312
313static int rndis_start_thread(void* arg) {
314    rndishost_t* eth = (rndishost_t*)arg;
315    void* buf = malloc(RNDIS_BUFFER_SIZE);
316    memset(buf, 0, RNDIS_BUFFER_SIZE);
317
318    // Send an initialization message to the device.
319    rndis_init* init = buf;
320    init->msg_type = RNDIS_INITIALIZE_MSG;
321    init->msg_length = sizeof(rndis_init);
322    init->major_version = RNDIS_MAJOR_VERSION;
323    init->minor_version = RNDIS_MINOR_VERSION;
324    init->max_xfer_size = RNDIS_MAX_XFER_SIZE;
325
326    zx_status_t status = rndis_command(eth, buf);
327    if (status < 0) {
328        zxlogf(DEBUG1, "rndishost bad status on initial message. %d\n", status);
329        goto fail;
330    }
331
332    // TODO: Save important fields of init_cmplt.
333    rndis_init_complete* init_cmplt = buf;
334    if (!command_succeeded(buf, RNDIS_INITIALIZE_CMPLT, sizeof(*init_cmplt))) {
335        zxlogf(DEBUG1, "rndishost initialization failed.\n");
336        status = ZX_ERR_IO;
337        goto fail;
338    }
339    eth->mtu = init_cmplt->max_xfer_size;
340
341    // Check the PHY, this is optional and may not be supported by the device.
342    uint32_t* phy;
343    memset(buf, 0, RNDIS_BUFFER_SIZE);
344    rndis_query* query = buf;
345    query->msg_type = RNDIS_QUERY_MSG;
346    query->msg_length = sizeof(rndis_query) + sizeof(*phy);
347    query->oid = OID_GEN_PHYSICAL_MEDIUM;
348    query->info_buffer_length = sizeof(*phy);
349    query->info_buffer_offset = RNDIS_QUERY_BUFFER_OFFSET;
350    status = rndis_command(eth, buf);
351    if (status == ZX_OK) {
352        // TODO: Do something with this information.
353        rndis_query_complete* phy_query_cmplt = buf;
354        if (command_succeeded(buf, RNDIS_QUERY_CMPLT, sizeof(*phy_query_cmplt) +
355                                                          phy_query_cmplt->info_buffer_length)) {
356            // The offset given in the reply is from the beginning of the request_id
357            // field. So, add 8 for the msg_type and msg_length fields.
358            phy = buf + 8 + phy_query_cmplt->info_buffer_offset;
359        }
360    }
361
362    // Query the device for a MAC address.
363    memset(buf, 0, RNDIS_BUFFER_SIZE);
364    query->msg_type = RNDIS_QUERY_MSG;
365    query->msg_length = sizeof(rndis_query) + 48;
366    query->oid = OID_802_3_PERMANENT_ADDRESS;
367    query->info_buffer_length = 48;
368    query->info_buffer_offset = RNDIS_QUERY_BUFFER_OFFSET;
369    status = rndis_command(eth, buf);
370    if (status < 0) {
371        zxlogf(ERROR, "Couldn't get device physical address\n");
372        goto fail;
373    }
374
375    rndis_query_complete* mac_query_cmplt = buf;
376    if (!command_succeeded(buf, RNDIS_QUERY_CMPLT, sizeof(*mac_query_cmplt) +
377                                                       mac_query_cmplt->info_buffer_length)) {
378        zxlogf(DEBUG1, "rndishost MAC query failed.\n");
379        status = ZX_ERR_IO;
380        goto fail;
381    }
382    uint8_t* mac_addr = buf + 8 + mac_query_cmplt->info_buffer_offset;
383    memcpy(eth->mac_addr, mac_addr, sizeof(eth->mac_addr));
384    zxlogf(INFO, "rndishost MAC address: %02x:%02x:%02x:%02x:%02x:%02x\n",
385           eth->mac_addr[0], eth->mac_addr[1], eth->mac_addr[2],
386           eth->mac_addr[3], eth->mac_addr[4], eth->mac_addr[5]);
387
388    // Enable data transfers
389    memset(buf, 0, RNDIS_BUFFER_SIZE);
390    rndis_set* set = buf;
391    set->msg_type = RNDIS_SET_MSG;
392    set->msg_length = sizeof(rndis_set) + 4; // 4 bytes for the filter
393    set->oid = OID_GEN_CURRENT_PACKET_FILTER;
394    set->info_buffer_length = 4;
395    // Offset should begin at oid, so subtract 8 bytes for msg_type and msg_length.
396    set->info_buffer_offset = sizeof(rndis_set) - 8;
397    uint8_t* filter = buf + sizeof(rndis_set);
398    *filter = RNDIS_PACKET_TYPE_DIRECTED |
399              RNDIS_PACKET_TYPE_BROADCAST |
400              RNDIS_PACKET_TYPE_ALL_MULTICAST |
401              RNDIS_PACKET_TYPE_PROMISCUOUS;
402    status = rndis_command(eth, buf);
403    if (status < 0) {
404        zxlogf(ERROR, "Couldn't set the packet filter.\n");
405        goto fail;
406    }
407
408    if (!command_succeeded(buf, RNDIS_SET_CMPLT, sizeof(rndis_set_complete))) {
409        zxlogf(ERROR, "rndishost set filter failed.\n");
410        status = ZX_ERR_IO;
411        goto fail;
412    }
413
414    free(buf);
415    device_make_visible(eth->zxdev);
416    return ZX_OK;
417
418fail:
419    free(buf);
420    rndishost_unbind(eth);
421    rndishost_free(eth);
422    return status;
423}
424
425static zx_status_t rndishost_bind(void* ctx, zx_device_t* device) {
426    usb_protocol_t usb;
427
428    zx_status_t status = device_get_protocol(device, ZX_PROTOCOL_USB, &usb);
429    if (status != ZX_OK) {
430        return status;
431    }
432
433    // Find our endpoints.
434    usb_desc_iter_t iter;
435    status = usb_desc_iter_init(&usb, &iter);
436    if (status < 0) {
437        return status;
438    }
439
440    // We should have two interfaces: the CDC classified interface the bulk in
441    // and out endpoints, and the RNDIS interface for control. The RNDIS
442    // interface will be classified as USB_CLASS_WIRELESS when the device is
443    // used for tethering.
444    // TODO: Figure out how to handle other RNDIS use cases.
445    usb_interface_descriptor_t* intf = usb_desc_iter_next_interface(&iter, false);
446    uint8_t bulk_in_addr = 0;
447    uint8_t bulk_out_addr = 0;
448    uint8_t intr_addr = 0;
449    uint8_t control_intf = 0;
450    while (intf) {
451        if (intf->bInterfaceClass == USB_CLASS_WIRELESS) {
452            control_intf = intf->bInterfaceNumber;
453            if (intf->bNumEndpoints != 1) {
454                usb_desc_iter_release(&iter);
455                return ZX_ERR_NOT_SUPPORTED;
456            }
457            usb_endpoint_descriptor_t* endp = usb_desc_iter_next_endpoint(&iter);
458            while (endp) {
459                if (usb_ep_direction(endp) == USB_ENDPOINT_IN &&
460                    usb_ep_type(endp) == USB_ENDPOINT_INTERRUPT) {
461                    intr_addr = endp->bEndpointAddress;
462                }
463                endp = usb_desc_iter_next_endpoint(&iter);
464            }
465        } else if (intf->bInterfaceClass == USB_CLASS_CDC) {
466            if (intf->bNumEndpoints != 2) {
467                usb_desc_iter_release(&iter);
468                return ZX_ERR_NOT_SUPPORTED;
469            }
470            usb_endpoint_descriptor_t* endp = usb_desc_iter_next_endpoint(&iter);
471            while (endp) {
472                if (usb_ep_direction(endp) == USB_ENDPOINT_OUT) {
473                    if (usb_ep_type(endp) == USB_ENDPOINT_BULK) {
474                        bulk_out_addr = endp->bEndpointAddress;
475                    }
476                } else if (usb_ep_direction(endp) == USB_ENDPOINT_IN) {
477                    if (usb_ep_type(endp) == USB_ENDPOINT_BULK) {
478                        bulk_in_addr = endp->bEndpointAddress;
479                    }
480                }
481                endp = usb_desc_iter_next_endpoint(&iter);
482            }
483        } else {
484            usb_desc_iter_release(&iter);
485            return ZX_ERR_NOT_SUPPORTED;
486        }
487        intf = usb_desc_iter_next_interface(&iter, false);
488    }
489    usb_desc_iter_release(&iter);
490
491    if (!bulk_in_addr || !bulk_out_addr || !intr_addr) {
492        zxlogf(ERROR, "rndishost couldn't find endpoints\n");
493        return ZX_ERR_NOT_SUPPORTED;
494    }
495
496    rndishost_t* eth = calloc(1, sizeof(rndishost_t));
497    if (!eth) {
498        return ZX_ERR_NO_MEMORY;
499    }
500
501    list_initialize(&eth->free_read_reqs);
502    list_initialize(&eth->free_write_reqs);
503    list_initialize(&eth->free_intr_reqs);
504
505    mtx_init(&eth->mutex, mtx_plain);
506
507    eth->usb_zxdev = device;
508    eth->control_intf = control_intf;
509    eth->bulk_in_addr = bulk_in_addr;
510    eth->bulk_out_addr = bulk_out_addr;
511    eth->intr_addr = intr_addr;
512    eth->ifc = NULL;
513    memcpy(&eth->usb, &usb, sizeof(eth->usb));
514
515    for (int i = 0; i < READ_REQ_COUNT; i++) {
516        usb_request_t* req;
517        zx_status_t alloc_result = usb_req_alloc(&eth->usb, &req, RNDIS_BUFFER_SIZE, bulk_in_addr);
518        if (alloc_result != ZX_OK) {
519            status = alloc_result;
520            goto fail;
521        }
522        req->complete_cb = rndis_read_complete;
523        req->cookie = eth;
524        list_add_head(&eth->free_read_reqs, &req->node);
525    }
526    for (int i = 0; i < WRITE_REQ_COUNT; i++) {
527        usb_request_t* req;
528        // TODO: Allocate based on mtu.
529        zx_status_t alloc_result = usb_req_alloc(&eth->usb, &req, RNDIS_BUFFER_SIZE, bulk_out_addr);
530        if (alloc_result != ZX_OK) {
531            status = alloc_result;
532            goto fail;
533        }
534        req->complete_cb = rndis_write_complete;
535        req->cookie = eth;
536        list_add_head(&eth->free_write_reqs, &req->node);
537    }
538
539    device_add_args_t args = {
540        .version = DEVICE_ADD_ARGS_VERSION,
541        .name = "rndishost",
542        .ctx = eth,
543        .ops = &rndishost_device_proto,
544        .proto_id = ZX_PROTOCOL_ETHERNET_IMPL,
545        .proto_ops = &ethmac_ops,
546        .flags = DEVICE_ADD_INVISIBLE,
547    };
548
549    status = device_add(eth->usb_zxdev, &args, &eth->zxdev);
550    if (status < 0) {
551        zxlogf(ERROR, "rndishost: failed to create device: %d\n", status);
552        goto fail;
553    }
554
555    thrd_t thread;
556    int ret = thrd_create_with_name(&thread, rndis_start_thread,
557                                    eth, "rndishost_start_thread");
558    if (ret != thrd_success) {
559        status = ZX_ERR_NO_RESOURCES;
560        goto fail;
561    }
562    // TODO: Save the thread in rndishost_t and join when we release the device.
563    thrd_detach(thread);
564
565    return ZX_OK;
566
567fail:
568    zxlogf(ERROR, "rndishost_bind failed: %d\n", status);
569    rndishost_free(eth);
570    return status;
571}
572
573static zx_driver_ops_t rndis_driver_ops = {
574    .version = DRIVER_OPS_VERSION,
575    .bind = rndishost_bind,
576};
577
578// TODO: Make sure we can bind to all RNDIS use cases. USB_CLASS_WIRELESS only
579// covers the tethered device case.
580ZIRCON_DRIVER_BEGIN(rndishost, rndis_driver_ops, "zircon", "0.1", 4)
581    BI_ABORT_IF(NE, BIND_PROTOCOL, ZX_PROTOCOL_USB),
582    BI_ABORT_IF(NE, BIND_USB_CLASS, USB_CLASS_WIRELESS),
583    BI_ABORT_IF(NE, BIND_USB_SUBCLASS, RNDIS_SUBCLASS),
584    BI_MATCH_IF(EQ, BIND_USB_PROTOCOL, RNDIS_PROTOCOL),
585ZIRCON_DRIVER_END(rndishost)
586